Java Actor简介

常见并发的模式

▲▲▲

❶ Threads: 一个任务创建一个新的线程,线程创建销毁开销比较大,一般用的比较少。

❷ Executors: 一个任务队列复用多个线程,减少了线程的创建和销毁的次数,常用。

❸ ForkJoin: 多个任务队列加多个线程,一个线程正常只消费一个队列,自己的任务结束了可以窃取其他线程队列的任务,减少了线程对队列的竞争,java8并行流常用。

❹ Actors: 一般一个任务队列加一个线程,单个actor串行消费队列的消息,几乎没有并发的冲突。可以通过actor分片提高任务的并行执行。

Actor简介

▲▲▲

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架构的时候,一定要结合自身的业务场景谨慎的选择。返回搜狐,查看更多

阅读 ()
平台声明
该文观点仅代表作者本人,搜狐号系信息发布平台,搜狐仅提供信息存储空间服务。