权衡利弊-可用性与数据一致性

整体背景:

在一个用户充值的场景下,整体的处理流程如下:

在这个处理流程中,我们的插入流水信息和更新用户信息是针对同一个数据库的操作。但是用作缓存的是redis,通知支付系统是通过网络请求,很明显,这个处理流程中存在分布式的事务,那就是缓存的更新,以及通知支付系统的处理结果和数据库的写入需要包装成一个整体的事务。

用户充值的处理,是通过调度任务来进行调度的,如果调度任务得到的结果是处理失败,那么间隔一定时间后,还会来重试请求。

在整体的实现过程中,开启了数据库的事务来处理插入流水和更新用户信息两个细节。在这个事务中,同时又针对更新redis缓存和通知支付系统两个环节的返回来判断了事务是否进行提交。整体的处理流程图如下:

这里不去讨论分布性事务的具体实现,我们只是来看这个处理流程,在这个处理流程中,redis的缓存更新 ,可以进行cancle(删除写入的数据即可),但是通知订单系统这个操作是不能够进行cancle的  ,数据库的执行是能够不提交的。整体上讲 。这应该是一个不典型的TCC分布式事务。这整个的处理流程中,存在一些比较棘手的情况。如下

遇到的问题

在这里比较难以处理的是  如下两种情况:

第一种情况:当我更新缓存成功了,但是通知订单系统失败了,此时我是否应该提交事务。

第二种情况: 当我最终处理完成的时候提交事务失败了,此时写入到缓存的数据是否需要也和数据库保持强一致性。

针对如上两种情况的处理:

如果更新缓存成功,但是通知订单失败了,此时我们选择了提交事务,同时更改了逻辑,以抛出异常,让调度任务能够捕获异常,同时修改了一部分逻辑将整个的处理请求流程幂等实现。更改后的请求流程如下:

此时相当于针对通知订单系统这件事情做了一个特案。这种实现方式并不是很优雅,但是能够保证即使通知订单系统有问题,我们也能够即使更新用户的信息,从而保证用户信息的正确。

针对第二个问题的解决方案:

我们选择了不去删除已经更新的缓存,这也是出于上面用户和对外服务可用性的一个折衷的考虑,当缓存成功了,但是事务提交失败了,此时数据库的数据和缓存的数据并不能保证强一致性。这在程序设计中也是一种不完善的地方。因为如果有调用方依赖数据库的数据,那么在下次重试请求正常被处理之前,数据库的数据装填是不正确的,但是针对对外的服务(接口级别)会先读取缓存,如果缓存为命中会读取数据库,缓存的存储周期假设是1小时,那么在这一小时内,只要能够保证数据库的数据和缓存的数据最终一致,那么整体服务的异常就不会被外部感知到。对于用户来讲是无损的 。

针对以上两点的设计,其实在整体的逻辑处理中,这样的逻辑并不是完美的,但是通过这样的逻辑处理,能够保证整体服务在出现异常的情况下尽可能保证可用性。所以很多时候,我们不得不去牺牲一些原则和底线。站在可用性的角度上去妥协。

Show Comments
备案信息: 京ICP备20002019号