1

由于调用方为了保证消息的发送是高可用的,所以会有重试机制。假如有这个场景:
服务A调用服务B,此时第一个请求,由于网络原因,服务B延迟收到了这个请求,或者服务B回复服务A的时候,服务A没收到,导致服务A又发送了相同的请求。此时,服务B就又处理了一次请求,如果这个请求是对数据库进行操作的,比如对金额的扣减,那这麻烦可大了。
image.png
首先,我们先看看接口是否需要幂等性。以下两个不需要保证幂等性:

  1. 如果只是查询操作,那无论调用多少次,返回的结果是一样的,所以查询操作不需要保证幂等性。
  2. 如果业务上允许,比如点赞个数是10000还是10001不太重要,那可以不需要保证幂等性。

基本上对数据库的新增、修改、删除都需要幂等性。

新增

新增的时候,唯一标识就是主键(分布式下的主键是怎么生成的?),所以尽管多次插入,成功的只能一条数据,此时新增是幂等性的。如果此时主键是数据库生成的,那需要唯一索引避免插入重复数据,这个时候也是幂等性的。如果既需要数据库生成主键,也没有唯一索引,那这个幂等就没办法保证了。所以要保证新增的幂等,必须自己生成主键(推荐),或者创建唯一索引。
以上方式只适合服务B调用数据库的情况,因为主键是服务B生成的。如果服务A多次调用服务B,此时,服务B依然会插入多次。
image.png
为了防止这种情况,每次新增的时候,服务A先申请服务B的一个token,服务B生成token后,放入缓存(如果是集群就放入分布式缓存)并且把token给服务A,服务A请求新增的时候,携带这个token。服务B在处理之前,先看token是否存在,如果不存在,则跳过,如果存在,进行新增,新增结束后,删除token。
image.png
在并发的情况下,有可能服务B在准备新增还没删除token的时候,服务A又像服务C发送了请求,所以服务C也觉得token存在,又重复新增了一条,此时,需要用分布式锁,把token锁住,预防这种情况。
image.png

修改

修改分为两种情况,一个是直接赋值,一个是相对赋值。

# 直接赋值
update table set num=5 where id=1;
# 相对赋值
update table set num=num-1 id=1;

如果是直接赋值的,不管给num赋值多少次5,结果还是5,那如果是相对赋值的,此时就会一直减1。此时应该先从数据库查询出num,得到结果为5,服务A计算num-1,再把结果4传给服务B,服务B不管执行多少次set num=4都一样,这样就是幂等的。
image.png
但是在并发的情况下,服务A取到num=5,服务B也取到num=5,服务A对num=5-1=4,服务B对num=5-2=3,再赋值给数据库的时候,数据就错误了。此时可以通过version或者分布式锁来解决这个问题。version的话sql如下:

update table set num=5,version=2 where version=1

删除

删除也分两种情况,一个是指定值删除,一个是范围值删除。

# 直接赋值
delete from table where id=1;
# 相对赋值
delete from table order by id desc limit 3;

如果是指定值删除,第一次删除返回1,第二次删除返回0,不管执行几次,数据库都没有id=1的信息,所以是幂等的。
如果是范围值删除,第一次执行删除3个,第二次执行又删除3个,这样就不是幂等的。方法跟上面类似,先把需要删除的id查询出来,得到10,9,8,然后把需要删除的id(10,9,8)传给服务B,服务B不管删除几次id(10,9,8),数据库都没有id(10,9,8)的信息,也没有额外多删除其他的信息,所以是幂等的。
image.png


大军
847 声望183 粉丝

学而不思则罔,思而不学则殆