学习Saprk的源代码,那么对Akka的通信原理一定要熟悉,我们通过Akka模拟Spark Master和Worker之间的通信过程,从而深入Spark的Master和Worker的通讯机制。学习Spark Master和Worker如何通信之前需要了解 Akka网络编程案例.
案例意义
- 深入理解Spark的Master和Worker的通讯机制。
- 深入理解Spark的底层源码。
- 了解Spark源码,方便对Spark进行二次开发
需求分析
- Worker启动向Master注册,Worker在Master中注册成功后,回复Worker注册成功。
- Worker定时向Master发送心跳。
- Master接收到Worker的心跳后,更新Worker的最近一次心跳时间。
- Master启动定时任务,定时检测注册的Worker中有哪些没有更新心跳,如Worker没有更新心跳,将其从hashMap中删除。
Spark Master和Worker通信案例
Master代码
import akka.actor.{Actor, ActorSystem, Props}
import akka.sparkmasterworker.common._
import com.typesafe.config.ConfigFactory
import scala.collection.mutable
import scala.concurrent.duration._
class SparkMaster extends Actor {
//定义个hm,管理workers
val workers = mutable.Map[String, WorkerInfo]()
override def receive: Receive = {
case "start" => {
println("master服务器启动了...")
//这里开始。。
self ! StartTimeOutWorker
}
case RegisterWorkerInfo(id, cpu, ram) => {
//接收到worker注册信息
if (!workers.contains(id)) {
//创建WorkerInfo 对象
val workerInfo = new WorkerInfo(id, cpu, ram)
//加入到workers
workers += ((id, workerInfo))
println("服务器的workers=" + workers)
//回复一个消息,说注册成功
sender() ! RegisteredWorkerInfo
}
}
case HeartBeat(id) => {
//更新对应的worker的心跳时间
//1.从workers取出WorkerInfo
val workerInfo = workers(id)
workerInfo.lastHeartBeat = System.currentTimeMillis()
println("master更新了 " + id + " 心跳时间...")
}
case StartTimeOutWorker => {
println("开始了定时检测worker心跳的任务")
import context.dispatcher
//说明
//1. 0 millis 不延时,立即执行定时器
//2. 9000 millis 表示每隔9秒执行一次
//3. self:表示发给自己
//4. RemoveTimeOutWorker 发送的内容
context.system.scheduler.schedule(0 millis, 9000 millis, self, RemoveTimeOutWorker)
}
//对RemoveTimeOutWorker消息处理
//这里需求检测哪些worker心跳超时(now - lastHeartBeat > 6000),并从map中删除
case RemoveTimeOutWorker => {
//首先将所有的 workers 的 所有WorkerInfo
val workerInfos = workers.values
val nowTime = System.currentTimeMillis()
//先把超时的所有workerInfo,删除即可
workerInfos.filter(workerInfo => (nowTime - workerInfo.lastHeartBeat) > 6000)
.foreach(workerInfo => workers.remove(workerInfo.id))
println("当前有 " + workers.size + " 个worker存活的")
}
}
}
object SparkMaster {
def main(args: Array[String]): Unit = {
val host = "127.0.0.1"
val port = 9999
val name = "MasterRef"
//先创建ActorSystem
val config = ConfigFactory.parseString(
s"""
|akka.actor.provider="akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname=${host}
|akka.remote.netty.tcp.port=${port}
""".stripMargin)
val sparkMasterSystem = ActorSystem("SparkMaster", config)
//创建SparkMaster -actor
val sparkMasterRef = sparkMasterSystem.actorOf(Props[SparkMaster], s"${name}")
//启动SparkMaster
sparkMasterRef ! "start"
}
}
Worker代码
import akka.actor.{Actor, ActorSelection, ActorSystem, Props}
import akka.sparkmasterworker.common.{HeartBeat, RegisterWorkerInfo, RegisteredWorkerInfo, SendHeartBeat}
import com.typesafe.config.ConfigFactory
import scala.concurrent.duration._
class SparkWorker(masterHost:String,masterPort:Int,masterName:String) extends Actor{
//masterProxy是Master的代理/引用ref
var masterPorxy :ActorSelection = _
val id = java.util.UUID.randomUUID().toString
override def preStart(): Unit = {
println("preStart()调用")
//初始化masterPorxy
masterPorxy = context.actorSelection(s"akka.tcp://SparkMaster@${masterHost}:${masterPort}/user/${masterName}")
println("masterProxy=" + masterPorxy)
}
override def receive:Receive = {
case "start" => {
println("worker启动了")
//发出一个注册消息RegisterWorkerInfo(id,cpu,ram)
masterPorxy ! RegisterWorkerInfo(id, 16, 16 * 1024)
}
case RegisteredWorkerInfo => {
println("workerid= " + id + " 注册成功~")
//当注册成功后,就定义一个定时器,每隔一定时间,发送SendHeartBeat给自己
import context.dispatcher
//说明
//1. 0 millis 不延时,立即执行定时器
//2. 3000 millis 表示每隔3秒执行一次
//3. self:表示发给自己,也即是获取到自己的ActorRef引用
//4. SendHeartBeat 发送的内容
context.system.scheduler.schedule(0 millis, 3000 millis, self, SendHeartBeat)
}
case SendHeartBeat =>{
println("worker = " + id + "给master发送心跳")
masterPorxy ! HeartBeat(id)
}
}
}
object SparkWorker {
def main(args: Array[String]): Unit = {
val workerHost = "127.0.0.1"
val workerPort = 8889
val workerName = "WorkerRef"
val masterHost = "127.0.0.1"
val masterPort = 9999
val masterName = "MasterRef"
val config = ConfigFactory.parseString(
s"""
|akka.actor.provider="akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname=${workerHost}
|akka.remote.netty.tcp.port=${workerPort}
""".stripMargin)
//创建ActorSystem
val sparkWorkerSystem = ActorSystem("SparkWorker",config)
//创建SparkWorker 的引用/代理
val sparkWorkerRef = sparkWorkerSystem.actorOf(Props(new SparkWorker(masterHost, masterPort.toInt,masterName)), s"${workerName}")
//启动actor
sparkWorkerRef ! "start"
}
}
- 说明
- 首先Master启动,然后Worker启动,Worker启动后就使用context.actorSelection()方法获取到MasterRef,然后开始向Master注册信息(RegisterWorkerInfo)。
- Master 接收到RegisterWorkerInfo通过hashMap添加Worker的信息 ,创建WorkerInfo 对象
val workerInfo = new WorkerInfo(id, cpu, ram),将信息加入到HashMap中workers += ((id, workerInfo))。 - 添加完workerInfo后,Master向Worker已注册完成RegisteredWorkerInfo,Worker启用定时器,定时的向Master发送自己的心跳信息(masterPorxy ! HeartBeat(id))。
- Master根据Worker的id更新workerInfo的最新的心跳时间。
- Master启动时会定时的调用RemoveTimeOutWorker来检测超时没有及时更新心跳的Worker,把没有及时更新心跳Worker移除hashMap。
样例类
// worker注册信息 //MessageProtocol.scala
case class RegisterWorkerInfo(id: String, cpu: Int, ram: Int)
// 这个是WorkerInfo, 这个信息将来是保存到master的 hm(该hashmap是用于管理worker)
// 将来这个WorkerInfo会扩展(比如增加worker上一次的心跳时间)
class WorkerInfo(val id: String, val cpu: Int, val ram: Int) {
var lastHeartBeat : Long = System.currentTimeMillis()
}
// 当worker注册成功,服务器返回一个RegisteredWorkerInfo 对象
case object RegisteredWorkerInfo
//worker每隔一定时间由定时器发给自己的一个消息
case object SendHeartBeat
//worker每隔一定时间由定时器触发,而向master发现的协议消息
case class HeartBeat(id: String)
//master给自己发送一个触发检查超时worker的信息
case object StartTimeOutWorker
// master给自己发消息,检测worker,对于心跳超时的.
case object RemoveTimeOutWorker
结果
- Worker结果
- Master 结果