akka 代码实现

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

整体的代码逻辑

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

Akka 远程调用的实现基于 Akka Remoting 模块。以下是一个简单的示例: 首先,需要在项目中添加 Akka Remoting 依赖: ```xml <dependency> <groupId>com.typesafe.akka</groupId> <artifactId>akka-remote_2.11</artifactId> <version>2.5.9</version> </dependency> ``` 然后,定义一个远程 Actor,它可以通过网络接收和处理消息: ```java import akka.actor.AbstractActor; import akka.event.Logging; import akka.event.LoggingAdapter; public class RemoteActor extends AbstractActor { private LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this); @Override public Receive createReceive() { return receiveBuilder() .match(String.class, message -> { log.info("Received message: {}", message); getSender().tell("Hello from remote actor", getSelf()); }) .build(); } } ``` 接下来,在本地 Actor 中创建一个远程 Actor 的引用,并向其发送消息: ```java import akka.actor.ActorRef; import akka.actor.ActorSystem; import akka.actor.Props; import akka.pattern.Patterns; import scala.concurrent.Await; import scala.concurrent.duration.Duration; import java.util.concurrent.TimeUnit; public class LocalActor { public static void main(String[] args) throws Exception { ActorSystem system = ActorSystem.create("MySystem"); // 创建远程 Actor 的引用 ActorRef remoteActor = system.actorOf(Props.create(RemoteActor.class), "remoteActor"); // 向远程 Actor 发送消息,并等待其回复 String message = "Hello from local actor"; Object response = Patterns.ask(remoteActor, message, 5000).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); System.out.println("Received response: " + response); system.terminate(); } } ``` 在上面的代码中,我们使用 `Patterns.ask` 方法向远程 Actor 发送一条消息,并等待其回复。该方法返回一个 `Future` 对象,我们可以使用 `toCompletableFuture()` 方法将其转换为 Java 8 的 `CompletableFuture` 对象,从而方便地使用 `get` 方法等待结果。 以上就是一个简单的 Akka 远程调用的示例。在实际应用中,还需要配置 Akka Remoting 的一些参数,例如远程 Actor 的地址、端口等。具体配置方法可以参考 Akka 官方文档。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值