You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

14 KiB

17 | 分布式计算模式之Actor一门甩锅的艺术

你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。

我在前两篇文章中带你一起学习了MapReduce和Stream计算模式相信你对批处理和流计算也有了一定的了解。虽然这两种计算模式对数据的处理方式不同但都是以特定数据类型分别对应静态数据和动态数据作为计算维度。

在接下来两篇文章中我将从计算过程或处理过程的维度与你介绍另外两种分布式计算模式即Actor和流水线。分布式计算的本质就是在分布式环境下多个进程协同完成一件复杂的事情但每个进程各司其职完成自己的工作后再交给其他进程去完成其他工作。当然对于没有依赖的工作进程间是可以并行执行的。

你是不是想说,分布式进程那么多,如果需要开发者自己去维护每个进程之间的数据、状态等信息,这个开发量可不是一般得大,而且特别容易出错。那么,有没有什么办法可以让开发者只关注自己的逻辑呢?

答案是肯定的Actor计算模式就能满足你的需求。也就是说你可以把数据、状态等都扔给Actor。这是不是“一门甩锅的艺术”呢

接下来我们就一起打卡分布式计算模式中的Actor模式。

什么是Actor

在第10篇文章“分布式体系结构之非集中式结构:众生平等”中我曾提到Akka框架基于Actor模型提供了一个用于构建可扩展的、弹性的、快速响应的应用程序的平台。

其中Actor类似于一个“黑盒”对象封装了自己的状态和行为使得其他Actor无法直接观察到它的状态调用它的行为。多个Actor之间通过消息进行通信这种消息类似于电子邮箱中的邮件。Actor接收到消息之后才会根据消息去执行计算操作。

那么,**Actor模型又是什么呢**Actor模型代表一种分布式并行计算模型。这种模型有自己的一套规则规定了Actor的内部计算逻辑以及多个Actor之间的通信规则。在Actor模型里每个Actor相当于系统中的一个组件都是基本的计算单元。

Actor模型的计算方式与传统面向对象编程模型Object-Oriented ProgrammingOOP类似,一个对象接收到一个方法的调用请求(类似于一个消息),从而去执行该方法。

但是OOP因为数据封装在一个对象中不能被外部访问当多个外部对象通过方法调用方式即同步方式进行访问时会存在死锁、竞争等问题无法满足分布式系统的高并发性需求。而Actor模型通过消息通信采用的是异步方式克服了OOP的局限性适用于高并发的分布式系统。

举一个最简单的例子假如你现在定义了三个对象A、B和C对象C中有一个函数Function现在对象A和对象B同时调用对象C中的Function此时对象C中的Function就成为了我们在第3篇文章“分布式互斥:有你没我,有我没你”中提到的共享资源,有可能会存在竞争、死锁等问题。

而对于Actor模式对象A、B和C对应着Actor A、Actor B和Actor C当Actor A和Actor B需要执行Actor C中的Function逻辑时Actor A和 Actor B会将消息发送给Actor C Actor C的消息队列存储着Actor A和 Actor B的消息然后根据消息的先后顺序执行Function即可。

也就是说Actor模式采用了异步模式并且每个Actor封装了自己的数据、方法等解决了OOP存在的死锁、竞争等问题。

Actor计算模式

接下来我们再一起看看Actor计算模式吧。如下图所示描述了具有3个Actor的Actor模型。

可以看到,Actor模型的三要素是状态、行为和消息有一个很流行的等式Actor模型=(状态+行为)+ 消息。

接下来,我们一起看看这三要素的具体含义吧。

  • 状态State。Actor的状态指的是Actor组件本身的信息相当于OOP对象中的属性。Actor的状态会受Actor自身行为的影响且只能被自己修改。
  • 行为Behavior。Actor的行为指的是Actor的计算处理操作相当于OOP对象中的成员函数。Actor之间不能直接调用其他Actor的计算逻辑。Actor只有收到消息才会触发自身的计算行为。
  • 消息Mail。Actor的消息以邮件形式在多个Actor之间通信传递每个Actor会有一个自己的邮箱MailBox用于接收来自其他Actor的消息因此Actor模型中的消息也称为邮件。一般情况下对于邮箱里面的消息Actor是按照消息达到的先后顺序FIFO进行读取和处理的。

