7. 事务与 Lua 脚本
7.1 MULTI / EXEC 事务
🚀 比喻:MULTI 就像在购物车里「先加购,最后一并结算」。期间所有命令排入队列,EXEC 时一次性执行。
bash
# 开始事务
MULTI
# 以下命令进入队列
SET account:A 800
SET account:B 1200
# 执行事务
EXEC
# 查看执行结果
GET account:A
GET account:Bbash
OK
QUEUED
QUEUED
1) OK
2) OK
"800"
"1200"bash
# 放弃事务
MULTI
SET test_key "value"
DISCARDbash
OK
QUEUED
OK7.2 WATCH —— 乐观锁
👀 WATCH 可以监视一个或多个键,如果在 EXEC 之前这些键被其他客户端修改,事务将自动放弃。实现类似 CAS(Compare-And-Swap)的效果。
bash
# 场景:转账 100 元从 A 到 B
# 客户端 1:监视余额
WATCH account:A
# 读取当前余额
GET account:A
# 假设返回 "1000"
# 开始事务
MULTI
DECRBY account:A 100
INCRBY account:B 100
EXEC
# 如果在 WATCH 和 EXEC 之间,其他客户端修改了 account:A
# 则 EXEC 返回 nil(事务被放弃),应用层需重试bash
OK
"1000"
OK
QUEUED
QUEUED
1) (integer) 900
2) (integer) 1100⚠️ 注意: Redis 事务不支持回滚(Rollback)。如果事务中某条命令执行失败,其他命令仍会执行。设计哲学是「保持简单和高效」。
7.3 Lua 脚本 —— 真正的原子操作
⚡ 比喻:如果说 MULTI/EXEC 是「打包执行」,那 Lua 脚本就是「写一段小程序」在服务器端运行,天然原子性,比事务更强大。
bash
# EVAL 语法:EVAL script numkeys key [key...] arg [arg...]
# 场景:扣减库存(原子操作,防止超卖)
EVAL "
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock and stock > 0 then
redis.call('DECR', KEYS[1])
return 1
else
return 0
end
" 1 stock:product:1001bash
(integer) 1bash
# 场景:分布式锁(加锁 + 设置过期时间的原子操作)
EVAL "
if redis.call('SET', KEYS[1], ARGV[1], 'NX', 'EX', ARGV[2]) then
return 1
else
return 0
end
" 1 lock:order:1001 "owner_001" 10bash
(integer) 1bash
# 场景:限流器(滑动窗口限流)
EVAL "
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
local count = redis.call('ZCARD', key)
if count < limit then
redis.call('ZADD', key, now, now .. '-' .. math.random(100000))
redis.call('EXPIRE', key, window)
return 1
else
return 0
end
" 1 rate:api:user1001 100 60 1704067200bash
(integer) 1💡 提示: Lua 脚本会被缓存到 Redis 中,可以用EVALSHA通过 SHA1 摘要调用,避免重复传输完整脚本,提升性能。