利用Actor来实现一个简单的模拟Spark中master和worker的通讯

我们先设计两个Actor,一个是Master 另一个Worker
Worker :

  1. 必须像master发送心跳信息,所以我要早Worker中获取Master的引用
  2. 第一次通讯的时候我应该先到Master注册自己的信息,方便Master来调度
  3. 定时向master发送心跳信息,防止Worker节点崩溃,Master却没有感知到的情况
    Master
  4. 接收Worker发送的信息存到一个Map里面
  5. 检测客户端的状态,将异常的节点从Map里面移除
  6. 接收客户端的心跳信息,加入接收到心跳的时间,这里Actor是一个单线程,所以就没有必要加锁,向worker发送注册成功的信息
    现在两端的工作清楚了,就可以进入到编程阶段了,我为了方便进行通讯,我们可以使用类而不是利用一段字符串来进行,所以我先声明在通讯中所需要的样例类
package com.ly.scala.Test5_Spard
/**
 * work-->master
 */
//worker 向master注册自己
case class RegisterWorkerInfo(id :String,core :Int,ram: Int)

//worker给master发送心跳信息
case class HearBeat(id:String)  //发送服务器的 用 serverActorRef -》服务器地址 ip,端口 ,服务名

//worker 发送给自己的消息 告诉自己说要开始周期性向 master发送心跳信息
case object SendHeartBeat //发送给自己 所以要用自己的 actorRef
/**
 * master -> worker
 */
// master 向 worker发送注册成功消息
case object RegisterWorkerInfo
//master 发送给自己发送一个检查超时worker的信息 并启动一个调度器 scheduler 周期检查删除超时worker
case object CheckTimeOutWorker

//master 发送给自己的消息 删除超时的worker
case object RemoveTimeOutWorker
//存储 worker信息的类 
class WorkerInfo(val id:String,core :Int,ram :Int){
  var lastHeartBeatTime:Long=_ //上一次 心跳记录的时间
  override def toString: String = id +"\tcpu:"+core+",mem:"+ram
}

开始写Master层 各个代码我都做类比较详细的解释,可以直接阅读代码

package com.ly.scala.Test5_Spard

