一、概念
幂等性,通俗的说就是一个接口,多次发起同一个请求,必须保证操作只能执行一次比如:
订单接口,不能多次创建订单
支付接口,重复支付同一笔订单只能扣一次钱
支付宝回调接口,可能会多次回调,必须处理重复回调
二、常见解决方案
唯一索引--防止新增脏数据
token机制--防止页面重复提交
悲观锁--获取数据的时候加锁(锁表或锁行)
乐观锁--基于版本号version实现,在更新数据那一刻校验数据
分布式锁--redis(jedis、redisson)或zookeeper实现
状态机--状态变更,更新数据时判断状态
三、本文实现
本文采用第2种方式实现,即通过redis+token机制实现接口幂等性校验
四、实现思路
为需要保证幂等性的每一次请求创建一个唯一标识token,先获取token,并将此token存入redis,请求接口时,将此token放到header或者作为请求参数请求接口,后端接口判断redis中是否存在此token:
如果存在,正常处理业务逻辑,并从redis中删除此token,那么,如果是重复请求,由于token已被删除,则不能通过校验,返回请勿重复操作提示
如果不存在,说明参数不合法或者是重复请求,返回提示即可
五、项目简介
springboot
redis
@ApiIdempotent注解+拦截器对请求进行拦截
@ControllerAdvice全局异常处理
压测工具:jmeter
说明:
本文重点介绍幂等性核心实现,关于springboot如何集成redis、ServerResponse、ResponseCode等细枝末节不在本文讨论范围之内,有兴趣的小伙伴可以查看作者的Github项目:
六、代码实现
pom

JedisUtil




自定义注解@ApiIdempotent

1.ApiIdempotentInterceptor拦截器

TokenServiceImpl


TestApplication

OK,目前为止,校验代码准备就绪,接下来测试验证
七、测试验证
获取token的控制器TokenController

TestController,注意@ApiIdempotent注解,在需要幂等性校验的方法上声明此注解即可,不需要校验的无影响

获取token
查看redis
测试接口安全性:利用jmeter测试工具模拟50个并发请求,将上一步获取到的token作为参数
header或参数均不传token,或者token值为空,或者token值乱填,均无法通过校验,如token值为"abcd"
八、注意点(非常重要)
上图中,不能单纯的直接删除token而不校验是否删除成功,会出现并发安全性问题,因为,有可能多个线程同时走到第46行,此时token还未被删除,所以继续往下执行,如果不校验(token)的删除结果而直接放行,那么还是会出现重复提交问题,即使实际上只有一次真正的删除操作,下面重现一下
稍微修改一下代码:
再次请求
再看看控制台
虽然只有一个真正删除掉token,但由于没有对删除结果进行校验,所以还是有并发问题,因此,必须校验
九、总结
其实思路很简单,就是每次请求保证唯一性,从而保证幂等性,通过拦截器+注解,就不用每次请求都写重复代码,其实也可以利用springaop实现。




