Scala入门六——Akka编写一个RPC框架,模拟多个Worker连接Master

1. Master

package com.ghq.rpc

import akka.actor.{Actor, ActorRef, ActorSystem, PoisonPill, Props}

import language.postfixOps
import com.typesafe.config.ConfigFactory
import akka.event.Logging

import scala.collection.mutable
/**
  * @author : Administrator
  */
class Master(val host:String,val port:Int) extends Actor{
  val log = Logging(context.system, this)

  //保存注册的Worker的信息 map
  val workerMaps = new mutable.HashMap[String,WorkerInfo]()
  //保存注册的Worker的信息 Set
  val workerSet = new mutable.HashSet[WorkerInfo]()


  println("constructor invoked")

  //超时检测的时间间隔
  val heart_check_interval = 3000

  override def preStart(): Unit = {
    println("preStart(): Unit")

    import scala.concurrent.duration._
    //导入隐式转换,否则报错
    import context.dispatcher
    context.system.scheduler.schedule(0 millis,heart_check_interval millis,self,CheckTimeOutWorker)
  }

  override def receive: Receive = {

    case CheckTimeOutWorker => {
      val currentTime = System.currentTimeMillis()
      val toRemove = workerSet.filter(x => currentTime-x.lastHeartBeatTime>heart_check_interval)
      for(worker <- toRemove){
        workerSet -= worker
        workerMaps -= worker.id
      }

      println("还存活的worker的个数:"+workerSet.size)
    }

    case RegisterWorker(id,memory,cores) => {
      println("master RegisterWorker")
      //将接收到的注册信息保存起来
      //先判断是不是已经注册过了
      if(!workerMaps.contains(id)){
        //把Worker的信息封装起来
        val workerInfo = new WorkerInfo(id,memory,cores)
        //将workerInfo保存到map
        workerMaps(id) = workerInfo
        //将workerInfo保存到set
        workerSet += workerInfo

        sender ! RegisteredWorker(s"akka.tcp://MasterSystem@$host:$port/user/Master")

        log.info("a client register")
      }else{
        log.info("已经注册了")
      }
    }

    case HeartBeat(id) =>{
      //更新worker状态
      if(workerMaps.contains(id)){
        val workerInfo = workerMaps(id)
        workerInfo.lastHeartBeatTime = System.currentTimeMillis()
      }

    }

    case "hello" => log.info("hello message")
  }
}

object Master {
  def main(args: Array[String]): Unit = {

    val host = "127.0.0.1"
    val port = 8888

    val str =
      s"""
         |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname = "$host"
         |akka.remote.netty.tcp.port = "$port"
       """.stripMargin
    val config = ConfigFactory.parseString(str)
    //获取ActorSystem,用来创建并监控Actor
    //MasterSystem 为ActorSystem 的名字
    val actorSystem = ActorSystem.apply("MasterSystem",config)

    //创建Actor
    val master = actorSystem.actorOf(Props(new Master(host,port)),"Master")
    master ! "hello"

    //actorSystem.
  }
}

2. Worker

package com.ghq.rpc

import java.util.UUID

import akka.actor.{Actor, ActorSelection, ActorSystem, Props}
import akka.event.Logging
import com.typesafe.config.ConfigFactory

/**
  * @author : Administrator
  */
class Worker(val masterHost:String,val masterPort:Int,val memory:Int,val cores:Int) extends Actor{
  val log = Logging(context.system, this)
  var master: ActorSelection = _

  val workerId = UUID.randomUUID().toString

  //在Worker启动前建立和Master的链接
  override def preStart(): Unit = {
    //path:String Master的路径
    val path:String = s"akka.tcp://MasterSystem@$masterHost:$masterPort/user/Master"
    //获取到Master的引用
    master = context.actorSelection(path)
    //通过Master的引用向Master发消息
    //向Master 注册Worker的信息,包含了Worker的很多信息
    //定义一个case class
    println("向Master 注册Worker的信息,包含了Worker的很多信息")
    master ! RegisterWorker(workerId,memory,cores)

  }

