什么是接口幂等性?
接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的
就像我们支付时,多次点支付按钮,只会扣一次。
哪些情况需要防止
用户多次点击按钮
用户页面回退再次提交
微服务互相调用,由于网络问题,导致请求失败。feign 触发重试机制
等等
解决方案:
1.token机制
1、服务端提供了发送 token 的接口。我们在分析业务的时候,哪些业务是存在幂等问题的, 就必须在执行业务前,先去获取 token,服务器会把 token 保存到 redis 中。
2、然后调用业务接口请求时,把 token 携带过去,一般放在请求头部。
3、服务器判断 token 是否存在 redis 中,存在表示第一次请求,然后删除 token,继续执行业 务。
4、如果判断 token 不存在 redis 中,就表示是重复操作,直接返回重复标记给 client,这样 就保证了业务代码,不被重复执行
危险性: 1、先删除 token 还是后删除 token; (1) 先删除可能导致,业务确实没有执行,重试还带上之前 token,由于防重设计导致, 请求还是不能执行。 (2) 后删除可能导致,业务处理成功,但是服务闪断,出现超时,没有删除 token,别 人继续重试,导致业务被执行两边 (3)我们最好设计为先删除 token,如果业务调用失败,就重新获取 token 再次请求。
2、Token 获取、比较和删除必须是原子性 (1) redis.get(token) 、token.equals、redis.del(token)如果这两个操作不是原子,可能导 致,高并发下,都 get 到同样的数据,判断都成功,继续业务并发执行 (2) 可以在 redis 使用 lua 脚本完成这个操作
2.各种唯一性约束(数据库的唯一约束,防重表,全局唯一性ID,redis的Set防重等等)
3.锁机制(数据库锁(乐观锁和悲观锁))
实战之用token机制防止订单重复提交
1.流程
核心:保证验,删令牌的原子性
2.代码
生成订单
//生成令牌
String orderToken = UUID.randomUUID().toString().replaceAll("-", "");
orderConfirmVO.setOrderToken(orderToken);
//保存到redis中
redisTemplate.opsForValue().set(
ORDER_TOKEN_REDIS_PREFIX + memberVO.getUserId(),
orderToken,
30,
TimeUnit.MINUTES);
//返回数据
return orderConfirmVO;
提交订单
String orderToken = orderSubmitVO.getOrderToken();
/**
*使用lua脚本,保证令牌比较和删除令牌是原子性操作,结果只能是Long, Boolean, List
* https://www.cnblogs.com/liuyu7177/p/10918250.html
* the script result type. Should be one of Long, Boolean, List, or deserialized value type
*/
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Long result = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class),
Arrays.asList(ORDER_TOKEN_REDIS_PREFIX + memberVO.getUserId()),
orderToken);
if (result != null && result == 1L) {
//令牌操作成功,创建订单
}else {
//提醒用户
}