分布式锁的使用——不要把锁加在事务内!

✅用了"一锁二判三更新",但是幂等被击穿

code 案例

在我们的项目中,我们很多地方都会为了避免并发,增加分布式锁,并且会采用一锁、二判、三更新的方式实现一个幂等的逻辑。

同时,为了方便大家使用分布式锁,我们自己定义了一个@DistributeLock的直接。也是就有以下代码:

同一个方法中,增加了多个注解,同时有@DistributeLock和@Transactional 两个注解。

这种情况在,我们自定义的注解@DistributeLock的切面默认会在最后执行,于是这段代码就是会先执行事务,然后再执行加锁。

最终的逻辑就像下面这段代码一样:

csharp 复制代码
@Transactional(rollbackFor = Exception.class)
public boolean register(Request request) {
    RLock lock = redisson.getLock(request.getIdentifier());
    try {
        //一锁       
        lock.lock();
        //二查        
        User user = userMapper.find(request.getIdentifier());
        if (user != null) {
            return false;
        }
        //三更新,保存订单数据    
        userMapper.insertOrder(request);
    } finally {
        lock.unlock();
    }
    return true;
}

按照这个顺序执行的:

  1. 进入事务
  2. 加锁
  3. 解锁
  4. 事务提交

这时候就会出现一种情况,在第三步和第四步中间,如果有一个其他的线程也调用这个 register 方法了。

那么就会出现一个问题,锁已经释放了,但是事务还没提交。这时候其他的线程在并发请求过来的时候:

一锁。拿锁可以拿到,因为锁被释放了

二查。查询数据也查不到,因为这时候之前的那个事务可能还没提交,未提交的数据,新的事务是看不到的。

三更新。执行更新操作,导致数据重复或者报错。

这就是我们需要解决的问题,那么看上去就是事务的切面执行顺序的问题,我们应该让锁的粒度大于事务的粒度就能解决了这个问题了。

那么,就想办法让分布式锁的注解的切面先执行。解决办法就是借助@Order 注解,他可以直接用在切面类上,用于指定切面的执行顺序。值越小,优先级越高,切面会越早执行。

所以,修改后的分布式锁的切面类如下:

新增 Order 注解,把他的优先级设置为最小值,即优先级最高,最先开始执行即可。

总结

在使用分布式锁的时候,习惯性的尽量缩小同步代码块的范围

但是如果数据库隔离级别是可重复读,这种情况下不要把分布式锁加在@Transactional注解的事务方法内部。

因为可能会出现这种情况:

线程1开启事务A后获取分布式锁,执行业务代码后在事务内释放了分布式锁。这时候线程1开启了事务B获取到了线程1释放的分布式锁,执行查询操作时查到的数据就可能出现问题。因为此时事务A是在事务内释放了锁,事务A本身还没有完成提交

相关推荐
杨DaB2 小时前
【SpringMVC】拦截器,实现小型登录验证
java·开发语言·后端·servlet·mvc
努力的小雨8 小时前
还在为调试提示词头疼?一个案例教你轻松上手!
后端
魔都吴所谓9 小时前
【go】语言的匿名变量如何定义与使用
开发语言·后端·golang
陈佬昔没带相机9 小时前
围观前后端对接的 TypeScript 最佳实践,我们缺什么?
前端·后端·api
Livingbody11 小时前
大模型微调数据集加载和分析
后端
Livingbody11 小时前
第一次免费使用A800显卡80GB显存微调Ernie大模型
后端
Goboy12 小时前
Java 使用 FileOutputStream 写 Excel 文件不落盘?
后端·面试·架构
Goboy12 小时前
讲了八百遍,你还是没有理解CAS
后端·面试·架构
麦兜*12 小时前
大模型时代,Transformer 架构中的核心注意力机制算法详解与优化实践
jvm·后端·深度学习·算法·spring·spring cloud·transformer
树獭叔叔12 小时前
Python 多进程与多线程:深入理解与实践指南
后端·python