谈谈幂等技术(二)

2020-04-16 19:14:42 浏览数 (1)

一、前言

前面我们讨论了《如何基于幂等表实现幂等处理》,本文我们就来看看如何基于乐观锁、悲观锁来做幂等处理。

二、基于数据库乐观锁进行幂等处理

首先我们看如何采用数据库的行锁 乐观锁来实现幂等。

在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多请求都处理了,但是处理的结果的效果和一次处理结果一样,所谓处理结果是指对业务的影响。

本节讲解的乐观锁方式相比基于幂等表方式,对业务入侵比较大,需要在业务表添加一个版本字段或者强依赖业务状态字段。

0 人点赞