一、前言
前面我们讨论了《如何基于幂等表实现幂等处理》,本文我们就来看看如何基于乐观锁、悲观锁来做幂等处理。
二、基于数据库乐观锁进行幂等处理
首先我们看如何采用数据库的行锁 乐观锁来实现幂等。
在mysql Innodb存储引擎里面实现了行锁功能,当我们根据id去更新记录时就会获取到行锁。多个线程根据同一个记录id去更新行记录时只有一个线程可以获取到锁,其他线程会阻塞。
乐观锁的实现方式常见有两种:
- 在业务表里面添加一个version版本字段
- 使用业务表里面自带的状态机字段 比如订单流程,每个订单状态有:创建->支付->发货->验货等等。但是需要注意状态机不能出现回路,因为这会导致ABA问题。
上面两种方式本质一样,不同在于如果业务表里面自带的状态机字段,那么我们就不必额外加一个version字段了。下面我们统一称version和状态字段为幂等字段。
基于乐观锁实现幂等流程:
- 根据
select ... from biz_table where id = #id and 幂等字段=幂等字段值
拿到DO对象 - 根据DO对象进行处理:可能是修改DO对象里面的某些值
- 进行乐观锁幂等:
update biz_table set 幂等字段=新幂等值... where id = #id and 幂等字段= #DO对象.幂等字段
;
如果使用version作为幂等处理字段,则上面第三步可以修改为:
update biz_table set version=version 1... where id = #id and version= #DO对象.version
;
如果使用业务状态作为幂等处理字段,则上面第三步可以修改为:
update biz_table set 状态字段=状态机的下一个状态... where id = #id and 状态字段= #DO对象.状态字段
;
可知基于乐观锁时,我们基于第三步做幂等处理。当多个相同id的请求同时(并发)或者先后(顺序)过来后,第一和第二步可能是并发或者顺序执行,但是第三步只有一个请求会返回1,其他都返回0,这就实现了幂等处理.
需要注意的是乐观锁方式在下面这种场景才用(以基于版本方案实现乐观锁为例):
image.png
也就是服务B内可以实现幂等处理前提是,调用方A把记录行id和行记录对应的版本号以参数形式传递过来了。
如下时序图中,服务A调用服务B时候如果只是把记录id传递给服务B,则当服务A顺序多次以相同记录id调用服务B时候,服务B是实现不了幂等的(因为多次调用时步骤2,3,4都会被执行)。
image.png
三、基于数据库悲观锁进行幂等处理
恕我直言,基于悲观锁实现不了通用的幂等处理,为何那?且让我们一一道来。
我们且来回忆一下幂等技术用来保证唯一性,就是相同参数的多次请求和一次请求对业务效果都一样。
而悲观锁处理流程一般为:
- 开启事务
- select ...from biz_table where id = #id for update 对行记录加锁,并返回DO对象
- 对DO对象进行处理
- update biz_table set ... where id = #id
- 提交或者回滚事务
那么当多个id一样的请求顺序或者并行过来后,会导致上面五个步骤都执行(虽然并发过来时候,可能多个请求会暂时hold到步骤2),如果步骤三本身不是幂等的,那么这就起不到幂等作用了。
四、总结
这里我们补充下,幂等技术不是简单的对N多相同请求参数的请求,只处理其中一个,其他的请求忽略,直接返回。而是要保证即使这N多请求都处理了,但是处理的结果的效果和一次处理结果一样,所谓处理结果是指对业务的影响。
本节讲解的乐观锁方式相比基于幂等表方式,对业务入侵比较大,需要在业务表添加一个版本字段或者强依赖业务状态字段。