1.1 概述
所谓幂等: 多次调用方法或者接口不会改变业务状态,可以保证重复调用的结果和单次调用的结果一致。
基于RESTful API的角度对部分常见类型请求的幂等性特点进行分析
举个例子:
假如你有个某多多 有个服务 服务提供一个接口,结果这个服务部署在了5台机器上,接着有个接口就是砍一刀的接口。
然后用户在前端上操作的时候,不知道为啥,总之就是一个砍一个订单 不小心发起了两次砍一刀请求,然后这俩请求分散在了这个服务部署的不同的机器上,结果造成一个用户被砍了扣两次。
所谓幂等性,就是说一个接口,多次发起同一个请求,你这个接口得保证结果是准确的,比如不能多扣款,不能多插入一条数据,不能将统计值多加了1。。
1.2 需要幂等的场景
1.2.1 网络波动
因网络波动,可能会引起重复请求
1.2.2 MQ消息重复
生产者已把消息发送给MQ,在MQ给生产者返回ack的时候网络中断,故生产者未收到确定消息,生产者认为消息未发送成功。但实际情况是,MQ已成功接收到了消息,在网络重连后,生产者会重新发送刚才的消息,造成MQ接收了重复的消息。
1.2.3 用户重复点击
用户在使用产品时,可能会误操作而触发多笔交易,或因为长时间没有响应,而有意触发多笔交易。
1.2.4 应用使用失败或超时重试机制;
为了考虑系统业务稳定性,开发人员一般设计系统时,会考虑失败了如何进行下一步操作或等待一定时间继续前端的动作的。
1.3 后端解决方案
数据库唯一索引
使用数据库提供的唯一索引来保证数据重复插入,避免脏数据产生
解决场景:新增
token+redis
● 第一次请求
○ 在后端生成一个唯一的token(比如:key:userid,value:UUID)
○ 将token存储到redis中
○ 将token返回前端
● 第二次请求
○ 在真正处理业务的时候需要携带过来之前的token
○ 到redis中查询token是否存在
○ 如果存在,则正常处理业务,同时删除redis中的token
○ 如果不存在,则操作失败
解决场景:新增、删除、修改
分布式锁
在分布式锁使用的时候,要注意粒度
在操作数据时,先添加一个分布式锁,当操作完成后再释放掉这把锁,同时在操作过程中,如果有人来抢锁,应当抛出异常,即
if (!lock) {
log.info("操作作者信息获取锁失败,operator:{}",request.getOperator());
throw new BaseBizException("新增/修改失败");
}
操作完成后,释放掉锁,因为幂等问题,通常是一个请求快速过来两次或者多次,所以在释放锁之前让后来的同一个用户的请求,直接失败即可,保证当前方法在短时间之内只能被执行一次,切记控制锁的粒度。
public SaveOrUpdateUserDTO saveOrUpdateUser(SaveOrUpdateUserRequest request) {
// 加入用户,要先取得一把分布式锁,针对的是操作人
// 同一个操作人,同时间只能新增用户,避免说重复请求短时间内发生,数据重复灌入
// 加分布式锁
String userUpdateLockKey = RedisKeyConstants.USER_UPDATE_LOCK_PREFIX + request.getOperator();
boolean lock = redisLock.lock(userUpdateLockKey);
if (!lock) {
log.info("操作作者信息获取锁失败,operator:{}", request.getOperator());
throw new BaseBizException("新增/修改失败");
}
//忽略代码
} finally {
redisLock.unlock(userUpdateLockKey);
}
}