幂等

      幂等(idempotent、idempotence), 在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。

      如下表示为幂等性的数学表示方式,无论函数f(x)执行多少次,值都不变。

幂等性的数学表达式

      接口幂等性听起来是个高大上的名称,其实大家很早就已经接触相关知识了。在学习Web开发时相信大家肯定实现过防止表单重复提交的功能^_^, 那就是你与幂等的初次邂逅。可是大家在后续开发过程中似乎总是忽略接口幂等性的设计,大概是不会吧hh。因为不知道接口幂等性的设计原则嘛!

核心思想

      通过唯一的业务单号保证接口的幂等性。
      具体实现时需要考虑并发情况和非并发情况:

  • 非并发情况:根据唯一业务单号查询是否执行过(比如查询订单是否已经生成了),没有则执行操作
  • 并发情况:整个过程加分布式锁

业务场景

      什么情况下需要考虑将接口设计成幂等性吗?这是个很宽泛的问题,一般来说以下情景需要考虑:

  • 重复提交:多次点击提交订单只能生成一笔订单
  • 接口重试:支付时无论重试多少次都只能扣一次w
  • 前端操作抖动

针对SELECT、DELETE、UPDATE、INSERT操作,是否都需要考虑幂等呢?

SELECT 不会对业务数据产生影响,天然幂等
DELETE 根据唯一业务单号删除数据,第一次删除时已经删除,即使再次删除也无影响
不使用唯一业务单号删除数据,使用 TOKEN 实现幂等
UPDATE 如果只是将某个字段有A变为B,那么即使多次更新也无影响(不涉及版本变化)
如果诸如更新库存操作,更新多次则会造成影响,需要考虑幂等性。使用乐观锁实现
INSERT 此时还没有唯一业务单号,使用TOKEN保证幂等
混合操作 若有唯一业务单号则可使用分布式锁,没有则通过TOKEN实现

Delete操作的幂等性

根据唯一业务号删除

      因为在第一次删除时已经将数据删除了,第二次再执行时由于记录已经被删除了,返回结果只能是0,对业务数据没有任何影响。在实现时可以在删除前进行数据的查询,若查询到则删除,否则返回查询不到记录的提示信息。
请输入图片描述

未根据唯一业务号删除

      有时候我们会根据其他字段去删除数据,例如我们删除所有当天的数据。在第一次删除时已经把数据库中当天的数据删除了,但是在第二次操作执行前,又有新的数据插入了,那么就需要使用 TOKEN 来实现幂等了。

Update操作的幂等性

无版本的更新操作

      如果每次操作仅仅是将某个字段从A变为B,这种情况具有天然幂等性,无需任何操作。

有版本的更新操作

      除了上文提及的更新库存操作,如果每次操作不仅仅将某个字段从A变为B,还会记录修改更新次数,这时候也需要实现接口的幂等性。我们可以借助乐观锁实现。更新操作SQL如下:

UPDATE SET version = version + 1,xxx = ${xx} WHERE id = ${id} and version = ${version}

      如果更新操作没有唯一业务单号则需要借助 TOKEN 实现。
      如下代码演示了基于UUID的 TOKEN 实现。

@GetMapping("/getToken")
public CommonResponse getToken(){
    String token = UUID.randomUUID().toString();
    setString(KEY+"."+token,token); // 存入Redis中
    return CommonResponse.SUCCESS().data(token);// 返回给前端
}

Insert操作的幂等性

有唯一的业务号

      也许你会奇怪,插入操作怎么会有业务号呢?都没数据呀!
      但是,在诸如秒杀场景下,一个用户只能购买一个商品,那么用户ID+商品ID就组成了唯一的业务单号。我们可以为其建立一个唯一索引,保证只能插入一条。

无唯一业务号

      在诸如用户注册场景下,用户即使点击多次也只能注册一次,这种情况没有唯一的业务号,需要借助 TOKEN 机制保证幂等性。
无唯一业务号业务流程

String token = userBo.getToken(); // 获取TOKEN
RLock lock = redisson.getLock(token); 
boolean isLock = lock.tryLock(); // 获取分布式锁
try {
    if(isLock) {
        if(!isExist(KEY+"."+token)){ // 判断 TOKEN 的合法性
            return CommonResponse.FAIL().message("不合法的提交");
        }
        sleep();// 模拟网络缓慢
        userService.insertUser(userBo);// 插入数据
        return CommonResponse.SUCCESS().message("添加成功");
    }else{
        return CommonResponse.SUCCESS();
    }
    // 获取分布式锁失败
}finally {
    if(isLock){
        lock.unlock();// 解锁
        delString(KEY+"."+token); // 删除TOKEN
    }
}

混合操作的幂等性

      混合操作即一个接口包含多种操作,比如修改和更新操作,有唯一业务单号则可以使用分布式锁完成,否则可以使用Token机制。

Last modification:August 17th, 2021 at 11:37 pm
如果觉得我的文章对你有用,请随意赞赏