谷粒商城高级篇—接口幂等性


接口幂等性

用户对于统一操作无论一次还是多次请求结果式一致的:下单,支付

哪些情况需要防止

  1. 多次按钮点击
  2. 回退再提交
  3. 微服务feign重试机制
  4. 其他业务情况

什么情况需要幂等

以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();
}