0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

在高并发下怎么保证接口的幂等性?

数据分析与开发 来源:苏三说技术 作者:苏三说技术 2021-05-14 10:23 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

前言

接口幂等性问题,对于开发人员来说,是一个跟语言无关的公共问题。本文分享了一些解决这类问题非常实用的办法,绝大部分内容我在项目中实践过的,给有需要的小伙伴一个参考。

不知道你有没有遇到过这些场景:

有时我们在填写某些form表单时,保存按钮不小心快速点了两次,表中竟然产生了两条重复的数据,只是id不一样。

我们在项目中为了解决接口超时问题,通常会引入了重试机制。第一次请求接口超时了,请求方没能及时获取返回结果(此时有可能已经成功了),为了避免返回错误的结果(这种情况不可能直接返回失败吧?),于是会对该请求重试几次,这样也会产生重复的数据。

mq消费者在读取消息时,有时候会读取到重复消息(至于什么原因这里先不说,有兴趣的小伙伴,可以找我私聊),如果处理不好,也会产生重复的数据。

没错,这些都是幂等性问题。

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

这类问题多发于接口的:

insert操作,这种情况下多次请求,可能会产生重复数据。

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

那么我们要如何保证接口幂等性?本文将会告诉你答案。

1. insert前先select

通常情况下,在保存数据的接口中,我们为了防止产生重复数据,一般会在insert前,先根据name或code字段select一下数据。如果该数据已存在,则执行update操作,如果不存在,才执行 insert操作。

d321e26e-b454-11eb-bf61-12bb97331649.png

该方案可能是我们平时在防止产生重复数据时,使用最多的方案。但是该方案不适用于并发场景,在并发场景中,要配合其他方案一起使用,否则同样会产生重复数据。我在这里提一下,是为了避免大家踩坑。

2. 加悲观锁

在支付场景中,用户A的账号余额有150元,想转出100元,正常情况下用户A的余额只剩50元。一般情况下,sql是这样的:

update user amount = amount-100 where id=123;

如果出现多次相同的请求,可能会导致用户A的余额变成负数。这种情况,用户A来可能要哭了。于此同时,系统开发人员可能也要哭了,因为这是很严重的系统bug。

为了解决这个问题,可以加悲观锁,将用户A的那行数据锁住,在同一时刻只允许一个请求获得锁,更新数据,其他的请求则等待。

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

select * from user id=123 for update;

具体流程如下:

d33da396-b454-11eb-bf61-12bb97331649.png

具体步骤:

多个请求同时根据id查询用户信息。

判断余额是否不足100,如果余额不足,则直接返回余额不足。

如果余额充足,则通过for update再次查询用户信息,并且尝试获取锁。

只有第一个请求能获取到行锁,其余没有获取锁的请求,则等待下一次获取锁的机会。

第一个请求获取到锁之后,判断余额是否不足100,如果余额足够,则进行update操作。

如果余额不足,说明是重复请求,则直接返回成功。

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

悲观锁需要在同一个事务操作过程中锁住一行数据,如果事务耗时比较长,会造成大量的请求等待,影响接口性能。 此外,每次请求接口很难保证都有相同的返回值,所以不适合幂等性设计场景,但是在防重场景中是可以的使用的。 在这里顺便说一下,防重设计和幂等设计,其实是有区别的。防重设计主要为了避免产生重复数据,对接口返回没有太多要求。而幂等设计除了避免产生重复数据之外,还要求每次请求都返回一样的结果。

3. 加乐观锁

既然悲观锁有性能问题,为了提升接口性能,我们可以使用乐观锁。需要在表中增加一个timestamp或者version字段,这里以version字段为例。

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

select id,amount,version from user id=123;

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

update user set amount=amount+100,version=version+1where id=123 and version=1;

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

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

update user setamount=amount+100,version=version+1where id=123 and version=1;

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

具体流程如下:

d3b2895e-b454-11eb-bf61-12bb97331649.png

具体步骤:

先根据id查询用户信息,包含version字段

根据id和version字段值作为where条件的参数,更新用户信息,同时version+1

判断操作影响行数,如果影响1行,则说明是一次请求,可以做其他数据操作。

如果影响0行,说明是重复请求,则直接返回成功。

4. 加唯一索引

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

alter table `order` add UNIQUE KEY `un_code` (`code`);

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

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

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

具体流程图如下:

d3c0ca0a-b454-11eb-bf61-12bb97331649.png

具体步骤:

用户通过浏览器发起请求,服务端收集数据。

将该数据插入mysql

判断是否执行成功,如果成功,则操作其他数据(可能还有其他的业务逻辑)。

如果执行失败,捕获唯一索引冲突异常,直接返回成功。

5. 建防重表

有时候表中并非所有的场景都不允许产生重复的数据,只有某些特定场景才不允许。这时候,直接在表中加唯一索引,显然是不太合适的。

