面试时面试官老是爱问这个东西,特此记录一下。
什么是幂等?
幂等:多次调用方法和接口时,可以保证重复调用和单次调用的结果是一样的。
使用幂等的场景有哪些?
- 前端重复提交
- 接口请求失败时的重试
- 消息重复消费
接口请求方面问题解决
-
前端解决方案
前端防重 (不可靠)
前端提交后跳转(较常见的用法,简称prg) -
后端解决方案
token(redis),需要前端配合,且并发情况下需要使用分布式锁保证原子性
这和登录验证token类似,只不过这个token是一次性的。
在实际接口请求之前,前端访问服务器接受token(全局唯一,一般使用流水号),前端携带token请求服务器,后端验证该token是否存在于redis,存在则执行并删除redis中的token,不存在则直接返回。 -
redis-SETNX(分布式锁)
和token类似,不过逻辑相反,如果setnx存入key值失败,则表示已存在对应key,说明是重复提交,反之不是。考虑到并发,可以使用lua表达式注销查询和删除方法。
``
/**
* 创建 Token 存入 Redis,并返回该 Token
* @param value 用于辅助验证的 value 值
* @return 生成的 Token 串
*/
public String generateToken(String value) {String token = UUID.randomUUID().toString();
String key = IDEMPOTENT_TOKEN_PREFIX + token;
/**- 在真实业务中 采用唯一标志 例如 流水号啊
*/
redisTemplate.opsForValue().set(key, value, 5, TimeUnit.MINUTES);
return token;
}
- 在真实业务中 采用唯一标志 例如 流水号啊
/**
* 分布式锁实现幂等性
*/
@PostMapping("/distributeLock")
@ApiOperation(value = "分布式锁实现幂等性")
public String distributeLock(HttpServletRequest request) {
String token = request.getHeader("token");
// 获取用户信息(这里使用模拟数据)
String userInfo = "mydlq";
RLock lock = redissonClient.getLock(token);
lock.lock(10, TimeUnit.SECONDS);
try {
Boolean flag = tokenUtilService.validToken2(token, userInfo);
// 根据验证结果响应不同信息
if (flag) {
/**
* 执行正常的逻辑
*/
log.info("执行正常的逻辑………………");
}
return flag ? "正常调用" : "重复调用";
} catch (Exception e) {
e.printStackTrace();
return "重复调用";
} finally {
lock.unlock();
}
}
``asd
服务层面问题
- 防重表
字面意思,建一张数据库表,多个字段作为唯一主键,每次向数据表插入数据之前,先向防重表中插入数据,如果重复则表示是重复数据。 - mysql 乐观锁(基于版本号和基于条件)
- zookeeper分布式锁
rabbitmq重复消息问题
- 重复发送:发送前,从redis获取消息唯一标识,如果已存在,则不发送,如果不存在则发送并设置唯一标识
- 重复处理:消费前,根据消息的唯一标识查询,如果已存在于数据库或redis,则丢弃,否则消费并确认