java-幂等性

news2024/9/28 9:27:58

幂等性

1.1幂等性定义:

在计算机领域中,幂等(Idempotence)是指任意一个操作的多次执行总是能获得相同的结果,不会对系统状态产生额外影响。在Java后端开发中,幂等性的实现通常通过确保方法或服务调用的结果具有确定性,无论调用次数如何,结果都是可预期的。

1.2注意:

在实际的互联网服务开发中,幂等性的理论定义与业务逻辑间的冲突是常见的。
例如,考虑查询操作,当A系统调用B系统的查询接口时,如果首次调用由于B系统中的程序错误而导致业务逻辑失败,即使在程序修复后系统A重新使用相同参数进行重试,B系统可能仍然返回相同的失败响应。尽管这符合幂等性的定义,却与实际业务逻辑不符。同样,以订单支付为例,首次调用由于账户余额不足而返回“余额不足”提示,用户充值后再次使用相同参数发起支付请求,服务仍然返回“余额不足”响应,也符合幂等性的定义,但同样不符合业务逻辑。
因此,在实现幂等性方案时,应该遵循幂等性方案的目标,而不仅仅是严格遵循幂等性的定义。尤其是涉及写操作的服务,应当更关注防止重复请求带来的不良副作用,例如重复扣款或退款。

1.3 什么情况下会出现幂等?

在微服务和分布式架构中,一个请求可能需要多个服务协作才能完成。在这个过程中,网络抖动、系统运行异常等不确定因素使得请求的成功率不可能达到100%,一旦发生失败或未知异常,最常见的处理方式就是重试,而重试必然会导致重复请求问题。

幂等设计主要是为了处理重复请求而生的,好的幂等方案可以保证重复请求获得预期结果,而不产生副作用。

用户不可靠: 用户通过客户端发起请求,由于手抖或有意重复点击,很容易造成导致极短时间内发起多次重复请求。
网络不可靠:网络抖动、网关内部抖动有可能触发重试机制,这个在使用消息队列投递消息时经常会遇到。MQ 消息中间件,消息重复消费;

服务不可靠: 在需要保证数据一致性的场景中,如果调用下游服务超时,在无法确认执行结果的情况下,常用的处理方法是重试。比如:前端调用后端接口发起支付超时,然后再次发起重试,可能会导致多次支付。

1.3 幂等与并发的关系

在具有并发写操作的场景下,通常需要考虑幂等问题。例如,当用户在极短时间内多次提交表单或者使用特殊手段同时提交多个表单时,这就是典型的并发场景,需要进行幂等性处理。为了防止重复请求被执行,服务端需要实施幂等性控制,以避免产生不符合预期的结果。

虽然并发场景大都存在幂等问题,但幂等问题却并非并发场景所特有。幂等设计是为了识别并处理重复请求,而并发仅仅是重复请求的一种特殊情况。 事实上,只要重复请求涉及写操作,无论是否并发,都需要做好幂等处理。举个例子,用户在pc端同时开了两个窗口,间隔10分钟分别提交表单,所有参数完全相同,这显然不属于并发,但仍需要进行幂等处理。


二、幂等性解决方案

这些方案的技术路线可以总结成三条:唯一索引、唯一数据、状态机约束。

  • 唯一索引是指数据库主键、唯一索引,唯一索引大部分是基于业务流水表建立,也可单独建表实现;
  • 唯一数据是指悲观锁、乐观锁、分布式锁等机制;
  • 状态机约束,对于存在状态流转的业务,通过状态机的流转约束,可以实现有限状态机的幂等。

在实际开发中,单独使用这些方法往往效果有限,需要根据具体的业务场景灵活选择、合理的运用上述实现方法。

  1. 数据库:乐观锁、悲观锁、唯一主键、唯一索引
  2. 业务层:分布式锁、下游传递唯一序列号
  3. Token令牌
  4. 状态机

2.1 方案一:数据库唯一主键实现幂等性

在这里插入图片描述
缺点:无法使用change buffer,InnoDB为了进行唯一性检查,必须有一次磁盘IO读页

2.1.1 方案一延伸:唯一索引方案机制

唯一索引方案依赖于数据库表中不允许存在具有相同索引值的重复行。这种策略在关系型数据库中广泛支持,并且能有效利用唯一性约束来确保幂等性。 在高并发场景中,唯一索引能保证当多个线程尝试同时插入相同记录时,只有一个线程能成功执行,而其他线程将会因违反唯一性约束而抛出异常。

