分布式接口幂等性的实现

接口幂等性

幂等性:f(f(x)) = f(x),幂等元素运行多次,还等于它原来的运算结果。在系统中,一个接口运行多次,与运行一次的效果是一致的。

什么时候需要幂等性?

并不是所有的接口都要求幂等性,要根据业务设计。重复提交、接口重试、前端操作抖动等场景,例如用户一次提交一个订单,支付时只能扣一次钱。

幂等性策略

核心思想:通过**唯一的业务单号**保证幂等性。

非并发的情况,可以查询某个业务是否操作过,没有则执行(查询券是否使用过);并发时,操作过程加锁(分布式锁)。

select操作,不对数据有影响,天然幂等。

delete操作,第一次删除的话,不存在幂等性问题。

update操作

1、有唯一业务号的update操作

直接赋值是幂等的;set数据自增,不幂等。

策略是更新操作传入数据版本号,通过乐观锁实现幂等性。

2、没有唯一业务号,同下。

insert操作的幂等性

1、有唯一业务号的insert操作,例如:秒杀,商品ID+用户id

实现方式:

可通过分布式锁,保证接口幂等

业务执行完成后,不进行锁释放,让其自动过期释放

2、没有唯一业务号的insert操作,如用户注册,点击多次。

实现方式:没有唯一业务号,就要创建唯一业务号。

使用Token机制,保证幂等性

进入到注册页,后台统一生成Token,返回前台隐藏域中;用户提交,将token一同传入后台;使用token获取分布式锁,完成Insert操作

执行成功后,不释放锁,等待过期自动释放

3、混合操作幂等性

同样使用token机制。获取分布式锁,没有获取锁的就返回失败。

利用Token实现幂等性

假设用户提交订单前没有唯一业务id,此时可以考虑使用Token做幂等校验,创建一个唯一的业务id。

思路:用户进入订单提交页面,请求后端接口,根据sessionId保存一个Token到Redis中;用户提交订单时,携带Token,后端接口中校验Redis中Token与订单携带的Token是否一致,一致则允许订单操作,并删除Redis Token。

为了防止多次请求同时获取到同一个Redis Token,因此需要加分布式锁。

简要代码

@ApiOperation(value = "获取订单Token", notes = "获取订单Token", httpMethod = "POST")
@PostMapping("/getOrderToken")
public ServerResponse getOrderToken(HttpSession session) {
    String token = UUID.randomUUID().toString();
    redis.set("ORDER_TOKEN_" + session.getId(), token, 300);
    return ServerResponse.createBySuccess(token);
}

@ApiOperation(value = "用户提交订单", notes = "用户提交订单", httpMethod = "POST")
@PostMapping("/create")
public ServerResponse create(@RequestBody SubmitOrderBO submitOrderBO,
                             HttpServletRequest request,
                             HttpServletResponse response) {
    // 防止并发创建多个订单,加分布式锁
    String lockKey = "LOCK_KEY_" + request.getSession().getId();
    RLock lock = redissionClient.getLock(lockKey);
    lock.lock(5, TimeUnit.SECONDS);
    try {
        // 接口幂等性操作,校验Token
        String orderTokenKey = "ORDER_TOKEN_" + request.getSession().getId();
        String orderToken = redis.get(orderTokenKey);
        if (StringUtils.isBlank(orderToken)) {
            throw new RuntimeException("orderToken不存在");
        }
        if (!orderToken.equals(submitOrderBO.getToken())) {
            throw new RuntimeException("orderToken不正确");
        }
        // 请求正确后删除Token
        redis.del(orderTokenKey);
    } finally {
        lock.unlock(); // 也要try catch一下
    }
    
    // 其他订单校验、提交等操作。。。
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值