幂等性介绍-不古出品
幂等性介绍
一、幂等概念
1、幂等的数学概念
如果在一元运算中,x 为某集合中的任意数,如果满足 f(x) = f(f(x)) ,那么该 f 运算具有幂等性。
绝对值运算 abs(a) = abs(abs(a)) 就是幂等性函数
如果在二元运算中,x 为某集合中的任意数,如果满足 f(x,x) = x,前提是 f 运算的两个参数均为 x,那么我们称 f 运算也有幂等性。
求大值函数 max(x,x) = x 就是幂等性函数
2、幂等的业务概念
就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。
场景1:支付场景
用户购买商品使用支付宝支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了。因此需要对于每一笔订单,操作多次,也只能扣一次钱。
场景2:一键三连
小破站有一个一键三连的功能,长按可以对up主进行激励,每个人对每个视频只有一个一键三连的机会。就算再喜欢某个视频,多次操作,也只能有一键三连一次。
场景3:统计DAU/MAU
DAU/MAU,又叫日活/月活,是用于反映网站、互联网应用或网络游戏的运营情况的统计指标。所以一个用户当天或者当月登录多次(或者达到某种活跃用户判断机制多次),也只能看作一个活跃用户,不能重复计算。
在增删改查4个操作中,尤为注意就是增加或者修改,
(1) 查询对于结果是不会有改变的,
(2) 删除只会进行一次,用户多次点击产生的结果一样,
(3) 修改在大多场景下结果一样
(4) 增加在重复提交的场景下会出现
二、幂等概述
生产环境经常出现过重复的数据?在排查问题的时候,数据又是正常的。这个是何解呢?怎么会出现这种情况,而且还很难排查问题。
原因 :产生重复数据或数据不一致(假定程序业务代码没问题),绝大部分就是发生了重复的请求,重复请求是指同一个请求因为某些原因被多次提交。导致这个情况会有几种场景:(本质上:多次请求)
1)微服务场景,在我们传统应用架构中调用接口,要么成功,要么失败。但是在微服务架构下,会有第三个情况【未知】,也就是超时。如果超时了,微服务框架会进行重试。
2)用户交互的时候多次点击。如:快速点击按钮多次。
3)MQ消息中间件,消息重复消费。
4)第三方平台的接口(如:支付成功回调接口),因为异常也会导致多次异步回调。
5)其他中间件/应用服务根据自身的特性,也有可能进行重试。
接口幂等:接口的幂等性实际上就是 接口可重复调用,在调用方多次调用的情况下,接口最终得到的结果是一致的。更准确的讲:多次调用对系统的产生的影响是一样的,即对资源的作用是一样的,但是返回值允许不同。
三、幂等场景
1、查询,select * from user where xxx,不会对数据产生任何变化,具备幂等性
2、新增,insert into user(userid, name) values(1, ‘a’)
如 userid 为唯一主键,即重复操作上面的业务,只会插入一条用户数据,具备幂等性
如 userid 不是主键,可以重复,那上面业务多次操作,数据都会新增多条,不具备幂等性
3、修改,区分直接赋值和计算赋值
直接赋值,update user set point = 20 where userid = 1,不管执行多少次,point都一样,具备幂等性
计算赋值,update user set point = point + 20 where userid = 1,每次操作 point 数据都不一样,不具备幂等性
4、删除,delete from user where userid = 1,多次操作,结果一样,具备幂等性
上面场景中,我们发现新增没有唯一主键约束的数据,和修改计算赋值型操作都不具备幂等性
四、解决方案
1、token + redis机制
token + redis 的幂等方案,适用于绝大部分场景。主要思想:
token作为请求的唯一性标示
redis作为存储token的数据库
每次请求先去redis查看token是否存在
不存在,将返回结果缓存到redis
存在,直接返回缓存结果
设置缓存有效期
具体Java代码实现,可以参考我另一篇文章
Java 幂等 实现_苏格拉帝的博客-CSDN博客_java redis幂等实现
基于 redis 实现API操作幂等,为更新类接口添加特定的注解,增加幂等参数ClientToken,在基于ClientToken唯一的情况下先检查redis里是否有响应的结果,否则去请求service层再将结果放入redis,来达到请求幂等的效果。
https://blog.csdn.net/sugelachao/article/details/122046863
2、乐观锁机制
乐观锁这里解决了计算赋值型的修改场景
update user set point = point + 20, version = version + 1 where userid=1 and version=1
加上了版本号后,就让此计算赋值型业务,具备了幂等性
缺点:就是在操作业务前,需要先查询出当前的version版本。
3、唯一主键机制
这个机制是利用了数据库的主键唯一约束的特性,解决了在insert场景时幂等问题。但主键的要求不是自增的主键,这样就需要业务生成全局唯一的主键 ---------> 分布式唯一主键ID
如果是分库分表场景下,路由规则要保证相同请求下,落地在同一个数据库和同一表中,要不然数据库主键约束就不起效果了,因为是不同的数据库和表主键不相关。
因为对主键有一定的要求,这个方案就跟业务有点耦合了,无法用自增主键了。
4、去重表机制
这个方案业务中要有唯一主键,这个去重表中只要一个字段就行,设置唯一主键约束,当然根据业务自行添加其他字段。主要流程上图:
上面的主要流程就是 把唯一主键插入去重表,再进行业务操作,且他们在同一个事务中。这个保证了重复请求时,因为去重表有唯一约束,导致请求失败,避免了幂等问题。
这里要注意的是,去重表和业务表应该在同一库中,这样就保证了在同一个事务,即使业务操作失败了,也会把去重表的数据回滚。这个很好的保证了数据一致性。
这个方案也是比较常用的,去重表是跟业务无关的,很多业务可以共用同一个去重表,只要规划好唯一主键就行了。
5、门票机制
支付场景:单次支付请求,也就是直接支付了,不需要额外的数据库操作了,这个时候发起异步请求创建一个唯一的ticketId,就是门票,这张门票只能使用一次就作废。
具体步骤:
1.异步请求获取门票
2.调用支付,传入门票
3.根据门票ID查询此次操作是否存在,如果存在则表示该操作已经执行过,直接返回结果;如果不存在,支付扣款,保存结果
4.返回结果到客户端
如果步骤4通信失败,用户再次发起请求,那么最终结果还是一样的.