接口的幂等性如何设计

news2024/10/6 12:29:38

前言

所谓幂等: 多次调用方法或者接口不会改变业务状态,可以保证重复调用的结果和单次调用的结果一致

我们在开发中主要操作也就是CURD,其中读取操作和删除操作是天然幂等的,我们所关心的就是创建操作、更新操作。

创建操作一定是非幂等的因为要涉及到新数据的产生,而更新操作有可能幂等有可能非幂等,这个要看具体业务场景。

一、幂等性的使用场景

1、前端重复提交

就好比有个新增商品的功能,有个保存按钮,如果前端连续多次点击保存,后端就会收到多次请求接口,如果没做好幂等就会重复创建了多条记录,
就会出现脏数据。

这个也就是我们所说的如何防止前端重复提交的问题。

2、接口超时重试

当我们调取第三方接口的时候,有可能会因为网络等原因导致调用失败,所以我们会对接口调用添加失败重试的机制,Spring可以通过@Retryable注解实现重试机制。

既然重试就可能出现重复调用接口。这时再次调用时如果没有做好幂等,就可能出现脏数据。

3、消息重复消费

这个是无法避免的,因为我们说MQ在生产端和消费端都有重试机制,也就是同一消息很可能会被重复消费。

如果业务保证多次消费的结果是一样的那没问题,但是如果业务无法满足那就需要通过其它方式来保证消费端的幂等。

二、初级方式来保证尽量幂等

1、插入前先判断数据是否存在

这种是最基础的,也是我们在开发中必须要做的。我们会在插入或者更新前先判断下,当前这个数据数据库中是否已经存在,如果不存在则不允许重复插入,不存在则可插入。

代码示例如下:

public void save(Goods goods) {
        // 1、先通过商品唯一code,查询数据库属否存在   
        Goods goods = findGoods(goods.getCode);
        // 2、如果这条数据在db里已经存在了,此时就直接返回了   
        if (goods != null) {
            return;
        }
        // 3、如果要是这条数据在db里不存在,此时就会执行数据插入逻辑了   
        insertGoods(goods);
    }

2、前端做一些交互控制

好比有个新增商品的功能,有个保存按钮,用户点击保存按钮后,立马按钮置灰,或者页面跳转到商品列表页面,这样可以防止很大部分的前端重复提交。

三、高并发下如何保证幂等?

上面两种初级方法,在高并发下显然是无法保证接口幂等的,所以在高并发下,我们来如何保证接口的幂等呢,这里整理几种常见的解决办法。

1、基于悲观锁

定义: 当要对数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发。

这里以更新商品订单状态来举例:一般订单有订单创建订单确认订单支付订单完成取消订单等订单流程。

当我更新订单状态为订单完成的时候,我们首先通过判断该订单的状态是否是订单支付,如果是不是则直接返回,否则更新状态为已完成。

伪代码示例如下

begin; -- 1.开始事务
  -- 查询订单,判断状态
  select order_no,status from order where order_no='20200524-1' 
  if(status !=订单支付状态){
        -- 非订单支付状态,不能更新为已完成;
        return ;
    }
  -- 更新完成
  update order set status='订单完成' order_no='20200524-1' 
   commit; -- 2.提交事务

这是我们常见的一种写法,但这种写法在高并发环境下,可能会造成一个业务被执行两次的情况发生:

同时有两个请求过来,大家几乎同时查数据库订单状态,都是订单支付状态,然后就支持接下来一系列操作,这就导致一个业务被执行了两次,如果接下来一系列操作不是幂等的

那么就会出现脏数据。这里我们就可以通过悲观锁实现,也就是添加for update字段。

伪代码示例如下

begin;  --  1.开始事务
  --  查询订单,判断状态
  select order_no,status from order where order_no='20200524-1' for update 
  if(status !=订单支付状态){
        -- 非订单状态,不能更新为已完成;
        return ;
    }
 --  更新完成
  update order set status='完成' order_no='20200524-1' 
   commit; -- 2.提交事务

1)这里order_no需要添加索引,否则会锁表

2) 悲观锁在同一事务操作过程中,锁住了一行数据。悲观锁性能不佳所以一般不建议用悲观锁做这个事情。

2、基于乐观锁

定义:乐观锁就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制

所谓的乐观锁就是在表中新增一个version(版本号)字段。

通过版本号的方式,来控制update的操作的幂等性,用户查询出要修改的数据,系统将数据返回给页面,将数据版本号放入隐藏域,用户修改数据,点击提交,将版本号一同提交

给后台,后台使用版本号作为更新条件。

update set version = version +1 ,count=count+1 where id =xxx and version = ${version};

注意:乐观锁能够保证的是update的操作的幂等性,如果你的update本身就是幂等操作,或者install操作那就不能用乐观锁了。

3、基于状态码

很多业务表,都是有状态的,比如订单表,一般订单有1-订单创建2-订单确认3-订单支付、 4-订单完成5-取消订单等订单流程,当我们更新订单状态

update order_table set status=3 where order_no='20200524-1' and status=2;

第一个请求时,成功把 订单确认 状态修改成 订单支付,sql执行结果的影响行数是1。

