后端|一个分布式锁「失效」的案例分析

简介: 小猿最近很苦恼:明明加了分布式锁,为什么并发还是会出问题呢?

小猿最近很苦恼:明明加了分布式锁,为什么并发还是会出问题呢?

故事从接到需求开始说起。

接到需求

小猿前一阵接到一个小任务,里面有一个功能对应的场景如下:

  • 封装一个对账户余额进行加减操作的方法;

  • 所属服务部署了多个实例;

  • 这个方法可能会有并发调用。

注:实际业务场景比较复杂,已做简化。

小猿略作思考,就抓住了关键点:余额操作——要注意事务,多实例——要注意并发。

小猿的原始代码如下:

@Override
@Lock(key = "#accountNo")
@Transactional(rollbackFor = Exception.class)
public void updateBalance(String accountNo, AmountOperateParam param) {
    // do something
}

可以看到,这个方法上通过注解方式加了分布式锁和事务,锁的 key 是 accountNo,也就是账户业务主键。

自测和测试也没发现啥问题,就高高兴兴发完版回家了。

出问题了

第二天一早,就接到少量用户反馈,说自己的账户余额不对了。

小猿的第一反应是:我这块自测和测试都没问题,其它功能导致的吧?本地又是一通自测,也没有复现问题。但谨慎起见,还是往代码里加了一些日志,来确认是不是自己的方法引发的。

当又有用户反馈时,小猿根据日志的情况确认了:还真是自己方法的问题,对同一个账户的余额操作,多个并发请求会同时执行到方法体里面。

也就是说……分布式锁没锁住?

冥思苦想了好久,又在本地做了大量的测试,终于让小猿找到了问题所在:AOP 执行顺序问题。

小猿设计的时序:

图片

但实际的时序:

图片

也就是说期望是这样的执行顺序:

图片

但实际的执行顺序:

图片

分布式锁和事务,都是通过 AOP 来实现的,而 AOP 的执行顺序是根据切面的优先级来的,而小猿的分布式锁切面的优先级比事务切面的优先级低,所以就出现了上面的时序问题。

于是通过给分布式锁的切面指定 Order 的方式,让它的优先级高于事务切面(注:Order 值越小,执行优先级越高),验证完没问题后,就又高高兴兴地更新完版本,修复好历史问题数据后回家了。

还有问题

谁知道第二天一早,还是有极少量的用户反馈账户余额不对的问题。

这次小猿就有点懵了,为什么还会出现这种情况呢?

经过一番艰苦卓绝的排查,终于找到了问题所在:事务嵌套。

从前文中的示例代码中可以看到,小猿的方法上加了事务注解 @Transactional(rollbackFor = Exception.class) 里,没有指定事务的传播行为,默认是 Propagation.REQUIRED,也就是说如果当前没有事务,就新建一个事务;如果当前有事务,就加入到当前事务中。

小猿自己写的代码里没有在事务方法里嵌套调用这个方法的情况,但是同事写的代码里有,这样就会导致前文的时序问题再次发生。

找到问题就好办了,小猿将自己的方法上的事务传播行为改成了 Propagation.REQUIRES_NEW,也就是说如果当前没有事务,就新建一个事务;如果当前有事务,就将当前事务挂起,新建一个事务。

这次更新完版本后,小猿就再也没有收到用户反馈了,终于可以安心回家睡觉了。

小结

在日常的开发过程中,如果涉及到并发和事务,一定要多留几个心眼,考虑周全,确认以下要点是否都正确实现:

  • 是否做了必要的并发控制?

  • 事务的传播行为是否符合预期?

  • AOP 的执行顺序是否符合预期?

  • 对并发的场景是否做了充分的测试?

  • 对于比较关键的操作,是否打印了必要的日志?


假如读完文章有收获,可以关注我的微信公众号「闷骚的程序员」并🌟设为星标🌟,随时阅读更多内容。

