接口幂等性
幂等性: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一下
}
// 其他订单校验、提交等操作。。。
}
|