Akka事件驱动——模拟Spark注册、心跳

26 篇文章 3 订阅
12 篇文章 0 订阅

Akka事件驱动——模拟Spark注册、心跳

Akka简介

  • 对于Netty封装的网络通信框架
  • 基于事件驱动模型:异步、非阻塞、高性能
  • Actor的并发模型,单个线程内可以共存多个Actor,不需要为每个连接维护一个线程
  • 由Scala编写

Actor事件驱动示意图

事件驱动示意图

Spark注册、心跳模拟

  • Spark早期使用Akka进行通信,而在Spark2后完全由Netty重新实现此部分。新版的RpcEndpoint与RpcEndpointRef分别对应Actor与ActorRef,通信机制仍然是事件驱动。模拟此部分,有助于理解Spark通信机制,以及阅读Spark源码。
  • Maven依赖 pom.xml
<dependency>
	<groupId>org.scala-lang</groupId>
	<artifactId>scala-library</artifactId>
	<version>2.11.8</version>
</dependency>
<dependency>
    <groupId>com.typesafe.akka</groupId>
    <artifactId>akka-actor_2.11</artifactId>
    <version>2.4.17</version>
</dependency>
<dependency>
    <groupId>com.typesafe.akka</groupId>
    <artifactId>akka-remote_2.11</artifactId>
    <version>2.4.17</version>
</dependency>
  • 传输的消息协议 MessageProtocol.scala
package com.skey.litespark.common

// 用于worker向master发送注册的消息
case class RegisterWorkerInfo(id: String, cores: Int, mem: Int)

// 用于master向worker发送注册成功的消息
case object RegisteredWorkerInfo

// 用于worker向master发送心跳
case class HeartBeat(id: String)

// 用于worker内部触发发送心跳的动作
case object SendHeartBeat

// 用于master内部启动worker心跳检测
case object StartTimeOutWorker

// 用于master内部移除超时的worker
case object RemoveTimeOutWorker

// master内部维护的worker信息
class WorkerInfo(val id: String, cores: Int, mem: Int) {
  var lastHeartBeat: Long = System.currentTimeMillis()
}
  • Master
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import com.skey.litespark.common._
import com.typesafe.config.ConfigFactory

import scala.collection.mutable
import scala.concurrent.duration._

/**
  * Master
  *
  * @author ALion
  * @version 2019/4/21 11:43
  */
class LiteMaster extends Actor {

  /**
   * 存储worker的信息
   */
  val workerMap = mutable.HashMap[String, WorkerInfo]()

  private val MAX_DELAY = 60 * 1000

  /**
   * 收到消息后会调用receive,然后匹配对应的消息,做出不同的响应
   * @return
   */
  override def receive: Receive = {
    case "start" =>
      // 启动
      println("Master 启动成功!")
      self ! StartTimeOutWorker
    case StartTimeOutWorker =>
      // 定时检测worker心跳
      println("启动定时检测Worker心跳任务")
      import context.dispatcher
      context.system.scheduler.schedule(0 millis, 30000 millis, self, RemoveTimeOutWorker)
    case RemoveTimeOutWorker =>
      // 移除心跳超时的worker
      val nowTime = System.currentTimeMillis()
      workerMap.values
        .filter(nowTime - _.lastHeartBeat > MAX_DELAY)
        .foreach { worker =>
          workerMap.remove(worker.id)
          println(s"移除了worker(id=${worker.id}), 因为心跳超时!")
        }
    case RegisterWorkerInfo(id, cores, mem) =>
      // 注册worker到master
      if (!workerMap.contains(id)) {
        workerMap.put(id, new WorkerInfo(id, cores, mem))
        // 注册成功后,向发送消息的worker返回消息
        sender() ! RegisteredWorkerInfo
        println(s"worker(id=$id) 注册成功!")
      }
    case HeartBeat(id) =>
      // 收到worker的心跳后,更新本地维护的worker心跳时间
      workerMap.get(id) match {
        case Some(workerInfo) =>
          workerInfo.lastHeartBeat = System.currentTimeMillis()
          println(s"更新 worker(id=$id) 心跳时间=${workerInfo.lastHeartBeat}")
        case None => println(s"worker(id=$id) 不存在!")
      }
  }
}

object LiteMaster {

