RPC通信框架
1. 启动Master,然后内部会启动一个定时器,定期检测超时的worker,将超时的worker删除
2. Master接收到了worker发送的注册信息 , 将信息保存到内存,也可以持久化到磁盘或者zooker
3. 启动worker,跟Master简历网络连接,将自己的信息(id,内存,cpu等信息)注册给Master
4. Master向worker发送一个注册成功的消息
5. Worker接收到Master的消息后,然后启动一个定时器,向Master定期发送心跳消息,发送心跳的消息是为了报活
运行时传入参数(右上角锤子旁边的 Edit Configurations) 列如: Master: localhost 8888 Worker: localhost 8888 localhost 9999 4
也可以打包在windows上或者linux上执行
本地: java -cp 包名 master的全路径 localhost 8888
java -cp 包名 worker的全路径 localhost 8888 localhost 9999 4096 4
linux: java -cp 包名 master的全路径 192.168.133.3 8888
java -cp 包名 worker的全路径 192.168.133.3 8888 192.168.133.4 9999 4096 4
package cn.doit.rpc
import java.util.concurrent.TimeUnit
import akka.actor.{Actor, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
import scala.collection.mutable
import scala.concurrent.duration._
/*
1. 启动Master,然后内部会启动一个定时器,定期检测超时的worker,将超时的worker删除
2. Master接收到了worker发送的注册信息 , 将信息保存到内存,也可以持久化到磁盘或者zooker
3. 启动worker,跟Master建立网络连接,将自己的信息(id,内存,cpu等信息)注册给Master
4. Master向worker发送一个注册成功的消息
5. Worker接收到Master的消息后,然后启动一个定时器,向Master定期发送心跳消息,发送心跳的消息是为了报活
*/
//要使Master编程一个Actor 需要它实现Actor接口
class Master extends Actor{
val CHECK_INTERVAL = 15000
//将接收到的worker的信息保存在一个map集合中 一worker的id为键
val idToWorker = new mutable.HashMap[String, WorkerInfo]()
//类加载之后,接收消息之前开启节点检测 每隔一段时间进行检测,并剔除连接超时的节点
override def preStart(): Unit = {
import context.dispatcher
//注册好之后向自己发送开始检测的信号,每隔15秒执行一次
context.system.scheduler.schedule(0.millisecond, CHECK_INTERVAL.millisecond, self, CheckTimeOutWorker)
}
//用来接收消息的
override def receive: Receive = {
//接收到开始检查超时节点的消息
case CheckTimeOutWorker => {
//过滤 将连接超时的节点提取出来
val deadWorkers: Iterable[WorkerInfo] = idToWorker.values.filter(w => System.currentTimeMillis() - w.lastHeartBeatTime > 20000)
//遍历
deadWorkers.foreach(dw => {
//将超时的节点剔除
idToWorker -= dw.workerId
})
//打印当前正在工作的节点的个数
println(s"current alive worker size: ${idToWorker.size}")
}
//接收来自worked的消息
case RegisterWorker(workerId, memory, cores) => {
println(s"workerId: $workerId, memory: $memory , cores: $cores")
//将接收到的消息保存在集合中
val workerInfo = new WorkerInfo(workerId, memory, cores)
workerInfo.lastHeartBeatTime = System.currentTimeMillis()
//将信息保存到内存中
idToWorker(workerId) = workerInfo
//返回Worker一个注册成功的消息
sender() ! RegisteredWorker
}
//接收来自worker的心跳
case HeartBeat(workerId) => {
//查看该id是否注册,注册则取出对应的WorkerInfo
if (idToWorker.contains(workerId)) {
//取出workerInfo
val workerInfo = idToWorker(workerId)
//更新上一次心跳的时间
workerInfo.lastHeartBeatTime = System.currentTimeMillis()
}
}
}
}
object Master {
val MASTER_ACTOR_SYSTEM_NAME = "MasterActorSystem"
val MASTER_ACTOR_NAME = "MasterActor"
def main(args: Array[String]): Unit = {
//配置文件信息
val host = args(0)
val port = args(1)
val configStr =
s"""
|akka.actor.provider = "akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname = $host
|akka.remote.netty.tcp.port = $port
|""".stripMargin
//获取配置信息对象
val config = ConfigFactory.parseString(configStr)
//创建一个ActorSystem(单例的)
val masterActorSystem = ActorSystem(MASTER_ACTOR_SYSTEM_NAME, config)
//使用ActorSystem创建Actor
masterActorSystem.actorOf(Props[Master], MASTER_ACTOR_NAME)
}
}
package cn.doit.rpc
import java.util.UUID
import java.util.concurrent.TimeUnit
import akka.actor.{Actor, ActorSelection, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
import scala.concurrent.duration._
class Worker(val masterHost:String, val masterPort:String, val workerMemory:Int, val workerCores:Int) extends Actor {
val HEARTBEAT_INTERVAL = 10000
//与master的连接,设置为成员变量,获取一次复制后其他都可以直接使用
var masterRef: ActorSelection = _
//创建属于worker的自己的id
var workerId = UUID.randomUUID().toString
//在构造方法之后,receive之前执行一定会调用一次
override def preStart(): Unit = {
//与Master建立连接
masterRef = context.actorSelection(s"akka.tcp://${Master.MASTER_ACTOR_SYSTEM_NAME}@$masterHost:$masterPort/user/${Master.MASTER_ACTOR_NAME}")
//给master发送消息
masterRef ! RegisterWorker(workerId, workerMemory, workerCores)
}
//用来接收消息的
override def receive: Receive = {
//接收到自己发送的心跳 在里面进行判断 之后发送心跳到master
case SendHeartBeat => {
// if (){
//
// }
//向Master发送心跳
masterRef ! HeartBeat(workerId)
}
//接收master返回的注册成功的消息
case RegisteredWorker => {
//注册成功向master定时发送心跳
//启动一个定时器 scheduler调度器 延迟0秒,时长10秒
import context.dispatcher
context.system.scheduler.schedule(Duration(0, TimeUnit.MILLISECONDS), HEARTBEAT_INTERVAL.millisecond, self, SendHeartBeat)
}
}
}
object Worker {
val WORKER_ACTOR_SYSTEM_NAME = "WorkerActorSystem"
val WORKER_ACTOR_NAME = "WorkerActor"
def main(args: Array[String]): Unit = {
val masterHost = args(0)
val masterPort = args(1)
val workerHost = args(2)
val workerPort = args(3)
val workerMemory = args(4).toInt
val workerCores = args(5).toInt
//设置配置文件的信息
val host = "localhost"
val port = 9999
val configStr =
s"""
|akka.actor.provider = "akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname = $workerHost
|akka.remote.netty.tcp.port = $workerPort
|""".stripMargin
//获取配置信息对象
val config = ConfigFactory.parseString(configStr)
//创建一个ActorSystem(单例)
val workerActorSystem = ActorSystem(WORKER_ACTOR_SYSTEM_NAME, config)
//通过ActorSystem创建Actor
workerActorSystem.actorOf(Props(new Worker(masterHost,masterPort,workerMemory,workerCores)), WORKER_ACTOR_NAME)
}
}
package cn.doit.rpc
//检查超时时间的
case object CheckTimeOutWorker
//发送心跳的
case class HeartBeat(workerId: String)
//Master返回给Worker的注册成功的消息
case object RegisteredWorker
//worker中自己给自己发送信息,字节给自己发麻烦,需要转接一下
case object SendHeartBeat
//给master发送消息
case class RegisterWorker(workerId: String, memory: Int, cores: Int)
package cn.doit.rpc
//用来封装worker数据的
class WorkerInfo(val workerId:String, var memory:Int, var cores:Int) {
//最后一次发送心跳的时间
var lastHeartBeatTime: Long = _
}