• Scala并发编程模型Akka


    [toc]

    ## Scala并发编程模型Akka

    ### 1. Akka简介

    #### Akka是什么?

    > 1. Akka是JAVA虚拟机JVM平台上构建高并发、分布式和容错应用的工具包和运行时,你可以理解成Akka是编写并发程序的框架。
    > 2. Akka用Scala语言写成,同时提供了Scala和JAVA的开发接口。
    > 3. Akka主要解决的问题是:可以轻松的写出高效稳定的并发程序,程序员不再过多的考虑线程、锁和资源竞争等细节。
    > 4. ![image-20210406221430939](assets/image-20210406221430939.png)

    #### Actor 模型解决什么问题?

    > 1. 处理并发问题关键是要保证共享数据的一致性和正确性,因为程序是多线程时,多个线程对同一个数据进行修改,若不加同步条件,势必会造成数据污染。但是当我们对关键代码加入同步条件synchronized 后,实际上大并发就会阻塞在这段代码,对程序效率有很大影响。
    > 2. 若是用单线程处理,不会有数据一致性的问题,但是系统的性能又不能保证。
    > 3. Actor 模型的出现解决了这个问题,简化并发编程,提升程序性能。 你可以这里理解:`Actor 模型是一种处理并发问题的解决方案,很牛!`

    ### 2. Akka中Actor模型

    #### Actor模型及其说明

    > ![image-20210406221510708](assets/image-20210406221510708.png)
    >
    > 1. Akka 处理并发的方法基于 Actor 模型。(示意图)
    > 2. 在基于 Actor 的系统里,所有的事物都是 Actor,就好像在面向对象设计里面所有的事物都是对象一样。
    > 3. Actor 模型是作为一个并发模型设计和架构的。Actor与Actor 之间只能通过消息通信,如图的信封。
    > 4. Actor 与 Actor 之间只能用消息进行通信,当一个 Actor 给另外一个 Actor发消息,消息是有顺序的(消息队列),只需要将消息投寄的相应的邮箱即可。
    > 5. 怎么处理消息是由接收消息的Actor决定的,发送消息Actor可以等待回复,也可以异步处理【ajax】
    > 6. ActorSystem 的职责是负责创建并管理其创建的Actor, ActorSystem 是单例的(可以ActorSystem是一个工厂,专门创建Actor),一个 JVM 进程中有一个即可,而 Acotr 是可以有多个的。
    > 7. Actor模型是对并发模型进行了更高的抽象。
    > 8. Actor模型是`异步、非阻塞、高性能`的事件驱动编程模型。[异步、非阻塞, 最经典的案例:就是ajax异步请求处理 ]
    > 9. Actor模型是轻量级事件处理(1GB 内存可容纳百万级别个 Actor),因此处理大并发性能高。

    #### Actor模型工作机制说明

    ![image-20210406222141148](assets/image-20210406222141148.png)

    > `Actor模型工作机制说明(对照工作机制示意图理解)`
    >
    > 1. ActorySystem创建Actor
    > 2. `ActorRef:可以理解成是Actor的代理或者引用。`消息是通过ActorRef来发送,而不能通过Actor 发送消息,通过哪个ActorRef 发消息,就表示把该消息发给哪个Actor
    > 3. 消息发送到`Dispatcher Message (消息分发器)`,它得到消息后,会将消息进行分发到对应的MailBox。(注: Dispatcher Message 可以理解成是一个线程池, MailBox 可以理解成是消息队列,可以缓冲多个消息,遵守FIFO)
    > 4. Actor 可以通过 receive方法来获取消息,然后进行处理。
    >
    > `Actor间传递消息机制(对照工作机制示意图理解)`
    >
    > 1. 每一个消息就是一个Message对象。Message 继承了Runable, 因为Message就是线程类。
    > 2. 从Actor模型工作机制看上去很麻烦,但是程序员编程时只需要编写Actor就可以了,其它的交给Actor模型完成即可。
    > 3. `A Actor要给B Actor 发送消息,那么A Actor 要先拿到(也称为持有) B Actor 的 代理对象ActorRef 才能发送消息。`

    ### 3. Actor模型快速入门

    > 1. 编写一个SayHelloActor
    > 2. SayHelloActor 可以给自己发送消息
    > 3. 要求使用Maven的方式来构建项目,这样可以很好的解决项目开发包的依赖关系。

    #### SayHelloActor

    ~~~~scala
    package com.atguigu.akka01.actor

    import akka.actor.{Actor}
    /**
    * @Date 2021/4/6 14:13
    * @Version 10.21
    * @Author DuanChaojie
    * 继承Actor后,SayHelloActor就是一个Actor
    * 重写核心方法receive
    */
    class SayHelloActor extends Actor {
    /**
    * 1、receive方法,会被该SayHelloActor的MailBox调用
    * 2、当该SayHelloActor的MailBox接收到消息,就会调用receive方法
    * 3、type Receive = scala.PartialFunction[scala.Any, scala.Unit],即Receive表示偏函数接收的参数类型是Any,返回类型是Unit
    * 4、isDefinedAt(x: Any) 如果返回true ,就会去调用 apply 构建对象实例,如果是false,过滤
    *
    * @return Receive
    */
    override def receive: Receive = {
    case "Hello" => println("SayHelloActor:Hello tom")
    case "Ok" => println("SayHelloActor:Ok jack")
    case "exit" => {
    println("SayHelloActor:退出系统...")
    // 停止actoref
    /**

    */

    /** 1、context.stop(xxx)阻止被指定的Actor,这是一个异步操作,即涉及一个消息发送。
    * 2、如果此方法应用于来自参与者内部的"self"引用,
    * 则该参与者保证不会在此调用后处理任何其他消息;
    * 请注意,当前消息的处理将继续,此方法不会立即终止此参与者。
    * 3、The 'self' field holds the ActorRef for this actor
    * 4、self可用于向自身发送消息,格式为:self ! message
    */
    context.stop(self)

    /**
    * Terminates this actor system
    */
    context.system.terminate()
    }
    case _ => println("SayHelloActor:匹配失败!")
    }

    }
    ~~~~

    #### SayHelloActorDemo

    ~~~~scala
    package com.atguigu.akka01.main

    import akka.actor.{ActorRef, ActorSystem, Props}
    import com.atguigu.akka01.actor.SayHelloActor

    import scala.io.StdIn
    import scala.util.control.Breaks.{break, breakable}

    /**
    * @Date 2021/4/6 15:42
    * @Version 10.21
    * @Author DuanChaojie
    */
    object SayHelloActorDemo {
    // 1、创建一个ActorSystem,专门用于创建Actor
    private val actorFactory = ActorSystem("actorFactory")

    /**
    * 创建SayHelloActor的同时,返回SayHelloActor的sayHelloActorRef
    * 1、Props[SayHelloActor] 通过反射创建一个SayHelloActor实例
    * 2、给创建的Actor(SayHelloActor)取名为sayHelloActor
    * 3、sayHelloActorRef: ActorRef 就是 Props[SayHelloActor] 的ActorRef
    * 4、创建的SayHelloActor实例被ActorSystem接管
    */
    private val sayHelloActorRef: ActorRef = actorFactory.actorOf(Props[SayHelloActor], "sayHelloActor")


    def main(args: Array[String]): Unit = {

    // 向sayHelloActorRef发送消息
    breakable {
    while (true) {
    println(Console.GREEN + "请输入你想发的消息(提示输入完毕按回车):")
    val command = StdIn.readLine()
    sayHelloActorRef ! command
    Thread.sleep(1000)
    if (command == "exit") {
    break()
    }
    }
    }

    }
    }
    ~~~~

    #### 效果如图:

    ![QQ截图20210406150212](assets/QQ截图20210406150212.png)

    #### 小结和说明:

    > 1. 当程序执行 `private val sayHelloActorRef: ActorRef = actorFactory.actorOf(Props[SayHelloActor], "sayHelloActor")`,会完成如下任务 [这是非常重要的方法]
    > 2. actorFactory 是 ActorSystem("actorFactory")这样创建的。
    > 3. 这里的 Props[SayHelloActor]会使用反射机制,创建一个SayHelloActor对象,如果是`actorFactory.actorOf(Props(new SayHelloActor), "sayHelloActor") 形式`,就是使用new 的方式创建一个SayHelloActor对象, 注意Props() 是小括号。
    > 4. 会创建一个SayHelloActor对象的代理对象 sayHelloActorRef, 使用sayHelloActorRef才能发送消息
    > 5. 会在底层创建 Dispather Message ,是一个线程池,用于分发消息, 消息是发送到对应的Actor的 MailBox
    > 6. 会在底层创建SayHelloActor的MailBox 对象,该对象是一个队列,可接收Dispatcher Message 发送的消息
    > 7. MailBox 实现了Runnable 接口,是一个线程,一直运行并调用Actor的receive 方法,因此当Dispather 发送消息到MailBox时,Actor 在receive 方法就可以得到信息
    > 8. `sayHelloActorRef! "hello"`, 表示把hello消息发送到sayHelloActorRef的mailbox (通过Dispatcher Message 转发)

    ### 4. Actor模型应用实例-Actor间通讯

    > 1. 编写2个 Actor , 分别是 DdActor和 MmActor
    > 2. DdActor和MmActor之间可以相互发送消息
    > 3. 加强对Actor传递消息机制的理解

    #### DdActor

    ```scala
    package com.atguigu.akka02.actor

    import akka.actor.{Actor, ActorRef}

    /**
    * @Date 2021/4/6 15:38
    * @Version 10.21
    * @Author DuanChaojie
    */
    class DdActor(mmActorRef: ActorRef) extends Actor {
    val myMmActorRef = mmActorRef

    override def receive: Receive = {
    case "Go!" => {
    println("3s后开启世界大战!")
    Thread.sleep(3000)
    myMmActorRef ! "DD"
    }
    case "MM" => {
    Thread.sleep(1000)
    println("MM:一起喵喵喵喵喵~")
    myMmActorRef ! "DD"
    }
    }
    }
    ```

    #### MmActor

    ~~~~scala
    package com.atguigu.akka02.actor

    import akka.actor.Actor

    /**
    * @Date 2021/4/6 15:38
    * @Version 10.21
    * @Author DuanChaojie
    */
    class MmActor extends Actor {
    override def receive: Receive = {
    case "DD" => {
    Thread.sleep(1000)
    println("DD:我们一起学猫叫~")
    sender() ! "MM"
    }
    }
    }
    ~~~~

    #### Main

    ```scala
    package com.atguigu.akka02.main

    import akka.actor.{ActorRef, ActorSystem, Props}
    import com.atguigu.akka02.actor.{DdActor, MmActor}

    import scala.io.StdIn

    /**
    * @Date 2021/4/6 15:40
    * @Version 10.21
    * @Author DuanChaojie
    */
    object Main extends App {
    private val actorFactory = ActorSystem("actorFactory")

    private val mmActorRef: ActorRef = actorFactory.actorOf(Props[MmActor], "mmActor")
    private val ddActorRef: ActorRef = actorFactory.actorOf(Props(new DdActor(mmActorRef)), "ddActor")

    println(Console.GREEN + "请输入Start...")

    val command = StdIn.readLine()

    if (command.toLowerCase == "start") {
    ddActorRef ! "Go!"
    }
    }
    ```

    #### 效果图:

    ![image-20210406223923598](assets/image-20210406223923598.png)

    #### 小结:

    > 1. 两个Actor通讯机制和Actor 自身发消息机制基本一样,只是要注意如下
    > 2. 如果DdActor 在需要给MmActor 发消息,则需要持有MmActor 的 ActorRef,可以通过创建时,传入MmActor的 代理对象(ActorRef)
    > 3. 当MmActor 在receive 方法中接收到消息,需要回复时,可以通过sender() 获取到发送Actor的 代理对象。

    ### 5. Akka网络编程

    > Akka支持面向大并发后端服务程序,网络通信这块是服务端程序重要的一部分。
    >
    > 网络编程有两种:
    >
    > 1. `TCP socket编程`,是网络编程的主流。之所以叫Tcp socket编程,是因为底层是基于Tcp/ip协议的. 比如: QQ聊天 [示意图]
    > 2. `B/S结构的Http编程`,我们使用浏览器去访问服务器时,使用的就是Http协议,而http底层依旧是用tcp socket实现的。 比如: 京东商城 【属于 web 开发范畴 】

    #### 网络编程基础知识

    ##### TCP/IP模型

    > 1. ![image-20210406225122074](assets/image-20210406225122074.png)
    >
    >
    >
    > 2. 深入理解:qq间相互通讯的案例
    > 3. ![image-20210406225227857](assets/image-20210406225227857.png)
    > 4. tracert的使用案例:
    > 5. ![image-20210406225335990](assets/image-20210406225335990.png)

    ##### IP地址

    > 每个internet上的主机和路由器都有一个ip地址,它包括网络号和主机号,ip地址有ipv4(32位)或者ipv6(128位). 可以通过ipconfig 来查看

    ##### 端口port

    > 1.
    > 我们这里所指的端口不是指物理意义上的端口,而是特指TCP/IP协议中的端口,是逻辑意义上的端口。
    > 2. 如果把IP地址比作一间房子,端口就是出入这间房子的门。真正的房子只有几个门,但是一个IP地址的端口可以有`65535`(即:`256×256-1`)个之多!端口是通过端口号来标记的。(端口号 0:Reserved)
    > 3. ==端口port的分类==
    > - 0号是保留端口
    > - 1-1024是固定端口,又叫有名端口,即被某些程序固定使用,一般程序员不使用
    > - 22: SSH远程登录协议
    > - 23: telnet使用
    > - 21: ftp使用
    > - 25: smtp服务使用
    > - 80: iis使用
    > - 7: echo服务
    > - 1025-65535是动态端口,这些端口,程序员可以使用。
    > 4. 端口port使用注意
    > 1. 在计算机(尤其是做服务器)要尽可能的少开端口
    > 2. 一个端口只能被一个程序监听( )
    > 3. 如果使用 netstat –an 可以查看本机有哪些端口在监听
    > 4. 可以使用 netstat –anb 来查看监听端口的pid,在结合任务管理器关闭不安全的端口。

    #### Akka网络编程-小黄鸡客服案例

    > 需求分析:
    >
    > 1. 服务端进行监听(8888)
    > 2. 客户端可以通过键盘输入,发送咨询问题给小黄鸡客服(服务端)
    > 3. 小黄鸡(服务端) 回答客户的问题

    ##### 服务端--ServerMain

    ```scala
    package com.atguigu.akka03.server

    import akka.actor.{ActorRef, ActorSystem, Props}
    import com.typesafe.config.ConfigFactory

    /**
    * @Date 2021/4/6 16:18
    * @Version 10.21
    * @Author DuanChaojie
    */
    object ServerMain extends App{
    val serverHost = "127.0.0.1"
    val serverPort = 8888

    /**
    * 对于此字符串中的每一行:
    * 从行中删除由空格或控制字符('|')组成的前导前缀。
    * 创建config对象,指定协议类型,监听的ip和端口
    */
    val config = ConfigFactory.parseString(
    s"""
    |akka.actor.provider="akka.remote.RemoteActorRefProvider"
    |akka.remote.netty.tcp.hostname=$serverHost
    |akka.remote.netty.tcp.port=$serverPort
    """.stripMargin)

    private val serverActorFactory = ActorSystem("serverActorFactory",config)

    private val yellowChickenServerRef: ActorRef = serverActorFactory.actorOf(Props[YellowChickenServer],"yellowChickenServer")

    // ServerMain启动
    yellowChickenServerRef ! "start".toLowerCase
    }
    ```

    ##### 服务端--YellowChickenServer

    ```scala
    package com.atguigu.akka03.server

    import akka.actor.Actor
    import com.atguigu.akka03.common.{ClientMessage, ServerMessage}

    /**
    * @Date 2021/4/6 16:17
    * @Version 10.21
    * @Author DuanChaojie
    */
    class YellowChickenServer extends Actor {
    override def receive: Receive = {
    case "start" => println(Console.BLUE + "YellowChickenServer已经启动....")
    case ClientMessage(msg) => {
    // TODO match模糊匹配
    msg match {
    case "java" => sender() ! ServerMessage("Java 是由 Sun Microsystems 公司于 1995 年 5 月推出的高级程序设计语言。")
    case "javascript" => sender() ! ServerMessage("JavaScript在1995年由Netscape公司的Brendan Eich,在网景导航者浏览器上首次设计实现而成。因为Netscape与Sun合作,Netscape管理层希望它外观看起来像Java,因此取名为JavaScript。但实际上它的语法风格与Self及Scheme较为接近。")
    case "大数据" => sender() ! ServerMessage("大数据(big data),IT行业术语,是指无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合,是需要新处理模式才能具有更强的决策力、洞察发现力和流程优化能力的海量、高增长率和多样化的信息资产。")
    case "作者" => sender() ! ServerMessage("https://blog.csdn.net/weixin_45267102/article/details/111472987")
    case _ => println("Nothing~")
    }
    }
    }
    }
    ```

    ##### 客户端--ClientMain

    ```scala
    package com.atguigu.akka03.client

    import akka.actor.{ActorRef, ActorSystem, Props}
    import com.atguigu.akka01.main.SayHelloActorDemo.sayHelloActorRef
    import com.typesafe.config.ConfigFactory

    import scala.io.StdIn
    import scala.util.control.Breaks.{break, breakable}

    /**
    * @Date 2021/4/6 16:18
    * @Version 10.21
    * @Author DuanChaojie
    */
    object ClientMain extends App {
    val (clientHost, clientPort, serverHost, serverPort) = ("127.0.0.1", 6666, "127.0.0.1", 8888)

    /**
    * 对于此字符串中的每一行:
    * 从行中删除由空格或控制字符('|')组成的前导前缀。
    * 创建config对象,指定协议类型,监听的ip和端口
    */
    val config = ConfigFactory.parseString(
    s"""
    |akka.actor.provider="akka.remote.RemoteActorRefProvider"
    |akka.remote.netty.tcp.hostname=$clientHost
    |akka.remote.netty.tcp.port=$clientPort
    """.stripMargin)
    val clientActorFactory = ActorSystem("clientActorFactory", config)
    val yellowChickenClientRef: ActorRef = clientActorFactory.actorOf(Props(new YellowChickenClient(serverHost, serverPort)), "yellowChickenClient")

    // ClientMain启动
    yellowChickenClientRef ! "start".toLowerCase

    // 向yellowChickenClientRef发送消息
    breakable {
    while (true) {
    Thread.sleep(1000)
    println(Console.GREEN + "请输入你咨询的问题(提示输入完毕按回车):")
    val command = StdIn.readLine()
    yellowChickenClientRef ! command
    if (command == "exit") {
    break()
    }
    }
    }
    }
    ```

    ##### 客户端--YellowChickenClient

    ```scala
    package com.atguigu.akka03.client

    import akka.actor.{Actor, ActorSelection}
    import com.atguigu.akka03.common.{ClientMessage, ServerMessage}


    /**
    * @Date 2021/4/6 16:18
    * @Version 10.21
    * @Author DuanChaojie
    */
    class YellowChickenClient(serverHost: String, serverPort: Int) extends Actor {

    var yellowChickenServerRef: ActorSelection = _

    /**
    * 1、在Actor中有一个方法PreStart方法,他会在actor运行前执行
    * 2、在akka的开发中,通常将初始化的工作,放在preStart方法
    */
    override def preStart(): Unit = {
    /** 注意:
    * serverActorFactory 是server端 ActorSystem("serverActorFactory",config)
    * user/后面是 serverActorFactory.actorOf(Props[YellowChickenServer],"yellowChickenServer")
    */
    yellowChickenServerRef = context.actorSelection(s"akka.tcp://serverActorFactory@${serverHost}:${serverPort}/user/yellowChickenServer")
    println(yellowChickenServerRef)
    }

    override def receive: Receive = {
    case "start" => println(Console.BLUE + "YellowChickenClient已经启动....")
    // 将咨询的问题发送到Server端
    case msg: String => {
    yellowChickenServerRef ! ClientMessage(msg.toLowerCase)
    }
    case ServerMessage(msg) => {
    println(s"YellowChickenServer: $msg")
    }
    }

    }
    ```

    ##### MessageProtocol

    ```scala
    /**
    * @Date 2021/4/6 16:19
    * @Version 10.21
    * @Author DuanChaojie
    */
    class MessageProtocol {
    }
    /**
    * 使用样例类来构建协议
    * 客户端发给服务器协议(序列化的对象)
    * @param mes
    */
    case class ClientMessage(mes: String)

    /**
    * 服务端发给客户端的协议(样例类对象)
    * @param mes
    */
    case class ServerMessage(mes: String)
    ```

    ##### 项目结构图:

    ![image-20210406230448869](assets/image-20210406230448869.png)

    ##### 效果图:

    ![image-20210406230528305](assets/image-20210406230528305.png)

    ![image-20210406230612163](assets/image-20210406230612163.png)

    ### 6. Spark Master Worker 进程通讯项目

    #### 项目意义:

    > 1. 深入理解Spark的Master和Worker的通讯机制
    > 2. 为了方便同学们看Spark的底层源码,命名的方式和源码几乎保持一致(如: 通讯消息类命名就是一样的)
    > 3. ==加深对主从服务心跳检测机制(HeartBeat)的理解==,方便以后spark源码二次开发。

    #### 项目需求分析:

    ![image-20210406230859899](assets/image-20210406230859899.png)

    > 1. worker注册到Master, Master完成注册,并回复worker注册成功
    > 2. worker定时发送心跳,并在Master接收到
    > 3. Master接收到worker心跳后,要更新该worker的最近一次发送心跳的时间
    > 4. 给Master启动定时任务,定时检测注册的worker有哪些没有更新心跳,并将其从workers(HashMap)中删除
    > 5. master worker 进行分布式部署(Linux系统)

    #### 功能实现:

    ##### SparkMaster

    ```scala
    package com.atguigu.spark.master

    import akka.actor.{Actor, ActorSystem, Props}
    import com.atguigu.spark.common.{HeartBeat, RegisterWorkerInfo, RegisteredWorkerInfo, RemoveTimeOutWorker, StartTimeOutWorker, WorkerInfo}
    import com.typesafe.config.ConfigFactory
    import scala.concurrent.duration._
    import scala.collection.mutable

    /**
    * @Date 2021/4/6 18:35
    * @Version 10.21
    * @Author DuanChaojie
    */
    class SparkMaster extends Actor {

    /**
    * SparkMaster维护一个SparkWorker的mutable.map(id,WorkerInfo(id, cpu, ram))
    */
    val workers = mutable.Map[String, WorkerInfo]()


    override def receive: Receive = {
    case "start" => {
    println(Console.BLUE + "SparkMaster启动了...")
    self ! StartTimeOutWorker
    }
    case RegisterWorkerInfo(id, cpu, ram) => {
    // SparkMaster处理注册信息
    if (!workers.contains(id)) {
    val workerInfo = new WorkerInfo(id, cpu, ram)
    // 将wokerInfo加入到workers中
    workers += (id -> workerInfo)
    // 告诉SparkWorker注册成功
    sender() ! RegisteredWorkerInfo
    }
    println("workers = " + workers)
    }
    case HeartBeat(id) => {
    val workerInfo = workers(id)
    workerInfo.lastHeartBeat = System.currentTimeMillis()
    println("SparkMaster更新了id = " + id + "的心跳时间为:" + workerInfo.lastHeartBeat)
    }
    case StartTimeOutWorker => {
    import context.dispatcher
    context.system.scheduler.schedule(0 millis, 9000 millis, self, RemoveTimeOutWorker)
    }
    case RemoveTimeOutWorker => {
    //拿到所有的workerInfo
    val workerInfos = workers.values
    val deadWorkerInfos = workerInfos.filter(workerInfo => (System.currentTimeMillis() - workerInfo.lastHeartBeat) > 6000)
    deadWorkerInfos.foreach(workerInfo => workers.remove(workerInfo.id))
    println(s"当前有${workers.size}个sparkWorker存活~")
    }
    }
    }

    object SparkMaster {
    /**
    * @param args (0) serverHost args(1) serverPort
    */
    def main(args: Array[String]): Unit = {

    val masterHost = args(0)
    val masterPort = args(1).toInt

    /** 创建config对象,指定协议类型,监听的ip和端口 */
    val config = ConfigFactory.parseString(
    s"""
    |akka.actor.provider="akka.remote.RemoteActorRefProvider"
    |akka.remote.netty.tcp.hostname=$masterHost
    |akka.remote.netty.tcp.port=$masterPort
    """.stripMargin)

    val sparkMasterSystem = ActorSystem("sparkMasterSystem", config)
    val sparkMaster = sparkMasterSystem.actorOf(Props[SparkMaster], "sparkMaster")

    sparkMaster ! "start"
    }
    }
    ```

    ##### SparkWorker

    ```scala
    package com.atguigu.spark.worker

    import akka.actor.{Actor, ActorSelection, ActorSystem, Props}
    import com.atguigu.spark.common.{HeartBeat, RegisterWorkerInfo, RegisteredWorkerInfo, SendHeartBeat}
    import com.typesafe.config.ConfigFactory
    import scala.concurrent.duration._

    /**
    * @Date 2021/4/6 18:36
    * @Version 10.21
    * @Author DuanChaojie
    */
    class SparkWorker(masterHost: String, masterPort: Int) extends Actor {

    var sparkMaster: ActorSelection = _

    override def preStart(): Unit = {
    sparkMaster = context.actorSelection(s"akka.tcp://sparkMasterSystem@${masterHost}:${masterPort}/user/sparkMaster")
    }

    // 使用UUID生成sparkWorker的id
    private val id: String = java.util.UUID.randomUUID().toString

    override def receive: Receive = {
    case "start" => {
    println(Console.BLUE + "SparkMaster启动了...")
    println("正在向SparkMaster注册自己...")
    //RegisterWorkerInfo(id: String, cpu: Int, ram: Int)
    sparkMaster ! RegisterWorkerInfo(id, 4, 256 * 1024)
    }
    case RegisteredWorkerInfo => {
    println("已经向SparkMaster注册成功!")

    import context.dispatcher
    context.system.scheduler.schedule(0 millis, 3000 millis, self, SendHeartBeat)
    }

    case SendHeartBeat => {
    println("给SparkMaster发送心跳~")
    sparkMaster ! HeartBeat(id)
    }
    }
    }

    object SparkWorker {
    /**
    * @param args
    * workerHost args(0)
    * workerPort args(1)
    * masterHost args(2)
    * masterPort args(3)
    */
    def main(args: Array[String]): Unit = {

    val (workerHost, workerPort, masterHost, masterPort) = (args(0),args(1).toInt,args(2),args(3).toInt)
    /** 创建config对象,指定协议类型,监听的ip和端口 */
    val config = ConfigFactory.parseString(
    s"""
    |akka.actor.provider="akka.remote.RemoteActorRefProvider"
    |akka.remote.netty.tcp.hostname=$workerHost
    |akka.remote.netty.tcp.port=$workerPort
    """.stripMargin)
    val sparkWorkerSystem = ActorSystem("sparkWorkerSystem", config)
    val sparkWorker = sparkWorkerSystem.actorOf(Props(new SparkWorker(masterHost, masterPort)), "sparkWorker")

    sparkWorker ! "start"
    }
    }
    ```

    ##### MessageProtocol

    ~~~~scala
    package com.atguigu.spark.common

    /**
    * @Date 2021/4/6 18:37
    * @Version 10.21
    * @Author DuanChaojie
    * MessageProtocol.scala
    */
    class MessageProtocol {
    }

    /**
    * worker注册信息
    * @param id
    * @param cpu
    * @param ram
    */
    case class RegisterWorkerInfo(id: String, cpu: Int, ram: Int)


    /**
    * 这个是WorkerInfo, 这个信息将来是保存到master的 hm(该hashmap是用于管理worker)
    * 将来这个WorkerInfo会扩展(比如增加worker上一次的心跳时间)
    * @param id
    * @param cpu
    * @param ram
    */
    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发现的协议消息
    * @param id
    */
    case class HeartBeat(id: String)


    /**
    * master给自己发送一个触发检查超时worker的信息
    */
    case object StartTimeOutWorker

    /**
    * master给自己发消息,检测worker,对于心跳超时的
    */
    case object RemoveTimeOutWorker
    ~~~~

    ##### 项目结构图:

    ![image-20210406231338055](assets/image-20210406231338055.png)

    ##### 项目效果图:

    ![image-20210406231424443](assets/image-20210406231424443.png)

    ![image-20210406231527269](assets/image-20210406231527269.png)

    ![image-20210406231540071](assets/image-20210406231540071.png)、

    ##### 进行分布式部署(Linux系统)

    > 打包

    ![image-20210406231833163](assets/image-20210406231833163.png)

    > 更名后上传到服务器(linux)上:

    ~~~SHELL
    #依次执行以下命令
    java -jar SparkMaster.jar 127.0.0.1 7777

    java -jar SparkWorker.jar 127.0.0.1 6666 127.0.0.1 7777
    java -jar SparkWorker.jar 127.0.0.1 5555 127.0.0.1 7777
    java -jar SparkWorker.jar 127.0.0.1 4444 127.0.0.1 7777
    ~~~

    > 效果图如下:

    ![image-20210406224411623](assets/image-20210406224411623.png)

    ![image-20210406224437635](assets/image-20210406224437635.png)

    ![image-20210406224501292](assets/image-20210406224501292.png)

    ![image-20210406224530020](assets/image-20210406224530020.png)

    ## ☆

  • 相关阅读:
    UITableView的一些事1
    Mac修改文件权限:You don’t have permission to save the file
    svn: is already a working copy for a different url 解决办法
    svn服务配置
    github push出错(1)You can't push to git:// Use https://
    判断系统版本
    浅谈 Qt 布局那些事
    Qt布局管理
    详解 QT 主要类 QWidget
    新手须知 QT类大全
  • 原文地址:https://www.cnblogs.com/huaobin/p/15782729.html
Copyright © 2020-2023  润新知