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