今天在做一个需求的时候,要求考虑接口的幂等性,后台根据模板生成pdf时,要求只能生成一次,后续请求都只能是查询到生成的pdf。
为什么会出现重复请求?
- 网络波动:内部调用会存在重试机制,dubbo接口默认有重试机制,服务的restful接口也有重试机制,axios也有重试机制。
- 用户重复点击:用户点击过快
- 浏览器:回退操作等都可能导致重复请求
在网络不稳定、客户端重试、用户重复提交等场景下,同一个请求可能被多次发送到服务端。如果没有幂等性保障,可能导致:
- 重复下单
- 重复扣款
- 数据重复插入
- 状态错误变更
从前端的角度去考虑幂等性
- 如果是按钮,防止用户点击过快,可以在用户点击后就进入loading状态,不可再使用
- 如果是表单,提交后跳转,且不允许后退重新提交表单
从后端角度考虑幂等性:能够正确识别出相同的请求,并且合理处理非首次到达的请求。
-
Token机制:
用户在进入到提交的页面时,自动从后台获取token,然后提交时携带token,然后保证token只能校验一次 Redis实现:进入页面颁发token,发生请求时删除token,删除成功则放行。
iniString script = """ if redis.call('EXISTS', KEYS[1]) == 1 then redis.call('DEL', KEYS[1]) return 1 else return 0 end """; RedisScript<Long> redisScript = RedisScript.of(script, Long.class); Long result = redisTemplate.execute(redisScript, Collections.singletonList("my_token")); if (result == 1) { // Token 存在并已删除,可以执行业务 } else { // Token 不存在,处理重复请求 }
某些场景,可以考虑将这个token交由客户端生成,能够保证客户端安全可控即可,可以直接用现成的
setnx
命令。 -
数据库唯一约束:通过接口的请求中一些能够唯一标识请求的数据作为key存放到数据库中 如提前生成订单号,在下单时再真正插入订单数据,用订单号作为数据库的主键。
-
乐观锁:默认获取锁,执行完逻辑,真正保存数据时再校验是否能成功获取到锁。
如:update的时候用当前订单状态(初始状态)作为条件。
-
悲观锁:用redis实现分布式锁,先获取锁再执行逻辑,执行完逻辑后再释放锁。
-
状态机: 如订单场景:进入通过set status = process where status=initial and order_id=xx,如果update影响条数为1则表明修改状态成功能执行对应逻辑,否则拒绝执行。