指導思想:
1、利用RPC通信框架(AKKA)
2、定義2個類Master、Worker
-------------------------------------------------------------------------------------------------------------------------------
首先啓動Master,然後啓動所有的Worker
1、Worker啓動後,在PreStart方法中與Master建立連接,向Master發送註冊,將Worker的信息通過case class封裝起來發送給Master。
2、Master接受到Worker的註冊消息後將Worker的信息保存起來,然後向Worker反饋註冊成功。
3、Worker定期向Master發送心跳,目的是爲了報告存活狀態。
4、Master會定時清理超時的Worker。
--------------------------------------------------------------------------------------------------------------------------------
首先打開Idea軟件,建立maven項目:
編寫Pom文件,Pom文件的代碼如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>cn.allengao.akka</groupId> <artifactId>my-rpc</artifactId> <version>1.0</version> <properties> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> <encoding>UTF-8</encoding> <scala.version>2.10.6</scala.version> <scala.compat.version>2.10</scala.compat.version> </properties> <dependencies> <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-library</artifactId> <version>${scala.version}</version> </dependency> <dependency> <groupId>com.typesafe.akka</groupId> <artifactId>akka-actor_2.10</artifactId> <version>2.3.14</version> </dependency> <dependency> <groupId>com.typesafe.akka</groupId> <artifactId>akka-remote_2.10</artifactId> <version>2.3.14</version> </dependency> </dependencies> <build> <sourceDirectory>src/main/scala</sourceDirectory> <testSourceDirectory>src/test/scala</testSourceDirectory> <plugins> <plugin> <groupId>net.alchim31.maven</groupId> <artifactId>scala-maven-plugin</artifactId> <version>3.2.2</version> <executions> <execution> <goals> <goal>compile</goal> <goal>testCompile</goal> </goals> <configuration> <args> <arg>-make:transitive</arg> <arg>-dependencyfile</arg> <arg>${project.build.directory}/.scala_dependencies</arg> </args> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.4.3</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <filters> <filter> <artifact>*:*</artifact> <excludes> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> </filter> </filters> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>reference.conf</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>cn.allengao.rpc.Worker</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>
RemoteMessage的代碼如下:
package cn.allengao.rpc /** * class_name: * package: * describe: TODO * creat_user: Allen Gao * creat_date: 2018/1/18 * creat_time: 16:36 **/ trait RemoteMessage extends Serializable //Worker -> Master case class RegisterWorker(id: String, host: String, port: Int, memory: Int, cores: Int) extends RemoteMessage case class Heartbeat(workId: String) extends RemoteMessage //Master -> Worker case class RegisteredWorker(masterUrl: String) extends RemoteMessage //Worker -> self case object SendHeartbeat // Master -> self case object CheckTimeOutWorker
WorkerInfo的代碼如下:
package cn.allengao.rpc /** * class_name: * package: * describe: 把worker啓動的信息封裝起來,並且傳給master。 * creat_user: Allen Gao * creat_date: 2018/1/18 * creat_time: 16:37 **/ class WorkerInfo(val id: String, val host:String, val port: Int, val memory: Int, val cores: Int) { //TODO 上一次心跳 var lastHeartbeatTime : Long = _ }
Master的代碼如下:
package cn.allengao.rpc import akka.actor.{Actor, ActorSystem, Props} import com.typesafe.config.ConfigFactory import scala.collection.mutable import scala.concurrent.duration._ /** * class_name: * package: * describe: TODO * creat_user: Allen Gao * creat_date: 2018/1/18 * creat_time: 16:36 **/ class Master(val host: String, val port: Int) extends Actor { // workerId -> WorkerInfo val idToWorker = new mutable.HashMap[String, WorkerInfo]() // WorkerInfo val workers = new mutable.HashSet[WorkerInfo]() //使用set刪除快, 也可用linkList //超時檢查的間隔 val CHECK_INTERVAL = 15000 //preStart方法在構造器執行構造方法以後,receive執行方法之前執行,並且只執行一次。 override def preStart(): Unit = { println("preStart invoked") //導入隱式轉換,在preStart啓動一個定時器,用於週期檢查超時的Worker import context.dispatcher //使用timer太low了, 可以使用akka的, 使用定時器, 要導入這個包 context.system.scheduler.schedule(0 millis, CHECK_INTERVAL millis, self, CheckTimeOutWorker) } // 用於接收消息,receive方法循環執行。 override def receive: Receive = { case RegisterWorker(id, host, port, memory, cores) => { //判斷一下,是不是已經註冊過 if(!idToWorker.contains(id)){ //把Worker的信息封裝起來保存到內存當中 val workerInfo = new WorkerInfo(id, host, port, memory, cores) idToWorker += (id -> workerInfo) //key是id,value是workInfo workers += workerInfo println("a worker registered") // sender ! RegisteredWorker(s"akka.tcp://MasterSystem@$host:$port/user/Master") sender ! RegisteredWorker(s"akka.tcp://${Master.MASTER_SYSTEM}" + s"@$host:$port/user/${Master.MASTER_ACTOR}")//通知worker註冊 } } case Heartbeat(workerId) => { if(idToWorker.contains(workerId)){ val workerInfo = idToWorker(workerId) //報活 val current_time = System.currentTimeMillis() workerInfo.lastHeartbeatTime = current_time } } case CheckTimeOutWorker => { val currentTime = System.currentTimeMillis() val toRemove : mutable.HashSet[WorkerInfo] = workers.filter(w => currentTime - w.lastHeartbeatTime > CHECK_INTERVAL) // for(w <- toRemove) { // workers -= w // idToWorker -= w.id // } toRemove.foreach(deadWorker =>{ idToWorker -= deadWorker.id workers -= deadWorker }) println("num of workers " + workers.size) } } } object Master { //聲明兩個變量MaterSystem和MasterActor val MASTER_SYSTEM = "MasterSystem" val MASTER_ACTOR = "Master" def main(args: Array[String]) { //解析傳入的參數主機名和端口號 val host = args(0) val port = args(1).toInt // 準備配置信息:(|之間是解析數據,以“=”分割,“=”前面是參數(key),後面是數值(value)。) val configStr = s""" |akka.actor.provider = "akka.remote.RemoteActorRefProvider" |akka.remote.netty.tcp.hostname = "$host" |akka.remote.netty.tcp.port = "$port" """.stripMargin //ConfigFactory配置文件信息類把配置文件信息傳入 val config = ConfigFactory.parseString(configStr) //ActorSystem老大,輔助創建和監控下面的Actor,他是單例的 val actorSystem = ActorSystem(MASTER_SYSTEM, config) //創建Actor actorSystem.actorOf(Props(new Master(host, port)), MASTER_ACTOR) //調用線程等待 actorSystem.awaitTermination() } }
Worker的代碼如下:
package cn.allengao.rpc /** * class_name: * package: * describe: TODO * creat_user: Allen Gao * creat_date: 2018/1/18 * creat_time: 16:37 **/ import java.util.UUID import akka.actor.{Actor, ActorSelection, ActorSystem, Props} import com.typesafe.config.ConfigFactory import scala.concurrent.duration._ /** * Created by root on 2016/5/13. */ class Worker(val host: String, val port: Int,val masterHost: String, val masterPort: Int, val memory: Int, val cores: Int) extends Actor{ val worker_id = UUID.randomUUID().toString var masterUrl : String = _ val HEART_INTERVAL = 10000 var master : ActorSelection = _ // override def preStart(): Unit = { //跟Master建立連接 // master = context.actorSelection(s"akka.tcp://MasterSystem@$masterHost:$masterPort/user/Master") master = context.actorSelection(s"akka.tcp://${Master.MASTER_SYSTEM}" + s"@$masterHost:$masterPort/user/${Master.MASTER_ACTOR}") //向Master發送註冊消息 master ! RegisterWorker(worker_id,host,port, memory, cores) } override def receive: Receive = { case RegisteredWorker(masterUrl) => { println(masterUrl) //啓動定時器發送心跳,心跳是一個case class //導入一個隱式轉換,才能啓動定時器 import context.dispatcher //多長時間後執行 單位,多長時間執行一次 單位, 消息的接受者 // (直接給master發不好, 先給自己發送消息, 以後可以做下判斷, 什麼情況下再發送消息), 信息 context.system.scheduler.schedule(0 millis, HEART_INTERVAL millis, self, SendHeartbeat) } case SendHeartbeat => { println("send heartbeat to master") //發送心跳之前要進行一些檢查 master ! Heartbeat(worker_id) } } } object Worker { val WORKER_SYSTEM = "WorkerSystem" val WORKER_ACTOR = "Worker" def main(args: Array[String]) { val host = args(0) val port = args(1).toInt val masterHost = args(2) val masterPort = args(3).toInt //分析任務用到的內存,CPU核數 val memory = args(4).toInt val cores = args(5).toInt // 準備配置 val configStr = s""" |akka.actor.provider = "akka.remote.RemoteActorRefProvider" |akka.remote.netty.tcp.hostname = "$host" |akka.remote.netty.tcp.port = "$port" """.stripMargin val config = ConfigFactory.parseString(configStr) //ActorSystem老大,輔助創建和監控下面的Actor,他是單例的 val actorSystem = ActorSystem(WORKER_SYSTEM, config) actorSystem.actorOf(Props(new Worker(host, port, masterHost, masterPort, memory, cores)), WORKER_ACTOR) //調用線程等待 actorSystem.awaitTermination() } }
運行Master的代碼:模擬傳入的參數如下
接着右鍵Run Master,效果如下:
運行Woker的程序:模擬傳入的參數如下
右鍵運行Worker的代碼,效果如下:
當停止worker運行時,可以從master的運行狀態中看出檢測到worker連接斷開,連接數重新回到“0”。
至此整個akka的RPC通信小實例測試完畢。