百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

一把“乐观锁”轻松搞定高并发下的幂等性问题(附视频教程)

mhr18 2025-05-10 23:26 30 浏览 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/

相关推荐

【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库

如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...

Pure Storage推出统一数据管理云平台及新闪存阵列

PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...

对Java学习的10条建议(对java课程的建议)

不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...

SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!

官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...

JDK21有没有什么稳定、简单又强势的特性?

佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...

「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了

在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...

Java面试题及答案最全总结(2025版)

大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...

数据库日常运维工作内容(数据库日常运维 工作内容)

#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...

分布式之系统底层原理(上)(底层分布式技术)

作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...

oracle 死锁了怎么办?kill 进程 直接上干货

1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...

SpringBoot 各种分页查询方式详解(全网最全)

一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...

《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略

《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...

LoadRunner(loadrunner录制不到脚本)

一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...

Redis数据类型介绍(redis 数据类型)

介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...

RMAN备份监控及优化总结(rman备份原理)

今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...

取消回复欢迎 发表评论: