使用 Akka 实现 Master 与 Worker 之间的通信

本文档详细介绍了如何利用 Akka 框架构建 Spark 中 Master 和 Worker 节点间的通信协议。通过 MessageProtocol.scala 定义消息协议,SparkWorker.scala 和 SparkMaster.scala 分别实现 Worker 和 Master 的逻辑,从而实现分布式任务调度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MessageProtocol.scala
package top.gldwolf.scala.akkademo.sparkmasterandworker.common

/**
 * @author: Gldwolf
 * @email: ZengqiangZhao@sina.com
 * @date: 2020/4/17 10:54
 */

/**
 * 用于 Work 注册时发送注册信息
 */
case class WorkerRegisterInfo(id: String, cpu: Int, ram: Int) {

}

/**
 * 用于保存到 Master 的 HashMap 中
 */
class WorkerInfo(var id: String, cpu: Int, ram: Int) {
    var lastHeartBeatTime = System.currentTimeMillis()
}

/**
 * 注册成功后,Master 回应此类型的消息,表示注册成功
 * Worker 接收到后,启动心跳机制
 */
case object RegisteredInfo

/**
 * worker 每隔一定时间由定时器发给自己的一个消息,用于触发自己给 Master 发送消息
 */
case object SendHeartBeat

/**
 * 由自己的消息触发,然后给 Master 发送 HeartBeat 消息,消息要带上自己的 id
 */
case class HeartBeat(id: String)

/**
 * Master 给自己发送一个触发检查超时 Worker 的消息,定时获取已离线的 Worker
 */
case object StartCheckTimeOutWorker

/**
 * Master 给自己发消息,检测 Worker 是否已离线,如果已离线,则移除
 */
case object RemoveTimeOutWorker
SparkWorker.scala
package top.gldwolf.scala.akkademo.sparkmasterandworker.worker

import akka.actor.{Actor, ActorRef, ActorSelection, ActorSystem, Props}
import com.typesafe.config.{Config, ConfigFactory}
import top.gldwolf.scala.akkademo.sparkmasterandworker.common.{HeartBeat, RegisteredInfo, SendHeartBeat, WorkerRegisterInfo}

import scala.concurrent.duration.FiniteDuration

/**
 * @author: Gldwolf
 * @email: ZengqiangZhao@sina.com
 * @date: 2020/4/17 10:03
 */

object SparkWorker {
    def main(args: Array[String]): Unit = {
        if (args.length < 6) {
            println("参数个数不正确:host, port, workerName, masterName, masterHost, masterPort...")
            System.exit(-1)
        }
        val host = args(0)
        val port = args(1).toInt
        val workerName = args(2)
        val masterName = args(3)
        val masterHost = args(4)
        val masterPort = args(5).toInt
        val config: Config = ConfigFactory.parseString(
            s"""
               |akka.actor.provider="akka.remote.RemoteActorRefProvider"
               |akka.remote.netty.tcp.hostname=$host
               |akka.remote.netty.tcp.port=$port
               |""".stripMargin)
        val workerFactory: ActorSystem = ActorSystem("WorkerFactory", config)
        val workerRef: ActorRef = workerFactory.actorOf(Props(new SparkWorker(masterHost, masterPort, masterName)), workerName)
        workerRef ! "start"
    }
}

class SparkWorker(masterHost: String, masterPort: Int, masterName: String) extends Actor {
    var masterRef: ActorSelection = _
    var id = java.util.UUID.randomUUID().toString

    override def preStart(): Unit = {
        masterRef = context.actorSelection("akka.tcp://MasterFactory@" +
                s"${masterHost}:${masterPort}/user/${masterName}")
    }

    override def receive: Receive = {
        case "start" => {
            println("Worker " + "已上线~~~")
            masterRef ! WorkerRegisterInfo(id, 4, 4096)
        }
        // 接收到这个消息表示注册成功,然后会给定时给自己发送 SendHeartBeat 消息,触发心跳机制
        case RegisteredInfo => {
            println("workerId: " + id + " 注册成功!")
            import context.dispatcher
            // 说明:
            // 1. 0 millis 表示不延时,立即执行定时器
            // 2. 3000 millis 表示每隔 3 秒执行一次
            // 3. self 表示发送给自己
            // 4. SendHeartBeat 表示发送的内容
            context.system.scheduler.schedule(FiniteDuration(0, "millis"), FiniteDuration(3000, "millis"), self, SendHeartBeat)
        }
        // 接收到自己的定时器发送给自己的消息时,触发下面内容,给 Master 发送心跳信息
        case SendHeartBeat => {
            println("worker: " + id + " 发送心跳信息~~~")
            masterRef ! HeartBeat(id)
        }
    }
}
SparkMaster.scala
package top.gldwolf.scala.akkademo.sparkmasterandworker.master

import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import com.typesafe.config.{Config, ConfigFactory}
import top.gldwolf.scala.akkademo.sparkmasterandworker.common._

import scala.collection.mutable
import scala.concurrent.duration.FiniteDuration

/**
 * @author: Gldwolf
 * @email: ZengqiangZhao@sina.com
 * @date: 2020/4/17 9:56
 */

object SparkMaster {
    def main(args: Array[String]): Unit = {
        if (args.length < 3) {
            println("参数不够,请转入 host, port, masterName...")
            System.exit(-1)
        }
        val host = args(0)
        val port = args(1)
        val masterName = args(2)
        val config: Config = ConfigFactory.parseString(
            s"""
               |akka.actor.provider="akka.remote.RemoteActorRefProvider"
               |akka.remote.netty.tcp.hostname=$host
               |akka.remote.netty.tcp.port=$port
               |""".stripMargin)
        val masterFactory: ActorSystem = ActorSystem("MasterFactory", config)
        val masterRef: ActorRef = masterFactory.actorOf(Props[SparkMaster], masterName)
        masterRef ! "start"
    }
}

class SparkMaster extends Actor {
    val workers: mutable.HashMap[String, WorkerInfo] = mutable.HashMap()

    override def receive: Receive = {
        case "start" => {
            // 这里开始启动 Master 定时检测任务,判断 Worker 有没有离线
            println("Master 已上线~~~")
            self ! StartCheckTimeOutWorker // 给自己发一个开始检测的消息
        }
        case WorkerRegisterInfo(id, cpu, ram) => { // 如果是注册信息,则将注册信息管理起来
            val workerInfo = new WorkerInfo(id, cpu, ram)
            if (!workers.contains(id)) { // 判断是否已经添加这个 worker
                workers += ((id, workerInfo)) // 如果没有则添加进来
                println("worker:" + id + " 注册成功~~~")
                println("目前已有的 Workers: " + workers)
                // 回复注册成功消息
                sender() ! RegisteredInfo
            }
        }
        case HeartBeat(id) => { // 接收到 worker 的心跳信息
            val workerInfo = workers(id)
            val lastHeartBeatTime: Long = System.currentTimeMillis()
            workerInfo.lastHeartBeatTime = lastHeartBeatTime
            println("Master 更新了 " + id + " 的心跳时间: " + lastHeartBeatTime)
        }
        case StartCheckTimeOutWorker => {
            // 获取到消息后开始定时检测离线的 Worker
            println("----- Master 开启定时任务,检测已离线的 Worker -----")
            import context.dispatcher
            context.system.scheduler.schedule(FiniteDuration(0, "millis"), FiniteDuration(9000, "millis"), self, RemoveTimeOutWorker)
        }
        // 检测哪些 Worker 超时了,并从 Worker 中删除
        case RemoveTimeOutWorker => {
            val now: Long = System.currentTimeMillis
            // 如果最后一次心跳距离现在有 6 秒,那么就代表离线了,则删除这个 Worker
//            for ((id, workerInfo) <- workers) {
//                if ((now - workerInfo.lastHeartBeatTime) / 1000  > 6) {
//                    workers.remove(id)
//                    println("worker: " + id + " 已离线,将其移除!")
//                }
//            }
            // 也可以使用函数式编程将其移除
            workers.values.filter(worker => now - worker.lastHeartBeatTime > 6000)
                    .foreach(worker => {
                        println("Worker: " + worker.id + " 离线,已将其移除!")
                        workers.remove(worker.id)
                    })
            println("当前有 " + workers.size + " 个 Worker 存活!")
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值