服务幂等性设计

服务幂等性设计

马草原 1,566 2023-09-05

服务幂等性设计

什么是幂等?

对于相同的业务请求,多次调用方法或者接口不会改变业务状态,可以保证重复调用的结果和单次调用的结果一致。

幂等使用场景举例:

1. 前端重复提交

用户注册,用户创建商品等操作,前端都会提交一些数据给后台服务,后台需要根据用户提交的数据在数据库中创建记录。如果用户不小心多点了几次,后端收到了好几次提交,这时就会在数据库中重复创建了多条记录。

2. 接口超时重试

对于给第三方调用的接口,有可能会因为网络原因而调用失败,这时,一般在设计的时候会对接口调用加上失败重试的机制。如果第一次调用已经执行了一半时,发生了网络异常。这时再次调用时就会因为脏数据的存在而出现调用异常。

3. 消息重复消费

当消息被其他消费者重新消费时,如果没有幂等性,就会导致消息重复消费时结果异常,如数据库重复数据,数据库数据冲突,资源重复等。

防重和幂等设计的区别

防重主要是为了避免产生重复数据,把重复请求拦截下来即可。
而幂等设计除了拦截已经处理的请求,还要求每次相同的请求都返回一样的结果

幂等的分类

按照幂等的数据控制范围,可以分为全局幂等和局部幂等

  • 全局幂等:从数据库的角度看,生效范围是整个系统所有的数据库表
  • 局部幂等:从数据库角度看,生效范围是单个数据库分表

按照幂等的设计目的,可以分为外部幂等和内部幂等

  • 外部幂等:用于保障外部幂等契约的设计实现 (与用户、渠道交互)
  • 内部幂等:用于保障内部幂等约束的设计实现 (内部应用之间的调用)

按照幂等字段的来源,可以分为业务幂等和请求幂等

  • 业务幂等:按照业务语义的字段组合作为幂等字段,例如用户支付的交易单号
  • 请求幂等:根据调用方的请求号作为幂等字段,例如requestId

1. 全局唯一ID

全局唯一性ID是幂等的重点,因为幂等意味着一条请求的唯一性,所以不管使用哪个方案去设计幂等,都需要一个全局唯一的ID来保证唯一性。

雪花算法:

雪花算法是一种生成分布式全局唯一ID的算法,生成的ID成为Snowflake IDs,这种算法由Twitter创建并用于推文的ID。

一个Snowflake ID有64位

  • 第一位:用于表示正负,正数为0负数为1,一般默认为0
  • 接下来的41位是时间戳,表示了从选定开始到现在的毫秒数
  • 接下来的10位代表计算机的ID,防止冲突
  • 其余12位代表每台机器上生成ID的序列号,这允许在同一毫秒内创建多个Snowflake ID
    雪花算法

幂等设计的基本流程

幂等流程

幂等实现方案

1. select+insert+主键/唯一索引冲突

日常开发中,为了实现交易接口幂等,可以这样实现:
交易请求过来,先根据请求的唯一流水号字段,先select一下数据库的表

  • 如果数据已经存在,就拦截是重复请求,直接返回成功;
  • 如果数据不存在,就执行insert插入,如果insert成功,则直接返回成功,如果insert产生主键冲突异常,则捕获异常,接着直接返回成功。
    流程图如下:
    幂等

2. 状态机幂等

很多业务表都是有状态的,例如支付流程有四种状态:初始化、支付中、成功、失败。在请求的过程中原子单返回结果推进组合单的状态会涉及到状态机的更新,所以在这个过程中增加对状态的判断也可以实现幂等。

  • 上游第一次请求过来的时候支付单据号是1,该状态是处理中,要更新为成功的状态,所以请求可以正常被处理。
  • 当上游发起了第二次请求,如果单据号还是1,会发现它的状态已经变成了成功的状态,所以该请求不会被处理,直接返回成功。

3. 防重表设计

很多使用我们的业务表不希望因为防重查询太多次,类似缓存的设计原理,我们可以单独建一个防重表,利用主键/索引的唯一性,如果插入防重表冲突即直接返回成功,如果插入成功则继续处理请求。当然也可以使用Redis缓存的方式。

4. token令牌

token令牌的幂等一般包含两个请求阶段:

  1. 客户端请求申请获取一次有效性的唯一token,服务端生成token并返回
  2. 客户端带着一次有效性的唯一token去发起请求,服务端对token进行校验

token-1693987310422

5. 悲观锁

悲观锁在每次操作数据时都会觉得别人中途会修改,所以每次在拿数据的时候都会上锁。即共享资源每次只给一个线程使用,其他线程阻塞,用完后再把资源转让给其他线程
悲观锁控制幂等的方式就是加锁,一般配合事务来实现,在请求业务处理的时候锁住索引或者主键即可。
但是也因为悲观锁在同一事务的操作过程中锁住了一行数据,别的请求过来只能等待,如果当前的事务耗时较长,会造成大量的请求等待,就会很影响接口的性能,所以悲观锁不适合高并发的场景,为了提升接口性能可以使用乐观锁。

6. 乐观锁

乐观锁在操作数据时非常乐观,认为别人不会同时在修改数据,因此乐观锁不会上锁,只是在执行更新的时候判断一下别人是否在此期间修改了数据

乐观锁在实现的时候给表多加一列version版本号,每次更新该行的时候都对version升级一下(自增)。具体流程就是先查出当前的版本号version,然后去更新修改数据时确认下是不是刚刚查出来的版本号,如果是才更新数据。
乐观锁

最后

业务中有幂等失败导致线上故障的案例,因此再次复习幂等的知识。需要强调的是:

幂等在系统设计中十分重要,特别是金融系统,幂等失败大概率会导致资损和客诉。