第二个请求时,同样想把 订单确认 状态修改成 订单支付,但是sql执行结果的影响行数为0。如果是0,那么我们直接可以返回成功了。而不需要做接下来的业务操作,以此来保证保证

接口的幂等性。

4、基于唯一索引

一般来讲悲观锁、乐观锁、状态码作用于update操作来实现幂等,而唯一索引是针对install操作来保证幂等。

1) 创建订单时,前端先通过接口获取订单号,再请求后端时带入订单号,订单表中订单号添加唯一索引,如果存在插入相同订单号则直接报错。

2) 消费MQ消息时,messageId是唯一的,我们可以新添加一种消费记录表,将messageId作为主键,如果重复消费那么就会存在相同的messageId,插入直接报错。

5、基于分布式锁

分布式锁实现幂等性的逻辑就是,请求过来时,先去尝试获得分布式锁,如果获得成功,就执行业务逻辑,反之获取失败的话,就舍弃请求直接返回成功。

其实前面介绍过的悲观锁,本质是使用了数据库的分布式锁,都是将多个操作打包成一个原子操作,保证幂等。但由于数据库分布式锁的性能不太好,

我们可以改用:redis或zookeeper来实现分布式锁。

6、基于 Token

token方案的特点就是:需要两次请求才能完成一次业务的操作。

一般包括两个请求阶段:

1)客户端请求申请获取token,服务端生成token返回。

2)第二次请求带着这个token,服务端验证token,完成业务操作。

注意:,在验证token是否存在,不要用redis.get(token)之后,在用redis.del(token),这样不是原子操作在高并发情况下依然会存在幂等问题。

我们可以直接用redis.del(token)的方式:

redis> SET key1 "Hello"
OK
redis> SET key2 "World"
OK
redis> DEL key1 key2 key3
(integer) 2
redis>

我们看返回是否大于0,就知道是否有数据了,而且因为redis命令操作是单线程的,所以不会出现同时返回1,所以是能够保证幂等的。

这种方式最大的缺点需要两次请求,其实简单点我们可以进行一次请求,那就是前端生成唯一token,而不通过后端获取。

Setnx 命令

在指定的 key 不存在时,为 key 设置指定的值。设置成功,返回1。 设置失败,返回 0。

实例

redis> EXISTS job      --  job 不存在
(integer) 0

redis> SETNX job "programmer"  --  job 设置成功
(integer) 1

redis> SETNX job "code-farmer" --  尝试覆盖 job ,失败
(integer) 0

redis> GET job                 --  没有被覆盖
"programmer"

如果返回1则说明第一次请求,如果返回0则说明不是第一次请求,直接返回。

这里需要注意的是Setnx命令key值不会自动过期的,所以不清除会一直占用内存,我们可以借助Expire命令来设置有效时间。

redis> SETNX mykey "programmer"  --  job 设置成功
(integer) 1
--  如果设置成功,那么设置将该键的超时设置为 10 秒
redis> expire mykey 10

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

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

相关文章

JVM包含哪几部分?JVM内存模型?线程的生命周期? 对Spring AOP的理解?布隆过滤器

目录 1. JVM包含哪几部分2. JVM内存模型3. 双亲委派模型4. Java内存模型5. Serializable接口为什么需要定义serialVersionUID常量6. 线程的生命周期7. 什么是MVC8. volatile关键字的理解9. 对Spring AOP的理解10. 布隆过滤器 1. JVM包含哪几部分 JVM由三部分组成:类…

WalkRE2019--构面流程

1、手动圈图 选中面层中的一般房屋面,双击,鼠标变成十字光标后,开始在图上圈出一个闭合形状。如下: 如上,完成一个一般房屋面的绘制。 2、加工菜单栏中的自动构面功能。 这里推荐点击构面方法,原因是操作起…

MachineLearningWu_13_AGI

AGI的全称是artificial general intelligence,通用人工智能,而我们现在做的关于医学影像的分析,可以说完全是ANI。 而我们使用MLP对于大脑中神经网络的模拟更是完全不同于人类大脑的行为。

mysql(三)InnoDB之自适应hash索引

目录 前言自适应哈希索引 (Adaptive Hash Index, AHI)既然是哈希,key 是什么,value 是什么?为啥叫 “自适应 (adaptive)****” 哈希索引?系统会不会判断失误,是不是一定能加速? 创建自定义的hash索引思路示…

华为申请注册盘古大模型商标;京东推出言犀大模型,率先布局产业应用

7月14日科技新闻早知道,一分钟速览。 1.华为申请注册盘古大模型商标: 据天眼查 App 显示,7 月 7 日,华为技术有限公司申请注册“华为云盘古”、“Huawei Cloud Pangu Models”文字及图形商标,国际分类为网站服务、社…

基础设施SIG月度动态:龙蜥官网新增CSDN第三方账号登录,内核CI新增测试任务停止功能

基础设施 SIG(OpenAnolis Infra SIG)目标:负责 OpenAnolis 社区基础设施工程平台的建设,包括官网、Bugzilla、Maillist、ABS、ANAS、CI 门禁以及社区 DevOps 相关的研发工程系统。 01 SIG 整体进展 1. 龙蜥社区官网与 CSDN dev…

