当前位置: 首页 > news >正文

Java开发过程中的幂等性问题

幂等性问题:

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

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

3. 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 操作。
该方案可能是我们平时在防止产生重复数据时,使用最多的方案。但是该方案不适用于并发场景,在并发场景中,要配合其他方案一起使用,否则同样会产生重复数据。

2. 加悲观锁
1)支付场景在加减库存场景中,用户A的账号余额有150元,想转出100元,正常情况下用户A 的余额只剩50元。一般情况下,sql是这样的:
update user amount = amount-100 where id=123;
如果出现多次相同的请求,可能会导致用户A的余额变成负数。
update user amount = amount-100 where id=123;
为了解决这个问题,可以加悲观锁,将用户A的那行数据锁住,在同一时刻只允许一个请求获得锁,更新数据,其他的请求则等待。
通常情况下通过如下sql锁住单行数据:
select * from user id=123 for update;
具体流程如下:
具体步骤:
1. 多个请求同时根据id查询用户信息。
2. 判断余额是否不足100,如果余额不足,则直接返回余额不足。
3. 如果余额充足,则通过for update再次查询用户信息,并且尝试获取锁。
4. 只有第一个请求能获取到行锁,其余没有获取锁的请求,则等待下一次获取锁的机会。
5. 第一个请求获取到锁之后,判断余额是否不足100,如果余额足够,则进行update操作。
6. 如果余额不足,说明是重复请求,则直接返回成功。

需要特别注意的是:如果使用的是mysql数据库,存储引擎必须用innodb,因为它才支持事
务。此外,这里id字段一定要是主键或者唯一索引,不然会锁住整张表。
悲观锁需要在同一个事务操作过程中锁住一行数据,如果事务耗时比较长,会造成大量的请求等
待,影响接口性能。此外,每次请求接口很难保证都有相同的返回值,所以不适合幂等性设计场
景,但是在防重场景中是可以的使用的。

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

3. 加乐观锁
既然悲观锁有性能问题,为了提升接口性能,我们可以使用乐观锁。需要在表中增加一个 version 字段,在更新数据之前先查询一下数据:
如果数据存在,假设查到的 version 等于 1 ,再使用 id version 字段作为查询条件更新数据:更新数据的同时 version+1 ,然后判断本次 update 操作的影响行数,如果大于0,则说明本次更新成功,如果等于0,则说明本次更新没有让数据变更。
由于第一次请求 version 等于 1 是可以成功的,操作成功后 version 变成 2 了。这时如果并发的请求过来,再执行相同的sql:
update 操作不会真正更新数据,最终sql的执行结果影响行数是 0 ,因为 version 已经变成 2 了, where 中的 version=1 肯定无法满足条件。但为了保证接口幂等性,接口可以直接返回成功,因为 version 值已经修改了,那么前面必定已经成功过一次,后面都是重复的请求。
具体流程如下:
具体步骤:
1. 先根据id查询用户信息,包含version字段
2. 根据id和version字段值作为where条件的参数,更新用户信息,同时version+1
3. 判断操作影响行数,如果影响1行,则说明是一次请求,可以做其他数据操作。
3. 如果影响0行,说明是重复请求,则直接返回成功。
4. 加唯一索引
绝大数情况下,为了防止重复数据的产生,我们都会在表中加唯一索引,加了唯一索引之后,第一次请求数据可以插入成功。但后面的相同请求,插入数据时会报 唯一索引冲突的异常。
虽说抛异常对数据来说没有影响,不会造成错误数据。但是为了保证接口幂等性,我们需要对该异常进行捕获,然后返回成功。
具体步骤:
1. 用户通过浏览器发起请求,服务端收集数据。
2. 将该数据插入mysql
3. 判断是否执行成功,如果成功,则操作其他数据。
4. 如果执行失败,捕获唯一索引冲突异常,直接返回成功。
5. 建防重表 有时候表中并非所有的场景都不允许产生重复的数据,只有某些特定场景才不允许。这时候,直接在表中加唯一索引,显然是不太合适的。 针对这种情况,我们可以通过 建防重表 来解决问题。该表可以只包含两个字段: id 唯一索引 ,唯一索引可以是多个字段比如:name、code等组合起来的唯一标识,例如:pauipai_0001。
具体流程图如下:

具体步骤:
1. 用户通过浏览器发起请求,服务端收集数据。
2. 将该数据插入mysql防重表
3. 判断是否执行成功,如果成功,则做mysql其他的数据操作(可能还有其他的业务逻辑)。
4. 如果执行失败,捕获唯一索引冲突异常,直接返回成功。
需要特别注意的是:防重表和业务表必须在同一个数据库中,并且操作要在同一个事务中。

