使用scala实现简单的rpc案例

题目:使用scala的actor构建一个简单的RPC调用实例
模仿ResourceManager和NodeManager之间的交互,
1、NodeManager向ResourceManager进行注册(传递的参数是:主机名、内存、CPU、端口)
2、ResourceManager将接收到的消息进行消息匹配,如果是注册消息,将消息保存
3、发送注册请求的response给NodeManager(传递hostsname)
4、NodeManager接收到消息,消息匹配(注册完成消息)
5、NodeManager发送心跳消息给ResourceManager(每隔3秒钟发送一次)(发送nodemanagerId)
6、ResourceManager接收到NodeManager的心跳消息之后将心跳消息存储起来(包含提交的时间,要实时更新)
7、ResourceManager端每隔3秒钟扫描一次心跳列表,如果当前时间减去节点注册心跳时间大于10秒,则从列表删除

分析:
1、首先无论是ResourceManager还是NodeManager在我们的需求中都是通过actor进行交互的
2、构建ResourceManger和NodeManager,并测试简单的通信
3、在NodeManager中主要完成注册和定时发送心跳两个功能,ResourceManager中完成注册信息和心跳信息的保存
以及相应NodeManager请求和宕机节点信息的清除的功能
    a、首先我们通过模式匹配来完成调用对方方法的功能
    b、我们将注册和心跳都使用模式匹配类来传递数据
    c、定义case class RegisterNodeManager来完成NodeManager到ResourceManager注册的任务
    d、定义case class Heartbeat来完成NodeManager向ResourceManager提交心跳的任务
    e、定义case class RegisteredNodeManager来完成ResourceManager向Nodemanager相应的任务
    f、在ResourceManager端使用class NodeManagerInfo存储NodeManager的信息
    g、在ResourceManager端使用 case object CheckTimeOut来做宕机校验的模式匹配,是单例的
    h、在NodeManager端使用case object SendMessage来做心跳消息发送的模式匹配
    i、对于定时调用,使用的是:
        context.system.scheduler.schedule(initialDelay,interval,receiver,message)
            *initialDelay:  FiniteDuration, 多久以后开始执行
            * interval:     FiniteDuration, 每隔多长时间执行一次
            * receiver:     ActorRef, 给谁发送这个消息
            * message:      Any  发送的消息是啥
    j、注意将硬性变量提取出来。
测试简单通信:先启动resourceManager然后在启动nodemanager。

简单的测试:

ResourceManager:

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

class ResourceManager(var hostname:String,var port:Int) extends Actor{


  override def preStart(): Unit = {

  }

  override def receive:Receive = {
    case "hello" => {
      println("received nodemanager's message")
      sender() ! "hi"
    }

  }


}

object ResourceManager{

  def main(args: Array[String]): Unit = {
    var str =
      """
        |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
        |akka.remote.netty.tcp.hostname =localhost
        |akka.remote.netty.tcp.port=19999
      """.stripMargin
    //通过字符串解析创建config
    val config = ConfigFactory.parseString(str)

    //因为要使用actor,所以先构建actorSystem
    val ractorSystem: ActorSystem = ActorSystem("ResourceManagerActorSystem", config)

    //构建resourceManger的actor进行通信
    ractorSystem.actorOf(Props(new ResourceManager("localhost",19999)),"ResourceManagerActor")

  }
}
NodeManager:
import akka.actor.{Actor, ActorSystem, Props}
import com.typesafe.config.ConfigFactory

class NodeManager(var resourceManagerHostName:String,
                  var resourceManagerPort:Int,
                  var memory:Int,var cpu:Int) extends Actor{


  override def preStart(): Unit = {
    var rmRef = context.actorSelection(s"akka.tcp://ResourceManagerActorSystem@localhost:19999/user/ResourceManagerActor")
    rmRef ! "hello"
  }

  override def  receive:Receive = {
    case "hi" =>{
      println("received resourceManager's response")

    }
  }
}

