Elixir Part2 : Actor Model 与多核服务器

1. 前言

上一篇文章简单说了一下关于 OOP 在多核环境下的一些开发问题,文章最后引出了 Actor Model 是解决这些问题的一个方法。大家可以通过以下链接查看系列文章:

1.Elixir Para1: OOP 的局限

这篇文章就重点讲解一下,什么是 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 是更加接近我们所熟悉的世界的。不妨如下想象:

  1. 现实世界里的每个个体,其实都是独立且自驱动的;
  2. 个体都会根据自身的情况,去响应外界的消息(message);
  3. 个体可以创建新的不同的个体(create)
  4. 个体会通过发送消息到其他个体,进行协作;

2.1 举例子1

在上述的几个规则下,我们就可以构建一整个业务的解决方案了。我们设想一个需求:设游戏场景中存在 100 个怪物,他们能够自主进行攻击,也可以被攻击。

采用 OOP 的设计方法,可能会有如下步骤:

  1. 设计一个怪物类,定义他们的方法:attack,beAttack,定义 state,如:hp;
  2. 由于考虑到会有同时攻击,所以这些方法还得是线程安全的,因此都有锁;
  3. 实例化 100 个对象;
  4. 使用线程池对这些对象进行心跳,使之有自主能力;

这样的步骤虽然能够实现功能,但相比之下还是比较别扭,不够自然。Actor Model 在这种场景下就会显得非常有优势,因为 Actor Model 本身就是自驱动,拥有自己的状态,能够通过消息与外界通信。所以我们只需要定义 Actor 的 state 和两个 behavior,剩余就交给怪物自己去运行了。

线程池驱动:

image.png

Actor Model:

image.png

2.2 举例子2

上述的例子是一个游戏例子,这个确实比较适合 Actor Model。现在我们思考一个交易系统,关于里面的用户互相转账的业务。

采用以往的设计方法,一般会有以下步骤:

  1. 设计用户帐号表;
  2. A向B用户转账,则先为 A、B 上锁,判断 A 金额,完成金额转账,解锁;
  3. 考虑到用户量比较巨大,我们可能会使用集群部署这个功能,所以 step 2 的锁会适用分布式锁;

这样其实也不错,但我们可以尝试适用 Actor Model 来设计这个转账系统,在设计这个系统前。我们需要想象一个属于自己账号“管家”,他知悉我的金额,并每次只会处理一次交易;

适用 Actor Model,我们的设计如下:

  1. 设计用户帐号表;
  2. 消息通知 管家A 向 管家B 转帐,管家A 处理自行判断且扣钱,然后向 管家B 发送一条加钱的消息,管家B 处理该消息,完成转账。由于管家只会串行处理,所以不需要锁;
  3. 管家 A 和 管家B 在部署上只需要能够通信即可,故是否集群是不需要考虑的;

在这里例子上,这并不是说 Actor Model 就更加适合转账系统,只是向引导大家思考,我们平时设计的需求,其实换一个想法去做也是可以的,如果这个想法更贴合我们现实世界,我们会非常容易理解。(我在学习 lock 这个概念的时候就花了不少时间)

2.3 小结

简单小结一下 Actor Model。Actor Model 是一种并发模型,它的思想在于:一切皆演员。在舞台上,每个演员是自我驱动的,根据外界的消息,共同协作完成一次次业务上的表演。

一个 Actor 包含以下几个要素:

  1. state 状态
  2. mailbox 邮箱
  3. address 地址
  4. behavior 行为

而用于通信的,我们统一适用 message 消息这个抽象。

image.png

3. Actor Model 的优劣

3.1 优势

  1. 易扩展
  2. 容错性
  3. 分布式
  4. 状态无共享

简单解释一下:

  1. 系统需要增加功能时,可以为对应的 Actor 增加 behavior,或者可以直接新增新的 Actor。就像你的公司需要新增业务线,我们可以直接构建一个部门的组织架构来负责该类工作;
  2. 要解释容错性,还需要使用到监督树的概念。简单来说,就是为不同的业务具备多个 Actor,假如一个 Actor 被破坏了,还能用另外一个 Actor 代替工作(计算机的冗余原则);
  3. 在我们讨论 Actor Model 时,我们都不需要定义 Actor 是存活在哪个物理主机上,只有他们处于同一个网络,并且知道彼此的 address,就可以通信了;
  4. 由于数据都是 Actor 自己拥有的,只有通过 message 才能对数据进行访问,所以状态无共享,自然不会有竞争问题;

3.2 劣势

  1. 死锁
  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 的虚拟机就是根据系统的核数定义调度器数量,而且调度器还能自行根据内存局部性,来提高运行的性能。

image.png

4. Actor Model 的编程语言和框架

  1. Erlang/Elixir
  2. Akka

5. 引用

  1. Elang 运行时系统#并发、并行,抢占式多任务
  2. How the Actor Model Meets the Needs of Modern, Distributed Systems
  3. Youtube Actor Model Explained

6. 预告

希望大家在读完文章后,还能对 Actor Model 产生一些兴趣。有了这些基础,下一篇文章就可以开始尝试正式介绍 Elixir 语言了。喜欢的同学可以点个赞,有疑问可以留言一起讨论一下,说得不对的地方,也希望诸位大方斧正。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享