目标:简单模拟Spark的通信机制,多个worker向Master注册并发送心跳,Master定时清理超时的worker。具体流程如下:
- 启动Master并开启清空超时Worker的定时任务
- Worker启动的时候,在preStart方法中连接Master,并向Master注册自己的相关信息
- Master收到worker的注册并返回自己的url给Worker,表示该Worker注册成功
- Worker收到注册成功的消息后,定时给Master发生心跳消息
Master节点的实现代码
import akka.actor.{Actor, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
import scala.concurrent.duration._
/*** Created by Javis on 2016/12/14 0014. * */
class Master(val host:String,val port:Int) extends Actor{
//workerId->workerInfo
val id2WorkInfo=new scala.collection.mutable.HashMap[String,WorkerInfo]
//为了便于一些额外的逻辑,比如按Worker的剩余可用memory进行排序
val workers= new scala.collection.mutable.HashSet[WorkerInfo]
//检查worker是否超时的时间间隔
val CHECK_INTERVAL=10000
override def preStart(): Unit = {
//使用schedule必须导入该扩展的隐式变量
import context.dispatcher
//millis是毫秒的单位,其在包scala.concurrent.duration下
context.system.scheduler.schedule(0 millis,CHECK_INTERVAL millis,self,CheckTimeOutWorker)
}
override def receive: Receive = {
case RegisterWorker(id,memory,cores)=> {//把Worker的注册消息封装到类WorkerInfo中
if (!id2WorkInfo.contains(id)) {
//当前workerId没有注册
val workerInfo = new WorkerInfo(id, memory, cores)
id2WorkInfo.put(id, workerInfo)
workers += workerInfo
//这里简单发生Master的url通知worker注册成功
sender ! RegisteredWorker(s"akka.tcp://MasterSystem@$host:$port/user/Master")
}
}
case HeartBeat(workerId)=>{ //处理Worker的心跳
if(id2WorkInfo.contains(workerId)){
id2WorkInfo(workerId).updateLastHeartBeatTime()
}
}
case CheckTimeOutWorker=>{ //定时检测是否有超时的worker并进行处理
val cur=System.currentTimeMillis
//过滤出超时的worker
val deadWorker= workers.filter(x=>cur-x.lastHeartBeatTime>CHECK_INTERVAL)
//从记录删除删除超时的worker
for(w <- deadWorker) {
id2WorkInfo -= w.id
workers -= w
}
println(workers.size)
}
}
}
object Master{
def main(args: Array[String]) {
val host=args(0)
val port=args(1).toInt
//构造配置参数值,使用3个双引号可以多行,使用s可以在字符串中使用类似Shell的变量
val confStr=
s"""
|akka.actor.provider = "akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname = "$host"
|akka.remote.netty.tcp.port = "$port"
""".stripMargin
//通过工厂方法获得一个config对象
val conf=ConfigFactory.parseString(confStr)
//初始化一个ActorSystem,其名为MasterSystem
val actorSystem= ActorSystem("MasterSystem",conf)
//使用actorSystem实例化一个名为Master的actor,注意这个名称在Worker连接Master时会用到
val master=actorSystem.actorOf(Props(new Master(host,port)),"Master")
//阻塞当前线程直到系统关闭退出
actorSystem.awaitTermination
}
}
Worker节点的实现代码
import java.util.UUID
import akka.actor.{Actor, ActorSelection, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
import scala.concurrent.duration._
/** Created by Javis on 2016/12/14 0014 */
class Worker(val masterHost:String,val masterPort:Int) extends Actor{
//保存使用akka-url获得的master,该对象可以发生消息
var master:ActorSelection=_
//worker唯一标识
val id_Worker=UUID.randomUUID().toString
//心跳间隔
val HEARTBEAT_INTERVAL=5000
override def preStart(): Unit = {
println("worker-preStart")
//通过masterUrl获得ActorSelectiond对象,该对象可以发生消息
master= context.actorSelection(s"akka.tcp://MasterSystem@$masterHost:$masterPort/user/Master")
val memory=8 //the default of memory is 8G
val cores=4 //the default number of cores is 4
//向master发送注册的信息
master ! RegisterWorker(id_Worker,memory,cores)
}
override def receive: Receive = {
case RegisteredWorker(masterUrl)=> {//处理注册成功的逻辑
import context.dispatcher
//由于第三个参数是传递一个ActorRef对象,但是目前的master是ActorSelection类型,因此先给自己发送下消息
context.system.scheduler.schedule(0 millis,HEARTBEAT_INTERVAL millis,self,SendHeartBeat)
}
case SendHeartBeat=>{ //发生心跳。在此可用添加额外的处理逻辑
println("定时发生心跳给Master")
master ! HeartBeat(id_Worker)
}
}
}
object Worker{
def main(args: Array[String]) {
val host=args(0)
val port=args(1).toInt
val masterHost=args(2)
val masterPort=args(3).toInt
val confStr=
s"""
|akka.actor.provider = "akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname = "$host"
|akka.remote.netty.tcp.port = "$port"
""".stripMargin
val conf=ConfigFactory.parseString(confStr)
val actorSystem= ActorSystem("WorkerSystem",conf)
val master=actorSystem.actorOf(Props(new Worker(masterHost,masterPort)),"Worker")
actorSystem.awaitTermination
}
}
通信消息的样本类
//远程通信可序列化特质,需要远程通信的样本类需继承该特质
trait RemoteMessage extends Serializable
//worker向master发生注册消息的
case class RegisterWorker(val id:String,val memory:Int,val cores:Int) extends RemoteMessage
//master向worker反馈注册成功的信息,这里只简单返回master的url
case class RegisteredWorker(val masterUrl:String) extends RemoteMessage
//该伴生对象用于worker本地发生消息给自己
case object SendHeartBeat
//worker发生心跳给master
case class HeartBeat(val workerId:String) extends RemoteMessage
//该伴生对象用于master定时检测超时的worker
case object CheckTimeOutWorker
/**
* Created by Javis on 2016/12/14 0014.
*/
//Master保存接受到的Worker信息
class WorkerInfo(val id:String,val memory:Int,val cores:Int) {
//上次心跳更新的时间
var lastHeartBeatTime: Long = 0
//更新上次心跳的时间
def updateLastHeartBeatTime() = {
lastHeartBeatTime = System.currentTimeMillis
}
}