【Akka系列】之 Actor模型如何满足现代分布式系统的需求

举报
荔子 发表于 2020/12/15 11:04:59 2020/12/15
【摘要】 正如前面主题叙述的一样,常见的编程实践没有很好的解决高要求的现代系统的需求。幸好,我们不需要废弃已有的编程实践。相反,Actor模型以一种原则性的方式解决了这些不足之处,使系统更加匹配我们的预期模型。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收到一条消息时:

  1. Actor将这条消息添加到队列尾部
  2. 如果Actor没有被调度执行,它将被标记为ready
  3. 一个(隐藏的)调度器实体获取这个Actor并开始执行它。
  4. Actor在队列头部取出一条消息。
  5. Actor改变内部状态,给相应actor发送消息。
  6. Actor被调度完毕。

为了完成以上行为,每个Actor有:

  • 一个邮箱 (存储收到消息的队列)
  • 一个行为(actor的状态,内部变量等等)
  • 消息 (代表一个信号的数据段,类似于方法调用及参数)
  • 一个执行环境 (响应消息的actors所处的并调用actors的消息处理代码的机器)
  • 一个地址 (more on this later)

  消息存到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提供的功能。

【版权声明】本文为华为云社区用户翻译文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容, 举报邮箱:cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。