了解了Actor的三要素后我们再一起看下Actor的工作原理吧。

Actor工作原理

为了方便你理解Actor的工作原理我会通过讲述3个Actor之间基于消息和消息队列的工作流程进行说明。这3个Actor的工作流程如下所示。

  1. Actor1和Actor3先后向Actor2发送消息消息被依次放入Actor2的MailBox队列的队尾;
  2. Actor2从MailBox队列的队首依次取出消息执行相应的操作由于Actor1先把消息发送给Actor2因此Actor2先处理Actor1的消息
  3. Actor2处理完Actor1的消息后更新内部状态并且向其他Actor发送消息然后处理Actor3发送的消息。

了解了Actor之间的消息交互和处理流程我再以一个具体案例和你详细解读一下Actor之间的消息传递过程吧。

我们已经知道,在系统中,不同的组件/模块可以视为不同的Actor。现在有一个执行神经网络的应用其中有两个组件A和B分别表示数据处理模块和模型训练模块。假设我们可以将组件A和B看作两个Actor训练过程中的数据可以通过消息进行传递。如上图所示完整的消息传输过程为

  1. 组件A创建一个Actor System用来创建并管理多个Actor。
  2. 组件A产生QuoteRequest消息即mail消息比如数据处理后的数据并将其发送给ActorRef。ActorRef是Actor System创建的组件B对应Actor的一个代理。
  3. ActorRef 将消息经过数据处理后的数据传输给Message Dispatcher模块。Message Dispatcher类似于快递的中转站负责接收和转发消息。
  4. Message Dispatcher将消息数据处理后的数据加入组件B的MailBox队列的队尾。
  5. Message Dispatcher将MailBox加入线程。需要注意的是只有当MailBox是线程时才能处理MailBox中的消息。
  6. 组件B的MailBox将队首消息数据取出并删除队首消息交给组件B处理进行模型训练。

Actor关键特征

通过上面的描述可以看出Actor的通信机制与日常的邮件通信非常类似。因此我们可以进一步总结出Actor模型的一些特点

  • **实现了更高级的抽象。**我在前面提到过Actor与OOP对象类似封装了状态和行为。但是Actor之间是异步通信的多个Actor可以独立运行且不会被干扰解决了OOP存在的竞争问题。
  • **非阻塞性。**在Actor模型中Actor之间是异步通信的所以当一个Actor发送信息给另外一个Actor之后无需等待响应发送完信息之后可以在本地继续运行其他任务。也就是说Actor模型通过引入消息传递机制从而避免了阻塞。
  • 无需使用锁。Actor从MailBox中一次只能读取一个消息也就是说Actor内部只能同时处理一个消息是一个天然的互斥锁所以无需额外对代码加锁。
  • **并发度高。**每个Actor只需处理本地MailBox的消息因此多个Actor可以并行地工作从而提高整个分布式系统的并行处理能力。
  • **易扩展。**每个Actor都可以创建多个Actor从而减轻单个Actor的工作负载。当本地Actor处理不过来的时候可以在远程节点上启动Actor然后转发消息过去。

虽然Actor模型有上述的诸多优点但它并不适用于分布式领域中所有的应用平台或计算框架。因为Actor模型还存在如下一些不足之处

  • Actor提供了模块和封装但缺少继承和分层这使得即使多个Actor之间有公共逻辑或代码部分都必须在每个Actor中重写这部分代码也就是说重用性小业务逻辑的改变会导致整体代码的重写。
  • Actor可以动态创建多个Actor使得整个Actor模型的行为不断变化因此在工程中不易实现Actor模型。此外增加Actor的同时也会增加系统开销。
  • Actor模型不适用于对消息处理顺序有严格要求的系统。因为在Actor模型中消息均为异步消息无法确定每个消息的执行顺序。虽然可以通过阻塞Actor去解决顺序问题但显然会严重影响Actor模型的任务处理效率。

