redisson分布式锁跨微服务请求进程的加锁(重入)问题
来源:4-19 基于Redis实现真正高可用的锁--RedLock

weixin_慕移动7390343
2022-06-14
老师好,我在自己写一个团菜的微服务项目,有场景需要使用分布式锁,在使用redisson来控制时,发现原本是锁重入却导致了死锁,自己把自己锁死了。
我使用分布式锁的场景:
- 团长在团单活动进行中可以补充菜品的库存
- 跟单用户下单时会扣减菜品的库存
order微服务的跟单后台逻辑:
package com.pf.zjt.microservices.order.service.impl;
import ...
@Service
@Slf4j
public class OrderFollowSaveServiceImpl implements OrderFollowSaveService {
...
@Resource
private RedissonClient redissonClient;
@Override
public FoodCheckResultDto createOrderFollow(OrderFollowCreateDto followCreateDto) throws BusinessException {
// 前置检查操作
...
/*
* 先检查库存,再更新库存,在food微服务中提供了这两个rest api
* 为了防止多个跟单请求并发执行时,相互干扰,比如一个跟单请求在更新库存前,另一个跟单请求取到旧值下单,就会发生超卖的问题
* 因此这两块操作在一个分布式锁中
*/
String lockKey = ResourceTypes.FOOD_MAIN.formatKey(foodMainId);
RLock rLock = redissonClient.getLock(lockKey);
rLock.lock();
// 检查库存
FoodCheckResultDto followResultDto = foodFeignService.checkStock(foodMainId, checkList).getBody();
log.info("检查完库存");
...
// 更新库存
foodFeignService.updateStock(stockUpdateDto);
rLock.unlock();
// 跟单记录入库操作
...
return followResultDto;
}
...
}
food微服务提供一个更新库存的api,主要逻辑:
package com.pf.zjt.microservices.food.service.impl;
import ...
@Service
@Slf4j
public class FoodServiceImpl implements FoodService {
...
@Resource
private RedissonClient redissonClient;
...
@Override
public void updateStock(FoodStockUpdateDto updateDto) {
String lockKey = ResourceTypes.FOOD_MAIN.formatKey(updateDto.getFoodMainId());
log.info("准备更新库存,尝试获取锁...");
RLock rLock = redissonClient.getLock(lockKey);
/*
* 团长调用补充库存的rest api调用下面的更新库存逻辑前,需要加分布式锁
* 因为在没有获取锁就执行时,很可能在一个跟单请求执行扣减库存之前补充了库存,然后紧接着就被扣减库存覆盖更新了
*/
rLock.lock();
String foodKey = getRedisKey(updateDto.getFoodMainId());
boolean useCache = redisTemplate.hasKey(foodKey);
if (useCache) {
// 更新redis缓存的库存
}
// 更新数据库库存
rLock.unlock();
}
...
}
因此跟单请求再去调更新库存的接口时,其实做的应该时锁重入的操作。但是在本地调试时发现,这种跨服务进程的对同一资源的重复加锁操作却造成了死锁,难道说redisson的锁重入只是实现了对同一个服务器jvm线程级别的重入锁实现嘛?因为具体的源码我还没用调试跟踪。
以下是我的测试情况
首先准备好服务:
仅发送一个跟单请求来测试下:
order微服务控制台:
food微服务的控制台
一直在等待锁,最后超时了。。。
关于这个问题,老师能帮忙看下吗,在线等。。。感谢!
1回答
-
同学你好,抱歉这个问题刚看到。对于你这个案例我总结了以下几点内容,你可以参考一下:
首先这样肯定就不是重入锁了,重入锁是针对同一个线程来说的,也就是这里要同一个线程才可以多次加锁
这个地方这样写的话,相当于就是一个线程获取到了锁但还没有释放,另一个线程尝试来获取同一把锁(并且没有设置等待时间),所以会造成死锁问题
这里我不太确定的你库存检查,是否只是为了保证去更新库存的时候有足够的库存,如果是这样的话,可以在更新操作里面同时增加库存的判断,这一点我们在课程里面也有相关案例。如果是这样的话,foodFeignService.checkStock这一步其实可以不用的。然后在createOrderFollow这个方法里面不需要锁的处理,只需要保留updateStock里面的锁,这样不同线程要并发更新库存的时候,就有锁的约束。
最后假如说checkStock确实是需要和updateStock一起加锁处理,那么有两种处理方式:在createOrderFollow和updateStock这两个方法里面,要么使用两个不同的锁;要么把updateStock里面的锁给去掉(假如其他地方没有调用的话)。但是这种方式其实还是不建议,这样锁的粒度太大了。
如果同学对这个内容有疑惑的话,可以到咱们的QQ群来继续探讨和交流
012022-06-24
相似问题