愿景
愿天下没有难写的代码
背景
在虚拟币提现业务中,用户发起提现,需要扣减用户余额,并写入提现单。
这里涉及到账号服务、订单服务一致性问题。
例如:扣款完成,但是订单未创建;或者订单创建,但扣款未完成。
该业务的基本流程如下
下面介绍,如何使用 2阶段提交的方式,来解决分布式事务问题。
虽然使用分布式事务框架也能解决该问题。但分布式事务框架不在本次讨论范畴之中。
业务问题描述
上诉业务流程,存在以下几个问题:
-
订单创建后,如果扣款失败怎么处理?
-
如果用 RocketMQ 的事务消息去实现,即生成订单作为本地事务,但是扣款则会变成异步,那么业务上,异步扣款是否可行?
-
扣款成功了,订单服务准备提交事务时,应用程序 crash,导致订单未创建成功,如何处理?
扣款失败,回滚事务?
扣款失败,是否可以直接回滚事务呢?
明确的告诉你:不能。
扣款失败有以下几个原因:
-
调用超时
-
真的失败
如果是调用超时,没办法确定账号服务是否扣款成功,因此不能回滚事务。
异步扣款?
从业务角度来看,不推荐异步扣款。
异步扣款会导致即使余额不足,也可以发起提现。
2阶段提交
我们可以利用 2阶段的思想 将业务流程拆解为如下
| 步骤1 | 步骤2 | 步骤3 | 步骤4 | 最终结果 |
|---|---|---|---|---|
| 成功 | 成功 | 应用程序 crash, 订单状态仍为 prepare | 补偿,订单被修改为成功 | 提现成功 |
| 成功 | 成功 | 执行成功,订单状态被修改为提现 | 不执行 | 提现成功 |
| 成功 | 失败 | 应用程序 crash, 订单状态仍为 prepare | 补偿,订单被修改为失败 | 提现失败 |
| 成功 | 失败 | 执行成功,订单状态被修改为失败 | 不执行 | 提现失败 |
| 成功 | 扣款超时,实际扣款失败 | 不做任何处理,订单状态仍为 prepare | 补偿,订单被修改为失败 | 提现失败 |
| 成功 | 扣款超时,实际扣款成功 | 不做任何处理,订单状态仍为 prepare | 补偿,订单被修改为成功 | 提现成功 |
综上所述来看,我们通过两阶段提交的思想,简单的实现了最终一致。
如果你对 Mysql redo 2阶段提交 过程熟悉的话,你会发现上述的流程其实跟其大差不差。