一、什么是接口幂等性?
所谓的接口幂等性就是,针对同一个接口,多次发出同一个请求,必须保证操作只执行一次。
系统中一些核心的接口如果没有实现接口幂等性会有很严重的后果,比如:
- 下单接口,同一个订单可能会创建多次,导致出现多张相同的订单
- 支付接口,重复支付会导致多次扣款
幂等性问题多发生于新增操作和修改操作当中:
- insert操作:这种情况下多次请求,可能会产生重复数据。
- update操作:这里要分两种情况,一种是单纯的更新数据,比如:
update order set status=0 where id = 1;
,这种是没有问题的,无论执行成功多少次状态都是一致的,是幂等操作,但是如果你的更新sql里面还包含着计算逻辑,比如:update order set num = num+1 where id =1;
,对于这种sql,如果多次执行,那就会导致数据错误了。
二、幂等性的解决方案
对于这个问题,我们有两个方向,一是在客户端浏览器做防止重复提交的处理,比如按钮置灰、加loading状态,但是这种方式不绝对可靠,比如别人可以直接通过类似postman这种工具来对后端发起请求,直接跳过你前端做的限制;另一个是在服务端做是否重复提交的校验。
接下来我们进行详细说明:
1.使用唯一索引来防止新增脏数据
利用数据库唯一索引机制,当数据重复时,插入数据库会抛出异常,保证不会出现脏数据。
2.乐观锁
这里的乐观锁指的是用乐观锁的原理去实现,在数据表中新增一个version字段,通过version字段来做乐观锁,当数据需要更新时,先去数据库里获取此时的version版本号,如:
sql
select version from test where id = 1;
更新数据时首先跟查出来的版本号去做对比,如果不相等,说明在此之前已经有其他的请求去更新过数据了,提示更新失败。
sql
update test set status=status+1,version=version+1 where id=1 and version=#{version}
更新的时候会同时将version字段进行加1。
3.加悲观锁
在获取数据时进行加锁,当同时有多个重复请求时其他请求无法进行操作。
下面进行举例说明,比如用户下订单,进行支付,用户的账号里面有200元,需要支付100元,正常情况下用户的账号支付完成后还会有100元。sql如下:
sql
update account set amount = amount - 100 where id = 1;
如果出现多次相同的请求,就会导致该用户的余额变成负数。此时我们可以使用悲观锁,将该用户的那行数据进行锁住,在同一时刻只允许一个请求获得锁,更新数据,其它请求需要等待。
通过如下sql锁住单行数据:
sql
select * from account where id = 1 for update;
下面我们画一下流程图,更直观一点:

注意:这里的id字段必须是主键或者唯一索引,不然会锁住整张表。(FOR UPDATE是否会锁表取决于查询条件是否使用了索引或主键:如果条件使用了索引或主键,则通常为行锁;否则可能升级为表锁。)
4.建立防重表
以支付功能为例: 使用唯一主键去做防重表的唯一索引,比如使用订单号作为防重表的唯一索引,每一次请求都根据订单号向防重表中插入一条数据,插入成功说明可以处理后面的业务,当处理完业务逻辑之后删除防重表中的订单号数据,后续如果有重复请求,则会因为防重表唯一索引原因导致插入失败,直接返回操作失败,可以看出防重表作用就是加锁的功能。
5.token机制
这种方案需要两次请求才能完成一次业务操作:
1.第一次请求是去获取token的。
2.第二次请求带着这个token去完成后续的业务操作。
举个例子来说明:
- 在进入到提交订单页面之前,需要调用获取token的接口,然后将token保存到redis当中。
- 支付的时候带上token,后端检查redis当中是否存在该token,如果存在,表示第一次发起支付请求,删除缓存中的token开始处理支付逻辑;如果缓存中不存在,说明之前已经支付过了,返回错误。