[面试]Actor模型
Actor模型
面试中自己说话不利落, 或者自己对知识点认识不全面.在这里进行一下记录. 理论部分都是收集(copy)自网上其他的博客.
什么是Actor模型
参与者模式(英语:Actor model)是一种并发运算上的模型。
“参与者”是一种抽象概念,被视为并发运算的基本单元:当一个参与者接收到一则消息,它可以做出一些决策、创建更多的参与者、发送更多的消息、决定要如何回答接下来的消息。Actor模型的理念非常简单:天下万物皆为Actor。
在使用Java进行并发编程时需要特别的关注锁和内存原子性等一系列线程问题,而Actor模型内部的状态由它自己维护即它内部数据只能由它自己修改(通过消息传递来进行状态修改).
Actor模型的组成是什么
1.状态(state):Actor中的状态指的是Actor对象的变量信息,状态由Actor自己管理,避免了并发环境下的锁和内存原子性等问题
2.行为(Behavior):行为指定的是Actor中计算逻辑,通过Actor接收到消息来改变Actor的状态
3.邮箱(mailBox):邮箱是Actor和Actor之间的通信桥梁,邮箱内部通过FIFO消息队列来存储发送方Actor消息,接受方Actor从邮箱队列中获取消息.
为什么同一个功能的Actor会有多个实例
尽管许多actors同时运行,但是一个actor只能顺序地处理消息。也就是说其它actors发送了三条消息给一个actor,这个actor只能一次处理一条。所以如果你要并行处理3条消息,你需要把这条消息发给3个actors。
消息异步地传送到actor,所以当actor正在处理消息时,新来的消息应该存储到别的地方。Mailbox就是这些消息存储的地方。
Actor做什么
当一个actor接收到消息后,它能做如下三件事中的一件:
1. Create more actors; 创建其他actors
2. Send messages to other actors; 向其他actors发送消息
3. Designates what to do with the next message. 指定下一条消息到来的行为
一个actor能维持一个私有状态。「指定下一条消息来到做什么」意味着可以定义下条消息来到时的状态。
设想有一个actor像计算器,它的初始状态是数字0。当这个actor接收到add(1)
消息时,它并不改变它原本的状态,而是指定当它接收到下一个消息时,状态会变为1。
Actor模型的优点
1. 事件模型驱动--Actor之间的通信是异步的,即使Actor在发送消息后也无需阻塞或者等待就能够处理其他事情
2. 强隔离性--Actor中的方法不能由外部直接调用,所有的一切都通过消息传递进行的,从而避免了Actor之间的数据共享,想要观察到另一个Actor的状态变化只能通过消息传递进行询问.
3. 位置透明--无论Actor地址是在本地还是在远程机上对于代码来说都是一样的
4. 轻量性--Actor是非常轻量的计算元,只需少量内存就能达到高并发
5. 如果不用actor模型, 随着项目体量增大,业务愈加复杂,不可避免大量使用“锁”,然而“锁”的其实是很耗性能的,所以大量使用锁的机制肯定会造成效率不高. 即使大量依赖“锁”解决了系统中资源竞争的情况,但是由于没有一个规范的编程模式,最后系统的稳定性肯定会出问题,最根本的原因是没把系统的任务调度抽象出来,由于任务调度和业务逻辑的耦合在一起,很难做一个很高层的抽象,保证任务调度有序。
6.作为开发者我们只需要关心每个Actor的逻辑就可以了,避免“锁”的“滥用”
Actor缺点
当所有逻辑都跑在Actor中时,很难掌控Actor的粒度,稍有不慎就可能造成系统中Actor个数爆炸的情况,Actor当出现必须共享数据或者状态时就很难避免使用“锁”,但似乎由于上面的“Actor可能会堵塞自己,但Actor不应该堵塞它运行的线程”准则冲突,这个时候也许可以选择使用redis做数据共享.
Actor模型的调度方式
Actor模型有两种任务调度方式:
1. 基于线程的调度:为每个Actor分配一个线程,在接收一个消息时,如果当前Actor的邮箱(mail box)为空,则会阻塞当前线程。基于线程的调度实现较为简单,但线程数量受到操作的限制,现在的Actor模型一般不采用这种方式;
2. 基于事件的调度:事件可以理解为上述任务或消息的到来,而此时才会为Actor的任务分配线程并执行。
基于线程的调度为每个Actor分配一个线程,在接受一个消息(如在Scala Actor中使用receive)时,如果当前Actor的“邮箱(mail box)”为空,则会阻塞当前线程直到获得消息为止。基于线程的调度实现起来较为简单. 但是线程数量一多就会影响到系统资源占用以及调度,而在某些情况下大部分的Actor会处于空闲状态,而大量阻塞线程既是系统的负担,也是资源的浪费。因此现有的Actor Model大都不会采取这种方式。
基于事件的调度中, “事件”在这里可以简单理解为“消息到达”事件,而此时才会为Actor的任务分配线程并执行, 既保证了运算资源的充分占用,也不会让系统在同时进行的太多任务中“疲惫不堪”,这样系统便可以得到很好的伸缩性。在Scala Actor中也可以选择使用“react”而不是“recive”方法来使用基于事件的方式来执行任务。
Actor的容错(Fault tolerance)
Erlang 引入了「随它崩溃」的哲学理念,这部分关键代码被监控着,监控者的唯一职责是知道代码崩溃后干什么(如将这个单元代码重置为正常状态),让这种理念成为可能的正是actor模型。
每段代码都运行在process中,process是erlang称呼actor的方式。这个process完全独立,意味着它的状态不会影响其他process。我们有个supervisor,实际上它只是另一个process(所有东西都是actor),当被监控的process挂了,supervisor这个process会被通知并对此进行处理。这就让我们能创建「自愈」系统了。如果一个actor到达异常状态并崩溃,无论如何,supervisor都可以做出反应并尝试把它变成一致状态,这里有很多策略,最常见的是根据初始状态重启actor。
Actor与分布式的关系
关于actor模型的有趣方面是它并不在意消息发送到的actor是本地的或者是另外节点上的。
转念一想,如果actor只是一些代码,包含了一个mailbox和一个内部状态,actor只对消息做出响应,谁会关注它运行在哪个机器上呢?只要我们能让消息到达就行了。这允许我们基于许多计算机上构建系统,并且恢复其中任何一台。
用WordCount举例
比如现在要在3台物理节点上运行一个WordCount作业,可以将这个作业细分为Split、Count和Merge三种任务(任务的target是物理节点的地址,communication可能包含文本、单词及计数等),根据需求要有Split Actor、Count Actor和Merge Actor。整个作业的处理流程以下:
1. Split Actor接收到消息后可以文本分割成10份,每份发送给一个Count Actor;
2. Count Actor统计好单词的数目后发送消息给Merge Actor;
3. Merge Actor收集完Count Actor发送的10个消息后,合并每个单词的数目,完成WordCount任务。
从以上例子可以看出,Actor系统跟数据驱动系统比如数据流相近,可以自定义任务的流向及其处理过程。
参考博客(就是copy+整理):
https://blog.csdn.net/gulianchao/article/details/7249117
https://www.cnblogs.com/lixiang-share/p/5829437.html
学如不及,犹恐失之