▲▲▲
Actor就是一个工人,能够不停的接受任务(消息),处理任务,任务结束以后以 消息的形式通知给别的actor。
用一个餐厅的场景来描述:
假设餐厅只有三个人,客人、服务员、厨师。我们把这三个人描述为三个actor,
其中服务员和厨师都有自己的工作信箱,他们的工作交互流程概述如下:
❶ 客人选好菜下单,把新订单消息发送到服务员的信箱中。
❷ 服务员收到消息,有新的订单,获取订单信息,然后确认用户的订单,挑选出需要厨师烹饪的菜,然后把烹饪菜肴的订单消息发送到厨师的信箱中。
❸ 厨师收到消息,有新的烹饪消息,获取订单信息,根据客户的需求烹饪完成之后,发送食物准备好了的消息发送给服务员。
❹ 服务员收到消息,获取订单信息,发现是上菜的订单,服务员去厨房取出食物,然后交给客人。
从以上的交互看出,每个actor都是独立的,只关心自己的状态和任务列表,按照自己速度有序的处理工作任务,这样整个系统就能并行的完成任务。
假设餐厅的客人越来越多,一个厨师无法快速的烹饪所有的菜肴,我们可以根据实际情况,增加厨师的个数,厨师之间按照某种规则来分摊订单的请求。
转账的案例
▲▲▲
假设我们有一个账务系统,系统有账户和余额。要实现账户A给账户B转账100元。
大概的业务流程如下:
❶ 账户A扣减100元,记录账户余额变动明细。
❷ 账户B增加100元,记录账户余额变动明细。
我们常见实现方式,依赖数据悲观锁和事务的方式。
假设我们设计了三张表。账户表、账户明细表、转账订单表,其作用如下:
账户表,记录用户的实时余额。
账户明细表,记录每次交易发生后,账户的时点余额。
转账订单表,记录转账交易以及转账的结果。
具体的交易流程如下:
❶ 根据请求存储转账订单信息
❷ 根据订单信息执行真正的转账操作。
▷账户排序(防止死锁),然后锁定账户。
▷检验扣款方的账户余额,余额充足,则扣减余额且记录账户余额明细,否则异常。
▷增加收款方的账户余额,记录账户余额明细。
❸ 根据转账事务的结果,更新转账订单的结果。
流程图如下
基于actor实现转账的功能(暂且不考虑消息丢失的情况下)。
基于actor模式下,我们需要对单个账户进行操作,为了保证账户的入账的正确性,我们需要引入一些额外的操作,实现类似事务的功能。账户入账的动作拆分为三个阶段:准备(可成功 失败)、提交(成功)、回滚(成功)。订单作为一个协调器,根据每个账户入账的结果,协调账户执行准备、提交或者回滚。
为了满足需求,因此我们设计了四张表,账户表、账户明细表、转账订单表、转账订单明细表。
账户表,记录用户的实时余额(增加一个冻结金额)
账户明细表,记录每次交易发生后,账户的时点余额。
转账订单表,记录转账交易以及转账整体的结果。
转账订单明细表,记录转账过程中每一个账户执行的结果(订单号 账号 阶段 状态)。
❶ 我们需要设计两个actor,订单和账户actor。
❷订单actor的职责。
▷ 接收转账的消息。收到消息存储转账订单记录。
▷ 生成转账订单明细记录,分别给账户A、账户B发送入账准备消息。
▷ 接受账户A、账户B的入账准备结果,更新订单明细状态。
▷ 根据准备阶段结果,执行下一步步骤。如果全部成功,则发送提交的消息。部分成功,则需要对准备阶段成功的订单进行回滚。全部失败,则订单失败,流程结束。
❸ 账户actor的职责。
▷ 接受入账的消息,根据消息执行账户更新的操作,并且记录账户明细。
▷ 根据账户入账的结果,给订单actor发送入账结果的消息。
▷ 账户actor不同阶段执行的操作。
账户A 准备:余额(减) 冻结余额(加)
提交:冻结余额(减)
回滚:余额(加) 冻结余额(减)
账户B 准备:冻结余额(加)
提交:余额(加) 冻结余额(减)
回滚:冻结余额(减)
一个正常转账成功的时序图
Actor模型和传统做法的简单对比
通过以上两个场景,简述了actor模型。actor模型是一个异步无锁化的设计,相比传统的依赖数据库的模式,在并发比较大的场景,具有很大的优势,横向扩展性也更好,但是基于actor模式,开发的复杂度要高,事物控制也更复杂,对于服务使用方也增加了一些复杂度(某些可以同步完成的场景也必须依赖异步通知等)。Actor一般是基于内存队列的异步架构,所以我们需要提前规划好队列的容量,以及考虑在极端情况(机器断电),队列消息丢失的场景, 因此我们在使用actor架构的时候,一定要结合自身的业务场景谨慎的选择。