问题记录
问题记录:redisson分布式锁和@Transactional在并发场景中的Bug
详细描述:线程A抢占到线程B未提交事务但已释放的锁的,以及线程A读取到未提交的数据,导致线程A处理用户流水明细记录时,出现流水账目错误。
业务场景:房间内用户可以相互打赏礼物,在高频刷礼物的场景下,导致用户余额扣增错误
解决方法:
1、通过生产环境的日志进行原因分析
2、猜测可能出现的错误原因
- 事务隔离机制导致的释放锁后,事务未提交,导致读取到旧数据
- 其它线程把正在执行任务线程的锁给释放了
3、模拟生产环境的业务代码,进行场景还原
4、确定问题是因为抢到锁之后,读取到了未提交的事务数据,问题进行修改
问题解决方案
原业务逻辑伪代码:
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public Action consume() {
//xxxx逻辑处理
Business giftShareAmountMessage = new Business();
serviceA.save(giftShareAmountMessage);
serviceB.operaUserAmountPolymerization(giftShareAmountMessage);
}
@Service
public class serviceB{
@Transactional(rollbackFor = Exception.class)
public void operaUserAmountPolymerization(){
boolean lock = redisLock.multiLock(lockKeys);
if (!lock) {
throw new ServiceException(ResponseCodeEnum.CHANGE_USER_AMOUNT_LOCK_FAIL);
}
selectUserAmountRecord();
saveBatchUserAmountDetail();
updateUserAmount();
}
}
调整后伪代码
public Action consume() {
//xxxx逻辑处理
Business giftShareAmountMessage = new Business();
serviceB.operaUserAmountPolymerization(giftShareAmountMessage);
}
@Service
public class serviceB{
public void operaUserAmountPolymerization(){
boolean lock = redisLock.multiLock(lockKeys);
if (!lock) {
throw new ServiceException(ResponseCodeEnum.CHANGE_USER_AMOUNT_LOCK_FAIL);
}
//手动开启事务
serviceA.save(giftShareAmountMessage);
selectUserAmountRecord();
saveBatchUserAmountDetail();
updateUserAmount();
//手动提交或者回滚事务
}
}
当Redisson分布式锁和事务一起使用时,可能会出现以下问题:
- 锁粒度过大:使用分布式锁时,锁的粒度通常是比较大的,即在一个事务中可能需要加锁的代码块比较多。这可能导致锁的粒度过大,造成了并发性能的损失。
- 死锁风险:在一些复杂的业务场景中,可能会出现多个并发事务之间相互等待对方释放锁的情况,从而导致死锁。这种情况下,需要谨慎设计事务和锁的顺序,以避免死锁风险。
- 锁超时问题:如果事务在加锁后执行时间过长,超过了锁的超时时间,那么其他事务可能会在此期间获取到锁并执行对应的操作,从而导致数据不一致的问题。为了避免这种情况,可以考虑增加锁的超时时间或者重新设计业务逻辑。
- 锁释放问题:在使用Redisson分布式锁时,需要确保在事务结束后正确释放锁。如果锁没有被释放,可能会导致其他事务无法获取到锁,从而影响系统的正常运行。
在并发场景中,结合Redisson分布式锁和@Transactional可能会遇到问题,如线程A读取到线程B未提交的事务数据,引发错误。在房间打赏礼物的业务中,发现用户流水账目错误。解决方案包括分析日志、模拟业务场景,调整业务逻辑,以降低锁粒度,避免死锁风险,处理锁超时和确保锁的正确释放。
3684

被折叠的 条评论
为什么被折叠?



