1,接口幂等性的适用场景
1, 有时我们在填写某些form表单时,保存按钮不小心快速点了两次,表中竟然产生了两条重复的数据,只是id不一样。
2, 我们在项目中为了解决接口超时问题,通常会引入了重试机制。第一次请求接口超时了,请求方没能及时获取返回结果(此时有可能已经成功了),于是会对该请求重试几次,这样也会产生重复的数据。
3, mq消费者在读取消息时,有时候会读取到重复消息,如果处理不好,也会产生重复的数据。
没错,这些都是幂等性问题。
接口幂等性是指用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。
这类问题多发于接口的:
insert操作,这种情况下多次请求,可能会产生重复数据。
update操作,如果只是单纯的更新数据,比如:update user set status=1 where id=1,是没有问题的。如果还有计算,比如:update user set status=status+1 where id=1,这种情况下多次请求,可能会导致数据错误。
在这里顺便说一下,防重设计 和 幂等设计,其实是有区别的。防重设计主要为了避免产生重复数据,对接口返回没有太多要求。而幂等设计除了避免产生重复数据之外,还要求每次请求都返回一样的结果。
那么我们如何保证接口幂等性呢,有下面几种方法
1. insert前先select
1, 一般会在insert前,先根据name或code字段select一下数据。如果该数据已存在,则执行update操作,如果不存在,才执行 insert操作。在并发场景中,要配合其他方案一起使用,否则同样会产生重复数据
2, 加悲观锁
通常情况下通过如下sql锁住单行数据:
select * from userid = 123 for update;
如果使用的是mysql数据库,存储引擎必须用innodb,因为它才支持事务。此外,这里id字段一定要是主键或者唯一索引,不然会锁住整张表。
3. 加乐观锁
既然悲观锁有性能问题,为了提升接口性能,我们可以使用乐观锁。需要在表中增加一个timestamp或者version字段.
每次更新前先select出version版本, 然后根据id和version进行更新, 同时version+1.
若更新成功再做其他操作, 若更新为0行,同样返回成功标识.
4. 加唯一索引
这个不多说,再数据库中加唯一索引,很简单.
当然若操作失败,若防重设计,则返回失败, 若幂等设计,则返回成功.
5. 建防重表
a, 用户通过浏览器发起请求,服务端收集数据。
b. 将该数据插入mysql防重表
c. 判断是否执行成功,如果成功,则做mysql其他的数据操作(可能还有其他的业务逻辑)。
d. 如果执行失败,捕获唯一索引冲突异常,直接返回成功。
需要特别注意的是:防重表和业务表必须在同一个数据库中,并且操作要在同一个事务中。
6. 根据状态机
该方案仅限于要更新的表有状态字段,并且刚好要更新状态字段的这种特殊情况,并非所有场景都适用。比如订单表中有:1-下单、2-已支付、3-完成、4-撤销等状态。如果这些状态的值是有规律的,按照业务节点正好是从小到大,我们就能通过它来保证接口的幂等性。
假如id=123的订单状态是已支付,现在要变成完成状态。
update`order`setstatus=3whereid=123andstatus=2;
7. 加分布式锁
目前主要有三种方式实现redis的分布式锁:
setNx命令
set命令
Redission框架
步骤如下:
a. 用户通过浏览器发起请求,服务端会收集数据,并且生成订单号code作为唯一业务字段。
b. 使用redis的set命令,将该订单code设置到redis中,同时设置超时时间。
c. 判断是否设置成功,如果设置成功,说明是第一次请求,则进行数据操作。
d. 如果设置失败,说明是重复请求,则直接返回成功。