1、背景
处理并发问题就是如何保证共享数据的一致性和正确性,一般来说有两种策略用来在并发线程中进行通信:共享数据和消息传递。
熟悉c和java并发编程的都会比较熟悉共享数据的策略,比如java程序员就会常用到java.util.concurrent包中同步、锁相关的数据结构。使用共享数据方式的并发编程面临的最大的一个问题就是数据条件竞争(data race)。共同思路就是将数据喂给线程,数据是被动的,自身不复杂,没有自身业务逻辑要求。适合大数据处理或互联网网站应用等等。但是如果数据自身要求有严格的一致性,也就是事务机制,数据就不能被动被加工,要让数据自己有行为能力保护实现自己的一致性。处理各种锁的问题是让人十分头痛的一件事。
要让数据自己有行为维护自己的一致性,才能真正安全实现真正的事务。所以,数据+行为=对象。
但是这还不够,因为即使数据能够自己有行为,发起行为的可能还是线程,本质上还是无法避免多线程环境下的数据共享问题。所以出现了消息机制,每个数据只接受消息,真正何时何种方式执行行为,完全由数据决定。和共享数据方式相比,消息传递机制最大的优点就是不会产生数据竞争状态(data race)。
实现消息传递有两种常见的类型:基于channel的消息传递和基于Actor的消息传递。本文主要是来分享Actor模型。
2、Actor模型
Actor是计算机科学领域中的一个并行计算模型,它把actors当做通用的并行计算原语:一个actor对接收到的消息做出响应,进行本地决策,可以创建更多的actor,或者发送更多的消息;同时准备接收下一条消息。
在Actor理论中,一切都被认为是actor,这和面向对象语言里一切都被看成对象很类似。但包括面向对象语言在内的软件通常是顺序执行的,而Actor模型本质上则是并发的。
一个Actor指的是一个最基本的计算单元。它能接收一个消息并且基于其执行计算。
Actor模型=数据+行为+消息
Actors一大重要特征在于actors之间相互隔离,它们并不互相共享内存。这点区别于上述的对象。也就是说,一个actor能维持一个私有的状态,并且这个状态不可能被另一个actor所改变。
Actor与Actor之间只能通过消息通信。Actor模型内部的状态由自己的行为维护,外部线程不能直接调用对象的行为,必须通过消息才能激发行为,这样就保证Actor内部数据只有被自己修改。这就解释了为什么Actor模型是一种处理并发问题的解决方案。
从上图中我们可以看到,Actor与Actor之前只能用消息进行通信,当某一个Actor给另外一个Actor发消息,消息是有顺序的,你只需要将消息投寄的相应的邮箱,至于对方Actor怎么处理你的消息你并不知道,当然你也可等待它的回复。
Actor有以下几个特点:
(1)每个Actor都有对应一个邮箱。
消息异步地传送到actor,所以当actor正在处理消息时,新来的消息应该存储到别的地方。Mailbox就是这些消息存储的地方。
(2)每个Actor是串行处理邮箱中的消息的。
也就是说其它actors发送了三条消息给一个actor,这个actor只能一次处理一条。所以如果你要并行处理3条消息,你需要把这条消息发给3个actors。
(3)Actor中的消息是不可变的
当一个actor接收到消息后,它能做如下三件事中的一件:
Create more actors; 创建其他actors
Send messages to other actors; 向其他actors发送消息
Designates what to do with the next message. 指定下一条消息到来的行为。一个actor能维持一个私有状态。意味着可以定义下条消息来到时的状态。更清楚地说,就是actors如何修改状态。
设想有一个actor像计算器,它的初始状态是数字0。当这个actor接收到add(1)消息时,它并不改变它原本的状态,而是指定当它接收到下一个消息时,状态会变为1。
3、优势
对并发模型进行了更高的抽象
异步、非阻塞、高性能的事件驱动编程模型
轻量级事件处理(1GB内存可容纳百万级别个Actor)
(1)简化并发编程:
并发导致最大的问题就是对共享数据的操作,我们在面对并发问题时多采用的是
用锁去保证共享数据的一致性,但这同样也会带来其他相关问题,比如要去考虑锁的粒度(对方法,程序块等),锁的形式(读锁,写锁等)等问题,这些问题对无疑给程序员在编程上提高了复杂性。但使用Actor就不导致这些问题,首先Actor的消息特性就觉得了在与Actor通信上不会有共享数据的困扰,另外在Actor内部是串行处理消息的,同样不会对Actor内的数据造成污染,用Actor编写并发程序无疑大大降低了编码的复杂度。
(2)提升程序性能:
我们之前说过既然用单线程处理,那如何保证程序的性能?首先Actor是非常轻量级的,你可以再程序中创建许多个Actor,而且Actor是异步的,那么如何利用它的这个特性呢,我们要做的就是把相应的并发事件尽可能的分割成一个个小的事件,让每个Actor去处理相应的小事件,充分去利用它异步的特点,来提升程序的性能。
4、实现
四种Actor框架比较
https://doc.akka.io/docs/misc/Comparison_between_4_actor_frameworks.pdf
一文比较了Scalaz Actors Lift Actors Scala Actors 和Akka Actors四种Actor框架。
复杂性最小的是Scalaz Actors和 Lift Actors。
可靠性方面Restart-on-failure,Lift Actors 和Scala Actors要好些。
都是基于JVM,没有Java的,他们称plaino jaino Java 需要矫正的Java(我认为Disruptor基本具备底层机制,需要上面再包装一下,Jdonfframework做了尝试,可以互相发消息,Restart-on-failure等没有)
5、应用
使用DDD领域驱动设计或CQRS架构就能明显发现这些情况,CQRS是读写分离,其中写操作是应领域专家要求编写的功能,在这类方向,我们都有必要使用Actor模型实现,因为在这个方向上,领域专家的要求都表达为聚合根实体,聚合根就是用Actor模型实现最合适不过了。而读方向,比如大数据处理,报表查询,OLTP等等都是数据喂机器的方式。