akka 代码实现

本文详细介绍了在Akka Actor系统中,Worker如何与Master进行注册及心跳交互的过程。Worker开机后向Master注册,Master接收并保存Worker信息,Worker定时发送心跳以维持连接。Master定时检查Worker心跳,超时则移除其信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

整体的代码逻辑

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 的区别就是一个是静态的  一个不是  类名就可以调用  一个需要用对象调用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值