高并发下保证接口幂等性的常用策略

news2024/11/18 21:47:57

接口幂等性问题,对于开发人员来说是一个常见的公共问题。这里分享一些我在项目中用到过的一些方法,给有需要的同学们一个参考。

你是否遇到过以下的场景:

  1. 在填写form页面表单时,如果前端没做loading或者防抖操作,保存按钮时不小心快速点了两次,表中竟然产生了两条重复的数据,只是id不一样。
  2. 在项目中为了解决接口超时问题,通常会使用重试机制。即第一次请求接口超时了,没有获取到返回结果,为了避免直接返回错误的结果,于是会对该请求重试几次,这样也会产生重复的数据。
  3. 消息队列在读取消息时,有时会读取到重复消息(各种原因导致,这里不展开),如果处理不好,也会产生重复的数据。

以上举例的这些都是幂等性问题。

接口幂等性是指用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用

这类问题多发于接口的:

  • insert操作,这种情况下多次请求,可能会产生重复数据;
  • update操作,如果只是单纯的更新数据,比如:update customer set status = 0 where id = 1,是没有问题的。但如果如果还有计算,比如:update customer set status = status + 1 where id = 1,这种情况下多次请求,可能会导致数据错误。

1.新增前先查询是否有(insert前先select)

通常情况下,在保存数据的接口中,我们为了防止产生重复数据,一般会在insert前,先根据namemobile字段select一下数据。如果该数据已存在,则执行update操作,如果不存在,才执行 insert操作。
在这里插入图片描述
此可能是我们平时在防止产生重复数据时,使用最多的方案。但是该方案不适用于并发场景,在并发场景中,要配合其他方案一起使用,否则同样会产生重复数据。

2. 数据库加锁

2-1、使用悲观锁

在支付场景中,比如A的账号余额有1000元,买东西支付100元,正常情况下用户A的余额还有900元。一般情况下,sql是这样的:

update customer set money = money-100 where id=1;

如果同一时间内出现多次相同的请求,可能会导致用户A的余额不对。这种情况,对于研发来说就是严重的生产事故了。

这是主角上场了——悲观锁,将用户A的那行数据锁住,在同一时刻只允许一个请求获得锁,进行更新数据,其他的请求则不好意思,请排队慢慢等待。

通常情况下通过如下sql锁住单行数据:

select * from user id = 1 for update;

具体流程如下:
在这里插入图片描述
具体步骤:

  1. 多个请求同时根据id查询用户信息。
  2. 判断余额是否充足,如果余额不足,则直接返回余额不足。
  3. 如果余额充足,则通过for update再次查询用户信息,并且尝试获取锁。
  4. 只有第一个请求能获取到行锁,其余没有获取锁的请求,则等待下一次获取锁的机会。
  5. 第一个请求获取到锁之后,判断余额是否不足,如果余额足够,则进行update操作。

需要特别注意的是:如果使用的是mysql数据库,存储引擎必须用innodb,因为它才支持事务。此外,这里id字段一定要是主键或者唯一索引,不然会锁住整张表。

悲观锁需要在同一个事务操作过程中锁住一行数据,如果事务耗时比较长,会造成大量的请求等待,影响接口性能。

此外,每次请求接口很难保证都有相同的返回值,所以不适合幂等性设计场景,但是在防重场景中是可以的使用的

在这里顺便说一下,防重设计和幂等设计是有区别的。防重设计主要为了避免产生重复数据,对接口返回没有太多要求。而幂等设计除了避免产生重复数据之外,还要求每次请求都返回一样的结果。

2-2、使用乐观锁

上面说到悲观锁有性能问题,为了提升接口性能,我们可以使用乐观锁。需要在表中增加一个version字段。

在更新数据之前先查询一下数据:

select id,money,version from customer where id = 1;

如果数据存在,假设查到的version等于1,再使用idversion字段作为查询条件更新数据:

update customer 
set money = money - 100,
    version = version + 1
    where id = 1 
    and version = 1;