管理大规模文件的挑战与解决方案

管理大规模文件是当今企业和组织面临的一项重要挑战。随着信息技术的迅速发展和数字化转型的推进,组织内外产生的文件数量呈指数级增长,如何高效地管理这些文件成为了亟待使用文件管理系统解决的问题。 挑战一:数据量巨大 随着企业和组织的…

基于STM32CUBEMX驱动TOF模块VL6180与VL6180X(5)----驱动多个VL6180X

基于STM32CUBEMX驱动TOF模块VL6180与VL6180X----5.驱动多个VL6180X 概述样品申请修改设备地址配置vl6180x主程序测试结果 概述 在本章中,我们将探讨如何同时驱动多个VL6180传感器进行距离测量。我们将介绍如何有效地管理多个传感器之间的通信和控制,以确…

❤️创意网页:创造精彩的登录体验-3D翻转登录页面

✨博主:命运之光 🌸专栏:Python星辰秘典 🐳专栏:web开发(简单好用又好看) ❤️专栏:Java经典程序设计 ☀️博主的其他文章:点击进入博主的主页 前言:欢迎踏入…

(9)基础强化:元字符,正则表达式,匹配,提取组,Regex,Match与Matches

一、作业 1、问:下面解压程序出错,什么原因? string src "E:\1.txt";string des "E:\2.txt";using (FileStream read File.OpenRead(src)){using (GZipStream gzip new GZipStream(read, CompressionMode.Decompress…

FPGA——按键控制led灯

文章目录 一、实验环境二、实验任务三、系统设计四、实验过程4.1 编写verilog代码4.2 引脚配置 五、仿真5.1 仿真代码5.2 仿真结果 六、实验结果七、总结 一、实验环境 quartus 18.1 modelsim vscode Cyclone IV开发板 二、实验任务 使用开发板上的四个按键控制四个LED灯。按…

“反AI斗士”马斯克进军AI,你怎么看?

“反AI斗士”马斯克进军AI,你怎么看? 当地时间7月12日,马斯克在Twitter上宣布:“xAI正式成立,去了解现实。”马斯克表示,推出xAI的原因是想要“了解宇宙的真实本质”。公司由马斯克本人亲自带队&#xff0c…

软件外包开发的原型图工具

在软件开发中需要用到原型图工具来将需求转化为图形界面,这样可以更好更准确的表达需求的实现方式。与传统的需求文档相比,原型图的表达更直接,不但可以画出UI,也支持UI之间的跳转连接,与最终的实现效果基本是一样的。…

【雕爷学编程】Arduino动手做(149)---MAX9814咪头传感器模块4

37款传感器与执行器的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块,依照实践出真知(一定要动手做)的理念,以学习和交流为目的&am…

如何在Windows 8和10中检查最后一次的启动模式

Windows 8、Windows 8.1 和 Windows 10 中的用户可以在 PC 上执行混合关机(快速启动)、完全关机或休眠。 快速启动(又名:hiberboot、混合启动或混合关机)在 Windows 中默认打开,是一种帮助你的电脑在关机后更快启动的设置。甚至比休眠还要快。 休眠是一种主要为笔记本电…

Linux下Nginx升级

nginx版本升级不会覆盖配置文件,但以防万一升级前请先备份配置文件或者配置文件夹 默认配置文件地址:/usr/local/nginx/conf/nginx.conf 1.下载 wget -c http://nginx.org/download/nginx-1.24.0.tar.gz 2.解压 tar -xvf nginx-1.24.0.tar.gz 3.nginx…

【图像处理】使用 Python 进行图像增强

一、说明 图像增强技术的深度和复杂性往往在一系列捕获和共享中被忽视。从傅里叶变换到白平衡和直方图处理,各种方法都可以将普通照片转换为引人注目的图像。这篇博文旨在解开这些技术。 我在节日期间拍了一张照片,在夜间庆祝活动中。遗憾的是&#xff0…

OpenCV中掩膜(Mask)、setTo()、copyTo()、clone()、inRange()的定义与使用

文章目录 1、掩膜(Mask)是什么(1)从物理的角度来看:(2)图像处理中的掩膜Mask(3)掩膜的用法:(4)掩膜Mask 的运算: 2、setTo()函数:将图…

【动手学习深度学习--逐行代码解析合集】17使用块的网络(VGG)

【动手学习深度学习】逐行代码解析合集 17使用块的网络(VGG) 视频链接:动手学习深度学习–使用块的网络(VGG) 课程主页:https://courses.d2l.ai/zh-v2/ 教材:https://zh-v2.d2l.ai/ 1、VGG网络…

【UniApp开发小程序】顶部导航栏和底部导航栏设置+iconfont图标引入

文章目录 顶部导航栏和底部导航栏设置创建几个需要底部导航栏切换的页面使用阿里巴巴矢量图标库完成底部导航栏tabBar设置页面顶部导航栏标题 样式优化 顶部导航栏和底部导航栏设置 在正式开发小程序的功能之前,首先需要确定小程序的主要框架。 创建几个需要底部导…