前面我们讨论了《如何基于幂等表实现幂等处理》,本文我们就来看看如何基于乐观锁、悲观锁来做幂等处理。
首先我们看如何采用数据库的行锁+乐观锁来实现幂等。
在mysql Innodb存储引擎里面实现了行锁功能,当我们根据id去更新记录时就会获取到行锁。多个线程根据同一个记录id去更新行记录时只有一个线程可以获取到锁,其他线程会阻塞。
乐观锁的实现方式常见有两种:
上面两种方式本质一样,不同在于如果业务表里面自带的状态机字段,那么我们就不必额外加一个version字段了。下面我们统一称version和状态字段为幂等字段。
基于乐观锁实现幂等流程:
select ... from biz_table where id = #id and 幂等字段=幂等字段值
拿到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
恕我直言,基于悲观锁实现不了通用的幂等处理,为何那?且让我们一一道来。
我们且来回忆一下幂等技术用来保证唯一性,就是相同参数的多次请求和一次请求对业务效果都一样。
而悲观锁处理流程一般为:
那么当多个id一样的请求顺序或者并行过来后,会导致上面五个步骤都执行(虽然并发过来时候,可能多个请求会暂时hold到步骤2),如果步骤三本身不是幂等的,那么这就起不到幂等作用了。
这里我们补充下,幂等技术不是简单的对N多相同请求参数的请求,只处理其中一个,其他的请求忽略,直接返回。而是要保证即使这N多请求都处理了,但是处理的结果的效果和一次处理结果一样,所谓处理结果是指对业务的影响。
本节讲解的乐观锁方式相比基于幂等表方式,对业务入侵比较大,需要在业务表添加一个版本字段或者强依赖业务状态字段。