更新数据的同时version+1,然后判断本次update操作的影响行数,如果大于0,则说明本次更新成功,如果等于0,则说明本次更新没有让数据变更。

由于第一次请求version等于1是可以成功的,操作成功后version变成2了。这时如果并发的请求过来,再执行相同的sql:

update customer 
set money = money - 100,
    version = version + 1
    where id = 1 
    and version = 1;

update操作不会真正更新数据,最终sql的执行结果影响行数是0,因为version已经变成2了,where中的version=1肯定无法满足条件。但为了保证接口幂等性,接口可以直接返回成功,因为version值已经修改了,那么前面必定已经成功过一次,后面都是重复的请求。
在这里插入图片描述
具体步骤:

  1. 先根据id查询用户信息,包含version字段
  2. 根据id和version字段值作为where条件的参数,更新用户信息,同时version+1
  3. 判断操作影响行数,如果影响1行,则说明是一次请求,可以做其他数据操作。
  4. 如果影响0行,说明是重复请求,则直接返回成功。

3.唯一索引

大多数情况下为了防止重复数据的产生,我们都会在表中加唯一索引,这是一个非常简单有效的方案。

alter table customer add UNIQUE KEY `uk_mobile` (`mobile`);

加了唯一索引之后,第一次请求数据可以插入成功。但后面的相同请求,插入数据时会报Duplicate entry '002' for key 'customer .uk_mobile异常,表示唯一索引有冲突。

虽说抛异常对数据来说没有影响,不会造成错误数据。但是为了保证接口幂等性,我们需要对该异常进行捕获,然后返回成功。

这里以java程序需要捕获:DuplicateKeyException异常,如果使用了spring框架还需要捕获:MySQLIntegrityConstraintViolationException异常。

具体流程图如下:
在这里插入图片描述
具体步骤:

  1. 用户发起请求,服务端收集数据;
  2. 插入mysql数据库;
  3. 判断是否执行成功,如果成功,则操作其他数据(如果有其他业务逻辑);
  4. 如果执行失败,捕获唯一索引冲突异常,直接返回成功。

4.防重表

有时候表中并非所有的场景都不允许产生重复的数据,只有某些特定场景才不允许。这时候,直接在表中加唯一索引就有点不太合适了。针对这种情况,我们可以通过建防重表来解决问题。

该表可以只包含两个字段:id唯一索引,唯一索引可以是多个字段比如:name、code等组合起来的唯一标识,例如:xiaoming_000001。

具体流程图如下:
在这里插入图片描述
具体步骤:

  1. 用户发起请求,服务端收集数据。
  2. 将该数据插入mysql防重表
  3. 判断是否执行成功,如果成功,则做mysql其他的数据操作(如果有其他业务逻辑)。
  4. 如果执行失败,捕获唯一索引冲突异常,直接返回成功。

需要特别注意的是:防重表和业务表必须在同一个数据库中,并且操作要在同一个事务中。

5.分布式锁

其实前面介绍过的加唯一索引或者加防重表,本质是使用了数据库的分布式锁,这也是分布式锁的一种。但由于数据库分布式锁的性能不太好,我们一般用redis分布式锁。

目前主要有三种方式实现redis的分布式锁:

  1. setNx命令
  2. set命令
  3. Redission框架

每种方案各有利弊,具体实现细节可以找其他相关的资料了解。

具体流程图如下:
在这里插入图片描述
具体步骤:

  1. 用户发起请求,服务端会收集数据,并且生成订单号id作为唯一业务字段。
  2. 使用redis的set命令,将该订单id设置到redis中,同时设置超时时间。
  3. 判断是否设置成功,如果设置成功,说明是第一次请求,则进行数据操作。
  4. 如果设置失败,说明是重复请求,则直接返回成功。

需要特别注意的是:分布式锁一定要设置一个合理的过期时间,如果设置过短,无法有效的防止重复请求。如果设置过长,可能会浪费redis的存储空间,需要根据实际业务情况而定。

6.使用Token