目录
相关文章
|
14天前
|
运维 NoSQL Java
后端架构演进:微服务架构的优缺点与实战案例分析
【10月更文挑战第28天】本文探讨了微服务架构与单体架构的优缺点,并通过实战案例分析了微服务架构在实际应用中的表现。微服务架构具有高内聚、低耦合、独立部署等优势,但也面临分布式系统的复杂性和较高的运维成本。通过某电商平台的实际案例,展示了微服务架构在提升系统性能和团队协作效率方面的显著效果,同时也指出了其带来的挑战。
54 4
|
14天前
|
jenkins Java 测试技术
如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例详细说明
本文介绍了如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例,详细说明了从 Jenkins 安装配置到自动构建、测试和部署的全流程。文中还提供了一个 Jenkinsfile 示例,并分享了实践经验,强调了版本控制、自动化测试等关键点的重要性。
46 3
|
1月前
|
jenkins Java 测试技术
如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例详细说明
【10月更文挑战第8天】本文介绍了如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例,详细说明了从 Jenkins 安装配置到自动构建、测试和部署的全流程。文中还提供了一个 Jenkinsfile 示例,并分享了实践经验,强调了版本控制、自动化测试等关键点的重要性。
34 5
|
1月前
|
分布式计算 NoSQL Java
Hadoop-32 ZooKeeper 分布式锁问题 分布式锁Java实现 附带案例和实现思路代码
Hadoop-32 ZooKeeper 分布式锁问题 分布式锁Java实现 附带案例和实现思路代码
43 2
|
1月前
|
存储 前端开发 Java
验证码案例 —— Kaptcha 插件介绍 后端生成验证码,前端展示并进行session验证(带完整前后端源码)
本文介绍了使用Kaptcha插件在SpringBoot项目中实现验证码的生成和验证,包括后端生成验证码、前端展示以及通过session进行验证码校验的完整前后端代码和配置过程。
91 0
验证码案例 —— Kaptcha 插件介绍 后端生成验证码,前端展示并进行session验证(带完整前后端源码)
|
2月前
|
网络协议
keepalived对后端服务器的监测方式实战案例
关于使用keepalived进行后端服务器TCP监测的实战案例,包括配置文件的编辑和keepalived服务的重启,以确保配置生效。
57 1
keepalived对后端服务器的监测方式实战案例
|
1月前
|
前端开发 JavaScript Java
导出excel的两个方式:前端vue+XLSX 导出excel,vue+后端POI 导出excel,并进行分析、比较
这篇文章介绍了使用前端Vue框架结合XLSX库和后端结合Apache POI库导出Excel文件的两种方法,并对比分析了它们的优缺点。
225 0
|
3月前
|
Java 数据库连接 Spring
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
文章是关于Spring、SpringMVC、Mybatis三个后端框架的超详细入门教程,包括基础知识讲解、代码案例及SSM框架整合的实战应用,旨在帮助读者全面理解并掌握这些框架的使用。
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
|
3月前
|
前端开发 大数据 数据库
🔥大数据洪流下的决战:JSF 表格组件如何做到毫秒级响应?揭秘背后的性能魔法!💪
【8月更文挑战第31天】在 Web 应用中,表格组件常用于展示和操作数据,但在大数据量下性能会成瓶颈。本文介绍在 JavaServer Faces(JSF)中优化表格组件的方法,包括数据处理、分页及懒加载等技术。通过后端分页或懒加载按需加载数据,减少不必要的数据加载和优化数据库查询,并利用缓存机制减少数据库访问次数,从而提高表格组件的响应速度和整体性能。掌握这些最佳实践对开发高性能 JSF 应用至关重要。
68 0
|
3月前
|
存储 设计模式 运维
Angular遇上Azure Functions:探索无服务器架构下的开发实践——从在线投票系统案例深入分析前端与后端的协同工作
【8月更文挑战第31天】在现代软件开发中,无服务器架构因可扩展性和成本效益而备受青睐。本文通过构建一个在线投票应用,介绍如何结合Angular前端框架与Azure Functions后端服务,快速搭建高效、可扩展的应用系统。Angular提供响应式编程和组件化能力,适合构建动态用户界面;Azure Functions则简化了后端逻辑处理与数据存储。通过具体示例代码,详细展示了从设置Azure Functions到整合Angular前端的全过程,帮助开发者轻松上手无服务器应用开发。
29 0

热门文章

最新文章