第17章 Actor 并发
17.1 Actor 模型
Actor 模型在并发程序中通过尽量避免锁和共享状态来设计并发,整个
Actor 模型是一个异步模型,系统中抽象出很多 Actor,每一个 Actor 是一个
消息的发送方和接受方,每一个 Actor 都有一个邮箱,类似于队列,ActorA
发送消息给 ActorB,不是通过直接调用,而是将消息发送的 ActorB 的邮箱里
面,ActorB 检测到消息后从阻塞状态恢复,处理消息,达到两个 Actor 之间
的消息通讯。AKKA 是使用 Scala 语言基于 Actor 模型实现的一个并发库。
17.1.1 消息传递
假设有通讯模型:学生和老师。学生每天早上都会给老师发送邮件,而
聪明的老师都会回复一句名言。这里需要解释:
1、学生发送邮件。一旦发送成功,邮件不能再修改。这天然就具备了不
可变性;
2、老师会自己决定何时检查邮箱;
3、老师还会回复一封邮件(也是不可变的);
4、学生会自己决定何时检查邮箱;
5、学生不会一直等待回信(非阻塞的)
这就可以总结出 Actor 模型的一个基本特征—异步消息传递
17.1.2 并发
假设有三个聪明的老师和三个学生。每个学生都会给每个老师发送邮
件。每个人都有他自己的邮箱。这里需要注意的一点:默认情况下,邮箱里
面的邮件是按照他们先后达到的次序进行阅读和处理的。很想一个队列。
17.1.3 错误恢复
假如这三个老师分别来自不同的院系:历史系、地理系和哲学系。
历史系的老师用过去的某个事件笔记进行回复;而地理系的老师回复了
一个有趣的地点;哲学系的老师回复了一个引用。每个学生分别给每个老师
发送消息并分别得到回复。学生并不关心邮件到底是系里的哪个老师回复
的。如果有一天有个老师生病了呢?系里至少得有一个老师在处理邮件才
行。这样的话,系里的另一位老师就会顶上这项工作。
这里需要注意的地方:
1、会有一个 Actor 池,每个 Actor 会处理不同的事件。
2、Actor 做的事情可能会抛出异常,而它自己无法从中恢复。在这种情
况下,需要再生成(created )一个新的 Actor 来顶替它。换句话说,这个新
的 Actor 会忽略刚才那条消息,继续处理剩余的消息。这些也被称为指令
(Directive)。
17.1.4 多任务
假设学生需要考试成绩,每个老师是通过邮件来发送的。也就是说,
Actor 可以处理多种类型的消息。
17.1.5 消息链
假如学生只想收到一封邮件而不是三件呢?
我们也可用用 Actor 来实现!我们可以通过分层来把老师连在一起。
学生和老师变成我们的 Actor,Email Inbox 对应 Mailbox 组件。请求和
响应不可修改、它们是不可变对象。最后,Actor 中的 MessageDispatcher 组
件将管理 mailbox,并且将消息路由到对应的 Mailbox 中。
17.2 Actor 消息传递
我们现在考虑消息从 StudentSimulatorApp 单独发送到 TeacherActor。这
里所说的 StudentSimulatorApp 只不过是一个简单的主程序。
这副图片解释如下:
1、Student 创建了一些东西,称为 ActorSystem;
2、他用 ActorSystem 来创建一个 ActorRef,并将 QuoteRequest message
发送到 ActorRef 中(到达 TeacherActor 的一个代理);
3、ActorRef 将 message 单独传输到 Dispatcher;
4、Dispatcher 将 message 按照顺序保存到目标 Actor 的 MailBox 中;
5、然后 Dispatcher 将 Mailbox 放在一个 Thread 中
6、MailBox 按照队列顺序取出消息,并最终将它递给真实的
TeacherActor 接受方法中。
17.2.1 App 程序
正如我们从图中了解到,App 程序的创建如下步骤:
1、创建一个 ActorSystem;
2、用创建好的 ActorSystem 来创建一个通往 Teacher Actor 的代理
(ActorRef)
3、将 message 发送到这个代理中。
17.2.2 创建一个 ActorSystem
ActorSystem 是进入 Actor 世界的切入点,通过 ActorSystem 你可以创建
和停止 Actors,甚至关掉整个 Actor 环境!
另一方面,Actor 是一个体系,ActorSystem 类似于 java.lang.Object or
scala.Any,能够容纳所有的 Actor!它是所有的 Actor 的父类。当你创建一个
Actor,你可以用 ActorSystem 的 actorOf 方法。
初始化 ActorSystem 的代码类似下面:
val system=ActorSystem("UniversityMessageSystem")
UniversityMessageSystem 就是你给 ActorSystem 取得名字。
17.2.3 为 Teacher Actor 创建代理
val teacherActorRef:ActorRef=actorSystem.actorOf(Props[TeacherActor])
actorOf 是 ActorSystem 中创建 Actor 的方法。但是正如你所看到的,它
并不返回我们所需要的 TeacherActor 对象,它的返回类型为 ActorRef。
ActorRef 为真实 Actor 的充当代理,客户端并不直接和 Actor 进行通信。
这就是 Actor Model 中的处理方式,该方式避免直接进入 TeacherActor 或者任
何 Actor 中的任何 custom/private 方法或者变量。
你仅仅将消息发送到 ActorRef ,该消息最终发送到你的 Actor 中,你无
法直接和你的 Actor 进行直接通信。
17.2.4 将 message 发送到代理中
message 一般是我们定义 case 类,只要通过 ! 操作符就能发送消息。
如下:
teacherActorRef ! QuoteRequest
17.2.5 DISPATCHER 和邮箱
ActorRef 取出消息并放到 Dispatcher 中。Dispatcher 将 message 从
ActorRef 传递到 MailBox 中。在这种模式下,当我们创建了 ActorSystem 和
ActorRef,Dispatcher 和 MailBox 也将会创建。
每个 Actor 都有一个 MailBox。在我们之前的模型中,每个 Teacher 也有
一个 MailBox。Teacher 需要检查 MailBox 并处理其中的 message。MailBox 中
有个队列并以 FIFO 方式储存和处理消息。
17.2.6 Actor 处理
当 MailBox 的 run 方法被运行,它将从队列中取出消息,并传递到 Actor
的 receive 方法匹配消息并进行处理。
17.3 Actor 配置
我们可以用 ActorSystem 类中的 actorOf 方法来创建 Actor。其实在
ActorSystem 中有大量的方法我们可以用,在这里我将介绍 Configuration 和
Scheduling 方法。
当我们创建 ActorSystem,用的是 ActorSystem 对象的 apply 方法,而该
并没有注明任何的配置,它将在 classpath 中寻找 application.conf、
application.json 和 application.properties,并自动的加载他们。所以这段代码
val system=ActorSystem("UniversityMessagingSystem")
如果你不喜欢用 application.conf,或者想拥有你自己的配置文件。你可
以通过重载来传递你自己的配置,而不是从 classpath 来获取。
ConfigFactory.parseString 就是一种方法:
val actorSystem=ActorSystem("UniversityMessageSystem",
ConfigFactory.parseString("""akka.loggers =
["akka.testkit.TestEventListener"]"""))
当然,你也可以用 ConfigFactory.load 来实现
val system = ActorSystem("UniversityMessageSystem",
ConfigFactory.load("uat-application.conf"))
如果你想在运行的时候获取你自己的配置参数,你可以通过下面的 API
获取:system.settings.config.getValue("akka.loggers"))
17.4 Actor 调度
在 ActorSystem 中有大量的方法调用 scheduler,而 scheduler 返回的是
Scheduler。Scheduler 中有大量的 schedule 方法,利用他们我们可以在 Actor
环境下做大量的调度事件。context.system.scheduler。