除了上述方案之外,还有最后一种使用token的方案。该方案跟之前的所有方案都有点不一样,需要两次请求才能完成一次业务操作。

  1. 第一次请求获取token
  2. 第二次请求带着这个token,完成业务操作。

具体流程图如下:
1、获取token
在这里插入图片描述
2、业务操作
在这里插入图片描述
具体步骤:

  1. 用户访问页面时,浏览器自动发起获取token请求。
  2. 服务端生成token,保存到redis中,然后返回给浏览器。
  3. 用户通过浏览器发起请求时,携带该token。
  4. 在redis中查询该token是否存在,如果不存在,说明是第一次请求,做则后续的数据操作。
  5. 如果存在,说明是重复请求,则直接返回成功。
  6. 在redis中token会在过期时间之后,被自动删除。

以上方案是针对幂等设计的。

如果是防重设计,流程图是这样:
在这里插入图片描述
注意:token必须是全局唯一的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/756401.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

松松商城上线“谷歌英文外链“资源,松松软文推出英文站点资源

我是卢松松,点点上面的头像,欢迎关注我哦! 近期,为了丰富资源,松松商城和松松软文迎来了一系列新的更新。松松商城推出了“谷歌外贸站英文"外链资源,而松松软文则上线了英文站点资源,为用…

MURF20100CT-ASEMI快恢复对管MURF20100CT

编辑:ll MURF20100CT-ASEMI快恢复对管MURF20100CT 型号:MURF20100CT 品牌:ASEMI 封装:TO-220F 恢复时间:50ns 正向电流:20A 反向耐压:1000V 芯片个数:2 引脚数量&#xff1…

场景图生成——RelTR训练自己的数据集

RelTR训练自己的数据集 省流量省时间版本框的标注关系的标注总的 前言Open Images V6的标注格式RelTR中使用的Open Images V6的数据标注格式具体步骤框的标注生成格式关系三元组的生成格式 结束语参考链接 省流量省时间版本 框的标注 共需要创建4个json标注文件 train.json, …

功能升级,数据同步更便捷!场景化数据同步助您提效60%!

在企业数仓建设初期,为了保障数字化转型的落地效果,需要提供充足的数据资源,除了基础的数据抽取、转换和加载等过程,数据的同步也是重要环节之一。数据同步常用于数仓ODS、ADS层的建设,通过不同数据源的同步&#xff0…

回归预测 | MATLAB实现基于BiGRU-AdaBoost双向门控循环单元结合AdaBoost多输入单输出回归预测

回归预测 | MATLAB实现基于BiGRU-AdaBoost双向门控循环单元结合AdaBoost多输入单输出回归预测 目录 回归预测 | MATLAB实现基于BiGRU-AdaBoost双向门控循环单元结合AdaBoost多输入单输出回归预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 1.MATLAB实现基于B…

Michael.W基于Foundry精读Openzeppelin第8期——Context.sol

Michael.W基于Foundry精读Openzeppelin第8期——Context.sol 0. 版本0.1 Context.sol 1. 目标合约2. 代码精读2.1 _msgSender()2.2 _msgSender() 0. 版本 [openzeppelin]:v4.8.3,[forge-std]:v1.5.6 0.1 Context.sol Github: https://gith…

MIT 6.S081 Lab 11 -- NetWork - 下

MIT 6.S081 Lab 11 -- NetWork -- 下 引言代码解析网络子系统初始化相关数据结构lab 分析e1000_transmit函数实现e1000_recv函数实现socket write全流程分析socket read全流程分析socket关闭ARP数据报的发送与接收 引言 本文为 MIT 6.S081 2020 操作系统 实验十一解析。 MIT …

HCIA-datacom认证最新资料共享

