事务中使用分布式锁问题
项目场景:
场景:监听订单中心支付完成消息,生成对应的用户订单,生成服务。加了分布式锁,但是还是有重复订单落库。
问题描述
集群模式下,采用分布式锁控制,保证只有一个服务能够得到执行。在获取到分布式锁之后,首先根据订单号查询有没有入库过,如果没有入库才能继续执行。因为要落多张表,所以在service层加了@Transactional控制事务。伪代码如下
@Transactional(rollbackFor = Exception.class) public void createOrder(String orderId) { // 使用redission获取分布式锁,获取不到锁时进行等待 lock.lock(); try { // 获取到锁之后,先查询订单有没有入库,如果入库则忽略 OrderDO existOrder = orderMapper.get(orderId); if (existOrder != null) { return; } // 以下落order表和orderService表 OrderDO newOrder = new OrderDO(); newOrder.setOrderId(orderId); orderMapper.insert(newOrder); OrderServiceDO orderService = new OrderServiceDO(); orderServiceMapper.insert(orderService); } finally { lock.unLock(); } }
原因分析:
分布式锁是在事务里面,假如有多个服务同时执行到了获取锁这一步,只会有一个服务能获取到锁,其他服务会等待锁的释放(redission是使用订阅的方式,由redis-server通知client锁的释放事件)。待方法业务逻辑执行完成之后,锁就进行了释放,但是事务还没有提交。其他服务这时获取到了锁,虽然在执行前有进行重复检查,但是因为前一个服务的事务还没有提交,这里是获取不到结果的(数据库隔离级别为可重复读),还是能正常执行下去。这就导致了重复数据入库。
解决方案:
1.分布式锁加事务的场景下,将分布式锁放在事务外面,当事务提交完成之后,才进行锁的释放。 2.数据库层面加唯一索引,防止重复的订单号入库。
上一篇:
通过多线程提高代码的执行效率例子