深入了解幂等性问题

836 阅读2分钟

幂等性介绍

同一个方法,在相同的参数和相同的路径,请求多次,只会保证一次成功。

相信大家在日常做开发的时候,或多或少都会遇到这类的问题,比如说消息重复消费,表单重复提交等。

其实解决这类问题的通用思路就是设置全局id,下面介绍具体几种解决幂等的方案,这个涉及到具体业务,所以需要具体情况具体分析,这里只是提供一个大概的解决方案。

  • 前端进行校验

点击提交按钮以后,将按钮改为禁用状态,但是这种不够严谨,使用接口调用工具的时候就能避开这种。

  • 使用JVM内置锁

比如synchronizereentrantLock锁,在处理业务之前,先加锁,判断这个业务是否被处理过,如果是的,就不执行业务并且释放锁,如果不是的,就执行业务代码,但是这种有一定的开销。

  • 使用Token的方式,将Token存储到Redis

比如现在新增接口需要进行幂等处理,那么在调用新增之前,先获取token(将token存储到Redis中),在新增的时候带上这个token进来,如果能够在Redis中查询到token的存在,说明还没有执行业务方法,这个时候就执行业务代码,并且将Redis中的token删除,如果查询不到,那么就证明已经被处理过了,现在就是重复处理了。

/**
 * 在对需要进行幂等处理的接口之前,前端需要先获取一下token,也就是执行getToken
 */
@GetMapping("/insert")
public void insert(@RequestParam("username") String username, @RequestParam("token") String token) {
    if (!checkToken(token)) {
        // 这里直接抛异常或者返回出去
        System.out.println("token不能为空或该操作存在重复!");
        return;
    }
    try {
        // TODO: 2021/1/22 正常处理业务
        insert();
        stringRedisTemplate.delete(token);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
 * 在执行幂等之前,发放token
 */
public String getToken() {
    String token = UUID.randomUUID().toString();
    stringRedisTemplate.opsForValue().set(token, token, 60 * 3, TimeUnit.SECONDS);
    return token;
}

/**
 * 调用需要幂等的方法的时候,先进行判断
 */
public Boolean checkToken(String token) {
    if (StringUtils.isEmpty(token)) {
        return Boolean.FALSE;
    }
    if (!stringRedisTemplate.hasKey(token)) {
        return Boolean.FALSE;
    }
    return Boolean.TRUE;
}
  • 结合数据库,加上唯一性约束

比如按照某一个字段建立唯一性约束,在数据库层面加锁,或者在表中添加token字段(给他加上约束),然后每次处理之前都先查询一下,是否存在,不存在再操作

总结:具体需要根据业务场景来选择,如果是比较敏感的业务,可能需要将以上几种情况结合起来使用,才能最终保证