hcia认证有哪些值得推荐的学习方向? 入门首选学习方向:HCIA:Datacom!但是HCIA云计算(Cloud Computing)方向、HCIA 无线(WLAN)方向、HCIA Data Center Facility方向、HCIA 安全(Secur…

TTX1995可调谐激光器控制软件系统

画了两周时间,利用下班时间,设计了一个ITLA可调谐激光器控制软件,从硬件到软件。 这是使用的界面,实现了下面的功能: 1、模块信息的读取,包括生产日期,生产厂家,型号,序…

产品流程图

流程图设计 1.什么是流程图2.流程图元素定义3.几种常见的产品流程图 3.1业务流程图 | 泳道图(给产品经理看)3.2任务流程图(给程序员看)3.3页面流程图(给UI设计人员看) 4.如何绘制流程图 4.1调查研究4.2梳理…

笔试题之地区经济数据分析

数据分析通常应用于商业领域,但对于政府、非盈利组织等机构而言,在考量城市发展、监控环境质量等方面,也会涉及到数据分析。这时,就需要我们根据实际场景,结合数据分析的理论知识,发现其中的规律&#xff0…

Tensorflow入门(2)——深度学习框架Tesnsflow 线程+队列+IO操作 文件读取案例

目录 一、二、Tesnsflow入门 & 环境配置 & 认识Tensorflow三、线程与队列与IO操作1.队列实例:完成一个出队列、1、入队列操作(同步操作) 2.队列管理器 创建线程3.线程协调器 管理线程案例:通过队列管理器来实现变量加1,入队&#xff…

强化学习:实现了基于蒙特卡洛树和策略价值网络的深度强化学习五子棋(含码源)

【强化学习原理项目专栏】必看系列:单智能体、多智能体算法原理项目实战、相关技巧(调参、画图等、趣味项目实现、学术应用项目实现 专栏详细介绍:【强化学习原理项目专栏】必看系列:单智能体、多智能体算法原理项目实战、相关技巧…

好用的门店信息管理系统推荐?门店信息系统系统应该注重什么?

传统门店的信息管理模式总是会存在人工成本高,效率低,流程麻烦、数据复盘繁琐等问题。围绕门店信息管理过程中面临的各类痛点,蚓链数字化门店信息管理系统可以帮助门店更好的管理门店经营,货品盘点,库存管理&#xff0…

「XKOI」Round 3 赛后题解

比赛链接:「XKOI」Round 3 本题解同步发表于 洛谷:传送门 CSDN:传送门 文章目录 比赛链接:[「XKOI」Round 3](https://www.luogu.com.cn/contest/117863)A [T343985 CRH的工作](https://www.luogu.com.cn/problem/T343985)1.1 …

CRC算法并行运算Verilog实现

因为CRC循环冗余校验码的算法和硬件电路结构比较简单,所以CRC是一种在工程中常用的数据校验方法。尽管CRC简单,但在工程应用中还是有些问题会对工程师产生困惑。这篇文章将介绍一下CRC,希望对大家有所帮助。 一、CRC算法介绍 CRC校验原理看起…

PPO(Proximal Policy Optimization Algorithms)论文解读及实现

论文标题:Proximal Policy Optimization Algorithms 核心思路:使用off policy 代替on policy,用一个策略网络来产生数据,用一个策略网络来更新参数,分别为policy_old和policy 0 摘要 Whereas standard policy gradient methods …

Python自动化办公:pptx篇

文章目录 简介能做什么PPT要素介绍官方demo高阶引申参考文献 202201笔记迁移 简介 python-pptx包是用来自动化处理ppt的。 使用的第一步是安装 pip install python-pptx相比python-docx,python-pptx的使用更为麻烦一些,原因有很多,比如说&…

波奇学Linux:make和Makefile

make和Makefile自动化构建并能决定源文件调用顺序,同时不必再写gcc命令 第一行依赖关系,第二行是tab键开头,是依赖方法 依赖关系:目标文件:依赖文件。 依赖方法:目标文件和依赖文件间的关系。 如果只有一条…

es下载历史的tar文件

第一步进入官网找到历史版本 第二步复制历史版本名称组合成下面的链接 直接get访问下载。如下链接所示只需要修改7.3.0这个版本号 https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.3.0-linux-x86_64.tar.gz