Akka并发编程模型及网络编程

一、 并发编程模型:

Akka 的介绍:
在这里插入图片描述

1.1、 Actor 模型用于解决什么问题

在这里插入图片描述

1.2、 Akka 中 Actor 模型详解

在这里插入图片描述
对上图的详解如下:

在这里插入图片描述

1.3、Actor 模型工作机制说明

在这里插入图片描述
Actor模型工作机制说明(对照工作机制示意图理解):

在这里插入图片描述
Actor 间传递消息机制(对照工作机制示意图理解):

在这里插入图片描述

Actor 模型应用实例

Actor 自我通讯
在这里插入图片描述在这里插入图片描述

构建maven项目pom文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.atguigu.akka</groupId>
    <artifactId>SayHelloActor</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!-- 定义一下常量 -->
    <properties>
        <encoding>UTF-8</encoding>
        <scala.version>2.11.8</scala.version>
        <scala.compat.version>2.11</scala.compat.version>
        <akka.version>2.4.17</akka.version>
    </properties>

    <dependencies>
        <!-- 添加scala的依赖 -->
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>${scala.version}</version>
        </dependency>

        <!-- 添加akka的actor依赖 -->
        <dependency>
            <groupId>com.typesafe.akka</groupId>
            <artifactId>akka-actor_${scala.compat.version}</artifactId>
            <version>${akka.version}</version>
        </dependency>

        <!-- 多进程之间的Actor通信 -->
        <dependency>
            <groupId>com.typesafe.akka</groupId>
            <artifactId>akka-remote_${scala.compat.version}</artifactId>
            <version>${akka.version}</version>
        </dependency>
    </dependencies>

    <!-- 指定插件-->
    <build>
        <!-- 指定源码包和测试包的位置 -->
        <sourceDirectory>src/main/scala</sourceDirectory>
        <testSourceDirectory>src/test/scala</testSourceDirectory>
        <plugins>
            <!-- 指定编译scala的插件 -->
            <plugin>
                <groupId>net.alchim31.maven</groupId>
                <artifactId>scala-maven-plugin</artifactId>
                <version>3.2.2</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>testCompile</goal>
                        </goals>
                        <configuration>
                            <args>
                                <arg>-dependencyfile</arg>
                                <arg>${project.build.directory}/.scala_dependencies</arg>
                            </args>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <!-- maven打包的插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.4.3</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <filters>
                                <filter>
                                    <artifact>*:*</artifact>
                                    <excludes>
                                        <exclude>META-INF/*.SF</exclude>
                                        <exclude>META-INF/*.DSA</exclude>
                                        <exclude>META-INF/*.RSA</exclude>
                                    </excludes>
                                </filter>
                            </filters>
                            <transformers>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                    <resource>reference.conf</resource>
                                </transformer>
                                <!-- 指定main方法 -->
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>xxx</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>


代码实现:

package com.test.akka.actor

import akka.actor.{Actor, ActorRef, ActorSystem, Props}

// 1. 当我们继承 Actor 后,就是一个 Actor,需要重写该 Actor 的核心方法 receive
class SayHelloActor extends Actor {
  // 循环的接收消息
  // 1. receive方法,会被该 Actor 的 MailBox(实现了 Runnable 接口)调用
  // 2. 当该 Actor 的 MailBox 接收到消息,就会调用 receive 方法
  // 3. Receive 的底层:type Receive = PartialFunction[Any, Unit]
  override def receive: Receive = {
    // 接受消息并处理,如果接收到 exit,就退出
    case "hello" => println("发送:hello\t\t回应:hello too:)")
    case "ok" => println("发送:ok\t\t\t回应:ok too:)")
    case "exit" => {
      println("接收到exit~指令,退出系统...")
      context.stop(self) // 停止自己的 ActorRef
      context.system.terminate() // 关闭 ActorSystem
    }
  }
}

object SayHelloActor {
  // 1. 先创建一个 ActorSystem,专门用于创建 Actor
  private val actoryFactory = ActorSystem("actoryFactory")

  // 2. 创建一个 Actor 的同时,返回 Actor 的 ActorRef
  private val sayHelloActorRef: ActorRef = actoryFactory.actorOf(Props[SayHelloActor], "sayHelloActor")
  // (1) Props[SayHelloActor] 创建了一个 SayHelloActor 实例,这里使用到了反射
  // (2) "sayHelloActor" 是 Actor 的名字
  // (3) sayHelloActorRef: ActorRef  =>是 Props[SayHelloActor] 的引用
  // (4) 创建的 SayHelloActor 实例被 ActorSystme 接管

  def main(args: Array[String]): Unit = {
    // 给 SayHelloActor 发消息(邮箱)
    sayHelloActorRef ! "hello"
    sayHelloActorRef ! "ok"
    sayHelloActorRef ! "ok~"
    // 研究异步如何退出 ActorSystem
    sayHelloActorRef ! "exit"
  }

}

输出结果如下:

在这里插入图片描述
小结:
当程序执行 private val sayHelloActorRef: ActorRef = actoryFactory.actorOf(Props[SayHelloActor], “sayHelloActor”) 会完成如下任务:

1、actorFactory 是 ActorSystem(“actorFactory”) 创建的。
2、这里的 Props[SayHelloActor] 会使用反射机制,创建一个 SayHelloActor 对象,如果是 actorFactory.actorOf(Props(new SayHelloActor(其他代理对象的引用)), “sayHelloActor”) 形式,就是使用 new 的方式创建一个 SayHelloActor 对象。注意:Props() 是小括号。
3、会创建一个 SayHelloActor 对象的代理对象 sayHelloActorRef,使用 sayHelloActorRef 才能发送消息。
4、会在底层创建 Dispather Message,是一个线程池,用于分发消息,消息是发送到对应的 Actor 的 MailBox。
5、会在底层创建 SayHelloActor 的 MailBox 对象,该对象是一个队列,可接收 Dispatcher Message 发送的消息。
6、MailBox 实现了 Runnable 接口,是一个线程,一直运行并调用 Actor 的 receive 方法,因此当Dispather 发送消息到 MailBox 时,Actor 在r eceive 方法就可以得到信息。
7、SayHelloActorRef ! “hello” ,表示把 hello 消息发送到 SayHello Actor 的 Mailbox (通过Dispatcher Message 转发)。

Actor 之间通讯:
在这里插入图片描述
在这里插入图片描述两个 Actor 的通讯机制原理图
在这里插入图片描述代码实现:
AActor.scala

package com.test.akka.actors

import akka.actor.{Actor, ActorRef}

class AActor(bActorRef: ActorRef) extends Actor {
  var count = 0
  override def receive: Receive = {
    case "start" => {
      println("AActor 出招了,start ok")
      bActorRef ! "我打"
    }
    case "我打" => {
      count += 1
      // 给 BActor 发出消息
      // 这里需要持有 BActor 的引用(BActorRef)才可以
      println(s"AActor(黄飞鸿) 厉害!看我佛山无影脚 第${count}脚")
      Thread.sleep(1000)
      bActorRef ! "我打" // 给 BActor 发出消息
    }
  }
}

BActor.scala

package com.test.akka.actors

import akka.actor.Actor

class BActor extends Actor {
  var count = 0
  override def receive: Receive = {
    case "我打" => {
      count += 1
      println(s"BActor(乔峰) 挺猛 看我降龙十八掌 第${count}掌")
      Thread.sleep(1000)
      // 通过 sender() 方法,可以获取到发送消息的 Actor 的 ActorRef
      sender() ! "我打"
    }
  }
}

ActorApp.scala

package com.test.akka.actors

import akka.actor.{ActorRef, ActorSystem, Props}

// 100招后,就退出
object ActorApp extends App {
  // 创建 ActorSystem
  val actorfactory = ActorSystem("actorfactory")
  // 先创建 BActor 的引用/代理
  val bActorRef: ActorRef = actorfactory.actorOf(Props[BActor], "bActor")
  // 创建 AActor 的引用时需要持有 BActor 的引用
  val aActorRef: ActorRef = actorfactory.actorOf(Props(new AActor(bActorRef)), "aActor")

  // aActor 先出招
  aActorRef ! "start"
}

代码的小结:

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

如何理解 Actor 的 receive 方法被调用?

1、每个 Actor 对应 MailBox。
2、MailBox 实现了 Runnable 接口,处于运行的状态。
3、当有消息到达 MailBox,就会去调用 Actor 的 receive 方法,即将消息推送给 receive 方法。

二、Akka 网络编程:

Akka 网络编程基本介绍:

在这里插入图片描述

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

需求分析
  1、服务端进行监听(9999)
  2、客户端可以通过键盘输入,发送咨询问题给小黄鸡客服(服务端)
  3、小黄鸡(服务端)回答客户的问题
在这里插入图片描述
示例代码如下:
YellowChickenServer.scala

package com.test.akka.yellowchicken.server

import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import com.atguigu.akka.yellowchicken.common.{ClientMessage, ServerMessage}
import com.typesafe.config.ConfigFactory

class YellowChickenServer extends Actor {
  override def receive: Receive = {
    case "start" => println("start 小黄鸡客服开始工作了...")
    // 如果接收到了服务端的发来的消息,即 ClientMessage
    case ClientMessage(mes) => {
      println("客户咨询的问题是:" + mes)
      mes match {
        // 使用 match case 匹配(模糊匹配)
        case "价格" => sender() ! ServerMessage("20000 RMB")
        case "地址" => sender() ! ServerMessage("北京市")
        case "技术" => sender() ! ServerMessage("大数据")
        case _ => sender() ! ServerMessage("你说的啥子:)")
      }
    }
  }
}

// 主程序入口
object YellowChickenServerApp extends App {
  val host = "127.0.0.1" // 服务端ip地址
  val port = 9999 // 端口
  // 创建 config 对象,指定协议类型、监听的ip和端口
  val config = ConfigFactory.parseString(
    s"""
       |akka.actor.provider="akka.remote.RemoteActorRefProvider"
       |akka.remote.netty.tcp.hostname=$host
       |akka.remote.netty.tcp.port=$port
        """.stripMargin)
  // 创建 ActorSystem
  val serverActorSystem = ActorSystem("Server", config)
  // 创建 YellowChickenServer 的 Actor 和 ActorRef
  val yellowChickenServerActorRef: ActorRef = serverActorSystem.actorOf(Props[YellowChickenServer], "YellowChickenServer-01")

  // 启动服务端
  yellowChickenServerActorRef ! "start"
}

CustomerActor.scala

package com.test.akka.yellowchicken.client

import akka.actor.{Actor, ActorRef, ActorSelection, ActorSystem, Props}
import com.test.akka.yellowchicken.common.{ClientMessage, ServerMessage}
import com.typesafe.config.ConfigFactory

import scala.io.StdIn

class CustomerActor(serverHost: String, serverPort: Int) extends Actor {

  // 定义一个 YellowChickenServerRef
  var serverActorRef: ActorSelection = _

  // 在 Actor 中有一个方法 preStart 方法,它会在 Actor 运行前执行
  // 在 Akka 开发中,通常将初始化的工作,放在 preStart 方法中
  override def preStart(): Unit = {
    this.serverActorRef = context.actorSelection(s"akka.tcp://Server@${serverHost}:${serverPort}/user/YellowChickenServer-01")
    println("this.serverActorRefer=" + this.serverActorRef)
  }

  override def receive: Receive = {
    case "start" => println("start 客户端运行,可以咨询问题")
    case mes: String => {
      // 发给服务端
      // serverActorRef ! mes // 不应该发送字符串,应该包装一把,应该发送一个(样例)对象(即协议)
      serverActorRef ! ClientMessage(mes) // 此时发送的是一个对象,该样例类默认实现了序列化 和 apply 方法
    }
    // 如果接受到了服务器端的消息
    case ServerMessage(mes) => {
      println(s"收到小黄鸡客服(Server)消息:$mes")
    }
  }
}

// 主程序入口
object CustomerActorApp extends App {
  val (host, port, serverHost, serverPort) = ("127.0.0.1", 9990, "127.0.0.1", 9999)
  val config = ConfigFactory.parseString(
    s"""
       |akka.actor.provider="akka.remote.RemoteActorRefProvider"
       |akka.remote.netty.tcp.hostname=$host
       |akka.remote.netty.tcp.port=$port
        """.stripMargin)

  // 创建 ActorSystem
  val clientActorSystem = ActorSystem("Client", config)
  // 创建 CustomerActor 的 Actor 和 ActorRef
  val clientActorRef: ActorRef = clientActorSystem.actorOf(Props(new CustomerActor(serverHost, serverPort)), "CustomerActor-01")

  // 启动客户端
  clientActorRef ! "start"

  // 客户端发送消息
  while (true) {
    val mes = StdIn.readLine()
    clientActorRef ! mes
  }
}

MessageProtocol.scala

package com.test.akka.yellowchicken.common

// 使用样例类来构建协议

// 1、客户端发送服务端的协议(序列化对象)
case class ClientMessage(mes: String)  // 回顾:样例类的构造器中的每一个参数都默认为 val ,即只可读。

// 2、服务器端发送给客户端的协议
case class ServerMessage(mes: String)

2.2、Akka 网络编程-Spark Master Worker 进程通讯项目

项目意义:
1、深入理解 Spark 的 Master 和 Worker 的通讯机制。
  2、为了方便同学们看 Spark 的底层源码,命名的方式和源码保持一致(如:通讯消息类命名就是一样的)。
  3、加深对主从服务心跳检测机制(HeartBeat)的理解,方便以后 spark 源码二次开发。
  
项目需求分析:
在这里插入图片描述

2.2.1、实现功能 1-Worker 完成注册

功能要求: Worker 注册到 Master,Master 完成注册,并回复 Worker 注册成功。
在这里插入图片描述
示例代码如下:
MasterActor.scala

package com.test.akka.sparkmasterworker.master

import akka.actor.{Actor, ActorSystem, Props}
import com.test.akka.sparkmasterworker.common.{RegisterWorkerInfo, RegisteredWorkerInfo, WorkerInfo}
import com.typesafe.config.ConfigFactory

import scala.collection.mutable

class MasterActor extends Actor {
  // 定义一个 mutable.HashMap 属性,用于管理 Worker
  val workers = mutable.HashMap[String, WorkerInfo]()

  override def receive: Receive = {
    case "start" => println("Master服务器启动了...")
    // 接收到 Worker 客户端注册的信息,保存进 HashMap
    case RegisterWorkerInfo(id, cpu, ram) => {
      if (!workers.contains(id)) {
        // 创建 WorkerInfo
        val workerInfo = new WorkerInfo(id, cpu, ram)
        // 加入到 HashMap
        workers += (id -> workerInfo)
        println("服务器的Workers= " + workers)
        // 回复客户端注册成功
        sender() ! RegisteredWorkerInfo
      }
    }
  }
}

object MasterActorApp {
  def main(args: Array[String]): Unit = {
    val host = "127.0.0.1" // 服务端ip地址
    val port = 10005 // 端口
    // 创建 config 对象,指定协议类型、监听的ip和端口
    val config = ConfigFactory.parseString(
      s"""
         |akka.actor.provider="akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname=$host
         |akka.remote.netty.tcp.port=$port
        """.stripMargin)
    // 先创建 ActorSystem
    val masterActorSystem = ActorSystem("Master", config)
    // 再创建 Master 的 Actor 和 ActorRef
    val masterActorRef = masterActorSystem.actorOf(Props[MasterActor], "MasterActor-01")

    // 启动 Master
    masterActorRef ! "start"
  }
}

WorkerActor.scala

package com.test.akka.sparkmasterworker.worker

import akka.actor.{Actor, ActorRef, ActorSelection, ActorSystem, Props}
import com.atguigu.akka.sparkmasterworker.common.{RegisterWorkerInfo, RegisteredWorkerInfo}
import com.typesafe.config.ConfigFactory

class WorkerActor(serverHost: String, serverPort: Int) extends Actor {
  // 定义一个 MasterActorRef
  var masterActorProxy: ActorSelection = _

  // 定义 Worker 的编号
  var id = java.util.UUID.randomUUID().toString

  // 在 Actor 中有一个方法 preStart 方法,它会在 Actor 运行前执行
  // 在 Akka 开发中,通常将初始化的工作,放在 preStart 方法中
  override def preStart(): Unit = {
    this.masterActorProxy = context.actorSelection(s"akka.tcp://Master@${serverHost}:${serverPort}/user/MasterActor-01")
    println("this.masterActorProxy=" + this.masterActorProxy)
  }

  override def receive = {
    case "start" => {
      println("Worker客户端启动运行")
      // 给服务器发送一个注册信息
      masterActorProxy ! RegisterWorkerInfo(id, 16, 16 * 1024)
    }
    case RegisteredWorkerInfo => {
      println("WorkedId= " + id + " 注册成功!")
    }
  }
}

object WorkerActorApp {
  def main(args: Array[String]): Unit = {
    val (host, port, serverHost, serverPort) = ("127.0.0.1", 10001, "127.0.0.1", 10005)
    val config = ConfigFactory.parseString(
      s"""
         |akka.actor.provider="akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname=$host
         |akka.remote.netty.tcp.port=$port
        """.stripMargin)

    // 创建 ActorSystem
    val workerActorSystem = ActorSystem("Worker", config)
    // 创建 WorkerActor 的 Actor 和 ActorRef
    val workerActorRef: ActorRef = workerActorSystem.actorOf(Props(new WorkerActor(serverHost, serverPort)), "WorkerActor-01")

    // 启动客户端
    workerActorRef ! "start"
  }
}

MessageProtocol.scala

package com.test.akka.sparkmasterworker.common

// 使用样例类来构建协议

// Worker 注册信息
case class RegisterWorkerInfo(id: String, cpu: Int, ram: Int)

// 这个是 WorkerInfo,是保存在 Master 的 HashMap 中的,该 HashMap 用于管理 Worker
// 将来这个 WorkerInfo 会扩展,比如 增加 Worker 上一次的心跳时间
class WorkerInfo(val id: String, val cpu: Int, val ram: Int)

// 当 Worker 注册成功,服务器返回一个 RegisteredWorkerInfo 对象
case object RegisteredWorkerInfo

2.2.2、实现功能 2-Worker 定时发送心跳

功能要求:Worker 定时发送心跳给 Master,Master 能够接收到,并更新 Worker 上一次心跳时间。
在这里插入图片描述
示例代码如下:
MessageProtocol.scala 中增加代码

package com.test.akka.sparkmasterworker.common

// 使用样例类来构建协议

// Worker 注册信息
case class RegisterWorkerInfo(id: String, cpu: Int, ram: Int)

// 这个是 WorkerInfo,是保存在 Master 的 HashMap 中的,该 HashMap 用于管理 Worker
// 将来这个 WorkerInfo 会扩展,比如 增加 Worker 上一次的心跳时间
class WorkerInfo(val id: String, val cpu: Int, val ram: Int) {
  // 新增属性:心跳时间
  var lastHeartBeatTime: Long = _
}

// 当 Worker 注册成功,服务器返回一个 RegisteredWorkerInfo 对象
case object RegisteredWorkerInfo

// 每隔一定时间定时器发送给 Master 一个心跳
case class HeartBeat(id: String)

// Worker 每隔一定时间定时器发送给 自己 一个消息
case object SendHeartBeat

MasterActor.scala 中增加代码

    case HeartBeat(id) => {
      // 更新对应的 Worker 的心跳时间
      // 1、先从 Worker 中取出 WorkerInfo
      val workerInfo = workers(id)
      workerInfo.lastHeartBeatTime = System.currentTimeMillis()
      println("Master更新了 " + id + " 的心跳时间 ")
    }

WorkerActor.scala 中增加代码

      // 当客户端注册成功后,就定义一个定时器,每隔一定时间,发送 SendHeartBeat 给自己
      import context.dispatcher
      context.system.scheduler.schedule(0 millis, 3000 millis, self, SendHeartBeat)
    case SendHeartBeat => {
      println("WorkedId= " + id + " 给Master发送心跳")
      masterActorProxy ! HeartBeat(id)
    }

2.2.3、实现功能 3-Master 启动定时任务,定时检测注册的 Worker

功能要求:Master 启动定时任务,定时检测注册的 Worker 有哪些没有更新心跳,已经超时的 Worker,将其从 HashMap 中删除掉。
在这里插入图片描述
示例代码如下:
MessageProtocol.scala 中增加代码

// Master 给自己发送一个触发检查超时 Worker 的信息
case object StartTimeOutWorker

// Master 给自己发消息,检测 Worker,对于心跳超时的
case object RemoveTimeOutWorker

MasterActor.scala 中增加代码

    case "start" => {
      println("Master服务器启动了...")
      // Master 启动定时任务,定时检测注册的 Worker 有哪些没有更新心跳,已经超时的 Worker,将其从 HashMap 中删除掉。
      self ! StartTimeOutWorker
    }
    
    // 开启定时器,每隔一定时间检测是否有 Worker 的心跳超时
    case StartTimeOutWorker => {
      println("开启了定时检测Worker心跳的任务")
      import context.dispatcher // 使用调度器时候必须导入dispatcher
      context.system.scheduler.schedule(0 millis, 9000 millis, self, RemoveTimeOutWorker)
    }
    
    // 判断哪些 Worker 心跳超时(nowTime - lastHeartBeatTime),对已经超时的 Worker,将其从 HashMap 中删除掉。
    case RemoveTimeOutWorker => {
      // 首先获取所有 Workers 的所有 WorkerInfo
      val workerInfos = workers.values
      val nowTime = System.currentTimeMillis()
      // 过滤出所有超时的 workerInfo 并删除即可
      workerInfos.filter(workerInfo => (nowTime - workerInfo.lastHeartBeatTime) > 6000)
        .foreach(workerInfo => workers.remove(workerInfo.id))
      println("当前有 " + workers.size + " 个Worker存活")
    }

2.2.4、实现功能 4-Master,Worker 的启动参数运行时指定

功能要求:Master,Worker 的启动参数运行时指定,而不是固定写在程序中的。

MasterActor.scala 中修改代码

    if (args.length != 3) {
      println("请输入参数 host port MasterActor的名字")
      sys.exit()
    }
    val host = args(0)  // 服务端ip地址
    val port = args(1)  // 端口
    val masterName = args(2)  // MasterActor的名字
    ......
    // 再创建 Master 的 Actor 和 ActorRef
    val masterActorRef = masterActorSystem.actorOf(Props[MasterActor], s"${masterName}")

WorkerActor.scala 中增修改代码

    if (args != 6) {
      println("请输入参数 host port WorkerActor的名字 serverHost serverPort MasterActor的名字")
    }

    val host = args(0)
    val port = args(1)
    val workerName = args(2)

    val serverHost = args(3)
    val serverPort = args(4)
    val masterName = args(5)
    ......
    // 创建 WorkerActor 的 Actor 和 ActorRef
    val workerActorRef: ActorRef = workerActorSystem.actorOf(Props(new WorkerActor(serverHost, serverPort.toInt, masterName)), s"${workerName}")

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值