分布式基础知识 — 幂等【转载】

本文探讨了幂等操作在系统间通信中的重要性,介绍了查询+唯一索引、MVCC、Tair锁以及前后端协同的统一幂等组件的实现方法,尤其针对浏览器与服务端交互中的幂等问题给出了创新解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载自阿里内网,内网文章链接没法贴出,只能设置为原创

1. 幂等的定义


在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。在系统设计上就是两个系统之间通信,同样的参数A系统调用B系统多次,得到的结果应该是一致的。

2. 为什么要幂等


在系统设计的过程,两个系统之间通信,系统A对系统B的订单插入服务进行的一次调用,由于网络超时调用失败,A系统不清楚B系统事务是否处理完成,会选择对B系统调用进行重试,B系统的订单插入服务必须支持幂等,否则就会出现重复单据。

3. 如何实现幂等


(1)查询+唯一索引


查询+唯一索引的幂等方式适合两个系统之间传递数据,例如A系统给B系统传递数据,A系统调用B系统服务,B系统服务内部的逻辑如下:

根据A系统传递的数据,提取唯一约束,在事务中用唯一约束作为查询条件在A系统数据库执行查询

  • 没有数据则执行插入
  • 存在数据则返回幂等成功

然而在并发度很高的分布式场景下,A系统的两次请求可能会在10ms之内先后传递到B系统,由于数据库事务的隔离性,两个数据库事务产生的数据彼此不可见,就会导致两个事务执行成功,产生重复数据。解决方案也很简单,在B系统主要业务表中对提取出来的唯一约束建立唯一索引,唯一索引可以是在业务表中添加,业务不能很好适配幂等逻辑的时候也可以专门建一张去重表,唯一索引建在去重表中。

(2)MVCC


MVCC俗称多版本并发控制,适合两个系统之间的更新逻辑,例如A系统调用商品更新接口更新B系统商品,每次调用都会带上自己系统商品的版本号(A_version),用版本号做乐观锁控制。

B系统接口内部逻辑如下:

执行update base_item set name = 'aa',version = #A_version where item_id = 1 and version < #A_version 。

  • 返回affectrows大于0,则代码更新成功
  • 返回affectrows等于0,则代表被幂等掉

(3)阿里Tair锁


tair分布式锁也是基于版本控制,不同机器不同线程对同一个资源进行访问时,对这个资源抽取唯一性约束作为tairkey,每次puteKey可以指定版本号,不指定版本号的话默认为1,tair锁是否要自旋根据自己的业务场景判断。当puteKey指定的版本号大于tair服务端的版本号时,可以put成功;反之指定的版本号小于等于tair服务端的版本号时,tair返回version_error。如果担心失效key的地方有遗漏的话,可以设置expireTime来控制失败时间。

并发量特别大的库存系统,一般会采用tair+DB唯一索引双重锁机制,对商品等热点数据加锁的时候一般选择先用tair加锁,再更新DB,这样tair可以为DB抵挡住大部分的并发量。由于tair和db没有强事务性的,某一个时刻会出现tair和db的锁不一致的情况,这个时候要根据自己的业务觉得失效锁的时候,是先释放DB锁还是先释放tair锁。

(4)前后端协同的统一幂等组件


前面说的两个方案查询+唯一索引、MVCC主要是解决不同系统之间的幂等解决方案,然而在浏览器页面和服务端之间会存在这样的一种幂等问题。
用包裹质检业务功能举例,分两个场景:

  • 用户A扫描包裹B中的两个商品进行组包,浏览器把组包数据报文发送到后端,返回成功组成包裹C,再次扫描包裹B中的两个商品进行组包,浏览器继续把组包报文发送到后端,返回成功组成包裹D,一共组成了两个包裹。
  • 用户A扫描包裹B中的两个商品进行组包,浏览器把组包数据报文发送到后端,网络超时然后后端事务成功,组成包裹C;用户A发现超时会重新点击组包按钮,浏览器重新把组包报文发送到后端,组成包裹D。这里用户A其实只是想组一个包裹,然后阴差阳错组了2个包裹出来。

上述两个场景下,场景一用户操作组包功能两次组成2个包裹,场景二用户自我感觉就使用了一次组包功能,然而也是组成了2个包裹。包裹质检这个业务,所有请求数据都来自于前端既没有唯一索引,又没有版本号,一旦出现后端超时情况后端就会出现脏数据。

幂等的场景抽象看来有两种场景:
多个会话(一般指不同系统)同一时刻入参相同的请求之间产生并发,需要幂等,可以使用前面的两种方案解决(查询+唯一索引、MVCC)。
一次会话(浏览器和服务端)正常业务入参报文相同的多次请求和客户端超时用户重试入参报文相同的多次请求之间的冲突。当业务逻辑足够复杂的情况,并没有很好的解决办法。

这里提出一种基于前后端协同的统一幂等方案

  1. 业务初始化后,由后端生成一个全局唯一的UUID给前端,这个UUID在前端和后端的版本号都暂时为V。
  2. 前端完成页面事务操作,需要同步给后端,请求报文中带上UUID,版本号为V。
  3. 如果后端响应超时,前端继续用V版本的UUID重试去请求后台。
  4. 如果后端响应未超时,则后端把收到的UUID前端版本和后端版本进行比对。
  5. 前端版本和后端版本一致,说明需要后端执行事务操作,然后后端版本加一,前端版本加一双方保持一致。
  6. 后端版本比前端版本大一,说明后端事务已经执行过,返回幂等成功,后端不需要做事务操作,最后前端版本加一双方保持一致。
  7. 其他情况说明UUID版本号前后端同步有异常,前端返回业务异常。提示用户异常,重新初始化UUID和版本号。
  8. 注意幂等组件和业务逻辑需要在一个事务中,业务逻辑失败回滚事务幂等组件也会回滚版本,前端任意情况下都使用后端返回的uuid的版本号即可。
     
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

【江湖】三津

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值