object NodeManager{

  def main(args: Array[String]): Unit = {
    var str =
      """
        |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
        |akka.remote.netty.tcp.hostname =localhost
        |akka.remote.netty.tcp.port=19997
      """.stripMargin
    val config = ConfigFactory.parseString(str)
    val nactorSystem = ActorSystem("NodeManagerActorSystem",config)
    nactorSystem.actorOf(Props(new NodeManager("localhost",19999,120,32)),"NodeManagerActor")
  }
}
结果:在两个类的控制台打印出定义的两句话即可。

接下来将硬性变量提取出来做成常量,并创建模式匹配工具类和对象。

Constant:

object Constant {
    val RMAS = "ResourceManagerActorSystem"
    val RMA = "ResourceManagerActor"
    val NMAS = "NodeManagerActorSystem"
    val NMA = "NodeManagerActor"
}
Message
//匹配NodeManager向ResourceManager注册信息
case class RegisterNodeManager(var nodeManagerId:String,var memory:Int,var cpu:Int)
//匹配ResourceManager响应NodeManager的请求
case class RegisteredNodeManager(var resourceManagerHostName:String)
//匹配NodeManager向ResourceManager发送的心跳信息
case class HeartBeat(var nodeManagerId:String)

//用于resourceManager中存储nodeManager的信息
class NodeManagerInfo(var nodeManagerId:String,var memory:Int,var cpu:Int)

//匹配宕机校验
case object CheckTimeOut
//匹配心跳发送
case object SendMessage
ResourceManager:
package cn.zhao.lianxi

