redis的lua脚本能保证原子性,因其在单线程中将整个脚本作为不可中断单元执行,期间其他命令必须排队;原子性仅限脚本内redis操作,外部调用不参与保障。

为什么 Redis 的 Lua 脚本能保证原子性
Redis 在执行 EVAL 或 EVALSHA 时,会把整个 Lua 脚本当作一个不可中断的单元运行:脚本执行期间,其他客户端命令必须排队等待。这不是靠锁实现的,而是 Redis 单线程模型 + 脚本内嵌执行机制共同决定的——只要脚本不调用阻塞命令(如 redis.call("KEYS", "*")),就不会中途让出控制权。
注意:原子性仅限于脚本内对 Redis 数据的操作;如果脚本里混了 os.time()、math.random() 或 HTTP 请求(需外部模块),那些部分不参与原子保障。
如何写一个带条件更新的 Lua 脚本(例如:余额扣减 + 记录日志)
典型场景:用户扣款前要检查余额是否充足,充足才扣减并写入操作日志。不能拆成 GET + DECRBY + LPUSH 三步,否则并发下可能超扣。
- 所有 key 必须通过
KEYS数组传入,不能硬编码;Redis 6.0+ 还要求只读命令也遵守该规则 - 使用
redis.call()执行 Redis 命令,失败会抛出异常并中止整个脚本;用redis.pcall()可捕获错误但不中止 - 返回值只能是 number、string、boolean、table(且 table 不能含函数或循环引用),否则客户端收不到正确结果
示例脚本(保存为 deduct.lua):
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
if redis.call("GET", KEYS[1]) == false then
return {err = "user not found"}
end
local balance = tonumber(redis.call("GET", KEYS[1]))
if balance <h3>怎么安全地在生产环境加载和调用 Lua 脚本</h3><p>直接用 <code>EVAL</code> 发送脚本文本容易触发 AOF 重写膨胀、网络开销大,且无法被 Redis 缓存复用。推荐先用 <code>SCRIPT LOAD</code> 预加载,再用 <code>EVALSHA</code> 调用。</p>
-
SCRIPT LOAD返回 SHA1 值,只需执行一次;若集群模式,需确保所有分片节点都加载过(Redis Cluster 不自动同步脚本) - 调用时
KEYS和ARGV必须严格按顺序传参,且数量不能超过 1024(Redis 默认限制) - 脚本超过 512KB 会被拒绝(
lua-maxmemory配置影响实际限制) - 调试时可用
redis.log(redis.LOG_WARNING, "balance:", balance),但上线前必须删掉,否则影响性能
调用示例(Redis CLI):
redis-cli SCRIPT LOAD "$(cat deduct.lua)" # 返回 "9e2b5a8e7a1f..." redis-cli EVALSHA 9e2b5a8e7a1f... 2 user:1001 log:user:1001 50 "deduct_20240520"
哪些坑会让 Lua 脚本看似原子、实则失效
最常见的是误以为“脚本执行快=安全”,忽略了 key 分布和集群约束。
- 在 Redis Cluster 中,所有
KEYS必须落在同一个 slot,否则报错CROSSSLOT Keys in request don't hash to the same slot;可加{user:1001}强制哈希到同 slot - 脚本里用了
redis.call("TIME")或redis.call("RANDOMKEY"),这些命令在只读副本上会失败(因 replica 默认禁用写命令) - 用
for i=1,1000 do redis.call("INCR", "counter") end看似简单,但实际执行 1000 次网络跳转模拟——不,Lua 脚本内redis.call是本地调用,但循环本身会拖慢单次响应,还可能触发lua-time-limit超时(默认 5 秒) - 忘记在客户端处理
EVALSHA返回NOSCRIPT错误:说明目标节点没加载该脚本,需 fallback 到EVAL
真正难的不是写脚本,而是判断哪些逻辑必须塞进 Lua,哪些可以拆到客户端做幂等校验。比如“发优惠券”这类业务,常需要查库存、扣库存、写订单、发 MQ,其中只有“查+扣库存”必须原子,其余步骤靠事务补偿更稳妥。










