深入理解幂等技术

什么是幂等

幂等(idempotent)是一个数学与计算机学概念,常见于抽象代数中。

在编程中,一个幂等操作的特点是,其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。幂等函数可以改变系统的状态。例如,setTrue()就是一个幂等函数,因为无论执行多少次,其结果都是一样的。

幂等的场景有很多,例如:

  • 前端重复提交选中的数据,后台只产生对应这个数据的一个反应结果;
  • 我们发起一笔付款请求,应该只扣用户账户一次钱,当遇到网络重发或系统bug重发,也应该只扣一次钱;
  • 发送消息,也应该只发一次,同样的短信发给用户,用户会崩溃;
  • 创建业务订单,一次业务请求只能创建一个,创建多个就会出大问题。

幂等的技术手段

幂等并不是并发场景下的特有问题。幂等处理的是多次执行的问题,而并发仅仅是多次执行的一种形式。不管是依次执行,还是并发执行,都需要做好幂等。有些技术人员将解决并发问题的技术手段,例如悲观锁、乐观锁和分布式锁,当成幂等的技术手段,这是不对的。

再次强调,幂等的核心是确保唯一性。

唯一索引

在数据库中建立唯一索引,用作幂等记录,可以防止插入重复的数据。 在幂等函数中,先执行一次查询操作,如存在幂等记录则返回第一次执行的结果,如不存在幂等记录则继续执行。在并发场景下,可能存在多个线程同时插入幂等记录,这时候唯一索引可以确保只有一个线程插入成功,其它线程抛出异常。

除了插入幂等记录,应该还要插入其它的业务数据,这个时候务必使用事务。在实际工作中,幂等记录与事务经常同时出现,如影相随。

唯一数据

使用redis、memcache和zookeeper都可以实现唯一数据,这里仅用redis的SETNX举例。笔者从redis的官方文档摘抄了SETNX的用法,如下所示。

SETNX key value

将 key 的值设为 value ,当且仅当 key 不存在。

若给定的 key 已经存在,则 SETNX 不做任何动作。

SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

可用版本:
>= 1.0.0
时间复杂度:
O(1)
返回值:
设置成功,返回 1 。
设置失败,返回 0 。
复制代码

在幂等函数中,将唯一标识作为key,任取value,调用SETNX。如果返回1,说明当前是第一次执行,继续执行幂等函数;如果返回0,取出第一次执行的结果并返回给调用方。在并发场景下,可能因尚未完成第一次执行而取不到结果,这时候可以稍作等待。

除了redis、memcache和zookeeper,还有其它手段可以实现唯一数据,读者可自行探索。只要可以实现唯一数据,就可以用来做幂等。

状态机约束

在单据相关的业务,或者是任务相关的业务,基本会涉及到状态机。业务单据上面有个状态,这个状态根据一个有限状态机进行跳转。如果状态机已经处于下一个状态,这时候是不能往回跳转到上一个状态的。通过状态机的跳转约束,可以做到有限状态机的幂等。

幂等的场景

幂等经常与事务同时出现,而事务适合小任务场景、不适合大任务场景,因此笔者将幂等场景分为以下两类进行介绍。为了方便描述,我们假设bizId可以唯一标识一笔业务。

小任务场景

这个场景的处理方式很简单,可以追求强一致性。在幂等函数中,先判断幂等记录是否存在。如果存在,直接返回;如果不存在,开启一个事务。在事务中,采用任务名+bizId作为幂等组合字段,插入幂等记录和业务数据。它的流程图如下。

image.png

大任务场景

在大任务场景下,需要将大任务拆成多个小任务分别执行。在每个小任务中,都可以有事务。但是,没有事务保证所有的小任务同时成功。因此,存在部分成功的场景。针对部分成功的场景,可以利用重试机制做到最终一致性。重试机制意味着多次执行,回到了幂等问题。这里只介绍需要幂等的场景。如果同时存在需要幂等和不需要幂等的场景,请加入一个判断标。

同步执行小任务

同步执行小任务的流程图如下。各小任务依次执行,中间的小任务不返回结果,仅在最后一个小任务或之后返回结果。

image.png

异步执行小任务

异步执行小任务的流程图如下。各小任务单独执行,互相不感知,也没有地方返回结果。

image.png

幂等字段

不管是同步执行小任务,还是异步执行小任务,都需要为每个小任务设置一个幂等字段或幂等组合字段。笔者推荐采用小任务名+bizId作为幂等组合字段。一方面,bizId可以标识这一批小任务属于同一笔业务;另一方便,小任务名可以区分不同的小任务。


猜你喜欢

转载自juejin.im/post/5c4d5a61518825260d7ef2e7