  def main(args: Array[String]): Unit = {
    // 加载master配置
    val config = ConfigFactory.load("master.conf")
    // 创建Actor系统
    val actorSystem = ActorSystem("Master", config)
    // 注册LiteMaster,并获取master引用
    val masterRef: ActorRef = actorSystem.actorOf(Props[LiteMaster], "LiteMaster")

    // 向master自己发送一个启动消息
    masterRef ! "start"
  }

}
  • Worker
import akka.actor.{Actor, ActorRef, ActorSelection, ActorSystem, Props}
import com.skey.litespark.common.{HeartBeat, RegisterWorkerInfo, RegisteredWorkerInfo, SendHeartBeat}
import com.typesafe.config.ConfigFactory

import scala.concurrent.duration._

/**
  * Worker
  *
  * @author ALion
  * @version 2019/4/21 11:43
  */
class LiteWorker01(masterHost: String, masterPort: Int) extends Actor {

  /**
   * worker自己的唯一id
   */
  val id: String = java.util.UUID.randomUUID().toString

  var masterActorRef: ActorSelection = _

  /**
   * 在启动时先调用,此处用于获取master的引用
   */
  override def preStart(): Unit = {
    masterActorRef = context.actorSelection(
      s"akka.tcp://Master@$masterHost:$masterPort/user/LiteMaster")
  }

  override def receive: Receive = {
    case "start" =>
      // 启动
      println("Worker 启动成功!")
      // 向master发送注册消息
      masterActorRef ! RegisterWorkerInfo(id, 8, 8 * 1024)
    case RegisteredWorkerInfo =>
      // 收到master注册的成功的回复
      println(s"worker(id=$id) 注册成功!")
      import context.dispatcher
      // 定时发送心跳
      context.system.scheduler.schedule(0 millis, 10000 millis, self, SendHeartBeat)
    case SendHeartBeat =>
      // 发送心跳
      println("发送 HeartBeat ---> Master")
      masterActorRef ! HeartBeat(id)
  }

}

object LiteWorker01 {

  def main(args: Array[String]): Unit = {
    // 读取master的配置,因为等会儿要连接master
    val masterConfig = ConfigFactory.load("master.conf")
    val masterHost = masterConfig.getString("akka.remote.netty.tcp.hostname")
    val masterPort = masterConfig.getInt("akka.remote.netty.tcp.port")

    println(s"masterHost = ${masterHost}")

    // 读取worker的配置
    val config = ConfigFactory.load("worker01.conf")
    // 创建Actor系统
    val actorSystem = ActorSystem("Worker", config)
    // 注册LiteWorker01,并获取worker引用
    val workerRef: ActorRef = actorSystem.actorOf(
      Props(new LiteWorker01(masterHost, masterPort)), "LiteWorker01")
    // 向worker自己发送一个启动消息
    workerRef ! "start"
  }

}

  • worker一共编写了Worker01、Worker02、Worker03,用于测试。代码一样,此处不做展示。
  • 配置 resources/master.conf
include "common.conf"

akka {

  remote {
//    enabled-transports = ["akka.remote.netty.tcp"]
    netty.tcp {
      hostname = 127.0.0.1
      port = 32100
    }
//    log-sent-messages = on
//    log-received-messages = on
  }

}
  • 配置 resources/worker01.conf
include "common.conf"

akka {

  remote {
//    enabled-transports = ["akka.remote.netty.tcp"]
    netty.tcp {
      hostname = 127.0.0.1
      port = 32001
    }
//    log-sent-messages = on
//    log-received-messages = on
  }

}
  • 配置 resources/common.conf
akka {

  actor {

    provider = "akka.remote.RemoteActorRefProvider"

    serializers {
      java = "akka.serialization.JavaSerializer"
      //      proto = "akka.serialization.ProtobufSerializer"
    }

    serialization-bindings {
      "java.lang.String" = java
      "com.skey.litespark.common.RegisterWorkerInfo" = java
      "com.skey.litespark.common.RegisteredWorkerInfo$" = java
      "com.skey.litespark.common.SendHeartBeat$" = java
      "com.skey.litespark.common.HeartBeat" = java
      "com.skey.litespark.common.StartTimeOutWorker" = java
      "com.skey.litespark.common.RemoveTimeOutWorker" = java
    }

  }

}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值