6. 根据状态机
很多时候业务表是有状态的,比如订单表中有:1-下单、2-已支付、3-完成、4-撤销等状态。如果这些状态的值是有规律的,按照业务节点正好是从小到大,我们就能通过它来保证接口的幂等性。
假如id=123的订单状态是 已支付 ,现在要变成 完成 状态。
第一次请求时,该订单的状态是 已支付 ,值是 2 ,所以该 update 语句可以正常更新数据,sql执行结果的影响行数是 1 ,订单状态变成了 3
后面有相同的请求过来,再执行相同的sql时,由于订单状态变成了 3 ,再用 status=2 作为条
件,无法查询出需要更新的数据,所以最终sql执行结果的影响行数是 0 ,即不会真正的更新数
据。但为了保证接口幂等性,影响行数是 0 时,接口也可以直接返回成功。
具体流程图如下:
具体步骤:
1. 用户通过浏览器发起请求,服务端收集数据。
2. 根据id和当前状态作为条件,更新成下一个状态
3. 判断操作影响行数,如果影响了1行,说明当前操作成功,可以进行其他数据操作。
4. 如果影响了0行,说明是重复请求,直接返回成功。
主要特别注意的是,该方案仅限于要更新的 表有状态字段 ,并且刚好要更新 状态字段 的这种特殊情况,并非所有场景都适用。

7. 加分布式锁 其实前面介绍过的 加唯一索引 或者 加防重表 ,本质是使用了 数据库 分布式锁 ,也属于分布
式锁的一种。但由于 数据库分布式锁 的性能不太好,我们可以改用: redis zookeeper
我们以 redis 为例介绍分布式锁。
目前主要有三种方式实现redis的分布式锁:
1. setNx命令
2. set命令
3. Redission框架

具体步骤:
1. 用户通过浏览器发起请求,服务端会收集数据,并且生成订单号code作为唯一业务字段。
2. 使用redis的set命令,将该订单code设置到redis中,同时设置超时时间。
3. 判断是否设置成功,如果设置成功,说明是第一次请求,则进行数据操作。 4. 如果设置失败,说明是重复请求,则直接返回成功。
需要特别注意的是:分布式锁一定要设置一个合理的过期时间,如果设置过短,无法有效的防止重复请求。如果设置过长,可能会浪费 redis 的存储空间,需要根据实际业务情况而定。

相关文章:

Java开发过程中的幂等性问题

幂等性问题: 1. 有时我们在填写某些 form表单 时,保存按钮不小心快速点了两次,表中竟然产生了两条重复的数据,只是id不一样。 2. 我们在项目中为了解决 接口超时 问题,通常会引入了 重试机制 。第一次请求接口超时了…...

基于Docker的软件环境部署脚本,持续更新~

使用时CtrlF搜索你想要的环境,如果没有你想要的环境,可以评论留言,会尽力补充。 本文提供的部署脚本默认参数仅适合开发测试,请根据实际情况调节参数。 数据库 MySQL version: 3.9 services:mysql:image: mysql:8.0.35container…...

C#上位机与欧姆龙PLC的通信08----开发自己的通讯库读写数据

1、介绍 前面已经完成了7项工作: C#上位机与欧姆龙PLC的通信01----项目背景-CSDN博客 C#上位机与欧姆龙PLC的通信02----搭建仿真环境-CSDN博客 C#上位机与欧姆龙PLC的通信03----创建项目工程-CSDN博客 C#上位机与欧姆龙PLC的通信04---- 欧姆龙plc的存储区 C#上…...

【Redis技术专区】「原理分析」探讨Redis6.0为何需要启用多线程

探讨Redis 6.0为何需要启用多线程 背景介绍开启多线程多线程的CPU核心配置IO多线程模式单线程处理方式多线程处理方式 为什么要开启多线程?充分利用多核CPU提高网络I/O效率响应现代应用需求 多线程实现启用多线程 最后总结 背景介绍 在Redis 6.0版本中,…...

simulink代码生成(六)——多级中断的配置

假如系统中存在多个中断,需要合理的配置中断的优先级与中断向量表;在代码生成中,要与中断向量表对应;中断相关的知识参照博客: DSP28335学习——中断向量表的初始化_中断向量表什么时候初始化-CSDN博客 F28335中断系…...

【Minikube Prometheus】基于Prometheus Grafana监控由Minikube创建的K8S集群

文章目录 1. 系统信息参数说明2. Docker安装3. minikube安装4. kubectl安装5. Helm安装6. 启动Kubernetes集群v1.28.37. 使用helm安装Prometheus8. 使用helm安装Grafana9. Grafana的Dashboard设定10. 设定Prometheus数据源11. 导入Kubernetes Dashboard12. 实验过程中的常见问题…...

无需翻墙|Stable Diffusion WebUI 安装|AI绘画

前言 最近终于有机会从围墙里往外看,了解到外面的世界已经有了天翻地覆的变化,感叹万千,笔者在本地mac,windows,linux,docker部署了不下20遍后,整理出来的linux极简避坑安装方案,供…...

在FC中手工创建虚拟机模板

