接口幂等性
目录
接口幂等性定义
定义
多次调用方法或者接口不会改变业务状态,可以保证重复调用的结果和单次调用的结果一致。
接口分类
- web接口
- 服务间调用api接口
接口幂等性的使用场景
业务场景
1、前端重复提交
就好比有个新增商品的功能,如果前端连续多次点击保存,后端就会收到多次请求接口
2、接口超时重试
当我们调取第三方接口的时候,有可能会因为网络等原因导致调用失败,所以我们会对接口调用添加失败重试的机制,Spring可以通过@Retryable
注解实现重试机制。
3、MQ消息重复消费
MQ在生产端和消费端都有重试机制,也就是同一消息很可能会被重复消费,业务无法保证多次消费的结果一致。
数据库场景
当一个方法内存在非幂等的数据库操作,那这个方法一定不幂等。
select 天然自带幂等性。
每次查询对数据都不会产生副作用。
insert 看是否自增
第一种情况:自增主键,没有幂等性。
eg:insert into product_info (id,name,type,price,tm)
执行多次,会新增多条记录。对结果集产生了副作用。
第二种情况:业务主键,具有幂等。
eg:insert into product_info (orderId,name,type,price,tm) orderId 为主键唯一
无论该sql执行多少次,对结果集产生的效果都是一样只增加了一条数据。
delete 看是否主键删除
第一种情况:绝对删除,具有幂等性。
eg;delete from order where id = 3 。
无论该sql执行多少次,对结果集产生的效果都是一样只删除了一条数据。
第二种情况: 相对删除,不具有幂等性。
eg:delete from order where id > 23 .
该操作每执行一次,对结果集产生的结果,可能都不一样,同一操作多次执行对数据产生了副作用。
update 看是否主键基于原数据变化
第一种情况:绝对更新,具有幂等性。
eg:update good set stock= 586 where goodId = 10;
该操作无论执行多少次操作对结果的影响都是一样。
第二种情况:相对更新,不具有幂等性。
eg:update good set stock = stock+10 where goodid= 10 ;
每次执行该操作库存数量都会加10,所以不具备幂等操作。
解决方案
非并发场景
-
前端:前端做一些交互控制
比如有个新增商品的功能,有个保存按钮
,用户点击保存按钮后,立马按钮置灰,或者页面跳转到商品列表页面,这样可以防止很大部分的前端重复提交 -
前端:RPG模式
Post-Redirect-Get
,当客户提交表单后,去执行一个客户端的重定向,转到提交成功页面。避免用户按F5刷新致使的重复提交,也能消除按浏览器后退键致使的重复提交问题。 -
后端:插入前先判断数据是否存在
public void save(Goods goods) {
// 1、先通过商品唯一code,查询数据库属否存在
Goods goods = findGoods(goods.getCode);
// 2、如果这条数据在db里已经存在了,此时就直接返回了
if (goods != null) {
return;
}
// 3、如果要是这条数据在db里不存在,此时就会执行数据插入逻辑了
insertGoods(goods);
}
高并发/分布式场景
加锁(性能不佳,不推荐)
-
(分布式)悲观锁:锁读又锁写,所有请求依次处理,不会出现。本质是将并发场景转化为非并发场景处理,请求顺序执行,需结合肺病发场景的下的后端验值方案使用
单机:使用synchronized关键字锁程序,也可以用MYSQL行锁for update
字段锁数据库
分布式:使用redisson中Fair Lock可实现相似效果 -
(分布式)乐观锁:锁写不锁读,所有写入操作校验版本号
单机及分布式的场景方案相同,都是对比数据库中version版本号
建立防重表(本质上是基于MySQL的分布式锁,可用锁机制替代)
以博客点赞为例,要想防止一个人重复点赞,可以设计一张去重表,将博客 id 与用户 id 绑定建立唯一索引,每当用户点赞时就往表中写入一条数据,这样重复点赞的数据就无法写入了。
token 机制(推荐)
token作为一次性门票,用完就销毁
- 客户端会先发送一个请求去获取 token,服务端会生成一个全局唯一的 ID 作为 token 保存在 redis 中,同时把这个 ID 返回给客户端
- 客户端第二次调用业务请求的时候必须携带这个 token,服务端会校验这个 token,如果校验成功,则执行业务,并删除 redis 中的 token
如果业务请求校验失败,说明 redis 中已经没有对应的 token,则表示重复操作,直接返回指定的结果给客户端
注意:
对 redis 中是否存在 token 以及删除的代码逻辑使用 Lua 脚本实现,保证原子性
CAS 保证接口幂等性(只针对update场景)
针对更新操作,将原有状态的查询加入sql中
例如:电商订单,订单支付状态,0 待支付,1 支付中 , 3 支付成功,4 支付失败。
update order set status = 1 where status =0 and orderId = “201251487987”
返回影响说为1 代表修改成功,可以支付,继续执行支付业务代码
返回影响数 0 代表修改失败,该订单已经不是待支付订单了
使用必要性评估
幂等性是为了简化客户端逻辑处理,能防止重复提交,但却增加了服务端的逻辑复杂性和成本:
- 把并行执行的功能改为串行执行,降低了执行效率;
- 增加了额外控制幂等的业务逻辑,业务功能变得更加复杂;
所以在使用时,需要考虑引入幂等性的必要性,一般在满足以下情况需要考虑幂等性问题解决。
- 接口主要负责数据写操作,且其选择的sql天然不满足数据库场景下的幂等性。
- 接口暴露在公网上,有被攻击的风险。
- 接口会承接大量的重试。