通常,业务流水表的建立是基于以下核心字段:
id(bigint 类型):作为主键,唯一标识每条记录。
gmt_create(datetime 类型):记录的创建时间。
gmt_modified(datetime 类型):记录的最后修改时间。
user_id(varchar(32) 类型):用户ID,这个字段也可以作为分表的依据。
out_biz_no(varchar(64) 类型):外部业务流水号,即调用方的幂等号。
biz_no(varchar(64) 类型):内部业务流水号,用于系统内部追踪。
status(char(1) 类型):记录执行状态。

在这种设计中,user_id和out_biz_no通常会组合成一个联合索引,这样做能有效避免在并发情况下的数据重复插入问题,从而保障了业务操作的幂等性。

2.2 方案二:数据库乐观锁实现幂等性

乐观锁主要依靠带条件更新 来确保多次外部请求的一致性。在系统设计中,可以在数据表中添加版本号字段,用于标识当前数据的版本。每次对该数据表的记录进行更新时,都需要提供上一次更新的版本号,示例操作如下:

//1. 取出要更新的对象,带有版本versoin
select * from tablename where id = xxx

//2. 更新数据
update tableName set sq = sq-#{quantity},version = #{version}+1 where id = xxx and version=#{version}

在这里插入图片描述
特点:乐观锁主要适用于更新场景,确保多次更新不会影响结果的一致性。
缺点:操作业务前,需要先查询出当前的version版本。会增加操作

2.3方案三:数据库悲观锁机制

悲观锁依赖数据库提供的锁机制来实现,整个数据处理过程中,数据处于锁定状态,并与事务机制配合,能够有效实现业务幂等性。操作示例如下:

// 1. 开启事务
begin;
// 2. 基于幂等号查询
record = select * from tbl_xxx where out_biz_no = 'xxx' for update;
// 3. 根据状态进行决策
if(record.getStatus() != 预期状态){
   return;
}
// 4. 更新记录
update tbl_xxx set status = '目标状态' where out_biz_no = 'xxx';
// 5. 提交事务
commit;

特点:
select for update,整个执行过程中锁定该条记录
缺点:
在DB读大于写的情况下尽量少用。悲观锁主要适用于更新场景,通过串行化请求处理来确保幂等性,但需要小心使用,因为在并发场景下,重复请求可能会导致线程长时间处于等待状态,浪费资源且降低性能。

2.4 方案四:业务层采用分布式锁机制

分布式锁与悲观锁本质上相似,都通过串行化请求处理来实现幂等性。与悲观锁不同的是,分布式锁更轻量。在系统接收请求后,首先尝试获取分布式锁。如果成功获取锁,则执行业务逻辑;如果获取失败,则立即拒绝请求。
在这里插入图片描述
分布式锁的核心是识别重复请求,实现串行化处理。但要注意,获取锁成功后,业务逻辑的执行并没有可靠保证。因此,在实际应用中,分布式锁需要结合事务机制和重试机制,以形成完整的幂等性解决方案。

2.5方案五:防重 Token 令牌实现幂等性

2.5.1 流程:

1)当用户访问表单页面时,客户端请求服务端接口以获取唯一的Token(可以是UUID或全局ID),服务端生成的Token会被存储在Redis或数据库中。
2)用户首次提交表单时,将Token与表单一起发送至服务端,服务端会验证Token的存在性,如果Token存在,则执行业务逻辑,并在完成后销毁Token。
3)用户再次提交表单时,同样携带Token一起发送至服务端。但由于Token已被销毁,服务端无法找到对应的Token,从而拒绝重复提交请求。
在这里插入图片描述
在这里插入图片描述

2.5.2实现:

(1)集群环境:token+redis
(2)单jvm环境:token+redis 或者token+jvm内存

2.5.3 Token特点

== 要申请,一次有效性,可以限流 ==

2.5.4缺点:

(1)产生过多额外请求
(2)先删除token,如果业务处理出现异常但token已经删除掉了,再来请求会被认定为重复请求
后删除token,如果删除redis中的token失败了,再来请求不会拦截,发生了重复请求
无论是先删除token还是后删除token,都会导致每次业务请求都产生一个额外的请求去获取token。然而,在生产环境中,业务失败或超时的情况并不多见,大多数请求都能成功完成。因此,为了处理这少数失败的请求,让绝大多数请求都产生额外的请求也算是一种资源的浪费。

2.5.5 存在问题:删除token时,是先完成业务操作后删除token,还是先删除token后执行业务操作呢?

