Bootstrap

【得物技术】供应链库存幂等实战分享

日常工作中,很多场景需要我们保证系统操作的幂等性,先来了解下什么是幂等。  

引自百度百科:

幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。

在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。

引自维基百科:

    Idempotence is the property of certain operations in mathematics and computer science whereby they can be applied multiple times without changing the result beyond the initial application. The concept of idempotence arises in a number of places in abstract algebra (in particular, in the theory of projectors and closure operators) and functional programming (in which it is connected to the property of referential transparency).

幂等理解总结:

我们提取一下关键信息:一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。明确目标,在服务不出错的情况下,尽量去达成这个目标。

实战背景

外部系统对接库存操作时,所有带业务含义的操作只能生效一次,否则会因为一些流量重放等操作导致库存紊乱。由于业务场景的需要,此时就需要保证接口的幂等性。如果针对每个接口单独去做幂等,非常费力,而且需要考虑业务的边边角角。除此之外,每个业务开发同学在针对不同场景写的幂等方案可能也不尽相同,后期维护成本也较高。

在此背景之下,我们想设计一个公共的幂等组件,想达成以下几个目标,达成的目标越多越好。

目标

设计思路

*设计流程图

疑难点

DB选型(mysql/mongo)

业务场景

要保证一个请求在响应后,之后每次返回结果都一样,需要保存response数据,当同一个请求进行重复请求时,直接查DB返回结果即可,所以需要保存response结果。

相同点不做赘述,主要说下对我们来说,两者之间对我们业务影响最大的区别:

选择

业务特点

综上所述,最终选型mongo。

数据落DB的时机(同步提交/异步提交)

业务场景

解决方案

这边选型是异步提交。

解决异步提交场景的问题换了个思路,看流程图便知。

详解

请求刚进来就会查一次DB,判断是否有此次请求的记录,若有,且此时mongo中response结果为null,便认为是往库里落数据的线程还没执行完,这边的线程会retry进行等待。直到response填充进去(retry间隔时间500ms/次,次数可配)大多数业务场景无特殊情况,都不需要这么久耗时。如果等待重试后,仍旧无结果。这个时候简单判定服务可能出现了问题,或是出现了不太合理的业务场景。

两个相同请求同一时间戳并发过来,另一个线程如何正确返回

业务场景

同一时间戳来了两个相同的请求,会因为唯一键约束报错。但更好的处理情况是,此时也能正确的返回结果(原因如引言中幂等所述:在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同)。

解决方案

这边采用spring-retry在查询发现插入报错后,在rpc框架允许的时间范围内进行重试。这种场景极少,retry会短暂的block一下线程。

被调用方,方法内部报错,产生异常,但是初始记录已经存在怎么办

业务场景

A服务调用B服务,B服务执行到一半,产生异常,但是库里已经写入数据,response结果还没更新进去。上游发生幂等重试,会无限失败,结果也没给出。

解决方案

try-catch业务流程,一旦发生业务流程执行抛出异常,则删除mongo里面记录的初始数据。

如果DB选型选择mongo,程序跑完,落数据的时刻,是异步执行的,如果此时服务发布,进程突然没了,而response记录没写进去,后续请求会有异常

业务场景

mongo落表是异步执行的,如果此时服务发布,进程突然没了,后续请求再进来会拿不到正确的response。此时发生幂等重复提交场景,去表里取结果就一直有问题。

解决方案

假如DB选择是mysql,通过事务rollback不需要考虑这种场景,此刻只以mongo去做讨论:

目前这个场景在设计上是不考虑的,曲线救国,通过优雅发布,去避免此场景。

理论上是不会出现进程执行到一半,进程突然被kill掉这种场景的。如果因为考虑此场景而引入事务回滚等机制,为了这一个很小很细微,目前不会出现的点,而引入很多其他技术手段来保证,会影响业务耗时。在进行取舍之后,选择了目前不一定是最好,但最适合我们的方案,不考虑进程突然被kill掉,如果实在是出现此问题,可以手动修下数据。

是否轻量级,最终引入的第三方依赖

mongoDB

最初的思想是为了保证各种各样边边角角的场景的幂等,当时想引入mysql,mongo,分布式锁,事务等一系列依赖,想做到尽善尽美。

放弃引入大批组件的原因如下:

最后瞄准目标,思量再三,结合目前绝大多数场景具体分析后,打算只引入mongo去实现,否则为了解决极少数特别细微末节的问题,引入一大批组件,损耗了很多没必要的性能,解决了几乎不可能发生的问题。反而起到了本末倒置的效果。

表字段

如何使用

学习成本:预计3分钟

主要3个步骤
三分钟教你如何保证接口幂

第一步:

第二步:

配置DB连接,涉及DB连接敏感数据,故不粘贴具体图片。

第三步:

直接在接口上添加。

第四步:

直接在接口上添加。

注意事项

requestId重复将会直接返回上一次相同requestId的处理结果, 请确保该接口是否适用幂等场景, 适用幂等的场景应该为 :requestId只能被唯一成功处理一次,相同requestId能被成功处理多次的场景,均不适用幂等场景!

未来发展

目前为了保证业务的快速发展,只是做了较为简单的一版,在自己项目内进行了使用。

后续想要做的更好,更通用,还有一些改进点:

END

文 | 平川

关注【得物技术】