【Akka系列】之 Actor模型如何满足现代分布式系统的需求
本文翻译自https://doc.akka.io/docs/akka/current/guide/actors-intro.html
Actor模型如何满足现代分布式系统的需求
正如前面主题叙述的一样,常见的编程实践没有很好的解决高要求的现代系统的需求。幸好,我们不需要废弃已有的编程实践。相反,Actor
模型以一种原则性的方式解决了这些不足之处,使系统更加匹配我们的预期模型。Actor
模型抽象使你以通信的方式思考你的代码,像一个大组织里人与人之间的交流一样。
Actors
使我们:
- 无需借助锁实现封装。
- 使用响应信号、改变状态和相互发送信号的协作实体的模型驱动整个应用向前。
- 不再担心和我们的世界观不匹配的执行机制。
消息传递机制避免了锁和阻塞
相对于调用方法,actors
之间发送消息。发送一条消息并未将“执行线程”从发送者转移到目标。一个actor
可以发送一条消息并无阻塞的继续运行。因此,在同样的时间内,它可以完成更多任务。
对于对象,当方法返回时,它会释放对其执行线程的控制。在这方面,actors
和对象非常像,处理完当前消息后它们回复该消息并返回执行。这样,actors
实际上实现了我们对“对象”设想的执行:
消息传递和方法调用之间一个重要的区别是消息没有返回值。通过发送一条消息,一个actor
委派一个任务给另一个actor
。正如我们在上篇《一个调用栈的错觉》部分所看到的,如果发送消息的actor
希望得到一个返回值,那么它要么需要阻塞要么在同一个线程内执行另一个actor
的任务。相反,接收消息的actor
在一个回复消息中传递结果。
我们的模型需要的第二个关键改变是恢复封装。Actors
像对象响应方法调用一样响应消息。区别在于不是多线程入侵了actor
并对内部状态和不变体造成破坏,而是actors
独立处理收到的消息,并且它们一个一个地响应连续到来的消息。虽然每个actor
连续地处理发给它的消息,不同的actors
之间并发地工作,所以一个actor
系统可以同时处理硬件可支持数量的消息。
因为每个actor
中最多一个消息正在被处理,一个actor
无需同步即可保持不变体。这无需使用锁而自然存在:
总之,当一个Actor
收到一条消息时:
Actor
将这条消息添加到队列尾部- 如果
Actor
没有被调度执行,它将被标记为ready
。- 一个(隐藏的)调度器实体获取这个
Actor
并开始执行它。Actor
在队列头部取出一条消息。Actor
改变内部状态,给相应actor
发送消息。Actor
被调度完毕。
为了完成以上行为,每个Actor
有:
- 一个邮箱 (存储收到消息的队列)
- 一个行为(
actor
的状态,内部变量等等)- 消息 (代表一个信号的数据段,类似于方法调用及参数)
- 一个执行环境 (响应消息的
actors
所处的并调用actors
的消息处理代码的机器)- 一个地址 (
消息存到actor
的邮箱。Actor
的行为描述actor
如何响应消息 (比如发送更多的消息和改变状态)。一个执行环境管理一个线程池以透明地驱动这些行为。
这是一个非常简单的模型,并且它解决了上篇文章枚举的问题:
- 通过将执行与信号解耦保持封装 (方法调用转移执行,消息传递没有转移)
- 不再需要锁。一个
actor
的内部状态只有通过一个一个被处理的消息修改,这消除了当试图保持不变体时的竞争。没有了锁,发送消息的actors
不会被阻塞。成千上万的actors
可以被高效的调度在十几个线程上以发挥现代CPUs
的最大潜力。任务委派是actors
操作的自然模式。Actors
的状态是本地的并且没有被共享,修改和数据通过消息传送,这映射了现代内存层次结构的真实工作方式。在许多情况下,这意味着只转移包含消息中数据的cache行
而将局部状态和数据cache
保持在原本的核。这个模型恰好映射了远程通信——状态被保存在机器RAM
内,修改和数据作为数据包(packets
)通过网络传送。
Actors优雅地处理error
因为没有了相互发送消息的actors
之间共享的调用栈,所以我们要以不同的方式处理error
情况。有两种我们需要考虑的errors
:
- 第一种情况是当目标
actor
被委派的任务由于任务出错而失败时 (典型的是校验问题,比如一个不存在的用户ID
)。这种情况下,目标actor
封装的服务是完好无损的,只是任务自身是错误的。目标actor
应该回复消息发送者actor
复一条消息,报告错误情况。这没什么特别的,错误是域的一部分,因此是普通信息。- 第二种情况是当一个服务自身遇到一个内部错误。
Akka
将所有actors
组织成一个树状层级结构,例如,一个actor
是它所创建的新actor
的父亲。这和操作系统将进程组织成一棵树十分相似。就像一个进程一样,当一个actor
失败的时候,它的父actor
被通知并且它可以对失败做出反应。同样地,如果父actor
停止了,它的所有子actors
也递归地停止。这个服务被称为监督,它是Akka
的核心。
一个监督者(parent
)可以决定在特定类型失败的时候重启或者在其他类型失败的时候完全停止它的子actors
。子actors
不会悄悄地死掉 (带有进入一个无限循环的明显异常),相反它们要么失败,它的父actor
处理error
,要么停止 (这种情况下相关的单元自动被通知)。总有一个负责管理一个actor
的实体:它的父actor
。重启在外部不可见:当目标actor
重启之后,相关的actors
可以继续发送消息。
现在,让我们简单看一下Akka
提供的功能。
- 点赞
- 收藏
- 关注作者
评论(0)