首页 > 数据库 >记录一次Redisson使用synchronized和分布式锁不生效的原因

记录一次Redisson使用synchronized和分布式锁不生效的原因

时间:2024-05-26 16:31:02浏览次数:14  
标签:方案 事务 Redisson synchronized Spring Transactional lock modelId 分布式

最近在开发的过程中,遇到了一个并发场景,用户进行方案复制的时候,当快速点击两次操作的时候,出现了复制方案重名的情况,实际上是复制方案的方案名称,是由后端根据数据库已有的方案名称和当前要复制的方案名称进行逻辑处理,保证方案名称不能重复,比如:要复制的方案名称为“我的方案”,那么复制得到的方案名称为“我的方案-副本”,在高并发场景下,就会出现重名情况。

1. 并发原因

每次在复制方案的时候,会有如下步骤:

  1. 首先校验要复制的方案是否存在。
  2. 查询所有已经存在的方案的所有名称。
  3. 根据要复制方案的名称生成一个新的方案名称,比如“某某方案-副本”。
  4. 新生成的方案是否和已存在的方案名称重名,如果重名,则添加后缀,比如“某某方案-副本(2)”。
  5. 最终做新方案的落库操作。

不知道大家有没有看到里面在高并发情况下存在的问题,当步骤五还没有落库,就已经有线程2进来,执行了查询操作,最后线程2落库生成的名称就会和线程1生成的方案名称重复。

@Transactional(rollbackFor = Exception.class)
public  void xxxCopy(Long modelId) throws GseException {
    	//业务逻辑代码
}

2. 初步解决办法

2.1 本地锁方式

我在本地做了两种尝试,首先通过本地锁(比如synchronized,Lock)相关手段进行锁定,当然这种肯定不能上生产,因为当多节点部署的时候,这种本地锁没有任何意义。

@Transactional(rollbackFor = Exception.class)
public  void xxxCopy(Long modelId) throws GseException {
    synchronized (this) {
       //业务逻辑代码
    }
}

这种写法没有生效,在我进行本地压测,开启多个线程的情况下,还是出现了重名情况,具体原因我待会会给大家分析。

2.2 分布式锁

这种才是生产上高并发经常会用到的,因为生产时多prod,采用本地锁没有任何意义,分布式锁我采用的是Redisson方案,相比较自己去写分布式锁,更稳定,更成熟。

    @Autowired
    private RedissonClient redissonClient;
    private static final String REDIS_COST_MODEL_ID_LOCK = "redis_cost_model_id_lock";
    
