一个较为健壮的下单方案
最近在做的一个新项目中,有一个下单兑换的流程。用户的积分可以用来兑换物品。
这个过程中,需要有几部分的操作:
- 积分表扣除积分
- 兑换表写用户兑换内容、状态
- 下单
- 更新用户兑换表为兑换完成状态
这个流程中需要保证扣除积分后,能够为成功为用户下单。一个服务的调用会出现三种状态:成功、失败、超时。超时的情况下,是无法确定下单是否真正成功的,这时要避免重试时重复下单。为了保证下单流程的健壮性,除了下单接口本身需要做好幂等之外,上层业务本身也需要记录下单过程中的状态流转,方便进行下单后的逻辑处理,兑换表中需要一个状态字段,可能存在的几种状态为:
- 1:扣除积分,未下单
- 2:扣除积分,已经下单
- 3:扣除积分,完成订单
- 4:下单失败,积分回退
通过数据库的事务,我们首先需要保证,下单出现非超时错误时,需要回滚下单之前的数据库操作:
「 事务
扣除积分
写兑换表,状态:1、扣除积分、已经下单
」---> 下单失败,回滚事务
下单成功自不用说,将兑换表中状态更新为3即可。当出现下单超时的时候,以上的事务就先不回滚了,通过消息队列来进行下单重试。消息队列的重试,也有可能再次出现超时的情况。队列的重试是有一定的时间间隔的,例如每隔/10/30/60秒重试一次。如果重试3次失败,应该有相应的告警出来,这时候一般是下游的订单服务不可用了。如果在重试下单时,出现了非超时错误,那么这时候应该给用户回退积分,并且将兑换表的状态更新为下单失败,金币回退。
重试下单失败-->积分回退
到这里其实已经可以较好地保证用户下单的健壮性的。但是还有一点,在成功下单后,需要更新用户的兑换表到状态3。这个时候有可能会抛出更新数据库表失败的异常,导致实际下单成功,但兑换表状态不一致的情况。解决的办法是当更新兑换表失败抛出异常时,捕获该异常,再利用消息队列发出更新数据库状态的消息,进行更新重试。整个流程如下所示:
(完)