题目:使用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)
}
}