@Transactional(rollbackFor = Exception.class)
public  void xxxCopy(Long modelId) throws GseException {
    RLock lock = redissonClient.getLock(REDIS_COST_MODEL_ID_LOCK + modelId);
        try {
            if (lock.tryLock(20, 2, TimeUnit.SECONDS)) {
                //业务逻辑代码
            } else {
                log.error("获取分布式锁失败");
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            // 判断当前线程是否持有锁
            if (lock.isHeldByCurrentThread()) {
                //释放当前锁
                lock.unlock();
                log.info(Thread.currentThread().getName() + "释放锁" + LocalDateTime.now());
            }
        }
}

但是,这种写法没有生效,在我本地压测的时候,还是存在重名问题。

3. 存在的问题以及原因

问题就是以上两种写法都没有生效,但是为什么呢?
在解释这个问题之前,我们首先要弄清楚两个问题:

  1. @Transactional的底层实现原理,开启事务和提交事务的时机是什么?
  2. 分布式锁,和本地锁机制释放锁的时机是什么时候?

3.1 @Transactional的底层实现原理,开启事务和提交事务的时机是什么?

它的底层实现原理主要依赖于 Spring 的面向切面编程(AOP)机制。
底层实现原理

  1. AOP 代理:当一个类或方法被 @Transactional 注解标记时,Spring 容器在初始化 Bean 时会检测到这个注解。对于使用 Spring 的代理模式(如 JDK 动态代理或 CGLIB),Spring 会为该 Bean 创建一个代理对象。这个代理对象会在调用实际方法前后插入事务管理相关的代码,即在方法执行前开启事务,在方法执行完毕后根据执行情况提交或回滚事务。

  2. 解析注解:Spring 通过扫描 Bean 定义,识别出带有 @Transactional 注解的方法或类,并配置相应的事务属性,如传播行为、隔离级别、超时时间、是否只读等。

  3. 事务拦截器:Spring 使用 AOP 机制中的拦截器(Interceptor)或Advice(通常为 TransactionInterceptor 或 AspectJ 的切面),在方法调用前后织入事务处理逻辑。在方法调用前,根据事务属性设置事务的开始;在方法正常结束时提交事务,如果方法抛出未检查异常(继承自 RuntimeException 的异常)或已检查异常(被 @Transactional 的 rollbackFor 属性指定的异常)则回滚事务。

开启事务和提交事务的时机

  1. 开启事务:事务通常在进入被 @Transactional 注解的方法之前立即开始。这意味着在执行业务逻辑之前,Spring 会确保与当前环境匹配的事务上下文已经建立。这包括选择合适的事务管理器,根据事务属性配置事务的隔离级别、传播行为等,并在数据库中实际开启事务。

  2. 提交事务:如果被注解的方法正常执行结束,没有抛出任何异常,Spring 会在离开该方法之前提交事务。提交事务意味着将所有挂起的更改永久化到数据库中,使事务中的所有操作对外可见。

  3. 回滚事务:如果在被注解的方法执行过程中抛出了异常,并且该异常未被 @Transactional 的 noRollbackFor 属性豁免,Spring 将在捕获到异常后立即回滚事务,撤销所有在事务中已完成但未提交的操作,保持数据的一致性。

3.2 分布式锁,和本地锁机制释放锁的时机是什么时候?

答案是:本地锁,如果是synchronized,看你包裹起来的范围。Lock的话 看你手动释放锁的时候。
分布式锁:看你手动释放锁的时候。

那么造成问题的原因就出来了,如下图:

在这里插入图片描述
也就是说最终提交事务和释放锁的顺序有问题,按照上面的代码写法,因为当只有方法执行完了,AOP切面才会提交事务,那么如果你将上锁的代码写到被@Transactional注解的方法里面,那么提交事务永远都会处于释放锁之后,那么在释放锁之后,提交事务之前的这段时间,就会有并发问题。

4. 正确的写法

4.1 本地锁

    public  void addLock(Long modelId)throws GseException {
        synchronized (this) {
            xxxCopy(modelId);
        }
    }
    
    @Transactional(rollbackFor = Exception.class)
	public  void xxxCopy(Long modelId) throws GseException {
	    synchronized (this) {
	       //业务逻辑代码
	    }
	}

4.2 分布式锁

public void addLock(Long modelId) throws GseException {
        RLock lock = redissonClient.getLock(REDIS_COST_MODEL_ID_LOCK + modelId);
        try {
            if (lock.tryLock(20, 2, TimeUnit.SECONDS)) {
                xxxCopy(modelId);
            } else {
                log.error("获取分布式锁失败");
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            // 判断当前线程是否持有锁
            if (lock.isHeldByCurrentThread()) {
                //释放当前锁
                lock.unlock();
                log.info(Thread.currentThread().getName() + "释放锁" + LocalDateTime.now());
            }
        }

    }

@Transactional(rollbackFor = Exception.class)
public  void xxxCopy(Long modelId) throws GseException {
    synchronized (this) {
       //业务逻辑代码
    }
}

这样的写法,成功避免了并发问题,被@Transactional注解的方法,在执行完毕以后,就会提交事务,然后到了调用方法里面,再去释放锁。

标签:方案,事务,Redisson,synchronized,Spring,Transactional,lock,modelId,分布式
From: https://blog.csdn.net/wu2374633583/article/details/139109944

相关文章

  • git分布式版本控制系统(一)
    目前世界上最先进的分布式版本控制系统官方网址:https://git-scm.com学习目标:1了解git前世今生2掌握git基础概念、基础操作3各种git问题处理4互联网常用gitflow(工作流程规范)5git代码提交规范6git分支管理及命名规范版本控制系统发展史1.1本地版......
  • 【赛题解析】【网络建设与运维】2023年全国职业院校技能大赛中职组“网络建设与运维”
    在此之前,欢迎关注波比网络波比网络官方公众号:blbinet波比网络工作室官方公众号:blbistudio技能大赛各赛项交流群:https://www.blbi.cn/threads/40/更多正式赛题源文件访问:https://www.blbi.cn获取技术支持访问:https://www.blbi.cn/form/1/selectNISP、CIPS、PTE证书可......
  • 大数据技术原理(二):搭建hadoop伪分布式集群这一篇就够了
    (实验一搭建hadoop伪分布式)--------------------------------------------------------------------------------------------------------------------------------一、实验目的1.理解Hadoop伪分布式的安装过程实验内容涉及Hadoop平台的搭建和配置,旨在提高对大数据处理框......
  • VMware上基于centos系统完全分布式Hadoop集群的搭建详解
    目录1.centos系统的环境的准备1.1.样本机的配置1.1.1.准备一个centos的虚拟机1.1.2.关闭系统的防火墙1.1.3.配置centos系统的IP1.1.4.修改主机名称1.1.5. 配置hosts映射文件1.2.jdk的安装1.2.1.jdk的下载1.2.2.虚拟机自带jdk的删除1.2.3.将jdk上传到虚拟机中1.2.4......
  • SpringCloud + Python 混合微服务架构,打造AI分布式业务应用的技术底层
    文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录博客园版为您奉上珍贵的学习资源:免费赠送:《尼恩Java面试宝典》持续更新+史上最全+面试必备2000页+面试必备+大厂必备+涨薪必备免费赠送:《尼恩技术圣经+高并发系列PDF》,帮你实现技术自由,完成职业升级,薪......
  • 端到端自适应大规模分布式训练技术
    端到端自适应大规模分布式训练技术随着2020年GPT-31750亿超大语言预训练模型的提出,语言、视觉、多模态等领域也随即发布多种超大规模预训练模型,不仅模型参数量越来越大,训练数据量和计算量也相应变大。针对大规模稠密参数模型高效训练问题,飞桨于2021年初在业内首发4D混合......
  • 分布式计算编程项目二
    利用RPC技术实现一个学生信息管理系统目录利用RPC技术实现一个学生信息管理系统一、具体要求二、相关理论理论迭代1.单机结构2.集群结构3.微服务结构微服务介绍RPC介绍使用到的相关包MySQL启动方法三、代码架构四、功能实现+界面展示基本功能:技术点:1.查询速度提高2.数据模型和......
  • 分布式任务调度内的 MySQL 分页查询优化
    作者:vivo互联网数据库团队- QiuXinbo本文主要通过图示介绍了用主键进行分片查询的过程,介绍了主键分页查询存在SQL性能问题,如何去创建高效的索引去优化主键分页查询的SQL性能问题。对于数据分布不均如何发现,提供了一些SQL查询案例来进行参考,对MySQLIndexConditionPushdown......
  • 分布式任务调度内的 MySQL 分页查询优化 等值在前,排序在中间,范围在最后
    分布式任务调度内的MySQL分页查询优化https://mp.weixin.qq.com/s/VhSzxYIRv83T3D3JD4cORg三、优化方案 3.1优化方案确定 当前SQL执行计划以主键进行顺序遍历,是一个范围扫描,有点像在一片很大的居民区按照序号挨家挨户寻找一些特定的人一样,比较简单也比较低效。 既然......
  • 分布式系统
    什么是分布式?分布式系统一定是由多个节点组成的系统。其中,节点指的是计算机服务器,而且这些节点一般不是孤立的,而是互通的。分布式与集群的区别?集群:集群是指在几个服务器上部署相同的应用程序来分担客户端的请求。它是同一个系统部署在不同的服务器上,比如一个登陆系统部署......