一、背景前言
前一段做的一个抢购秒杀的功能,用户只能限量抢到一个商品,但是后来发现有个用户抢到了两个商品?!排查问题时发现,是spring事务提交和redis锁释放的顺序,与预想中不一样导致的。
二、问题描述
@Transactional(rollbackFor = Exception.class)
public void test() {
if (!redisLock.tryLock("lock_1", "123", 5L, TimeUnit.SECONDS)) {
throw new ServiceException("已被其他线程锁住!");
}
try {
// 业务逻辑
} finally {
redisLock.releaseLock("lock_1", "123");
}
}
debug发现:
1. \color{#FF7D00}{1.} 1. 线程A,先执行finally语句,导致锁释放掉了,这时候,线程A事务还没提交;
2. \color{#FF7D00}{2.} 2. 线程B,拿到锁,后面在不发生异常的情况下,线程A和线程B的事务都会正确提交;
三、解决方案
1. \color{#FF7D00}{1.} 1. 如果事务里是insert方法,可以不要锁,改成数据库表里设置唯一索引的方法;
2. \color{#FF7D00}{2.} 2. 事务和锁分开,锁提取到controller层;或者一个无事务的service方法中加锁,锁住一个有事务的方法;