之前一直关于如何实现接口幂等性的问题,都是一知半解,最近终于弄清楚了一些,写这篇文章总结一下,至于什么是接口幂等性,不知道的小伙伴,可以自行百度。
什么时候会出现接口幂等性?在增删查改中,只有insert 和update会出现接口幂等性的问题,select和delete是不会的,并且比如如下的update不论执行多少次,都是幂等的:
UPDATE tab1 SET col1=1 WHERE col2=2
只有诸如此类的update才不是幂等的
UPDATE tab1 SET col1=col1+1 WHERE col2=2,
我们只要保证update和insert语句的接口是幂等的就可以了。
现在我总结的有比较常用的有三类情况: 前端多次点击按钮几乎同时发送多个请求给后端、分布式远程调用失败重试、消息队列重复消费。
1、方案:使用token令牌机制。
2、先删key还是先处理业务?
先说答案:先删key,在处理业务
3、为什么先处理业务,后删令牌不行? 因为前端点击的速断很快,所以非常容易当第二个请求来的时候,去redis中查找key到时候,key还是存在的,幂等性保证失败。
4、采用什么方式删除令牌的时候才是正确的?
设想一下,如果连续两次点击的速度非常快如下代码会不会有问题
//如果两个请求非常快,第二个请求去redis中获取token的时候,
//第一个请求还没有删,那么这两个请求就都会执行成功
if(redis.get(key)==token){
del(key);
doService();
}
所以,要将取reids中获取token进行比较和删除token设计成原子的,使用lua表达式
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Long res = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(key), orderToken);
这个问题是比如fegin这种远程接口调用失败,自动重试引起的,特点是这两次的远程调用请求完全相同。
直接说解决方案: 可以生命一个唯一请求id,放在请求的Header中,被调用方接收到请求以后,首先将请求的唯一id存在reids,可以利用setNx指令进行防重,也可以利用redis的set。
看到网上有说将fegin的自动重试功能禁止的,还有说创建一个拦截器,只要是完全一样的请求就拦截的,个人觉得不太靠谱
直接上结论吧:
以上三种情况是我们开发过程中比较容易遇到的问题和常见的解决方案,掌握上面基本就可以解决大部分的幂等性问题,接下来介绍的情况只有特殊情况下可以解决,但是也好用。
这种方案可以利用数据库完成幂等性的操作
1. 利用数据库的唯一性约束
比如我们新增一个订单,在已经生成订单号的时候,再insert,这个时候是可以保证唯一性的。
2. 利用乐观锁
//首先这个sql是原子的,其次能保证幂等
//但是我想了下使用场景,只有version作为参数穿个某个方法,该方法再调用下面这个sql才可以,要不然
//根据我们常见的业务,更新中之前去数据select,你查到的就是别人更新过的,不能保证幂等。
update t_goods set count = count -1 , version = version + 1 where good_id=2 and version = 1
这种场景也是很常见的,必须要做幂等性,比如多台机器,每天机器上都有定时任务,当到达时间多台机器同时开始执行时候,如果不做幂等处理,那很容易重复处理,这个时候是使用redisson的提供的分布式锁就行,同时只允许一台机器执行,并且业务也要做成幂等的,执行之前先查询下数据的状态是否合格。