import akka.actor.{Actor, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
import scala.collection.mutable

class ResourceManager(var hostname:String,var port:Int) extends Actor{
  //可变的集合
  private var id2nodeManagerInfo = new mutable.HashMap[String,NodeManagerInfo]()
  private var nodeManagerInfoes = new mutable.HashSet[NodeManagerInfo]()


  override def preStart(): Unit = {
      import scala.concurrent.duration._
      import context.dispatcher  //目前不知道为什么使用
      //一开始就进行检查
      context.system.scheduler.schedule(0 millis, 5000 millis, self, CheckTimeOut)
  }



  override def receive:Receive = {
    //通过偏函数的模式匹配 进行处理NodeManager的注册请求
    case RegisterNodeManager(nodeManagerId,memory,cpu) => {

      //将NodeManager注册时传递过来的参数封装成类
      val nodeManagerInfo = new NodeManagerInfo(nodeManagerId,memory,cpu)
      //将封装好的NodeManager的信息保存在hashmap和hashset中
      //hashmap方便重复提交心跳时存储
      id2nodeManagerInfo.put(nodeManagerId,nodeManagerInfo)
      //hashset方便checkTimeOut时遍历 而且set会覆盖旧的记录
      nodeManagerInfoes += nodeManagerInfo


      //响应nodemanager的注册请求  返回resourceManager的hostname和端口的组合
      sender() ! RegisteredNodeManager(hostname+":"+port)
    }

    case HeartBeat(nodeManagerId) => {
      val currentTime = System.currentTimeMillis()
      val nodeManagerInfo = id2nodeManagerInfo(nodeManagerId)
      //为了后面的宕机校验 这里要在心跳注册添加最新的时间
      //这就要求nodeManagerInfo中含有类似time,这里命名为lastHeartBeatTime
      nodeManagerInfo.lastHeartBeatTime = currentTime
      //本来不用再次保存 因为这里使用的是引用
      //但是为了清晰易懂还是在手动保存
      id2nodeManagerInfo(nodeManagerId)=nodeManagerInfo
      //在hashSet中进行更新
      nodeManagerInfoes += nodeManagerInfo
    }

    //检验是不是过期
    case CheckTimeOut => {
      val currentTime = System.currentTimeMillis()
      nodeManagerInfoes.filter(nm => currentTime - nm.lastHeartBeatTime > 10000)
        .foreach(downed => {
          //从hashMap和hashSet中移除宕机的NodeManager的注册信息
          nodeManagerInfoes -= downed
          id2nodeManagerInfo.remove(downed.nodeManagerId)

        })
      println("健康的节点数量:"+nodeManagerInfoes.size)
    }

  }


}

object ResourceManager{
  def main(args: Array[String]): Unit = {
    val RESOURCEMANAGER_HOSTNAME = args(0)
    val RESOURCEMANAGER_PORT = args(1).toInt
    var str =
      s"""
        |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
        |akka.remote.netty.tcp.hostname =$RESOURCEMANAGER_HOSTNAME
        |akka.remote.netty.tcp.port=$RESOURCEMANAGER_PORT
      """.stripMargin
    //通过字符串解析创建config
    val config = ConfigFactory.parseString(str)

    //因为要使用actor,所以先构建actorSystem
    val ractorSystem: ActorSystem = ActorSystem(Constant.RMAS, config)

    //构建resourceManger的actor进行通信
    ractorSystem.actorOf(Props(new ResourceManager(RESOURCEMANAGER_HOSTNAME,RESOURCEMANAGER_PORT)),Constant.RMA)

  }
}

NodeManager:

package cn.zhao.lianxi

import java.util.UUID

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

import scala.language.postfixOps

//resourceManagerHostName ==> rmHostName
//resourceManagerPort ==> rmPort
class NodeManager(var rmHostName:String,
                  var rmPort:Int,
                  var memory:Int,var cpu:Int) extends Actor{

  //ActorSelection在伴生对象中可以看出类型,而且我们也可以通过.var+tab键查看
  // _ 是占位符,可以自动赋值
  var rmRef:ActorSelection = _

  var nodeManagerId:String = _

  override def preStart(): Unit = {
    rmRef = context.actorSelection(s"akka.tcp://${Constant.RMAS}@${rmHostName}:${rmPort}/user/${Constant.RMA}")
    nodeManagerId = UUID.randomUUID().toString
    rmRef ! RegisterNodeManager(nodeManagerId,memory,cpu)
  }

  override def  receive:Receive = {
    //通过模式匹配获取resourceManager返回的注册请求的响应
    case RegisteredNodeManager(resourceManagerHostName) =>{


      println(resourceManagerHostName)

      //发送心跳
      //根据源码中得到首先给自己发送一次心跳(原因目前不知,以后补上)
      import scala.concurrent.duration._
      import context.dispatcher
      //
      context.system.scheduler.schedule(0 millis,4000 millis,self,SendMessage)
    }

    case SendMessage =>{
      import scala.concurrent.duration._
      import context.dispatcher
      //rmRef要提升为全局变量,因为给自己发送了一次 所以不提升代表的就是本身
      //将nodeManageId提升为全局变量 在这里才可以使用
      rmRef ! HeartBeat(nodeManagerId)

    }
  }
}

object NodeManager{

  def main(args: Array[String]): Unit = {
    val RESOURCEMANAGER_HOSRNAME = args(0)
    val RESOURCEMANAGER_PORT = args(1).toInt
    val NODEMANAGER_HOSTNAME = args(2)
    val NODEMANAGER_PORT = args(3).toInt
    val RESOURCEMANAGER_MEMORY = args(4).toInt
    val RESOURCEMANAGER_CPU = args(5).toInt
    var str =
      s"""
        |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
        |akka.remote.netty.tcp.hostname =$NODEMANAGER_HOSTNAME
        |akka.remote.netty.tcp.port=$NODEMANAGER_PORT
      """.stripMargin
    val config = ConfigFactory.parseString(str)
    val nactorSystem = ActorSystem(Constant.NMAS,config)
    nactorSystem.actorOf(Props(new NodeManager(RESOURCEMANAGER_HOSRNAME,RESOURCEMANAGER_PORT,
                                                     RESOURCEMANAGER_MEMORY,RESOURCEMANAGER_CPU)),Constant.NMA)
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值