答案:要先删除 token ,再执行业务代码 。『后删除 token』的缺陷太致命
(1)先执行业务操作再删除token
情况:在高并发下,可能出现第一次访问时token存在,完成具体业务操作,但在还没有删除token时,客户端又携带token发起请求。此时,因为token还存在,第二次请求也会验证通过,执行具体业务操作。
对于这个问题有如下两种解决方案:
第一种方案: 对于业务代码执行和删除token整体加线程锁,使得后续线程阻塞排队,但可能造成一定性能损耗与吞吐量降低。
第二种方案: 借助Redis单线程和INCR原子性特性,在获取token时对其进行自增操作。当客户端携带token访问执行业务代码时,继续对其进行自增,如果自增后的返回值为2,则是一个合法请求允许执行,否则认为是非法请求,直接返回。
在这里插入图片描述

(2)先删除token再执行业务
如果业务执行超时或失败,没有向客户端返回明确结果,客户端就会进行重试,但此时之前的token已经被删除,导致被认为是重复请求,不再进行业务处理。
在这里插入图片描述

这种方案无需额外处理,一个token只能代表一次请求。一旦业务执行出现异常,则让客户端重新获取令牌,重新发起一次访问即可。
先删除token,再执行业务逻辑,中间如果出现宕机,可能会导致业务调用失败,对于这种情况,大不了就重新获取token再次请求

2.6 方案六:状态机机制

在许多业务单据中,存在有限数量的状态,并且这些状态之间的流转顺序是固定的。如果状态已经处于下一个状态,那么再次应用上一个状态的变更逻辑是不会产生任何效果的,这就确保了有限状态机的幂等性。
例如,库存状态通常包括"预扣中"、“扣减中”、“占用中"和"已释放"等状态。如果系统重复调用扣减接口,而库存状态已经是"扣减中”,则可以直接返回结果。
状态机可以与乐观锁机制结合使用,示例操作如下:

update tableName set sq=sq-#{quantity},status=#{udpate_status} where id =#{id} and status=#{status}

特点:和任务、状态相关的业务,肯定会涉及状态机,业务的一个属性状态,可以作为幂等的一个根据

2.7方案七:下游传递唯一序列号实现幂等性

在这里插入图片描述
缺点:无法控制下游唯一序列号的生成规则,如果序列号由时间戳生成,那么无法拦截类似重复点击这种情况下的重复请求

3.1 补充 redis幂等性

3.1.1 redis幂等性

在Redis中,幂等性是指相同的操作可以被多次执行而不会产生额外的影响或副作用。简而言之,就是无论执行多少次相同的操作,结果都是一样的。 在Redis中,可以通过以下几种方式来实现redis的幂等性:
(1)使用Redis的原子性操作:Redis提供了一些原子性操作,如SETNX、INCR、SADD等。 这些操作在执行时是原子性的,即是一个操作的结果要么成功执行,要么没有执行。通过使用这些原子性操作,可以保证相同的操作在执行时只会生效一次。
(2)使用Redis的事务:Redis的事务可以将一系列的操作包装在一个事务中,然后一起执行。在事务执行期间,其他客户端的请求不会干扰到事务的执行。通过将幂等操作放在一个事务中执行,可以保证这些操作只会被执行一次。
(3)使用Redis的分布式锁:通过使用Redis的分布式锁,可以保证同一时间只有一个客户端可以执行特定的操作。当一个客户端获取到锁后,其他客户端尝试获取锁的操作会被阻塞,直到锁被释放。通过使用分布式锁,可以保证相同的操作只会被执行一次。

总结起来,Redis中可以通过原子性操作、事务和分布式锁等方式来实现redis的幂等性。 这样可以保证相同的操作在执行时不会产生额外的影响或副作用。

3.1.2 redis SETNX分布锁详解

1.SETNX:向Redis中添加一个key,只用当key不存在的时候才添加并返回1,存在则不添加返回0。并且这个命令是原子性的。
2. 使用SETNX作为分布式锁时,添加成功表示获取到锁,添加失败表示未获取到锁。至于添加的value值无所谓可以是任意值(根据业务需求),只要保证多个线程使用的是同一个key,所以多个线程添加时只会有一个线程添加成功,就只会有一个线程能够获取到锁。而释放锁锁只需要将锁删除即可。
3.设置过期时间防止死锁
在添加时存在则添加,不存在则不添加。同时设置过期时间,单位秒
示例:

/**
     * 计算结果写入到kafka幂等性的实现
     *
     * @param json
     * @return
     */
    public boolean idempotent(String json) {
        try {
            RedisPool redisPool = RedisPool.instance(properties);
            Jedis jedis = redisPool.getResource();
            String value = "1";
            if (jedis.setnx(json, value) > 0) {
                jedis.expire(json, 24 * 3600);
                redisPool.returnResource(jedis);
                return true;
            }
            redisPool.returnResource(jedis);
        } catch (Exception e) {
            System.err.println("redis pool get redis failed: " + json);
        }
        return false;
}  