1、Linux去除个性化信息 (1)编辑网卡配置文件,只保留以下内容(以RHEL 7为例) (2)清除主机密钥信息(开机会自动生成) (3)清除Machine ID&#xff…...

OpenSSL provider

提供者 标准提供者默认提供者传统提供者FIPS 提供者基本提供者空提供者加载提供者 标准提供者 提供者是算法实现的容器。每当通过高级别 API 使用加密算法时,都会选择一个提供者。实际上是由该提供者实现执行所需的工作。OpenSSL 自带了五个提供者。在未来&#…...

pandas处理双周数据

处理文件题头格式 部门名称 年度名称 季节名称 商品名称 商品代码 品牌名称 品类名称 颜色名称 商店名称 0M 1L 1XL 27 28 29 2XL 30 31 32 33 3XL 4XL 5XL 6XL S 均1.导入包 导入源 pip install openpyxl -i https://pypi.doubanio.com/simple pip install pandas -i https…...

2023结婚成家,2024借势起飞

您好,我是码农飞哥(wei158556),感谢您阅读本文,欢迎一键三连哦。 💪🏻 1. Python基础专栏,基础知识一网打尽,9.9元买不了吃亏,买不了上当。 Python从入门到精…...

linux SHELL语句

shell编程 shell编程 一、初识shell 程序 语言 编程语言 自然语言 汉语 英语 计算机语言 c语言cjava php python go shell 编译型语言 c c java解释型语言 php python bash (不能闭源,开发难度低) 编译型语言:运行编译型语言是相对于解释型语言存在的&#xff…...

音频修复和增强软件:iZotope RX 10 (Win/Mac)中文汉化版

iZotope RX 是一款专业的音频修复和增强软件,一直是电影和电视节目中使用的行业标准音频修复工具,iZotope能够帮助用户对音频进行制作、后期合成处理、混音以及对损坏的音频进行修复,再解锁更多功能之后还能够对电影、游戏、电视之中的音频进…...

复试 || 就业day03(2023.12.29)算法篇

文章目录 前言同构字符串存在重复元素有效的字母异位词丢失的数字单词规律 前言 💫你好,我是辰chen,本文旨在准备考研复试或就业 💫文章题目大多来自于 leetcode,当然也可能来自洛谷或其他刷题平台 💫欢迎大…...

处理urllib.request.urlopen报错UnicodeEncodeError:‘ascii‘

参考:[Python3填坑之旅]一urllib模块网页爬虫访问中文网址出错 目录 一、报错内容 二、报错截图 三、解决方法 四、实例代码 五、运行截图 六、其他UnicodeEncodeError: ascii codec 问题 一、报错内容 UnicodeEncodeError: ascii codec cant encode charac…...

数据结构模拟实现LinkedList双向不循环链表

目录 一、双向不循环链表的概念 二、链表的接口 三、链表的方法实现 (1)display方法 (2)size方法 (3)contains方法 (4)addFirst方法 (5)addLast方法 …...

性能优化-如何提高cache命中率

本文主要介绍性能优化领域常见的cache的命中率问题,旨在全面的介绍提高cache命中率的方法,以供大家编写出性能友好的代码,并且可以应对性能优化领域的面试问题。 🎬个人简介:一个全栈工程师的升级之路! &am…...

分布式【4. 什么是 CAP?】

什么是 CAP? C 代表 Consistency,一致性,是指所有节点在同一时刻的数据是相同的,即更新操作执行结束并响应用户完成后,所有节点存储的数据会保持相同。 A 代表 Availability,可用性,是指系统提…...

<软考高项备考>《论文专题 - 39采购管理(3) 》

3 过程2-实施采购 3.1 问题 4W1H过程做什么获取卖方应答、选择卖方并授予合同的过程作用:选定合格卖方并签署关于货物或服务交付的法律协议。本过程的最后成果是签订的协议,包括正式合同。为什么做实际进行采购谁来做组织中的职能部门或项目经理什么时…...

Java在SpringCloud中自定义Gateway负载均衡策略

Java在SpringCloud中自定义Gateway负载均衡策略 一、前言 spring-cloud-starter-netflix-ribbon已经不再更新了,最新版本是2.2.10.RELEASE,最后更新时间是2021年11月18日,详细信息可以看maven官方仓库:org.springframework.clou…...

浅谈 React Hooks

React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...

K8S认证|CKS题库+答案| 11. AppArmor

目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八

现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

2021-03-15 iview一些问题

1.iview 在使用tree组件时,发现没有set类的方法,只有get,那么要改变tree值,只能遍历treeData,递归修改treeData的checked,发现无法更改,原因在于check模式下,子元素的勾选状态跟父节…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

06 Deep learning神经网络编程基础 激活函数 --吴恩达

深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...

如何理解 IP 数据报中的 TTL?

目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...

是否存在路径(FIFOBB算法)

题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)

上一章用到了V2 的概念,其实 Fiori当中还有 V4,咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务),代理中间件(ui5-middleware-simpleproxy)-CSDN博客…...