5.4 服务幂等性设计 幂等性最初是一个数学概念,后来被引入计算机科学中,用来描述某个操作可以安全地重试,即多次执行的结果与单次执行的结果完全一致。 柔性事务普遍基于“最大努力交付”机制。也就是说,当网络通信失败、节点宕机或者进程崩溃时,采用重复请求的方式来容错。因此,如果某些关键服务不具备幂等性,重复处理可能导致数据不一致或其他风险。例如,在退款接口中,缺乏幂等性可能导致重复退款。 接下来,笔者将介绍两种实现系统幂等的方式,供读者参考。 5.4.1 全局唯一 ID 全局唯一ID 是在应用层使用最广泛的一种。它的核心思想是为每个操作生成一个独一无二的标识符,以此来判断是否已经执行过该操作。
:::tip 什么是幂等性
幂等性最初是一个数学概念,后来被引入计算机科学中,用来描述某个操作可以安全地重试,即多次执行的结果与单次执行的结果完全一致。
:::
柔性事务普遍基于“最大努力交付”机制。也就是说,当网络通信失败、节点宕机或者进程崩溃时,采用重复请求的方式来容错。因此,如果某些关键服务不具备幂等性,重复处理可能导致数据不一致或其他风险。例如,在退款接口中,缺乏幂等性可能导致重复退款。
接下来,笔者将介绍两种实现系统幂等的方式,供读者参考。
全局唯一ID 是在应用层使用最广泛的一种。它的核心思想是为每个操作生成一个独一无二的标识符,以此来判断是否已经执行过该操作。
全局唯一 ID 的实施步骤如下:
值得一提的是,唯一ID 生成算法 snowflake,取自世界上没有两片相同的雪花之意。使用分布式部署的情况下每秒可生成百万个不重复、递增 id。已经广泛应用于需要分布式系统中生成唯一标识符的场景。
接下来,再看数据库中关于修改相关的操作。
假设有一个账户表 accounts,包含字段 id(账户ID)和 balance(账户余额)。现在要给账户 ID 为 1 的账户增加余额
UPDATE accounts SET balance = balance + 100 WHERE id = 1;
如果这个SQL语句执行一次,那么账户的余额会增加100。但是如果由于某些原因(比如网络重试或者程序逻辑错误),这个 SQL 语句被执行了两次,账户的余额将会增加 200,而不是预期的 100。
每次执行这个语句都会对账户余额产生不同的影响,属于典型的非幂等性操作。对于此类的非幂等性操作,我们看看使用乐观锁(Optimistic Locking)如何解决。
乐观锁基本思想是,假设并发操作发生冲突的概率较低,允许多个事务或线程在不加锁的情况下同时读取数据,但在写入数据时再进行冲突检测。如果在写入前检测到数据已被其他事务修改,则放弃当前操作,避免数据不一致的情况。
结合上述增加余额的 SQL,请看下面具体的操作:
UPDATE accounts SET balance = balance + ?, version = version + 1 WHERE id = ? AND version = ?;
这个例子中,? 代表将要被更新的数值,id 是账户的唯一标识符,version 是用于检查数据在读取后是否被修改的版本号。如果更新操作失败,意味着数据库内的数据已经被修改。此时,业务层面请求最新的数据,更新本地 version 并发起重试,直至成功或达到最大重试次数。
上面乐观锁的操作模式,是一种典型的 CAS(Compare And Swap | Compare And Set,比较并交换)操作。CAS 有时也被称为“轻量级事务”。由于乐观锁不需要在读取和写入时持有锁,在并发冲突不频繁的情况下(也就是读多写少的场景),使用乐观锁除保证一致性之外,还可提供更好的并发性能。