高并发下保证接口幂等性的策略
前言
1.接口幂等性问题常见场景:
- 有时我们在填写某些
form表单
时,保存按钮不小心快速点了两次,表中竟然产生了两条重复的数据,只是id不一样。 - 我们在项目中为了解决
接口超时
问题,通常会引入了重试机制
。第一次请求接口超时了,请求方没能及时获取返回结果,为了避免返回错误的结果,于是会对该请求重试几次,这样也会产生重复的数据。 - mq消费者在读取消息时,有时候会读取到
重复消息
,如果处理不好,也会产生重复的数据。
没错,这些都是幂等性问题。
接口幂等性
是指用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。
这类问题多发于接口的:
insert
操作,这种情况下多次请求,可能会产生重复数据。update
操作,如果只是单纯的更新数据,比如:update user set status=1 where id=1
,是没有问题的。如果还有计算,比如:update user set status=status+1 where id=1
,这种情况下多次请求,可能会导致数据错误。
2.接口幂等性和防重设计的区别
防重设计
和 幂等设计
,其实是有区别的。防重设计主要为了避免产生重复数据,对接口返回没有太多要求。而幂等设计除了避免产生重复数据之外,还要求每次请求都返回一样的结果。
1. insert前先select(不适用于并发场景)
通常情况下,在保存数据的接口中,我们为了防止产生重复数据,一般会在insert
前,先根据name
或code
字段select
一下数据。如果该数据已存在,则不执行update
操作,如果不存在,才执行 insert
操作。
该方案可能是我们平时在防止产生重复数据时,使用最多的方案。但是该方案不适用于并发场景,在并发场景中,要配合其他方案一起使用,否则同样会产生重复数据。我在这里提一下,是为了避免大家踩坑。
2. 加悲观锁(只防重)
在支付场景中,用户A的账号余额有150元,想转出100元,正常情况下用户A的余额只剩50元。一般情况下,sql是这样的:
update user amount = amount-100 where id=123;
如果出现多次相同的请求,可能会导致用户A的余额变成负数。这种情况,用户A来可能要哭了。于此同时,系统开发人员可能也要哭了,因为这是很严重的系统bug。
为了解决这个问题,可以加悲观锁,将用户A的那行数据锁住,在同一时刻只允许一个请求获得锁,更新数据,其他的请求则等待。
通常情况下通过如下sql锁住单行数据:
select * from user id=123 for update;
具体流程如下:
具体步骤:
- 多个请求同时根据id查询用户信息。
- 判断余额是否不足100,如果余额不足,则直接返回余额不足。
- 如果余额充足,则通过for update再次查询用户信息,并且尝试获取锁。
- 只有第一个请求能获取到行锁,其余没有获取锁的请求,则等待下一次获取锁的机会。
- 第一个请求获取到锁之后,判断余额是否不足100,如果余额足够,则进行update操作。
- 如果余额不足,说明是重复请求,则直接返回成功。
需要特别注意的是:如果使用的是mysql数据库,存储引擎必须用innodb,因为它才支持事务。此外,这里id字段一定要是主键或者唯一索引,不然会锁住整张表。
悲观锁需要在同一个事务操作过程中锁住一行数据,如果事务耗时比较长,会造成大量的请求等待,影响接口性能。
此外,每次请求接口很难保证都有相同的返回值,所以不适合幂等性设计场景,但是在防重场景中是可以的使用的。
- 使用悲观锁可以防止重复数据的产生,但它不能保证每次请求都会返回相同的结果。例如,如果一个操作涉及多个步骤,其中某些步骤可能依赖于外部条件或状态变化,那么即使加了悲观锁,每次请求的结果也可能不同。
只防重不幂等性例子说明:
- 假设有一个转账操作,从账户A向账户B转账100元。
3. 加乐观锁
既然悲观锁有性能问题,为了提升接口性能,我们可以使用乐观锁。需要在表中增加一个timestamp
或者version
字段,这里以version
字段为例。
在更新数据之前先查询一下数据:
select id,amount,version from user id=123;
如果数据存在,假设查到的version
等于1
,再使用id
和version
字段作为查询条件更新数据:
update user set amount=amount+100,version=version+1where id=123 and version=1;
更新数据的同时vers
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
不想当架构师的后端开发工程师不是好码农! 此专栏用于存放我的架构设计学习笔记,内容一部分源于网上资料,一部分源于gpt和我自己的理解。建议喜欢本专栏的盆友尽早订阅。欢迎给本专栏的文章点赞、评论!