最核心的概念
执行一次或多次操作,其产生的影响都是一样的。也就是说,针对同一次操作,请求参数相同,操作最后产生的结果相同。
简单举例
前端防抖是保证幂等最简单的方式,但是其无法完全解决幂等问题,如果有大牛的话还是有办法直接跳过前端防抖进行重复请求的。
Token去重的主要思路其实和分布式锁的思路有点相似,token值一般会被缓存起来,比如缓存到redis中。不过其需要前、后端配合进行操作,大致流程如下:
为保证原子性,这里删除redis - key可以采用lua脚本来进行操作
悲观锁的核心思想其实就是在更新数据的时候,保证当前的更新操作是全局唯一的。一般会在MySQL层面采用 for update
的操作,但是这种操作如果不指定主键或唯一id的话,是会锁全表,出大事的!
“ 由于 InnoDB 预设是 Row-Level Lock,所以只有「明确」的指定主键,MySQL 才会执行 Row lock (行锁) ,否则 MySQL 将会执行 Table Locck(锁表)Lock”
悲观锁的核心SQL思想是锁行:
select * from table_1 where id = 10 for update
在不指定主键的情况下进行查询,锁表极有可能会导致死锁的发生:
用户A查询表1,然后想访问表2,此时表1是被锁死的;突然,有用户B查询表2,想访问表1,此时表1是锁死的。因为用户B访问的表1已经被锁表了,用户B在等待表1释放表锁;用户A访问的表2也已经被锁表了,用户A在等待表2释放表锁;其互相等待,最终产生死锁
这里将MVCC、版本号机制、状态机制、乐观锁思想放在一起,其实是因为他们的核心思想太相似了!
版本号机制 & 乐观锁:其实乐观锁会产生ABA问题,为了解决ABA问题,乐观锁可以引用版本号机制来进行处理,来看下面三条SQL来加强一下这方面的思想:
update table1 set count = count + 1 where account_id = #{account_id} and version = #{version}
update table1 set count = count - #{num} where market_id = #{market_id} and count - #{num} > 1
状态机制在我们的业务开发过程中,再常见不过了:
update table1 set order_status = #{has_pay} where order_no = #{order_no} and order_status = #{not_pay}
分布式锁在业务中经常使用,其相比于token机制来说,纯后台来实现即可,减少了很多没有必要的交互操作。但是分布式锁需要后台维护好唯一的分布式锁的key,比如采用 order_no
+ user_id
作为key,保证分布式锁的key是唯一的。但是这个分布式锁的粒度不能太大,比如有的通过user_id
来加锁(尼玛我真的见过,排查起来真心难,粒度过大摆明了坑人 )
具体步骤:
SETNX
的方式设置redis-key,并且设置过期时间SETNX
成功,说明是首次操作,后续的逻辑继续进行,记得最后finally处要release掉这个分布式锁SETNX
失败,说明是重复操作,直接returnSETNX 其实使用范围还挺广的,譬如发生MQ重复消费的话,我们可以采用 SETNX 来保证消费唯一:
if(SETNX成功){ 则进行MQ消费 } else { 消费失败,直接return,避免重复消费 }
这里其实主要是针对MySQL新增唯一索引
alter table
t_table_1
add UNIQUE KEYt_table_1
(order_no
);
当有重复异常时会抛出: Duplicate entry '002' for key 't_table_1.order_no
,其实这种方式从最底层的MySQL解决了幂等问题,在业务中也很常用,操作简单且有效。
定义唯一索引来达到表数据唯一的目的,其实还有一种比较花哨的玩法,那就是定义一张防重表。这种场景大致是出现在,目前我们有业务表A,我们的业务表A无法接受定义唯一索引的操作来完成去重操作。定义防重表的方式其实也很容易。
基本流程
表A
,其无法、定义一个唯一索引;那么我们定义 表B
,表B根据业务需求指定唯一索引表B
过滤,判断 表B
插入数据的时候是否会产生 Duplicate
相关的未唯一异常表B
发生了异常,说明当前数据已经是重复数据,直接return表B
插入数据时暂未有异常,则可以将当前数据插入到目标表 表A
中这种场景常见在数仓ETL开发中,大致的思想就是,我们的表数据按天进行分区,并且编写hive-sql的时候需要指定覆盖插入:
insert into override table xxx partition(date)
此hive-sql就是定义了一个覆盖写的分区表,插入数据的时候就按照天维度进行插入,即可完成分区覆盖写了(数仓开发保证幂等常用手段)
这个思想是在近期在业务开发中在同事中学到的,大致场景就是有一种周报的数据场景,每周会定时推送周报数据给用户,那么定义一张表:用户 id+ 当前周作为唯一标识,周报数据对应一个唯一的周报id,三个字段聚合成一个表(暂称表A);周表详情表(暂称表B)通过周报id关联表A。
倘若上周周报数据异常,我们可以重新执行周报生成任务,重新给表A中的数据生成周报数据(根据用户id + 周)逐一生成新周报,替换表A中旧的周报id。表B数据继续生成,新的周报增量加到表B中去,旧的周报数据不受任何影响。
截至目前为止,常见的九种幂等解决方案列举了出来,其实幂等的解决方案真的可以有很多,针对不同的业务场景和需求使用不同的方式来进行应用,没有绝对之说