2.防止死锁

SET key value NX EX time

//通过java代码实现SETNX同时设置过期时间 //key--键 value--值 time--过期时间 TimeUnit--时间单位枚举
stringRedisTemplate.opsForValue().setIfAbsent(key, value , time, TimeUnit); 

优点:
1.程序可以分组,可以分布式,亦可以用于数据恢复程序
2.减少了对redis操作频度,提高了程序的并发性.

3.2 参考文章:

https://mp.weixin.qq.com/s/7YDtl8EfYvre49Al9yVZIw
https://blog.csdn.net/sinat_32023305/article/details/119610885
https://blog.csdn.net/q7w8e9r4/article/details/132533849

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

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

相关文章

68-解构赋值,迭代器,生成器函数,Symbol

1.解构赋值(针对数组array&#xff0c;字符串String及对象object以) 结构赋值是一种特殊的语法&#xff0c;通过将各种结构中的元素复制到变量中达到"解构"的目的&#xff0c;但是数组本身没有改变 1.1解构单层数组 <script>let arr [1,2,3,4,5];//获取数组…

C++之queue和deque

1、queue queue&#xff08;队列&#xff09;&#xff0c;一种数据结构&#xff0c;可以让某些数据结构的操作变得简单。队列&#xff08;queue&#xff09;最大的特点就是先进先出。就是说先放入queue容器的元素一定是要先出队列之后&#xff0c;比它后进入队列的元素才能够出…

【python】遵守 robots.txt 规则的数据爬虫程序

程序1 编写一个遵守 robots.txt 规则的数据爬虫程序涉及到多个步骤&#xff0c;包括请求网页、解析 robots.txt 文件、扫描网页内容、存储数据以及处理异常。由于编程语言众多&#xff0c;且每种语言编写爬虫程序的方式可能有所不同&#xff0c;以下将使用 Python 语言举例&am…

Material UI 5 学习01-按钮组件

Material UI 5 学习01-按钮组件 一、安装Material UI二、 组件1、Button组件1、基础按钮2、variant属性3、禁用按钮4、可跳转的按钮5、disableElevation属性6、按钮的点击事件onClick 2、Button按钮的颜色和尺寸1、Button按钮的颜色2、按钮自定义颜色3、Button按钮的尺寸 3、图…

Topaz Gigapixel AI:让每一张照片都焕发新生mac/win版

Topaz Gigapixel AI 是一款革命性的图像增强软件&#xff0c;它利用先进的人工智能技术&#xff0c;能够显著提升图像的分辨率和质量。无论是摄影爱好者还是专业摄影师&#xff0c;这款软件都能帮助他们将模糊的、低分辨率的照片转化为清晰、细腻的高分辨率图像。 Topaz Gigap…

每日五道java面试题之mysql数据库篇(三)

目录&#xff1a; 第一题. 百万级别或以上的数据如何删除&#xff1f;第二题. 前缀索引第三题. 什么是最左前缀原则&#xff1f;什么是最左匹配原则?第四题. B树和B树的区别第五题. 使用B树和B树好处 第一题. 百万级别或以上的数据如何删除&#xff1f; 关于索引&#xff1a;…

【微服务】微服务中常用认证加密方案总结

目录 一、前言 二、登录认证安全问题 3.1 认证方式选择 三、常用的加密方案 3.1 MD5加密算法 3.1.1 md5特点 3.1.2 md5原理 3.1.3 md5使用场景 3.2 AES加密算法 3.2.1 AES简介 3.2.2 AES加解原理 3.2.3 AES算法优缺点 3.2.4 AES算法使用场景 3.3 RSA加密算法 3.3…

【IDEA+通义灵码插件】实现属于你的大模型编程助手

目录 1.前言 2.下载安装 3.解释代码 4.生成单元测试 5.生成注释 6.智能补全 1.前言 大模型到底该以一种什么方式落地&#xff0c;从而嵌入我们的工作当中&#xff0c;助力我们工作效率的提升&#xff0c;其实最好的方式也许就是虚拟助手的方式&#xff0c;就像钢铁侠的&…

无极低码:无极低码部署版操作指南

无极低码 &#xff1a;https://wheart.cn 无极低码是一个面向开发者的工具&#xff0c;旨在为开发者、创业者或研发企业&#xff0c;提供快速&#xff0c;高效&#xff0c;标准化&#xff0c;可定制&#xff0c;私有化部署的平台&#xff0c;在兼顾开发速度的同时&#xff0c;兼…

ChemDraw Pro 2022:呈现专业化学绘图的极 致之作 mac/win版

PerkinElmer ChemDraw Pro 2022是一款功能强大的化学绘图软件&#xff0c;专为化学家、科研工作者和教育者设计。这款软件凭借其卓越的性能和丰富的功能&#xff0c;已经成为化学绘图领域的领导者。 PerkinElmer ChemDraw Pro 2022软件获取 ChemDraw Pro 2022提供了广泛的化学…

可重入锁-隐式锁与显式锁

可重入锁 隐式锁&#xff08;即synchronized关键字使用的锁&#xff09;&#xff0c;默认是可重入锁 在一个synchronized修饰的方法或者代码块的内部调用本类的其他synchronized修饰的方法或者代码块时&#xff0c;是永远可以得到锁。 显式锁&#xff08;即Lock&#xff09;也…

【yolov8部署实战】VS2019环境下使用Onnxruntime环境部署yolov8目标检测|含源码

一、前言 部署yolo项目&#xff0c;是我这几个月以来做的事情&#xff0c;最近打算把这几个月试过的方法&#xff0c;踩过的坑&#xff0c;以博客的形式&#xff0c;分享一下。关于下面动态中讲到的如何用opencv部署&#xff0c;我在上一篇博客中已经详细讲到了&#xff1a;【…

【C++】const成员

个人主页 &#xff1a; zxctscl 如有转载请先通知 文章目录 1. 前言2. const成员3. 取地址及const取地址操作符重载 1. 前言 在之前已经已经分享过了关于 【C】类和对象之常引用与运算符重载&#xff0c;这次分享的有关const的内容&#xff0c;话不多说&#xff0c;正文开始。…

Mybatis 框架 基础语法

Mybtais 中文网站 Maven 仓库 考点&#xff1a; SQL 注入 #{}最终生成预编译sql&#xff0c;预编译Sql语句中?替换#{}内容 一个#{} 替换一个? 在模糊查询时要使用 &#xff0c; ′ {}&#xff0c;% &#xff0c;′{}%’ &#xff0c;因为&#xff1f;不能出现在‘’中&…

2024最新算法:鹦鹉优化算法(Parrot optimizer,PO)求解23个基准函数

一、鹦鹉优化算法 鹦鹉优化算法&#xff08;Parrot optimizer&#xff0c;PO&#xff09;由Junbo Lian等人于2024年提出的一种高效的元启发式算法&#xff0c;该算法从驯养的鹦鹉中观察到的觅食、停留、交流和对陌生人行为的恐惧中汲取灵感。这些行为被封装在四个不同的公式中…

2024年自动化测试五大趋势

目录 2024年QA自动化趋势&#xff1a;塑造软件开发的未来 1.自动化测试中的AI和ML集成 2.DevOps中的持续测试 3.无代码自动化工具的使用 4.更加重视安全测试 5.定制化测试解决方案 有效实施2024年QA自动化趋势 1.集成AI和ML实现更智能的测试 2.在敏捷和DevOps文化中嵌入…

5.STL源码解析-算法、仿函数、适配器

算法 STL算法总览 仿函数与适配器 C标准模板库&#xff08;STL&#xff09;是C程序员的得力工具&#xff0c;提供了许多强大而高效的数据结构和算法。在STL中&#xff0c;仿函数&#xff08;Functor&#xff09;和适配器&#xff08;Adapter&#xff09;是两个重要的概念…

windows上elasticsearch的ik分词器的安装

下载 下载地址 在elasticsearch下的plugins文件夹下创建ik的文件夹 下载的ik压缩包解压到plugins/ik 重启elasticsearch 验证 http://ip:9200/_cat/plugins

OSCP靶场--Squid

OSCP靶场–Squid 考点(1.squid代理绕过 2.phpmyadmin写webshell 3.受限服务账户【LOCAL SERVICE或NETWORK SERVICE】恢复特权 4.SeImpersonatePrivilege提权) 1.nmap扫描 ## ┌──(root㉿kali)-[~/Desktop] └─# nmap -sV -sC -p- 192.168.188.189 --min-rate 2000 Starti…

05-Linux部署MySQL

Linux部署MySQL 在今后的使用过程中&#xff0c;需要频繁使用Linux系统&#xff0c;所以在Linux上安装软是必不可少的操作 。 前置要求 需要学习前四章知识&#xff0c;初识Linux、Linux基础命令、Linux权限管理、Linux高阶技巧这4个章节。需要开启多态虚拟机&#xff0c;电…