尽管Actor模型在需要同步处理的应用等场景具有局限性但它在异步场景中应用还是比较广泛的。接下来我们就一起看看Actor目前都应用在哪些地方吧。

Actor模型的应用

Actor模型在1973年被提出已广泛应用在多种框架和语言中。可以说很多框架或语言支持Actor编程模型是为了给开发者提供一个通用的编程框架让用户可以聚焦到自己的业务逻辑上而不用像面向对象等编程模型那样需要关心死锁、竞争等问题。

那么到底有哪些框架或语言支持Actor编程模型呢接下来我就和你列举几个典型的框架或语言吧以方便你参考。

  • Erlang/OTP。Erlang是一种通用的、面向并发的编程语言使用Erlang编写分布式应用比较简单而OTP就是Erlang技术栈中的标准库。Actor模型在Erlang语言中得到广泛支持和应用其他语言的Actor逻辑实现在一定程度上都是参照了Erlang的模式。实现了Actor模型逻辑的Erlang/OTP可以用于构建一个开发和运行时环境从而实现分布式、实时的、高可用性的系统。
  • Akka。Akka是一个为Java和Scala构建高度并发、分布式和弹性的消息驱动应用程序的工具包。Akka框架基于Actor模型提供了一个用于构建可扩展的、弹性的、快速响应的应用程序的平台。通过使用Actors和Streams技术 Akka为用户提供了多个服务器使用户更有效地使用服务器资源并构建可扩展的系统。
  • Quasar (Java) 。Quasar是一个开源的JVM库极大地简化了高度并发软件的创建。Quasar在线程实现时参考了Actor模型采用异步编程逻辑从而为JVM提供了高性能、轻量级的线程可以用在Java和Kotlin编程语言中。

知识扩展Akka中Actor之间的通信可靠性是通过Akka集群来保证的那么Akka集群是如何检测节点故障的呢

在第10篇文章“分布式体系结构之非集中式结构:众生平等”中我与你介绍了Akka 集群是一个去中心化的架构比如现在集群中有n个节点这n个节点之间的关系是对等的。节点之间采用心跳的方式判断该节点是否故障但未采用集中式架构中的心跳检测方法。

Akka 集群中的故障检测方法是集群中每个节点被k个节点通过心跳进行监控比如k = 3节点1被节点2、节点3和节点4通过心跳监控当节点2发现节点1心跳不可达时就会标记节点1为不可达unreachable并且将节点1为不可达的信息通过Gossip传递给集群中的其他节点这样集群中所有节点均可知道节点1不可达。

其中k个节点的选择方式是将集群中每个节点计算一个哈希值然后基于哈希值将所有节点组成一个哈希环比如从小到大的顺序最后根据哈希环针对每个节点逆时针或顺时针选择k个临近节点作为监控节点。

总结

接下来我们小结一下吧。今天我与你介绍了分布式计算中一门甩锅的计算模型即Actor模型。

首先我介绍了什么是Actor模型以及Actor模型的三要素包括状态、行为和消息。

其次我介绍了Actor的工作原理并通过实例介绍了Actor之间通过消息及消息队列进行异步通信的流程以便于你进一步理解Actor的工作原理。

最后我为你介绍了几个当前支持Actor编程模型的框架和语言以便于你在需要采用Actor模型编程时做一个参考。

最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。

著名的Erlang并发编程语言以及Akka这一分布式计算框架都实现了Actor模型的计算逻辑。因此即使你在之前未曾接触过Actor模型学习了这篇文章后你也可以根据开源的Erlang或Akka项目去更深刻地理解Actor模型了加油

思考题

Actor是否可以采用阻塞方式去运行呢原因是什么呢

我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!