分布式锁超时情况和业务重试并发

先讲一个应用中的场景,有一天接到客户公司电话,说是对账1分钱的差错,无法处理。某产品的年化收益率正常情况下用户在转入57元后一共收益3分钱,合计57.03元。但是该客户当日却有一笔消费57.04元,导致客户公司系统多出1分钱处理不了。再进一步分析,发现用户收益结转时多了1分钱的收益,并且已消费。

也就是本身3分钱的收益,结果多发了1分钱出来。多出来的1分钱收益多哪来呢。

经过数据库分析以及线上日志发现,存在超时情况,数据库链接数已满,线程等待提交。

分布式锁超时时间是5s,第一笔记录从创建到修改提交经历了6s,由此可见是在分布式锁失效之后,获得了数据库链接,进行提交成功。

经过一系列逆推后发现问题真正的原因在系统设计上。系统A的事务允许一定时间的等待,而上层业务的重试时间又允许一定时间的等待,而上层业务的重试时间又比这个等待的时间要短。这就存在一个问题:系统A的事务还在等待中,业务就又发起了重试。如果是这在这个应用场景下(可能业务上对重试要求更高一些),而仅仅通过一个分布式锁来控制,如果分布式锁的超时时间设置的比事务允许等待的时间短,那么在锁失效之后就一定会同时提交两笔请求。

解决方案有两种,方案一:调整超时时间。方案二:增加幂等控制。

在系统并发幂等控制设计中,单纯的分布式锁并不具备严格控制并发幂等作用,建议在系统设计时,将第三方唯一性的幂等控制作为幂等控制的兜底方案,控制好这道幂等防线,这样不论业务如何设计,就万变不离其宗了。

另类解法,分布式一致性。

在阿里巴巴红包系统中,红包的发放操作会涉及两个数据库的事务操作,一个数据库进行预算的扣减,另一个进行用户红包数据的写入,那么如何保证这两个事务操作的一致性呢?

最终的解决方案是开发人员设计了一个轻型的一致性消息组件,把预算扣减成功等诸多业务操作事件写入数据库中,该消息组件保证事件与业务操作处在同一个数据库中,所以仅仅是一次简单的本地事务操作。该消息组件会对写入的事件进行分发,通知每一个事件订阅者(比如在当前的场景中就是进行用户红包数据写入),然后对事件的处理结果进行记录。

在一个成功的红包发放操作中,对于预算端来说,进行了一次预算的扣减,一次事件写入,可能会进行一次事件读取,一次事件状态更新;对于用户红包数据端,仅需要进行一次用户红包数据的写入。可以看到,对于预算扣减的热点数据操作,新的方案并没有放大它。同时,失败的红包发放操作就更简单了,仅仅是一次失败的预算扣减,没有事件,没有用户红包端的任何操作。

另外,在实际使用中开发人员发现,使用这样的一套方案还可以降低系统的编码以及运维的复杂度,不再需要为预算端设计冻结操作以及用户红包数据端设计不可用数据,也不需要引入另外一套独立的事务协调系统。

这套系统命名为 MiniBus,即微型总线,如图 4-7 所示。它包括以下三部分。

(1)事件发布者Publisher。由业务逻辑在业务事务中调用,将事件写入到与业务相同的数据库中。

(2)事件配置Metadata。管理事件的基本信息,订阅关系等。

(3)事件调试器Scheduler。读取待分发的事件,根据订阅关系进行分发,调用相应的事件处理器,然后记录事件分发的结果。

你可能感兴趣的:(读书分享,编程思想,架构,分布式,数据库,java)