Akka事件驅動——模擬Spark註冊、心跳

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
    }

  }

}
發佈了143 篇原創文章 · 獲贊 52 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章