不用incr而用lua是因为需原子性完成“判断短码是否存在→生成新id→写入映射”三步,避免并发时重复发号;lua脚本在redis中执行具有原子性,可彻底规避竞态条件。

为什么不用 INCR 而要套 Lua
直接用 INCR 看似能发号,但短链接生成器常需「原子性判断 + 生成 + 写入映射」三步,比如:若 short_url 不存在,才用新 ID 生成并存到 url:map。单纯靠客户端串行调用 EXISTS + INCR + HSET 会丢号或重复——并发时两个请求同时判空,都拿到同一个 INCR 结果,写进同一个短码。
用 Lua 把这三步锁死在服务端执行,就彻底规避竞态。Redis 执行单个 Lua 脚本是原子的,不被打断。
实操建议:
- 脚本里别做网络请求、sleep、循环上万次——它会阻塞整个 Redis 实例
- 所有 key 名必须提前用
KEYS[1]、KEYS[2]显式声明,不能硬编码;否则在集群模式下报错ERR hash slot not match - 返回值统一用
return输出整数或字符串,避免nil或嵌套 table——客户端解析容易出错
Lua 脚本怎么写才安全可靠
核心逻辑:检查短码是否已被占用 → 没有则取新 ID → 写入映射 → 返回短码;有则返回空或错误标识。不要在 Lua 里拼接 URL 或做 Base62 编码——那些该由客户端做,Lua 只管发号和防重。
示例脚本(保存为 gen_short.lua):
if redis.call('EXISTS', KEYS[1]) == 0 then
local id = redis.call('INCR', KEYS[2])
redis.call('HSET', KEYS[3], KEYS[1], id)
return id
else
return -1
end
说明:
-
KEYS[1]是待检查的短码(如"abc"),对应业务 key -
KEYS[2]是自增 counter key(如"short:id") -
KEYS[3]是映射哈希表(如"short:url_map") - 返回
-1表示冲突,客户端应重试或换码
客户端调用时的三个关键点
不是把脚本丢过去就行,参数传错、缓存没设、错误没判,上线后立刻出问题。
实操建议:
- 用
EVALSHA替代EVAL:先SCRIPT LOAD一次获取 sha1,后续全用EVALSHA,减少网络传输和 Redis 解析开销 - 务必设置超时:Node.js 的
redis.evalsha()、Python 的redis.evalsha()都支持timeout参数;否则 Lua 卡住,整个连接池可能被拖死 - 对返回值做严格分支处理:
if result === -1就换随机后缀重试,不能直接当成功 ID 用
集群环境下最容易漏掉的坑
本地单机 Redis 测试全过,一上集群就 ERR Cluster keys don't hash to the same slot——因为 KEYS[1](短码 key)和 KEYS[2](counter key)落在不同分片上,而 Lua 要求所有 key 必须同 slot。
解决办法只有一条:用 {} 强制 hash tag。
- 把所有 key 改成带相同 tag 的形式,例如:
"short:{abc}"、"short:{id}"、"short:{url_map}" - 这样它们都按
{abc}的 hash 计算,落到同一 slot - 注意:tag 内容必须完全一致,
{abc}和{ABC}是不同 slot
没加 tag 的脚本在集群里根本跑不起来,这个限制没法绕过,也别想用 redis.call('EVAL') 在服务端再 dispatch——语法不支持。










