项目场景
场景:监听订单中心支付完成消息,生成对应的用户订单,生成服务。加了分布式锁,但是还是有重复订单落库。
问题描述
集群模式下,采用分布式锁控制,保证只有一个服务能够得到执行。在获取到分布式锁之后,首先根据订单号查询有没有入库过,如果没有入库才能继续执行。因为要落多张表,所以在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.数据库层面加唯一索引,防止重复的订单号入库。
评论区