面试题答案
一键面试基于Redis分布式锁的库存管理方案
1. 库存扣减
- 加锁:使用Redis的SETNX(SET if Not eXists)命令获取分布式锁。例如,使用
SETNX lock_key value
,其中lock_key
是锁的唯一标识,value
可以是一个唯一的标识符(如UUID),用于后续解锁操作。 - 检查库存:在获取锁成功后,从Redis中获取当前库存数量,例如使用
GET stock_key
。 - 扣减库存:如果库存数量足够,进行库存扣减操作,如
DECRBY stock_key amount
,amount
为需要扣减的数量。 - 解锁:操作完成后,使用Lua脚本来确保解锁的原子性。例如:
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
通过EVAL script numkeys key [key ...] arg [arg ...]
执行该Lua脚本,KEYS[1]
为锁的键,ARGV[1]
为加锁时设置的唯一标识符。
2. 库存预警
- 定期检查:可以使用定时任务(如Spring Task、Quartz等)定期从Redis获取库存数量,例如
GET stock_key
。 - 触发预警:当库存数量低于设定的预警阈值时,触发预警机制,如发送邮件、短信通知相关人员。
3. 库存回滚(订单支付超时)
- 监听支付状态:通过消息队列(如RabbitMQ、Kafka等)监听订单支付状态。
- 支付超时处理:当检测到订单支付超时,获取对应的分布式锁(同库存扣减的加锁方式)。
- 库存回滚:获取锁成功后,从Redis获取当前库存数量并加上需要回滚的数量,如
INCRBY stock_key amount
。 - 解锁:使用上述相同的Lua脚本解锁。
处理分布式环境下的问题
1. 锁竞争
- 优化加锁逻辑:尽量缩短持有锁的时间,将不必要的操作放在锁外执行。
- 重试机制:当获取锁失败时,设置合理的重试次数和重试间隔,例如使用指数退避算法。以下是Java示例代码:
public boolean tryLock(String lockKey, String value, int retryCount, int baseSleepTime) {
while (retryCount > 0) {
if (stringRedisTemplate.opsForValue().setIfAbsent(lockKey, value)) {
return true;
}
try {
Thread.sleep(baseSleepTime * (1 << (retryCount - 1)));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
retryCount--;
}
return false;
}
2. 死锁
- 设置锁的过期时间:在加锁时,给锁设置一个合理的过期时间,如
SET lock_key value EX expiration_time
,expiration_time
为过期时间(单位秒)。这样即使在业务逻辑执行过程中出现异常导致未解锁,锁也会自动过期释放。 - 监控与报警:可以通过Redis的INFO命令监控锁的使用情况,当发现某个锁长时间未释放时,触发报警通知相关人员排查问题。