  //时间间隔 1s
  val checkInterval = 1000
  override def receive: Receive = {
    //接收Master返回的已经注册的Worker的信息RegisteredWorker
    case RegisteredWorker(masterUrl) => {
      println("masterUrl:"+masterUrl)
      //得到信息后处理的业务逻辑
      log.info("获取到Master的回复信息")
      //向Master发送心跳,启动定时任务
      import scala.concurrent.duration._

      //能不能获取到Master的引用呢?不能
      //解决:自己给自己发消息,然后发给Master
      //导入隐式转换,否则报错
      import context.dispatcher
      context.system.scheduler.schedule(0 millis,checkInterval millis,self,SendHeartBeat)

    }
      //接收到自己发给自己消息只知道再给master发消息
    case SendHeartBeat =>{
      log.info("send heartbeat to master")
      //向master发送心跳
      master ! HeartBeat(workerId)
    }
  }
}

object Worker {
  def main(args: Array[String]): Unit = {
    val host = "127.0.0.1"
    val port = 9999
    val masterHost = "127.0.0.1"
    val masterPort = 8888
    val path:String = s"akka.tcp://MasterSystem@$masterHost:$masterPort/user/Master"

    val memory = 23
    val cores = 4

    val str =
      s"""
         |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname = "$host"
         |akka.remote.netty.tcp.port = "$port"
       """.stripMargin
    val config = ConfigFactory.parseString(str)
    //获取ActorSystem,用来创建并监控Actor
    val actorSystem = ActorSystem.apply("WorkerSystem",config)

    actorSystem.actorOf(Props(new Worker(masterHost,masterPort,memory,cores)),"Worker")

  }
}

3. WorkerInfo

package com.ghq.rpc

/**
  * @author : Administrator
  *
  * 保存Worker的信息
  */
class WorkerInfo(val id:String,val memory:Int,val cores:Int) {
  //TODO 上一次心跳时间
  var lastHeartBeatTime:Long = _
}

4. RemoteMessage

package com.ghq.rpc

/**
  * RemoteMessage 需要跨进程通信,必须序列化
  *
  */
trait RemoteMessage extends Serializable

/**
  * Worker -> Master的class
  * 很多Worker需要向Master注册,因此是多例
  *
  * 需要实现RemoteMessage,该消息也需要序列化
  */
case class RegisterWorker(id:String,memory:Int,cores:Int) extends RemoteMessage

/**
  * 已经注册的Worker
  *
  * Master -> Worker
  * @param masterUrl 返回masterUrl
  */
case class RegisteredWorker(val masterUrl:String) extends RemoteMessage

/**
  * 进程内部消息,不需要序列化
  * worker -> self
  */
case object SendHeartBeat

/**
  * worker -> Master 的心跳信息
  * @param workerId
  */
case class HeartBeat(val workerId:String)extends RemoteMessage

/**
  * Master -> Master
  */
case object CheckTimeOutWorker

启动Master,控制台输出:

