Actor模型由状态(State)、行为(Behavior)和邮箱(MailBox,可以认为是一个消息队列)三部分组成:

 

1.状态

Actor模型中的状态指Actor对象的变量信息,状态由Actor模型自己管理,避免了并发环境下的锁和内存原子性等问题。

 

2.行为

Actor模型中的计算逻辑,通过Actor模型接收到的消息来改变Actor模型的状态。

 

3.邮箱

邮箱是Actor和Actor之间的通信桥梁,邮箱内部通过FIFO(先入先出)消息队列来存储发送方Actor的消息,接收方Actor再从邮箱队列中获取消息(如图3所示)。

 

               图3 Actor概念模型

 

按消息的流向可以看出,可以将Actor模型分为发送方和接收方,一个Actor模型既可以是发送方也可以是接收方。另外,我们可以了解到Actor模型是串行处理消息的,Actor中消息不可变。

 

二、Actor模型的特点

 

1.实现了更高级的抽象

Actor模型类似面向对象编程(OO)中的对象,每个Actor模型实例封装了自己相关的状态,并且和其他Actor处于物理隔离状态。举一个游戏玩家的例子,每个玩家在Actor模型系统中是Player这个Actor的一个实例,每个player都有自己的属性,比如ID、昵称、攻击力等,体现到代码级别其实和面向对象编程的代码并无多大区别,在系统内存级别方面也出现了多个面向对象编程的实例。

 

2.无锁

在使用Java/C#等语言进行并发编程时需要特别关注锁和内存原子性等一系列线程问题,而Actor模型内部的状态由它自己维护,即它内部数据只能由它自己修改(通过消息传递来进行状态修改),所以使用Actors模型进行并发编程可以很好地避免这些问题。Actor模型内部是以单线程的模式来执行的,类似于redis,所以Actor模型完全可以实现分布式锁及类似的应用。

 

3.异步

每个Actor模型都有一个专用的MailBox来接收消息,这也是Actor模型实现异步的基础。当一个Actor模型实例向另外一个Actor模型发消息的时候,并非直接调用Actor的方法,而是把消息传递到对应的MailBox里,就好像邮递员,并不是把邮件直接送到收信人手里,而是放进每家的邮箱,这样邮递员就可以快速地进行下一项工作。所以在Actor模型系统里,Actor模型发送一条消息是非常快的(如图4所示)。

 

 

              图4

 

这样设计的主要优势就是解耦了Actor,数万个Actor并发的运行,每个actor都以自己的步调运行,且发送消息、接收消息都不会被阻塞。

 

4.隔离

每个Actor模型的实例都维护这自己的状态,与其他Actor模型实例处于物理隔离状态,并非像多线程+锁模式那样基于共享数据。Actor模型通过消息的模式与其他Actor进行通信,与面向对象编程式的消息传递方式不同,Actor模型之间消息的传递是真正物理上的消息传递。

 

5.天生分布式

每个Actor模型实例的位置透明,无论Actor地址是在本地还是在远程机器上,对于代码来说都是一样的。每个Actor的实例非常小,最多几百字节,所以单机几十万的Actor的实例很轻松。如果你写过golang代码,就会发现其实Actor在重量级上很像Goroutine。由于位置的透明性,所以Actor系统可以随意横向扩展来应对并发,对于调用者来说,调用的Actor的位置就在本地,当然这也得益于Actor系统强大的路由系统。

 

6.生命周期

每个Actor模型实例都有自己的生命周期,就像java中的GC机制一样,对于需要淘汰的Actor模型,系统会销毁并释放内存等资源来保证系统的持续性。其实在Actor模型系统中,Actor模型的销毁完全可以手动干预,或者做到系统自动化销毁。

 

7.容错

说到Actor模型的容错,不得不说还是挺令人意外的。传统的编程方式是在将来可能出现异常的地方去捕获异常来保证系统的稳定性,这就是所谓的防御式编程。但是防御式编程也有自己的缺点,类似于现实中防御的一方永远不能防御住所有将来可能出现的代码缺陷。比如在java代码中很多地方充斥着判断变量是否为null,这些就属于防御式编码最典型的案例。但是Actor模型的程序并不进行防御式编程,而是遵循“任其崩溃”的哲学,让Actor模型的管理者们来处理这些崩溃问题。比如一个Actor模型崩溃之后,管理者可以选择创建新的实例或者记录日志,每个Actor模型的崩溃或者异常信息都可以反馈到管理者那里,这就保证了Actor模型系统在管理每个Actor模型实例的灵活性。

 

三、Actor模型应用场景

 

1.多线程并发应用

由于Actor模型系统的执行模型是单线程,并且异步,所以凡是有资源竞争类似功能的应用都非常适合使用Actor模型,比如秒杀活动,下面以前文提到的转账为例子进行说明。

 

首先,得有两个Actor,这两个Actor表示了两个账户,分别用账户A Actor(以下简称“账户A”)和账户B Actor(以下简称“账户B”)表示,再引入一个转账Actor,用于协调账户A Actor和账户B Actor两个Actor(如图5所示)。转账Actor收到转账50元的消息,向账户A发送取出50元的消息,向账户B发出存入50元的消息。然后账户A和账户B收到消息,进行处理。每个Actor都是独立的个体,它们之间靠消息交互就行了,根本不用锁。