一把“乐观锁”轻松搞定高并发下的幂等性问题(附视频教程)
mhr18 2025-05-10 23:26 4 浏览 0 评论
什么是幂等性?
幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。在编程中.一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。例如,“getUsername()和setTrue()”函数就是一个幂等函数. 更复杂的操作幂等保证是利用唯一交易号(流水号)实现. 我的理解:幂等就是一个操作,不论执行多少次,产生的效果和返回的结果都是一样的 。
高并发下的幂等性问题
这里以两个实例来看下高并发下的幂等性问题;
一,购票实例
购票实现流程如下:
step1:查询是否有票,有票的话,继续下一步,否则提示无票,结束;
step2:从用户账户扣除票款;
step3:余票减一操作;
这里的话,正常情况没问题,但是比如用户连续多点了几次,或者网络问题导致的再或者多人同时购买的时候的并发情况下,step1步骤会有两个或者多个线程同时进入,这时候判断都是有票的,然后继续进入step2,step3,这时候,就可能会出现余票负数,多卖的情况;
二,充值实例
充值实现流程如下:
step1:用户输入充值金额,请求后端业务系统;
step2:后端生成订单,订单状态是未支付,然后再请求第三方支付接口;
step3:用户端确认支付;
step4:第三方支付通过我方提供的回调接口异步通知支付结果;
具体step4 demo代码如下:
System.out.println("查询订单");
Order order = orderMapper.getByOrderId(orderId); // 根据订单id获取订单
if(order.getStatus()==0){ // 假如是未支付状态
System.out.println("未支付状态");
order.setStatus(1); // 设置支付成功状态
System.out.println("更新支付状态...");
orderMapper.update(order); // 更新支付状态
System.out.println("账户充值...");
userAccountMapper.addAmount(order.getAmount(),userAccount.getUserId()); // 账户充值
System.out.println("充值完毕...");
return true;
}else{ // 已经支付成功,订单已处理
System.out.println("发现订单已处理");
return true;
}
这个第四步是有缺陷的,假如第三方支付系统问题或者网络问题,有多个线程同时执行进入
Order order = orderMapper.getByOrderId(orderId);
根据订单id查询订单信息,发现status状态都是未支付,所以都进入if里面,这时候就出现了账户重复充值的情况;
幂等性问题总结
只要更新数据是依赖读取的数据作为基础条件的,当遇到高并发的时候,就可能会出现幂等性问题;
又比如在更新数据不依赖查询的数据的就不会有问题,例如修改用户的名称,多人同时修改,结果并不依赖于之前的用户名字,这就不会有并发更新问题。
幂等性问题解决方案
关于幂等性问题的解决方案,业界提供了很多解决方案,如单机系统的Java 同步锁,乐观锁,悲观锁,分布式锁,唯一性索引,token机制防止页面重复提交等,每种方案各有利弊;不过主流的话,还是乐观锁和分布式锁这两个方案;
Java同步锁方案
我们可以使用synchronized同步锁,把查询状态的代码和更新的代码放一个同步锁内,这样同一时刻只能有一个线程进入执行,等执行完其他线程才能进入,这样能解决幂等性问题,但是假如同步块里面的业务代码执行时间比较长,这样会严重影响用户体验,和系统的吞吐量。所以不是最佳方案;
悲观锁方案
悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。
Java synchronized 就属于悲观锁的一种实现,每次线程要修改数据时都先获得锁,保证同一时刻只有一个线程能操作数据,其他线程则会被block。
数据库的悲观锁通过 for update 实现的;
select * from t_order where orderId=#{orderId} for update
悲观锁使用时一般伴随事务一起使用,数据锁定时间可能会很长,影响用户体验和系统吞吐量,所以一般也不采用。
乐观锁方案
乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于读多写少的应用场景,这样可以提高吞吐量。
乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
乐观锁一般来说有以下2种方式:
1. 使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。
2. 使用时间戳(timestamp)。乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。
乐观锁方案在不影响系统性能的情况下,解决了高并发幂等性问题,所以被得到广泛使用。唯一的缺点就是对代码具有入侵性。
分布式锁
对于分布式系统,多个系统独立运行,所以同步锁肯定是不行的;对于分布式系统,可以用乐观锁或者分布式锁来解决幂等性问题;
具体方案有:
1. 基于缓存(Redis等)实现分布式锁;
2. 基于Zookeeper实现分布式锁;
(备注:下期我们会提供具体实现方案的视频教程,感谢关注)
基于“乐观锁”解决幂等性视频教程
感谢各位兄弟姐妹关注,锋哥为了大伙能更深刻的掌握“乐观锁”解决幂等性问题,专门录制了一期视频教程。主要以账户充值为例,采用IDEA开发工具,数据库Mysql5.7,demo基于springboot+mybatis架构,用JMeter测试工具模拟,高并发,来测试出幂等性问题,也就账户被重复充值的场景。然后通过基于状态机version字段的乐观锁解决方案,解决幂等性问题,也同时附有完整代码。
纸上得来终觉浅,绝知此事要躬行。
需要多实战练习和思考。
B站视频教程在线地址:
https://www.bilibili.com/video/BV1uk4y1m7cP/
https://www.bilibili.com/video/BV1uk4y1m7cP/
相关推荐
- Docker安装详细步骤及相关环境安装配置
-
最近自己在虚拟机上搭建一个docker,将项目运行在虚拟机中。需要提前准备的工具,FinallShell(远程链接工具),VM(虚拟机-配置网络)、CentOS7(Linux操作系统-在虚拟机上安装)...
- Linux下安装常用软件都有哪些?做了一个汇总列表,你看还缺啥?
-
1.安装列表MySQL5.7.11Java1.8ApacheMaven3.6+tomcat8.5gitRedisNginxpythondocker2.安装mysql1.拷贝mysql安装文件到...
- Nginx安装和使用指南详细讲解(nginx1.20安装)
-
Nginx安装和使用指南安装1.检查并安装所需的依赖软件1).gcc:nginx编译依赖gcc环境安装命令:yuminstallgcc-c++2).pcre:(PerlCompatibleRe...
- docker之安装部署Harbor(docker安装hacs)
-
在现代软件开发和部署环境中,Harbor作为一个企业级的容器镜像仓库,提供了高效、安全的镜像管理解决方案。通过Docker部署Harbor,可以轻松构建私有镜像仓库,满足企业对镜像存储、管理和安全性...
- 成功安装 Magento2.4.3最新版教程「技术干货」
-
外贸独立站设计公司xingbell.com经过多次的反复实验,最新版的magento2.4.3在oneinstack的环境下的详细安装教程如下:一.vps系统:LinuxCentOS7.7.19...
- 【Linux】——从0到1的学习,让你熟练掌握,带你玩转Linu
-
学习Linux并掌握Java环境配置及SpringBoot项目部署是一个系统化的过程,以下是从零开始的详细指南,帮助你逐步掌握这些技能。一、Linux基础入门1.安装Linux系统选择发行版:推荐...
- cent6.5安装gitlab-ce最新版本-11.8.2并配置邮件服务
-
cent6.5安装gitlab-ce最新版本-11.8.2并配置邮件服务(yum选择的,时间不同,版本不同)如果对运维课程感兴趣,可以在b站上搜索我的账号:运维实战课程,可以关注我,学习更多免费的运...
- 时隔三月,参加2020秋招散招,终拿字节跳动后端开发意向书.
-
3个月前头条正式批笔试4道编程题只AC了2道,然后被刷了做了200多道还是太菜了,本来对字节不抱太大希望,毕竟后台竞争太大,而且字节招客户端开发比较多。后来看到有散招免笔试,抱着试一试的心态投了,然而...
- Redisson:Java程序员手中的“魔法锁”
-
Redisson:Java程序员手中的“魔法锁”在这个万物互联的时代,分布式系统已经成为主流。然而,随着系统的扩展,共享资源的争夺成为了一个棘手的问题。就比如你想在淘宝“秒杀”一款商品,却发现抢的人太...
- 【线上故障复盘】RPC 线程池被打满,1024个线程居然不够用?
-
1.故障背景昨天晚上,我刚到家里打开公司群,就看见群里有人讨论:线上环境出现大量RPC请求报错,异常原因:被线程池拒绝。虽然异常量很大,但是异常服务非核心服务,属于系统旁路,服务于数据核对任务,即使...
- 小红书取消大小周,有人不高兴了!
-
小红书宣布五一节假日之后,取消大小周,恢复为正常的双休,乍一看工作时长变少,按道理来说大家应该都会很开心,毕竟上班时间缩短了,但是还是有一些小红书的朋友高兴不起来,心情很复杂。因为没有了大小周,以前...
- 延迟任务的多种实现方案(延迟机制)
-
场景订单超时自动取消:延迟任务典型的使用场景是订单超时自动取消。功能精确的时间控制:延时任务的时间控制要尽量准确。可靠性:延时任务的处理要是可靠的,确保所有任务最终都能被执行。这通常要求延时任务的方案...
- 百度java面试真题(java面试题下载)
-
1、SpingBoot也有定时任务?是什么注解?在SpringBoot中使用定时任务主要有两种不同的方式,一个就是使用Spring中的@Scheduled注解,另一个则是使用第三方框架Q...
- 回归基础:访问 Kubernetes Pod(concurrent.futures访问数据库)
-
Kubernetes是一头巨大的野兽。在它开始有用之前,您需要了解许多概念。在这里,学习几种访问集群外pod的方法。Kubernetes是一头巨大的野兽。在它开始有用之前,您需要了解许多不同的...
- Spring 缓存神器 @Cacheable:3 分钟学会优化高频数据访问
-
在互联网应用中,高频数据查询(如商品详情、用户信息)往往成为性能瓶颈。每次请求都触发数据库查询,不仅增加服务器压力,还会导致响应延迟。Spring框架提供的@Cacheable注解,就像给方法加了一...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- Docker安装详细步骤及相关环境安装配置
- Linux下安装常用软件都有哪些?做了一个汇总列表,你看还缺啥?
- Nginx安装和使用指南详细讲解(nginx1.20安装)
- docker之安装部署Harbor(docker安装hacs)
- 成功安装 Magento2.4.3最新版教程「技术干货」
- 【Linux】——从0到1的学习,让你熟练掌握,带你玩转Linu
- cent6.5安装gitlab-ce最新版本-11.8.2并配置邮件服务
- 时隔三月,参加2020秋招散招,终拿字节跳动后端开发意向书.
- Redisson:Java程序员手中的“魔法锁”
- 【线上故障复盘】RPC 线程池被打满,1024个线程居然不够用?
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)