针对这种情况,我们可以通过建防重表来解决问题。

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

具体流程图如下:

d3cc0b22-b454-11eb-bf61-12bb97331649.png

具体步骤:

用户通过浏览器发起请求,服务端收集数据。

将该数据插入mysql防重表

判断是否执行成功,如果成功,则做mysql其他的数据操作(可能还有其他的业务逻辑)。

如果执行失败,捕获唯一索引冲突异常,直接返回成功。

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

6. 根据状态机

很多时候业务表是有状态的,比如订单表中有:1-下单、2-已支付、3-完成、4-撤销等状态。如果这些状态的值是有规律的,按照业务节点正好是从小到大,我们就能通过它来保证接口的幂等性。

假如id=123的订单状态是已支付,现在要变成完成状态。

update `order` set status=3 where id=123 and status=2;

第一次请求时,该订单的状态是已支付,值是2,所以该update语句可以正常更新数据,sql执行结果的影响行数是1,订单状态变成了3。

后面有相同的请求过来,再执行相同的sql时,由于订单状态变成了3,再用status=2作为条件,无法查询出需要更新的数据,所以最终sql执行结果的影响行数是0,即不会真正的更新数据。但为了保证接口幂等性,影响行数是0时,接口也可以直接返回成功。

具体流程图如下:

d3d63dae-b454-11eb-bf61-12bb97331649.png

具体步骤:

用户通过浏览器发起请求,服务端收集数据。

根据id和当前状态作为条件,更新成下一个状态

判断操作影响行数,如果影响了1行,说明当前操作成功,可以进行其他数据操作。

如果影响了0行,说明是重复请求,直接返回成功。

主要特别注意的是,该方案仅限于要更新的表有状态字段,并且刚好要更新状态字段的这种特殊情况,并非所有场景都适用。

7. 加分布式锁

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

鉴于现在很多公司分布式配置中心改用apollo或nacos,已经很少用zookeeper了,我们以redis为例介绍分布式锁。

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

setNx命令

set命令

Redission框架

每种方案各有利弊,具体实现细节我就不说了,有兴趣的朋友可以加我微信找我私聊。

具体流程图如下:

d3e1dcf4-b454-11eb-bf61-12bb97331649.png

具体步骤:

用户通过浏览器发起请求,服务端会收集数据,并且生成订单号code作为唯一业务字段。

使用redis的set命令,将该订单code设置到redis中,同时设置超时时间。

判断是否设置成功,如果设置成功,说明是第一次请求,则进行数据操作。

如果设置失败,说明是重复请求,则直接返回成功。

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

8. 获取token

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

第一次请求获取token

第二次请求带着这个token,完成业务操作。

具体流程图如下:

第一步,先获取token。

d4066358-b454-11eb-bf61-12bb97331649.png

第二步,做具体业务操作。

d413fbee-b454-11eb-bf61-12bb97331649.png

具体步骤:

用户访问页面时,浏览器自动发起获取token请求。

服务端生成token,保存到redis中,然后返回给浏览器。

用户通过浏览器发起请求时,携带该token。

在redis中查询该token是否存在,如果不存在,说明是第一次请求,做则后续的数据操作。

如果存在,说明是重复请求,则直接返回成功。

在redis中token会在过期时间之后,被自动删除。

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

如果是防重设计,流程图要改改:

d41fb092-b454-11eb-bf61-12bb97331649.png

需要特别注意的是:token必须是全局唯一的。

编辑:jq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 数据
    +关注

    关注

    8

    文章

    7314

    浏览量

    93969
  • MySQL
    +关注

    关注

    1

    文章

    897

    浏览量

    29226

原文标题:高并发下如何保证接口的幂等性?

