什么是ACTORS
Akka Actors遵循Actor模型(废话!)
我们这把Actor当作是一个人,这个人不会自己和其他的人直接说话,他们只通过mail来进行交流。
现在来探讨Actors的一些特性:
一、消息传递
假设有两个人:学生和聪明的老师。学生每天早上都会给老师发送邮件,而聪明的老师都会回复一句名言。这里需要解释:
1、学生发送邮件。一旦发送成功,邮件不能再修改。这天然就具备了不可变性;
2、老师会自己决定何时检查邮箱;
3、老师还会回复一封邮件(也是不可变的);
4、学生会自己决定何时检查邮箱;
5、学生不会一直等待回信(非阻塞的)
这就可以总结出Actor模型的一个基本特征——消息传递
二、并发
现在,假设有三个聪明的老师和三个学生。每个学生都会给每个老师发送邮件。这会发生什么事?其实什么都没改变!每个人都有他自己的邮箱。这里需要注意的一点:默认情况下,邮箱里面的邮件是按照他们先后达到的次序进行阅读和处理的。
本质上,这很像是ConcurrentLinkedQueue。没有人去等待邮件被阅读,简单来说这就是一个非阻塞的消息(在Akka中内置了许多的mailboxes,这里http://doc.akka.io/docs/akka/snapshot/scala/mailboxes.html,包括了有界和基于优先级的。其实,我们自己也可以去实现)。
三、错误恢复
假如这三个老师分别来自不同的院系:历史系、地理系和哲学系。
历史系的老师用过去的某个事件笔记进行回复;而地理系的老师回复了一个有趣的地点;哲学系的老师回复了一个引用。每个学生分别给每个老师发送消息并分别得到回复。学生并不关心邮件到底是系里的哪个老师回复的。如果有一天有个老师生病了呢?系里至少得有一个老师在处理邮件才行。这样的话,系里的另一位老师就会顶上这项工作。
这里需要注意的地方:
1、会有一个Actor池,每个Actor会处理不同的事件。
2、Actor做的事情可能会抛出异常,而它自己无法从中恢复。在这种情况下,需要再生成(created )一个新的Actor来顶替它。换句话说,这个新的Actor会忽略刚才那条消息,继续处理剩余的消息。这些也被称为指令(Directive),后面我们会再讲到它们。
四、多任务
假设学生需要考试成绩,每个老师是通过邮件来发送的。也就是说,Actor可以处理多种类型的消息。
五、消息链
假如学生只想收到一封邮件而不是三件呢?
我们也可用用Actor来实现!我们可以通过分层来把老师连在一起。这个我们将在后面讲到Supervisor和Future的时候再回来讲。
应Mohan的要求,我们把类比的实体和Actor模型中的组件做一下映射。
1.学生和老师都是Actor,Actor是用来收发消息,处理逻辑的
2.教学总监和班主任:ActorSystem,它是用来创建Actor的,并且监控和管理他创建的Actor
3.ActorSystem是单例的,一个进程中只要有一个即可
4.Actor是多例的,可以创建多个实例
5.Actor编程模式就通过送消息来实现并发的。
Akka就是基于Actor编程模型实现的,可以是单机,也可以是分布式的
总结:
1.Actor就是通过发生消息实现并发
2.一个人就相当于一个Actor
3.Actor和Actor直接是可以发送消息的,自己也可以给自己发生消息
4.消息是有类型的,里面封装的数据(case class)
5.Actor是有生命周期的,是由ActorSystem创建的,ActorSystem负责管理和监控Actors,ActorSystem是单例的(object),Actor是多例的
6.消息的接收者可以将消息返回给消息的发送者
RPC通信流程图
代码实现:(简单发送信息)
首先创建一个maven项目,导入如下依赖:
<!-- 常量 -->
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<encoding>UTF-8</encoding>
<scala.version>2.12.12</scala.version>
<scala.compat.version>2.12</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>
<!-- akka远程通信依赖 -->
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-remote_${scala.compat.version}</artifactId>
<version>${akka.version}</version>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<!-- 编译scala的插件 -->
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<!-- 编译java的插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<executions>
<execution>
<id>scala-compile-first</id>
<phase>process-resources</phase>
<goals>
<goal>add-source</goal>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>scala-test-compile</id>
<phase>process-test-resources</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- 打jar插件 -->
<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>
<!-- 指定maven方法 -->
<!-- <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">-->
<!-- <mainClass>cn._51doit.rpc.Master</mainClass>-->
<!-- </transformer>-->
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import com.typesafe.config.{Config, ConfigFactory}
class Master extends Actor{
//用来接收消息的
override def receive: Receive = {
//采用匹配模式进行判断接收
case "hello" => println("你好")
case "hello world" => println("你好世界")
//当接收到Worker发送的消息后,说明连接已经打通,此时可以使用sender()方法直接返回一个消息
sender() ! "hello china"
}
}
object Master{
def main(args: Array[String]): Unit = {
//设置配置信息
val masterHost = "localhost"
val masterPort = 8888
val configStr =
s"""
|akka.actor.provider = "akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname = $masterHost
|akka.remote.netty.tcp.port = $masterPort
|""".stripMargin
//获取配置信息
val config: Config = ConfigFactory.parseString(configStr)
//创建ActorSystem(Master的管理者),要求是单例的(object)
//参数一:ActorSystem的名字,参数二:配置信息
val system: ActorSystem = ActorSystem("Master-Actor-System", config)
//获取actor对象(Master的执行者),
//参数一:Props[Master],类似于java中的Master.class , 参数二:actoer的名字
val actorRef: ActorRef = system.actorOf(Props[Master], "Master-Actor")
// ! 表示发送单向异步消息。异步:“即发即弃,发送后就不管了,
//可以去做其他的事”,同步:"发送后会一直等待接收"。
//如果从actor内部调用,那么actor引用将作为隐式的“sender”参数隐式传递。
//如果从一个actor内部调用,那么这个actor 'sender'引用在接收actor的'sender()'
//成员变量中可用。如果不是,则没有发送方可用。
actorRef ! "hello" //没有指定发送对象,默认发送给自己
}
}
import akka.actor.{Actor, ActorRef, ActorSelection, ActorSystem, Props}
import com.typesafe.config.{Config, ConfigFactory}
class Worker extends Actor{
//preStart()方法在构造方法之后,receive方法之前执行一次
override def preStart(): Unit = {
//跟Master建立连接(context是Actor提供的一个隐式参数,可以直接使用)
//akka.tcp: ak通信协议
//Master-Actor-System 我们自定义的MasterActorSystem的名字
//Master-Actor 我们自定义的MasterActor的名字
//user不可省略
val masterRef: ActorSelection = context.actorSelection("akka.tcp://Master-Actor-System@localhost:8888/user/Master-Actor")
//使用连接对象发送异步信息,到Master
masterRef ! "hello world"
}
override def receive: Receive = {
case "hello china" => println("你好中国")
}
}
object Worker{
def main(args: Array[String]): Unit = {
//设置配置信息
val workerHost = "localhost"
val workerPort = 8889
val configStr =
s"""
|akka.actor.provider = "akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname = $workerHost
|akka.remote.netty.tcp.port = $workerPort
|""".stripMargin
//获取配置信息
val config: Config = ConfigFactory.parseString(configStr)
//创建ActorSystem(worker的管理者)
val actorSystem: ActorSystem = ActorSystem("Worker-Actor-System", config)
//创建actor对象(worker的执行者)
val actorRef: ActorRef = actorSystem.actorOf(Props[Worker], "Worker-Actor")
}
}
代码实现:(完整实现)
package com.doit.akka
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import com.typesafe.config.{Config, ConfigFactory}
//导入时间参数
import scala.concurrent.duration._
import scala.collection.mutable
class Master extends Actor{
//new一个装注册信息的Map集合
private val hashMap = new mutable.HashMap[String, WorkerInfo]()
//preStart在构造方法之后,receive方法之前执行一次
override def preStart(): Unit = {
//导入隐式参数
import context.dispatcher
//每隔15秒检测是否有超时节点发送一个指令(CheckTimeOutWorker)给master自己
context.system.scheduler.schedule(0 milliseconds,15000 milliseconds,self,CheckTimeOutWorker)
}
//用来接收消息的
override def receive: Receive = {
//采用匹配模式进行判断接收
//case "hello" => println("你好")
//接收注册信息,存入Map集合中
case RegisterWorker(workerId,memory,cores) => {
if( ! hashMap.contains(workerId)) {
hashMap(workerId)=WorkerInfo(workerId,memory,cores)
}
//println("注册成功")
//当接收到Worker发送的消息后,说明连接已经打通,此时可以使用sender()方法直接返回一个消息
sender() ! RegisteredWorker
}
case HearBeat(workerId) => {
//报告心跳信息时,更新worker的注册时间
val workerInfo: WorkerInfo = hashMap(workerId)
//获取当前时间,赋给workerInfo
workerInfo.lastHeartBeatTime=System.currentTimeMillis()
}
//接收到检测超时节点的指令
case CheckTimeOutWorker => {
//筛选出心跳信息和当前时间超过10s每跳的节点
val deadWorkers: Iterable[WorkerInfo] = hashMap.values.filter(w => (System.currentTimeMillis() - w.lastHeartBeatTime) > 10000)
//移除该节点
deadWorkers.foreach(w => hashMap -= w.workerId)
//打印现有存活的节点数
println(s"The number of remaining workers in master :${hashMap.size}")
}
}
}
object Master{
val MASTER_ACTOR_SYSTEM = "MASTER_ACTOR_SYSTEM"
val MASTER_ACTOR = "MASTER_ACTOR"
def main(args: Array[String]): Unit = {
//设置配置信息
val masterHost = args(0)
val masterPort = args(1).toInt
val configStr =
s"""
|akka.actor.provider = "akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname = $masterHost
|akka.remote.netty.tcp.port = $masterPort
|""".stripMargin
//获取配置信息
val config: Config = ConfigFactory.parseString(configStr)
//创建ActorSystem(Master的管理者),要求是单例的(object)
//参数一:ActorSystem的名字,参数二:配置信息
val system: ActorSystem = ActorSystem(MASTER_ACTOR_SYSTEM, config)
//获取actor对象(Master的执行者),参数一:Props[Master],类似于java中的Master.class , 参数二:actoer的名字
val actorRef: ActorRef = system.actorOf(Props[Master], MASTER_ACTOR)
// ! 表示发送单向异步消息。异步:“即发即弃,发送后就不管了,可以去做其他的事”,同步:"发送后会一直等待接收"。
//如果从actor内部调用,那么actor引用将作为隐式的“sender”参数隐式传递。
//如果从一个actor内部调用,那么这个actor 'sender'引用在接收actor的'sender()'成员变量中可用。如果不是,则没有发送方可用。
//actorRef ! "hello" //没有指定发送对象,默认发送给自己
}
}
package com.doit.akka
import java.util.UUID
import akka.actor.{Actor, ActorRef, ActorSelection, ActorSystem, Props}
import com.typesafe.config.{Config, ConfigFactory}
//导入时间参数
import scala.concurrent.duration._
//将这四个参数放在主构造器中
class Worker(val masterHost: String, val masterPort: Int, var workerMemory: Int, var workerCores: Int) extends Actor{
//使用随机数,获取workerId
val workerId = UUID.randomUUID().toString
var masterRef: ActorSelection = null
//preStart在构造方法之后,receive方法之前执行一次
override def preStart(): Unit = {
//跟Master建立连接(context是Actor提供的一个隐式参数,可以直接使用)
//akka.tcp: ak通信协议
//Master-Actor-System 我们自定义的MasterActorSystem的名字
//Master-Actor 我们自定义的MasterActor的名字
//user不可省略
masterRef = context.actorSelection(s"akka.tcp://${Master.MASTER_ACTOR_SYSTEM}@$masterHost:$masterPort/user/${Master.MASTER_ACTOR}")
//使用连接对象发送异步信息,到Master
masterRef ! RegisterWorker(workerId,workerMemory,workerCores)
}
override def receive: Receive = {
//接收到注册成功的回复
case RegisteredWorker => {
//导入隐式参数
import context.dispatcher
//向自己发送心跳信息(SendHeartBeat),设置从0秒开始发送,间隔10秒发送一次,然后再转发给master
context.system.scheduler.schedule(0 milliseconds,10000 milliseconds,self,SendHeartBeat)
}
//接收到自己发送过来的心跳信息
case SendHeartBeat => {
//转发给master,并附带上workerId
masterRef ! HearBeat(workerId)
}
}
}
object Worker{
//WORKER_ACTOR_SYSTEM和WORKER_ACTOR定义为常量
val WORKER_ACTOR_SYSTEM = "WORKER_ACTOR_SYSTEM"
val WORKER_ACTOR = "WORKER_ACTOR"
def main(args: Array[String]): Unit = {
//设置配置信息通过运行jar包调用mian方法时传入
val masterHost = args(0)
val masterPort = args(1).toInt
val workerHost = args(2)
val workerPort = args(3).toInt
val workerMemory = args(4).toInt
val workerCores = args(5).toInt
val configStr =
s"""
|akka.actor.provider = "akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname = $workerHost
|akka.remote.netty.tcp.port = $workerPort
|""".stripMargin
//获取配置信息
val config: Config = ConfigFactory.parseString(configStr)
//创建ActorSystem(worker的管理者)
val actorSystem: ActorSystem = ActorSystem(WORKER_ACTOR_SYSTEM, config)
//创建actor对象(worker的执行者)
//new Worker 时可以将配置信息传入到class Worker类中
actorSystem.actorOf(Props(new Worker(masterHost, masterPort, workerMemory, workerCores)), WORKER_ACTOR)
}
}
package com.doit.akka
case class WorkerInfo(val workerId : String,var memory : Int,var cores :Int){
var lastHeartBeatTime: Long = _
}
package com.doit.akka
//Master发送给自己的检测消息
case object CheckTimeOutWorker
//Worker -> Master的心跳消息
case class HearBeat(workerId: String)
//Master -> Worker注册成功的消息
case object RegisteredWorker
//Worker发送给Master的注册消息
case class RegisterWorker(workerId : String,memory : Int,cores :Int)
//Worker -> 自己的内部消息
case object SendHeartBeat
运行时可以打成jar包运行将master和worker运行在不同的机器上
jar包运行命令:
Master:
java -cp akka.jar(jar包路径) com.doit.akka.Master(main方法所在类的全类名) 192.168.128.2(本机ip)
8888(自定义的端口号)
Worker:
java -cp akka.jar(jar包路径) com.doit.akka.Master(main方法所在类的全类名) 192.168.128.2(Master机器ip)
8888(Master机器端口号) 192.168.128.201(本机ip) 8888(自定义的端口号,不同机器上运行,端口号可以相同)
2048(worker分配的内存) 2(worker分配的核数)