import akka.actor.{Actor, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
import scala.concurrent.duration._
class SparkMaster extends Actor{
  //存储 worker注册过来的信息
  val id2WorkerInfo=collection.mutable.HashMap[String,WorkerInfo]()
  //接收另一个actor的信息
  override def receive: Receive = {
    //收到 worker注册过来的信息 提取      这里是上面步骤的第4步,注意样例类
    case RegisterWorkerInfo(wkId,core,ram)=>{
      //将 worker的信息存储起来 一起存储到HashMap
      if (!id2WorkerInfo.contains(wkId)){
      //声明一个工作节点的信息,就是存储Worker工作节点的信息,这就是Actor的好处,两个Actor是单线程操作,所以不必担心出现线程争抢的问题
        val workerInfo=new WorkerInfo(wkId,core,ram)
        id2WorkerInfo +=((wkId,workerInfo))//将 worker发送过的消息存起来
        //master存储完 worker注册数据之后 Master告诉worker说你已经注册成功
        // sender()  当前接收的这条数据的发送者 Worker的引用 因为Actor在发送消息的时候会将自己的引用也传过去
        sender() ! RegisterWorkerInfo //此时worker会收到注册成功消息
      }
    }
      //接收到心跳信息   这是第6步
    case HearBeat(wkId)=>{
      //master 收到worker的心跳信息之后 更新worker的上一次心跳信息
      val workerInfo=id2WorkerInfo(wkId)
      //更改心跳时间
      val currentTime=System.currentTimeMillis()
      workerInfo.lastHeartBeatTime=currentTime
    }
      //检查客户端状态 CheckTimeOutWorker 第5步 采用轮询的策略检测异常的工作节点
    case CheckTimeOutWorker=>{
      import context.dispatcher //使用调度器的时候必须要导入 dispather 
      //这个调度器的作用是每隔6秒中清楚一次出现异常的节点
      context.system.scheduler.schedule(0 millis,6000 millis,self,RemoveTimeOutWorker)
    }
      //检测超时的节点 承接第5步
    case RemoveTimeOutWorker=>{
      //将 hashMap入所有的value都拿出开 查看当前 时间和上一次心跳时间的差是 3000 这是work节点向master发送信息的时间间隔
      val workerInfo=id2WorkerInfo.values
      val currenttime=System.currentTimeMillis()
      //过滤掉超时的节点
      workerInfo.filter(wkInfo=>currenttime-wkInfo.lastHeartBeatTime>3000).foreach(wk=>id2WorkerInfo.remove(wk.id))
      println(s"=====还剩${id2WorkerInfo.size} 存活的worker======")
      workerInfo.foreach(println)
      println("============================")
    }
  }
}
object SparkMaster {
  private var name=""
  private val age=100
  def main(args: Array[String]): Unit = {
    var host="localhost"
    var port ="10000"
    var masterName="master1"
//这里是在命令行传参
    if(args.length==3){
      host=args(0)
      port=args(1)
      masterName=args(2)
    }
    val config=ConfigFactory.parseString(
      s"""
         |akka.actor.provider="akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname=$host
         |akka.remote.netty.tcp.port=$port
         |""".stripMargin
    )
    //指定 ip端口
    val actorSystem=ActorSystem("sparkMaster",config)
    //注意 这里创建了一个名字为 robot_server 的服务器 Actor发的引用 这里是可以创建多个的 名字不同的接口
    val masterActorRef=actorSystem.actorOf(Props[SparkMaster],masterName)
    //发送消息给自己 开始检监控work节点是否注册
    masterActorRef ! CheckTimeOutWorker

  }
}

==worker=:注意,worker端第一次发起通讯也就是建立连接需要之名是与那个Actor的连接,所以要有Master的url 详情请看代码

package com.ly.scala.Test5_Spard

import java.util.UUID

import akka.actor.{Actor, ActorSelection, ActorSystem, Props}
import com.ly.scala.Test4_Spart.SparkWorker
import com.typesafe.config.ConfigFactory
import scala.concurrent.duration._
class SparkWorker(masterUrl:String) extends Actor{
  //这里很关键 声明Master的引用对象 也就是获取Master的代理 这样的话我可以发送第一条信息 这里对应步骤一
  var masterProxy:ActorSelection= _
  val workId=UUID.randomUUID().toString //客户端的id
  //生命周期函数 这个函数在最前面调用
  override def preStart(): Unit = {
  //根据masterUrl获取到master的代理
    masterProxy=context.actorSelection(masterUrl)
  }
  override def receive: Receive = {
  //worker开始工作
    case "started" => {
      //自己已就绪
      //向master注册自己的信息 id,core,ram
      //TODO:获取自己的cpu和内存
      println("客户端"+workId+"启动.....")
      //这里步骤二 开始建立与Master的连接了
      masterProxy ! RegisterWorkerInfo(workId,4,32*1024)//此时向 master发送注册信息
    }
    //步骤三,当Master接收注册成功返回给Worker的注册成功
    case RegisterWorkerInfo=>{//master 通过响应发送给自己注册成功信息 ,接收、这个信息之后则定时发送心跳信息给服务器
      //worker 启动一个定时器 定时向 master发送心跳
      import context.dispatcher //因为 schedule中需要参数 implicit executor
      context.system.scheduler.schedule(0 millis,1500 millis,self,SendHeartBeat)

    }
    //开始发送心跳信息
    case SendHeartBeat =>{
      //开始向 master发送心跳了
      println(s"==========$workId 发送心跳===========")
      //向Master发送心跳信息
      masterProxy ! HearBeat(workId)//此时master就会接收到信息
    }
  }
}
object SparkWorker{
  def main(args: Array[String]): Unit = {
    var host="localhost"
    var port ="10002"
    //工作节点的名字
    var workName="spark_worker"
    //master节点的地址
    var  masterURL="akka.tcp://sparkMaster@localhost:10000/user/master1"//获取Master代理的地址
    //检验命令行参数
    if(args.length==4) {
      host = args(0)
      port = args(1)
      workName = args(2)
      masterURL = args(3)
    }
    val config=ConfigFactory.parseString(
      s"""
         |akka.actor.provider="akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname=$host
         |akka.remote.netty.tcp.port=$port
         |""".stripMargin
    )
    //指定 ip端口
    val actorSystem: ActorSystem =ActorSystem("sparkWorker",config)
    //注意 这里创建了一个名字为 robot_server 的服务器 Actor发的引用 这里是可以创建多个的 名字不同的接口
    val workerActorRef=actorSystem.actorOf(Props(new SparkWorker(masterURL)),workName)
    //worker发送给自己的一条信息 ,告诉自己开始建立与Master的连接
    workerActorRef ! "started"
  }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值