高并发下的接口幂等性如何实现
转自https://mp.weixin.qq.com/s/odRypb6YqF3xuRn-4YAwlg
https://blog.csdn.net/wanglei303707/article/details/88298211
幂等就是一个操作,不论执行多少次,产生的效果和返回的结果都是一样的。
1、防止页面重复提交——token
1.1 场景
页面的数据只能被点击提交一次
1.2 发生原因
由于重复点击或者网络重发,或者nginx重发等情况会导致数据被重复提交
1.3 解决办法
集群环境:采用token加redis(redis单线程的,处理需要排队)
单JVM环境:采用token加redis或token加jvm内存
1.4 处理流程:
1.4.1 数据提交前要向服务的申请token,token放到redis或jvm内存,token有效时间;
1.4.2 前端提交后,后台校验token,同时删除token,执行业务生成新的token返回
token特点:要申请,一次有效性,可以限流
因为redis单线程的原因,当多次提交时,redis需要排队处理,所以只有第一次提交可以删除token成功,当删除成功代表token校验通过,删除失败,说明是重复提交。

2、防止新增脏数据(重复数据)——唯一索引
查询操作和删除操作天然就是幂等的:
-
查询操作:查询一次和查询多次,在数据不变的情况下,查询结果是一样的。select是天然的幂等操作;
-
删除操作:删除操作也是幂等的,删除一次和多次删除都是把数据删除。(注意可能返回结果不一样,删除的数据不存在,返回0,删除的数据多条,返回结果多个)
-
新增操作:有些服务是有重试机制的(ribbon),在这种情况下,就可能会出现新增两条一样的数据,这时候服务要支持幂等操作,否则会出问题,这时候可以通过数据库唯一索引来防止新增脏数据。
比如:支付宝的资金账户,支付宝也有用户账户,每个用户只能有一个资金账户,怎么防止给用户创建资金账户多个,那么给资金账户表中的用户ID加唯一索引,所以一个用户新增成功一个资金账户记录
2.1 唯一索引或唯一组合索引来防止新增数据存在脏数据
当表存在唯一索引,并发时新增报错时,再查询一次就可以了,数据应该已经存在了,返回结果即可
2.2 防重表
使用订单号orderNo做为去重表的唯一索引,每次请求都根据订单号向去重表中插入一条数据。第一次请求查询订单支付状态,当然订单没有支付,进行支付操作,无论成功与否,执行完后更新订单状态为成功或失败,删除去重表中的数据。后续的订单因为表中唯一索引而插入失败,则返回操作失败,直到第一次的请求完成(成功或失败)。可以看出防重表作用是加锁的功能。

防重表可以在一些主业务表不方便加唯一索引或者唯一组合索引时其作用,通过单独新建一张防重表来解决问题
3、对外提供接口的api如何保证幂等
如银联提供的付款接口:需要接入商户提交付款请求时附带:source来源,seq序列号,source+seq在数据库里面做唯一索引,防止多次付款,(并发时,只能处理一个请求)
重点: 对外提供接口为了支持幂等调用,接口有两个字段必须传,一个是来源source,一个是来源方序列号seq,这个两个字段在提供方系统里面做联合唯一索引
这样当第三方调用时,先在本方系统里面查询一下,是否已经处理过,返回相应处理结果;没有处理过,进行相应处理,返回结果。
注意,为了幂等友好,一定要先查询一下,是否处理过该笔业务,不查询直接插入业务系统,会报错,但实际已经处理了。
这里使用的还是数据库唯一索引(联合唯一索引)。
对于一些有时效性的业务处理接口来说,事实上还有一种方法,就是使用redis。
- 当第三方调用时,先判断是否过期,如果过期了,就不处理,如果没有过期,则下一步;
- 判断redis中是否存在source_seq key,如果存在,说明已经处理过了,是重复调用,直接返回;如果不存在,则set source_seq key ,注意这里的存在则返回,不存在则set key,是需要保持原子操作的,可以通过lua脚本来提交;
- key有效期和业务有关系, 比如说我支付消息有效期只有1h,那么key 有效期也是1h;
4、同一时间只能完成一次请求——乐观锁、分布式锁
在分布式环境下,因为网络、重试等原因导致一个长流程请求经过不同的实例时,会发生重复操作,为了防止这种情况,可以采用乐观锁,或者分布式锁来解决。

