SpringBoot中@Transactional与Redisson分布式锁结合使用时需要注意的问题

484 阅读3分钟

1.问题

//使用Redisson组件实现分布式锁
@Autowired
private Redisson redisson;

//创建资产的伪代码
@Transactional(rollbackFor = Exception.class)
public void save() {
    //获取锁
    RLock lock = redisson.getLock("lock_name");

     //根据资产唯一key查询数据库
     //判断资产是否存在,如果存在则直接返回并且提示资产已存在
     //如果资产不存在,则进行资产的创建
     
     //资产创建的逻辑.....

    //释放锁
    lock.unlock();
}

首先解释上面的代码逻辑:

  1. 因为创建资产逻辑中涉及到很多表的操作,所以需要保证原子性,因此使用了声明式事物@Transactional注解来保证其原子性.
  2. 又因为资产不能重复,所以需要保证唯一性,因此使用了锁(Redisson)的Lock使请求串行化,并且根据唯一key进行判断,以此来保证唯一性.

产生的问题:

  1. 首先资产表中的唯一标识没有加唯一索引(也是一个问题,但是不在本次讨论范围之内)
  2. 在uat环境的压测中发现偶尔会出现重复的资产数据

2.原因

首先说明我们Mysql数据库的隔离级别为:可重复读

  1. 使用了@Transactional注解,所以Spring会对这个接口进行代理,并且进行事物的控制,只有在当前方法执行完成之后,才会去提交事物或者回滚事物.
  2. 而lock.unlock()释放锁的逻辑是定义在当前方法中的,也就是说当前方法执行完成之后,锁就已经被释放掉了,这个时候后面的线程就能够获取锁执行业务逻辑了.
  3. 但是此时事物还没提交完成,因为隔离级别的原因,后面线程根据唯一key查询出来的结果为空,所以会继续执行后面的代码逻辑,从而就导致的数据的重复.

3.解决方案

1.将事物的代码放到lock的逻辑当中进行处理

//使用Redisson组件实现分布式锁
@Autowired
private Redisson redisson;

@Autowired
private TestService testService;

public void save() {
    //获取锁
    RLock lock = redisson.getLock("lock_name");
   
    //注意,这里不能直接调用当前类的方法,否则会使事物失效,因为调用当前类的方法时,没办法走代理逻辑
    //具体可以百度Spring事物失效的几种情况
    testService.save();

    //释放锁
    lock.unlock();
}


@Service
public class TestService{

    @Transactional(rollbackFor = Exception.class)
    public void save() {
         //根据资产唯一key查询数据库
         //判断资产是否存在,如果存在则直接返回并且提示资产已存在
         //如果资产不存在,则进行资产的创建

         //资产创建的逻辑.....
     }
}

2.使用编程式事物的方式进行处理

//使用Redisson组件实现分布式锁
@Autowired
private Redisson redisson;

@Autowired
private PlatformTransactionManager transactionManager;

public void save() {
    //获取锁
    RLock lock = redisson.getLock("lock_name");
    
    //手动获取一个事物
    //这里只说明解决问题的思路,一般项目不需要这么写编程式事物,都会进行封装,然后直接使用
    TransactionStatus transaction = transactionManager.getTransaction(new DefaultTransactionDefinition());
    
    try {
        //根据资产唯一key查询数据库
        //判断资产是否存在,如果存在则直接返回并且提示资产已存在
        //如果资产不存在,则进行资产的创建

        //资产创建的逻辑.....

        //手动提交事物
        transactionManager.commit(transaction);
    } catch (Exception e) {
        //手动回滚事物
        transactionManager.rollback(transaction);
    }
    
    //释放锁
    lock.unlock();
}

}

4.参考来源

  1. segmentfault.com/q/101000004…
  2. mp.weixin.qq.com/s/BblYtJ17A…