1. 前言
上一篇文章简单说了一下关于 OOP 在多核环境下的一些开发问题,文章最后引出了 Actor Model 是解决这些问题的一个方法。大家可以通过以下链接查看系列文章:
这篇文章就重点讲解一下,什么是 Actor Model,以及为什么 Actor Model 会在目前的多核服务器环境中重新被重视。
2. 什么是 Actor Model
Actor Model 在 wiki 上的定义是这样的:
The actor model in computer science is a mathematical model of concurrent computation that treats actor as the universal primitive of concurrent computation
Actor Model 在计算机科学里是一种并行的计算模型,actor 表示一个计算单元。
这样说,可能有点难理解。但事实上,Actor Model 是更加接近我们所熟悉的世界的。不妨如下想象:
- 现实世界里的每个个体,其实都是独立且自驱动的;
- 个体都会根据自身的情况,去响应外界的消息(message);
- 个体可以创建新的不同的个体(create)
- 个体会通过发送消息到其他个体,进行协作;
2.1 举例子1
在上述的几个规则下,我们就可以构建一整个业务的解决方案了。我们设想一个需求:设游戏场景中存在 100 个怪物,他们能够自主进行攻击,也可以被攻击。
采用 OOP 的设计方法,可能会有如下步骤:
- 设计一个怪物类,定义他们的方法:attack,beAttack,定义 state,如:hp;
- 由于考虑到会有同时攻击,所以这些方法还得是线程安全的,因此都有锁;
- 实例化 100 个对象;
- 使用线程池对这些对象进行心跳,使之有自主能力;
这样的步骤虽然能够实现功能,但相比之下还是比较别扭,不够自然。Actor Model 在这种场景下就会显得非常有优势,因为 Actor Model 本身就是自驱动,拥有自己的状态,能够通过消息与外界通信。所以我们只需要定义 Actor 的 state 和两个 behavior,剩余就交给怪物自己去运行了。
线程池驱动:
Actor Model:
2.2 举例子2
上述的例子是一个游戏例子,这个确实比较适合 Actor Model。现在我们思考一个交易系统,关于里面的用户互相转账的业务。
采用以往的设计方法,一般会有以下步骤:
- 设计用户帐号表;
- A向B用户转账,则先为 A、B 上锁,判断 A 金额,完成金额转账,解锁;
- 考虑到用户量比较巨大,我们可能会使用集群部署这个功能,所以 step 2 的锁会适用分布式锁;
这样其实也不错,但我们可以尝试适用 Actor Model 来设计这个转账系统,在设计这个系统前。我们需要想象一个属于自己账号“管家”,他知悉我的金额,并每次只会处理一次交易;
适用 Actor Model,我们的设计如下:
- 设计用户帐号表;
- 消息通知 管家A 向 管家B 转帐,管家A 处理自行判断且扣钱,然后向 管家B 发送一条加钱的消息,管家B 处理该消息,完成转账。由于管家只会串行处理,所以不需要锁;
- 管家 A 和 管家B 在部署上只需要能够通信即可,故是否集群是不需要考虑的;
在这里例子上,这并不是说 Actor Model 就更加适合转账系统,只是向引导大家思考,我们平时设计的需求,其实换一个想法去做也是可以的,如果这个想法更贴合我们现实世界,我们会非常容易理解。(我在学习 lock 这个概念的时候就花了不少时间)
2.3 小结
简单小结一下 Actor Model。Actor Model 是一种并发模型,它的思想在于:一切皆演员。在舞台上,每个演员是自我驱动的,根据外界的消息,共同协作完成一次次业务上的表演。
一个 Actor 包含以下几个要素:
- state 状态
- mailbox 邮箱
- address 地址
- behavior 行为
而用于通信的,我们统一适用 message 消息这个抽象。
3. Actor Model 的优劣
3.1 优势
- 易扩展
- 容错性
- 分布式
- 状态无共享
简单解释一下:
- 系统需要增加功能时,可以为对应的 Actor 增加 behavior,或者可以直接新增新的 Actor。就像你的公司需要新增业务线,我们可以直接构建一个部门的组织架构来负责该类工作;
- 要解释容错性,还需要使用到监督树的概念。简单来说,就是为不同的业务具备多个 Actor,假如一个 Actor 被破坏了,还能用另外一个 Actor 代替工作(计算机的冗余原则);
- 在我们讨论 Actor Model 时,我们都不需要定义 Actor 是存活在哪个物理主机上,只有他们处于同一个网络,并且知道彼此的 address,就可以通信了;
- 由于数据都是 Actor 自己拥有的,只有通过 message 才能对数据进行访问,所以状态无共享,自然不会有竞争问题;
3.2 劣势
- 死锁
- mailbox 溢出
Actor 模型是会容易出现死锁的。假如 A 发送消息给 B,B 发送消息给 A,同时彼此在等待回信。就会产生死锁了。
mailbox 是用于通信的不得不存在的概念,在消息量巨大的时候,mailbox 可能会成为瓶颈。
我认为,缺少共享内存,一定程度还是影响性能的。例如一些读多写少的业务上,共享内存是会让性能更加好的。
3.3 多核时代和 Actor Model
最近些年,单核 CPU 的性能物理上限基本上已经达到了,接下来 CPU 的计算能力要进一步提升,只能往多核维度上去发展。现在很多云供应商都提供了多核、大内存的服务器。
我们知道,多核 CPU 在存储金字塔上,也是存在一定问题的。因为 CPU 有自己的 L1、L2、L3 缓存,在共享内存的环境下,多核 CPU 需要通过一致性算法保证缓存的可见性,这一定程度上是影响性能的,而且这个问题还会因为核数越多,影响越大。
但 Actor Model 天然就会适应这种多核服务器。举 Erlang 的虚拟机为例子,Erlang 的虚拟机就是根据系统的核数定义调度器数量,而且调度器还能自行根据内存局部性,来提高运行的性能。
4. Actor Model 的编程语言和框架
- Erlang/Elixir
- Akka
5. 引用
- Elang 运行时系统#并发、并行,抢占式多任务
- How the Actor Model Meets the Needs of Modern, Distributed Systems
- Youtube Actor Model Explained
6. 预告
希望大家在读完文章后,还能对 Actor Model 产生一些兴趣。有了这些基础,下一篇文章就可以开始尝试正式介绍 Elixir 语言了。喜欢的同学可以点个赞,有疑问可以留言一起讨论一下,说得不对的地方,也希望诸位大方斧正。