文章出处:【微信号:DBDevs,微信公众号:数据分析与开发】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    星火电子:罗氏线圈接口及应用场景总结

    罗氏线圈(Rogowski Coil)因其灵活、精确、带宽的特性,电力监测、工业自动化、智能楼宇和可再生能源领域广泛应用。不同的接口类型可以满足不同设备和安装环境的需求。选择合适接口
    的头像 发表于 11-21 10:23 97次阅读
    星火电子:罗氏线圈<b class='flag-5'>接口</b>及应用场景总结

    兼容,延迟低,慧视定制CVBS接口AI图像处理板

    模拟信号CVBS因其兼容、低延迟的特点,许多行业中应用广泛。特别是一些无人设备的控制领域,模拟信号传输有着很大的优势。为了应对这些行业的需求,成都慧视利用RV1126、RK35
    的头像 发表于 11-10 17:58 1023次阅读
    兼容<b class='flag-5'>性</b><b class='flag-5'>高</b>,延迟低,慧视定制CVBS<b class='flag-5'>接口</b>AI图像处理板

    破解工业电商痛点:vipmro 商品详情接口技术方案与性能调优指南

    vipmro工业MRO商品详情接口针对参数嵌套、大文件加载、批量查询痛点,构建多维参数解析、分级缓存、标准化错误三大引擎,实现并发下毫秒级响应,助力电商
    的头像 发表于 10-29 17:32 83次阅读

    工业物联网数据中台的并发性有什么作用

    数据请求,支撑工业场景中实时、规模、稳定性的多重需求。 1.保障大规模设备的实时接入与数据采集 工业场景中,一条生产线或一个工厂可能存在数千甚至数万台传感器、PLC、机床设备。
    的头像 发表于 10-28 11:28 182次阅读
    工业物联网数据中台的<b class='flag-5'>高</b><b class='flag-5'>并发</b>性有什么作用

    如何理解工业数据中台的并发能力

    工业数据中台的并发能力是指其同一时间段内高效处理大量设备数据读写、分析请求的能力,这是保障工业数据实时采集、传输、处理与决策响应稳定性和高效的关键。以下从核心价值、技术实现、应用
    的头像 发表于 10-15 11:49 240次阅读

    商品详情页内容更新接口设计与实现

    ​  1. 接口核心价值 商品详情页是电商平台的核心页面,其内容更新接口需满足: 实时:价格/库存变动需秒级同步 原子:避免更新过程中出现中间状态
    的头像 发表于 10-11 15:36 219次阅读
    商品详情页内容更新<b class='flag-5'>接口</b>设计与实现

    别再卡分页!淘宝全量商品接口实战开发指南:从并发优化到数据完整闭环

    淘宝店铺全量商品接口实战指南:详解权限申请、分页优化、并发拉取与增量更新,结合代码实现高效稳定的数据获取,解决超时、限流、数据丢失核心难题,助力电商数据分析避坑提效。
    的头像 发表于 09-30 10:47 327次阅读

    如何秒级实现接口间“”补偿:一款轻量级仿数据校正处理辅助工具

    导语 本文分析了在网络超时场景下,RPC服务调用数据一致的问题,对于接口接口
    的头像 发表于 09-15 16:55 605次阅读
    如何秒级实现<b class='flag-5'>接口</b>间“<b class='flag-5'>幂</b><b class='flag-5'>等</b>”补偿:一款轻量级仿<b class='flag-5'>幂</b><b class='flag-5'>等</b>数据校正处理辅助工具

    请问如何保证UID(唯一ID)的唯一

    如何保证UID(唯一ID)的唯一
    发表于 08-21 08:18

    Nginx并发优化方案

    作为一名在生产环境中摸爬滚打多年的运维工程师,我见过太多因为Nginx配置不当导致的性能瓶颈。今天分享一套完整的Nginx并发优化方案,帮助你的系统从10万QPS突破到百万级别。
    的头像 发表于 08-13 15:51 667次阅读

    SMA接口汽车电子复杂环境下的适应剖析

    德索SMA接口汽车电子复杂环境下既具备一定的适应优势,也面临诸多挑战。通过持续的技术创新与工艺改进,德索有望进一步提升SMA接口汽车电
    的头像 发表于 06-04 09:04 662次阅读
    SMA<b class='flag-5'>接口</b><b class='flag-5'>在</b>汽车电子复杂环境下的适应<b class='flag-5'>性</b>剖析

    Ingress网关并发请求的解决方案

    当 Ingress 网关面临高并发请求(如 QPS 超过 10万+)时,可能导致服务崩溃、响应延迟激增或资源耗尽。
    的头像 发表于 05-14 11:52 656次阅读

    RAKsmart服务器如何重塑AI并发算力格局

    AI大模型参数量突破万亿级、实时推理需求激增的当下,传统服务器架构的并发处理能力已逼近物理极限。RAKsmart通过“硬件重构+软件定义”的双引擎创新,推出新一代AI服务器解决方案。下面,AI部落小编为您解析RAKsmart服务器如何重塑AI
    的头像 发表于 04-03 10:37 668次阅读

    TurMass™ 如何帮助解决 UWB 定位系统大规模终端标签并发通信冲突问题?

    大容量定位终端数据并发场景中,现有通信技术因信号冲突、系统容量受限问题,难以满足需求。TurMass™ 通信技术通过多信道设计、时隙划分、定位与通信一体化
    的头像 发表于 03-17 14:38 784次阅读
    TurMass™ 如何帮助解决 UWB 定位系统大规模终端标签<b class='flag-5'>高</b><b class='flag-5'>并发</b>通信冲突问题?

    鸿蒙文件传输三方库上线开源鸿蒙社区 十行代码实现大文件高速传输

    精力自行封装解决方案,导致开发成本增加、开发周期延长。 为了解决这一问题,“SFFT”结合了TaskPool、RDB关键技术,RCP的基础上实现了多线程并发下载、异步并发上传、断点
    发表于 03-06 10:29