一、背景:
分布式场景下,接口的开发大都需要保证幂等性。
幂等性:一个接口被调用,不管几次,产生一样的效果,一样返回结果。
接口调用过程中,很可能因为网络等原因进行重试调用,如果不能保证幂等性,那就完犊子了。
例如:用户支付的接口,用户有可能连续点击支付,总不能扣好几次钱吧。
二、场景:
1、前端重复提交:
用户快速重复点击多次,造成后端生成多个内容重复的数据。
2、接口超时重试:
对于给第三方调用的接口,为了防止网络抖动或其他原因造成请求丢失,这样的接口一般都会设计成超时重试多次。
HTTP,RPC等在超时的情况下会有重试机制。
3、消息重复消费:
MQ消息中间件,消息重复消费。
三、幂等性方案:
幂等性的保证,很明显无法通过一个方案解决所有问题,只能具体场景具体分析的。
1、业务表唯一索引:
对于数据插入的场景来说,这是最常见的方案。
核心业务字段设置为唯一索引,多次插入就会报错,从而保证幂等性。
2、状态流转控制:
状态流转也是最常见的幂等性保证手段。
如配送业务中,骑手的操作肯定会对业务流转状态进行校验。如果骑手已经提货,那就肯定不能再次提货的。
3、乐观锁版本号:
在业务表中新建一个字段version,int类型。
服务A调用服务B需要更新的时候,需要提前查询到version,然后作为参数传过去。
如果数据更新的时候将version + 1,接口如果发生重试的时候,version已经发生变更,那么也能保证幂等性。
PS:老实说,这个方案我本人没用过,有点麻烦。大部分的更新不需要保证幂等性,最终的数据也能保证一致。
4、去重:
对于前端调用的接口,有些场景无法通过前面的方案保证幂等性。
接口中可以新增一个参数,这个参数保证每次请求都是唯一的。
然后将这个参数保存,每次请求的入参校验都会查询这个参数是否存在,如果存在就返回。
参数保存的方案可以是MySQL或者Redis。
对于本身并发较低的场景,不会对MySQL服务造成压力,可以直接使用MySQL。否则,就要考虑Redis了,Redis这个key设置超时时间不用太长。
5、分布式锁:
考虑到分布式环境下,很多方案的校验如果无法保证串行的情况下,还是无法保证幂等性的。
例如,前面的状态机校验,并发环境下,可能还是会有问题,所以具体场景再进行分析。