谷粒商城高级篇—接口幂等性
接口幂等性
用户对于统一操作无论一次还是多次请求结果式一致的:下单,支付
哪些情况需要防止
- 多次按钮点击
- 回退再提交
- 微服务feign重试机制
- 其他业务情况
什么情况需要幂等
以SQL为例
? select update delete 主键insert——天然具备幂等
? 叠加update、非主键插入——不具备幂等性
解决方案
1、token机制
? 执行业务前,发送请求从服务端获取token,服务器会把token存到redis
? 调用接口携带token
? 服务器判断redis中有无token,有表示第一次请求,删除掉,后面的就都没了
? 危险性:
? 删除顺序
? token获取、比较、删除 原子性操作
2、各种锁机制
-
数据库悲观锁
select * from xxxx where id = 1 for update;
悲观锁一般伴随事务,数据锁定时间可能很长,注意id一定是主键/唯一索引,不然可能锁表。
-
数据库乐观锁(适合更新场景)
update t_goods set count = count-1, version = version+1 where good_id=2 and version=1
操作前获取version,操作时带上,版本号不对就不会执行。只会真正的处理一次。处理读多写少
-
业务层分布式锁
多个机器同时处理相同数据,加分布式锁。
3、各种唯一约束
- 数据库唯一约束
- redis set防重
- 防重表
- 全局请求唯一id
redis防重令牌
@RequestMapping("/orderToken")
public String getOrderToken(@RequestBody Long memberId){
String token = UUID.randomUUID().toString().replace("-","");
redisTemplate.opsForValue().set(OrderConstant.USRE_ORDER_TOKEN_PREFIX+memberId,token,30, TimeUnit.MINUTES);
return token;
}
// 下单~
@Override
public R submitOrder(OrderSubmitVo vo) {
// 1、验证令牌【使用lua脚本保证原子性 对比 删除 】
String orderToken = vo.getOrderToken();
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
// 原子验证令牌 0 失败 1 删除成功
Long result = redisTemplate.execute(new DefaultRedisScript(script, Long.class), Arrays.asList(OrderConstant.USRE_ORDER_TOKEN_PREFIX + vo.getMemberId()), orderToken);
if(result == 0L){
// 令牌验证失败
...
} else {
// 令牌删除成功 继续执行业务
...
}
return R.ok();
}