整体的代码逻辑
1.Worker开机之后 会连接并向Master注册信息(也就是发送自己的信息)
2.Master接收到信息之后,会将worker的信息存到专门存储workers信息的hashmap中,再返回给worker一个连接成功的信息
3.worker接受到注册成功的信息之后开始向master定时输出心跳信息,但是又不能直接定时给master发送心跳,只能定时给自己发送信息,
每次接受到这个信息之后,自己就会给master发送心跳信息(带有worker的id信息)
4.master会就收到带有worker id信息的消息之后,如果已经是注册在hashmap的worker的话 就会 记录 每次接收到心跳的时间并一直更新
5.master也要定时监视着worker的心跳信息,如果不发sing心跳信息的时候就会在hashmap中删除worker的信息
所以要在master开启的时候就开始开启着定时监控 给自己发消息 定时处理检查连接超时的worker,在这个方法中超过抓取心跳的周期时间就直接从hashmap中删除
Master
package cn.doit.rpc
import akka.actor.{Actor, ActorSystem, Props}
import com.typesafe.config.{Config, ConfigFactory}
import scala.collection.mutable
import scala.concurrent.duration._
class Master extends Actor {
val idToWorker = new mutable.HashMap[String, WorkerInfo]()
override def preStart(): Unit = {
//在preStart方法启动一个定时器,定期检测超时的Worker
import context.dispatcher
context.system.scheduler.schedule(0 millisecond,15000 millisecond,self,CheckTimeOutWorker)
}
//发送消息的
override def receive: Receive = {
case RegisterWorker(id,memory,cores) =>{
//将Worker的信息保存起来
println(s"workerId:$id,memory:$memory,core:$cores")
val workerInfo = new WorkerInfo(id, memory, cores)
workerInfo.lastHeartbeatTime = System.currentTimeMillis()
println("=====================加入时间:"+workerInfo.lastHeartbeatTime)
//添加到map集合中
idToWorker(id) = workerInfo //这个方法是真的吊 得多理解理解
println("========================================加入了一个节点:id"+id)
//向Worker返回一个注册成功的信息
sender() ! RegisteredWorker //为啥这个返回的是case object
}
//Worker 定期发给 Master 的定期心跳信息
case Heartbeat(workerId ) => {
if(idToWorker.contains(workerId)){
val workerInfo = idToWorker(workerId)
//更新对应Worker的时间
workerInfo.lastHeartbeatTime = System.currentTimeMillis()
println("==============更新时间:"+workerInfo.lastHeartbeatTime)
idToWorker(workerId) = workerInfo
}
}
case CheckTimeOutWorker =>{
//获取当前时间
val currentTime = System.currentTimeMillis()
val deadWorkers: Iterable[WorkerInfo] = idToWorker.values.filter(w => currentTime - w.lastHeartbeatTime > 15000)
//移除超时的Worker
deadWorkers.foreach(w =>{
println("发现一个死亡节点,id="+w.id+",上一次时间:"+w.lastHeartbeatTime+",当前时间:"+System.currentTimeMillis())
//将超时的Worker移除
idToWorker -= w.id
})
println(s"current alive worker is : ${idToWorker.size}")
}
}
}
object Master {
def main(args: Array[String]): Unit = {
//指定一下配置
val confStr =
s"""
|akka.actor.provider = "akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname = "localhost"
|akka.remote.netty.tcp.port = 8888
|""".stripMargin
//解析配置
val configs: Config = ConfigFactory.parseString(confStr)
//创建ActorSystem(单例的)
val actorSystem = ActorSystem("MASTER_ACTOR_SYSTEM", configs)
//再创建Actor
val actorRef = actorSystem.actorOf(Props[Master],"MASTER_ACTOR")
}
}
Worker
package cn.doit.rpc
import java.util.UUID
import akka.actor.{Actor, ActorSelection, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
import scala.concurrent.duration._
class Worker extends Actor{
var workerId = UUID.randomUUID().toString
//var workerId = "id10010"
var masterRef: ActorSelection = _
//在构造方法执行之后,receive执行之前,跟Master建立连接,并注册信息
override def preStart(): Unit={
//Worker跟Master建立上了连接
masterRef = context.actorSelection("akka.tcp://MASTER_ACTOR_SYSTEM@localhost:8888/user/MASTER_ACTOR")
//向Master发送信息
masterRef ! RegisterWorker(workerId,10240,8)
}
override def receive: Receive = {
case RegisteredWorker => {
//启动定时器
//导入隐式转换
import context.dispatcher
context.system.scheduler.schedule(0.millisecond,5000 millisecond,self,SendHeartbeat)//第一次延迟多少秒执行(为啥需要自己导包啊),interval: 周期\间隔 receiver:接受者 ,message:
}
//自己发给自己的消息,定期的接收到该消息
case SendHeartbeat => {
//向Master发消息
masterRef ! Heartbeat(workerId)
}
}
}
object Worker{
def main(args: Array[String]): Unit = {
val confStr =
s"""
|akka.actor.provider = "akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname = "localhost"
|akka.remote.netty.tcp.port = 8889
|""".stripMargin
val configs = ConfigFactory.parseString(confStr)
val actorSystem = ActorSystem("WORKER_ACTOR_SYSTEM", configs)
actorSystem.actorOf(Props[Worker])//通过反射创建Worker
}
}
Massage
Worker和Master中的定义的类
package cn.doit.rpc
//Worker -> Master
case class Heartbeat(workerId: String) {
}
//
case class RegisterWorker(workerId: String, memory: Int, cores:Int)
////////////
//Master -> Worker 注册成功
case object RegisteredWorker
//Worker自己发给自己的内部消息
case object SendHeartbeat
//Master发给自己的消息
case object CheckTimeOutWorker
WorkerInfo
package cn.doit.rpc
class WorkerInfo (val id: String ,var memory: Int , cores: Int){
var lastHeartbeatTime: Long = _
}
scala中发现的一些问题
问:case class 实现了序列化???
答:case class和case object 在网络通信中使用
问:为啥case class RegisterWorker 中的属性不用var val修饰
答: 相当于定义临时变量
问:sender() 是个什么玩意
答:就是Master向接收到信息来源的Worker发消息
接口中的方法 被final修饰 是啥意思
case class 和 case object 的区别就是一个是静态的 一个不是 类名就可以调用 一个需要用对象调用