面试题答案
一键面试使用Redis单命令特性保障多命令原子性
在Redis中,可以使用 SET key value [EX seconds] [PX milliseconds] [NX|XX]
这一单命令来替代 SETNX
和 EXPIRE
的组合。该命令在设置键值对的同时可以设置过期时间,并且只有在键不存在时才会设置成功,从而保证了加锁和设置过期时间这两个操作的原子性。
SETNX 和 EXPIRE 组合的问题
- 原子性问题:
SETNX
和EXPIRE
是两个独立的命令,在高并发场景下,当SETNX
执行成功后,还未来得及执行EXPIRE
时,系统发生崩溃或其他异常情况,此时该锁没有设置过期时间,可能会导致死锁。 - 竞争条件:在多节点的分布式系统中,不同节点执行
SETNX
和EXPIRE
命令的时间差可能会引发竞争条件,导致锁的控制出现混乱。
改进方法
- 使用 SET 命令替代:如前文所述,使用
SET key value EX seconds NX
命令来替代SETNX
和EXPIRE
的组合,确保加锁和设置过期时间的原子性。 - Lua 脚本:通过Lua脚本来保证多个命令的原子性。Lua脚本在Redis中是原子执行的,可以将多个相关命令封装在一个Lua脚本中,从而避免多命令执行过程中的竞争条件。例如:
-- 获取参数
local key = KEYS[1]
local value = ARGV[1]
local expireTime = ARGV[2]
-- 尝试加锁
if (redis.call('SETNX', key, value) == 1) then
-- 加锁成功,设置过期时间
redis.call('EXPIRE', key, expireTime)
return 1
else
-- 加锁失败
return 0
end
在客户端使用时,通过 EVAL
命令执行该Lua脚本,这样即使在高并发场景下,也能保证加锁和设置过期时间操作的原子性。