不能直接用INCR或HSET处理购物车,因为其操作需原子性(如“加1件但不超过库存”),单条命令无法完成读-判-写流程,易并发覆盖;Lua脚本能封装多步逻辑为原子操作,但须注意nil处理、KEYS传参、禁用非纯函数、避免长循环,并通过SCRIPT LOAD预热+EVALSHA调用以提升性能。

为什么不能直接用 INCR 或 HSET 处理购物车?
因为购物车操作常需原子性:比如「加1件但不超过库存」或「删除时同步更新总价」,单条 Redis 命令无法判断库存、读取旧数量再写回,中间可能被并发覆盖。Lua 脚本能将多步逻辑封装成一个原子操作,避免客户端多次往返和竞态。
常见错误是把 Lua 当普通脚本写——比如在脚本里调用 redis.call("GET", ...) 后用 tonumber 转换,却忽略 Redis 返回的可能是 nil,导致后续计算报错 attempt to perform arithmetic on a nil value。
- 所有 key 必须通过
KEYS参数传入,硬编码 key 名会导致集群模式下失败 - 脚本内不能使用
math.random()、os.time()等非纯函数,Redis 会拒绝执行 - 避免在脚本中做复杂循环(如遍历上百个商品),超时(默认 5s)会被强制终止
EVAL 和 EVALSHA 该选哪个?
开发阶段用 EVAL 直接提交脚本最方便;上线后必须切到 EVALSHA,否则每次请求都传输完整脚本,浪费带宽且影响性能。关键是预热:用 SCRIPT LOAD 提前加载脚本并拿到 SHA1 值,之后只传 SHA 和参数。
容易踩的坑是没做加载失败兜底——如果 SCRIPT LOAD 返回 OK 但后续 EVALSHA 报错 NOSCRIPT No matching script,说明 Redis 实例重启过,缓存清空了,得重新加载。
- Python 客户端(如 redis-py)调用
evalsha时,可设raise_on_error=False捕获NOLOAD错误,再 fallback 到eval - Node.js 的
ioredis自动处理EVALSHA失败重试,但需确认版本 ≥ 4.0 - SHA 值对脚本内容敏感,哪怕多一个空格都会变,CI/CD 中建议用文件哈希校验脚本一致性
购物车增删改查的 Lua 脚本怎么写才安全?
核心是统一用 Hash 结构存:cart:{user_id},field 是商品 ID,value 是 JSON 字符串(含数量、价格、选中状态等),而不是用多个 key 分散存储——否则没法原子读写整个购物车。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
示例:安全增加商品(检查库存 + 原子更新)
local cart_key = KEYS[1]
local item_id = ARGV[1]
local add_count = tonumber(ARGV[2])
local stock_key = "stock:" .. item_id
local cur_stock = redis.call("GET", stock_key)
if not cur_stock or tonumber(cur_stock) < add_count then
return {err="out_of_stock", stock=cur_stock or 0}
end
local cur_item = redis.call("HGET", cart_key, item_id)
local new_count = add_count
if cur_item then
local data = cjson.decode(cur_item)
new_count = data.count + add_count
if tonumber(cur_stock) < new_count then
return {err="exceed_stock", stock=cur_stock}
end
end
redis.call("HSET", cart_key, item_id, cjson.encode({count=new_count, price=ARGV[3], checked=true}))
return {ok=true, count=new_count}
- 必须用
cjson.decode/encode,别用string.format拼 JSON,易注入且不支持嵌套 - 库存检查放在写入前,且用
GET而非DECRBY,否则扣减后才发现不够,还得回滚 - 返回值用 table,Redis 会自动序列化为数组,客户端解析时注意类型(如 Lua 的
true变成 1)
如何调试 Lua 脚本里的逻辑错误?
Redis 不支持断点调试,唯一可靠方式是用 redis.log() 输出中间值,但仅在 loglevel 设为 debug 时生效,且日志刷盘有延迟,生产环境慎用。
更实用的方法:把脚本复制到本地 Lua 环境(如 Lua 5.1 + cjson 库)模拟运行,用固定 KEYS 和 ARGV 输入验证逻辑分支。注意 Redis 的 redis.call 在本地不可用,要 mock 返回值。
- 测试时重点覆盖边界:数量为 0、库存为 nil、JSON 解析失败、field 不存在等
- 用
redis-cli --eval直接运行脚本,比代码调用更快定位语法错误(如 missing comma) - 线上出问题时,先用
MONITOR抓到实际执行的EVAL命令,对比脚本 SHA 和参数是否匹配预期
真正麻烦的是跨服务协同逻辑,比如扣库存成功但订单创建失败,这时购物车状态和库存已不一致——Lua 只能保证 Redis 内原子,外部系统必须用补偿事务或消息队列对账。

