[INFO] [09/17/2018 14:23:39.374] [main] [akka.remote.Remoting] Starting remoting
[INFO] [09/17/2018 14:23:39.795] [main] [akka.remote.Remoting] Remoting started; listening on addresses :[akka.tcp://[email protected]:8888]
[INFO] [09/17/2018 14:23:39.797] [main] [akka.remote.Remoting] Remoting now listens on addresses: [akka.tcp://[email protected]:8888]
constructor invoked
preStart(): Unit
[INFO] [09/17/2018 14:23:40.011] [MasterSystem-akka.actor.default-dispatcher-5] [akka.tcp://[email protected]:8888/user/Master] hello message
还存活的worker的个数:0
还存活的worker的个数:0

启动Worker

  • master输出结果:
[WARN] [SECURITY][09/17/2018 14:24:00.365] [MasterSystem-akka.remote.default-remote-dispatcher-7] [akka.serialization.Serialization(akka://MasterSystem)] Using the default Java serializer for class [com.ghq.rpc.RegisteredWorker] which is not recommended because of performance implications. Use another serializer or disable this warning using the setting 'akka.actor.warn-about-java-serializer-usage'
还存活的worker的个数:1
还存活的worker的个数:1
还存活的worker的个数:1
还存活的worker的个数:1
还存活的worker的个数:1
还存活的worker的个数:1
还存活的worker的个数:1
还存活的worker的个数:1
还存活的worker的个数:1
还存活的worker的个数:1
  • worker输出结果:
[INFO] [09/17/2018 14:23:59.543] [main] [akka.remote.Remoting] Starting remoting
[INFO] [09/17/2018 14:23:59.897] [main] [akka.remote.Remoting] Remoting started; listening on addresses :[akka.tcp://[email protected]:9999]
[INFO] [09/17/2018 14:23:59.900] [main] [akka.remote.Remoting] Remoting now listens on addresses: [akka.tcp://[email protected]:9999]
向Master 注册Worker的信息,包含了Worker的很多信息
[WARN] [SECURITY][09/17/2018 14:24:00.295] [WorkerSystem-akka.remote.default-remote-dispatcher-6] [akka.serialization.Serialization(akka://WorkerSystem)] Using the default Java serializer for class [com.ghq.rpc.RegisterWorker] which is not recommended because of performance implications. Use another serializer or disable this warning using the setting 'akka.actor.warn-about-java-serializer-usage'
masterUrl:akka.tcp://[email protected]:8888/user/Master
[INFO] [09/17/2018 14:24:00.386] [WorkerSystem-akka.actor.default-dispatcher-4] [akka.tcp://[email protected]:9999/user/Worker] 获取到Master的回复信息
[INFO] [09/17/2018 14:24:00.386] [WorkerSystem-akka.actor.default-dispatcher-4] [akka.tcp://[email protected]:9999/user/Worker] send heartbeat to master
[WARN] [SECURITY][09/17/2018 14:24:00.388] [WorkerSystem-akka.remote.default-remote-dispatcher-5] [akka.serialization.Serialization(akka://WorkerSystem)] Using the default Java serializer for class [com.ghq.rpc.HeartBeat] which is not recommended because of performance implications. Use another serializer or disable this warning using the setting 'akka.actor.warn-about-java-serializer-usage'
[INFO] [09/17/2018 14:24:01.394] [WorkerSystem-akka.actor.default-dispatcher-14] [akka.tcp://[email protected]:9999/user/Worker] send heartbeat to master
[INFO] [09/17/2018 14:24:02.405] [WorkerSystem-akka.actor.default-dispatcher-14] [akka.tcp://[email protected]:9999/user/Worker] send heartbeat to master
[INFO] [09/17/2018 14:24:03.402] [WorkerSystem-akka.actor.default-dispatcher-14] [akka.tcp://[email protected]:9999/user/Worker] send heartbeat to master
[INFO] [09/17/2018 14:24:04.403] [WorkerSystem-akka.actor.default-dispatcher-14] [akka.tcp://[email protected]:9999/user/Worker] send heartbeat to master
[INFO] [09/17/2018 14:24:05.410] [WorkerSystem-akka.actor.default-dispatcher-4] [akka.tcp://[email protected]:9999/user/Worker] send heartbeat to master
[INFO] [09/17/2018 14:24:06.390] [WorkerSystem-akka.actor.default-dispatcher-4] [akka.tcp://[email protected]:9999/user/Worker] send heartbeat to master
[INFO] [09/17/2018 14:24:07.391] [WorkerSystem-akka.actor.default-dispatcher-4] [akka.tcp://[email protected]:9999/user/Worker] send heartbeat to master
[INFO] [09/17/2018 14:24:08.403] [WorkerSystem-akka.actor.default-dispatcher-4] [akka.tcp://[email protected]:9999/user/Worker] send heartbeat to master

猜你喜欢

转载自blog.csdn.net/guo20082200/article/details/82736418