首页 > 数据库 >Redis Lua脚本实现分布式信号量控制

Redis Lua脚本实现分布式信号量控制

来源:互联网 2026-07-15 19:45:06

利用RedisLua脚本实现分布式信号量控制,通过原子操作避免INCR与EXPIRE非原子问题。acquire脚本检查计数并自增且设置过期,release脚本递减计数。集群环境需添加hashtag,客户端需处理网络异常与僵尸锁问题。

先说说最常见的一个坑:直接用 INCREXPIRE 来做信号量,看起来简单,但线上大概率会翻车。问题就出在“原子性”上——这两条命令不是一起执行的,中间随时可能被其他请求打断,或者网络超时只执行了一半。结果就是:key 有值但没设过期时间,或者设了过期时间但自增没成功,后续请求一拥而入,超卖、并发越界全都来了。

Redis Lua脚本实现分布式信号量控制

长期稳定更新的攒劲资源: >>>点此立即查看<<<

为什么不能直接用 INCR + EXPIRE 实现信号量?

两条命令非原子执行,中间被中断或超时后,key 要么有值但没过期时间,要么过期时间设了但没成功自增。后续请求一查,发现还能加,接着就超卖了。只有在 Lua 脚本里,Redis 才能保证“检查 → 加锁 → 设过期”三步走完,中间不被任何操作插队。

EVAL 脚本里怎么安全地实现 acquire 和 release?

acquire 脚本的逻辑很直接:先看当前计数是否小于最大许可数,满足条件才执行 INCR 并设置过期时间;release 则是先检查 key 是否存在、是不是数字类型,再 DECR,归零后可以顺手删掉 key。这里有个容易踩的坑:不能先用 GET 拿到值再自己判断,必须用 redis.call('GET', KEYS[1]) 配合类型检查,否则空值或字符串值会让 INCR 直接报错 ERR value is not an integer or out of range

一个常见的 acquire 脚本写法参考:

if redis.call('EXISTS', KEYS[1]) == 0 then
  redis.call('SET', KEYS[1], 0)
  redis.call('EXPIRE', KEYS[1], ARGV[2])
end
local current = tonumber(redis.call('GET', KEYS[1]))
if current and current < tonumber(ARGV[1]) then
  redis.call('INCR', KEYS[1])
  return 1
else
  return 0
end

EVALSHA 替代 EVAL 有什么坑?

脚本的 SHA1 值需要提前用 SCRIPT LOAD 加载,否则直接 EVALSHA 会返回 NOSCRIPT 错误。更关键的是集群环境:所有 KEYS 必须落在同一个 slot,否则 Redis Cluster 直接拒绝执行。解决方案是给信号量 key 加上固定 hash tag,比如 semaphore:{order},这样集群才能正确路由。

容易忽略的细节还有:

  • SCRIPT LOAD 返回的 SHA 是字符串,调用 EVALSHA 时必须原样传入,大小写敏感
  • 脚本中所有 key 必须通过 KEYS 数组传入,硬编码 key 名会导致集群路由失败
  • 释放操作如果用 DEL 粗暴删除,可能误删别人刚 acquire 的计数,应该只 DECR 不删

客户端如何处理 acquire 失败和连接异常?

Redis 执行 Lua 后返回整数:1 表示成功,0 表示拒绝。但网络中断、timeout、LOADING 状态等不会返回 0,而是直接抛异常。这时候不能简单重试,否则可能重复 acquire——必须配合唯一 client token(比如 UUID)写入 value,或者用 HASH 结构记录持有者,否则你根本分不清“到底有没有拿到锁”。

更现实的做法是:把信号量 key 的 value 设计成 current_count:timestamp:client_id 这类复合结构,方便 debug 和主动清理过期持有者。当然,脚本会因此变复杂,要不要这么做,取决于你是否真的需要强持有追溯能力。

真正难处理的是:acquire 成功但客户端在后续业务逻辑中崩溃,又没来得及 release。这时只能靠 EXPIRE 时间兜底,所以 ARGV[2](TTL)不能设太长,也不能太短——要略大于最长业务耗时,才能确保不留下“僵尸锁”。

侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述

热游推荐

更多
湘ICP备2026025700号-3 湘公网安备 43070302000280号
All Rights Reserved
本站为非盈利网站,不接受任何广告。本站所有软件,都由网友
上传,如有侵犯你的版权,请发邮件给xiayx666@163.com
抵制不良色情、反动、暴力游戏。注意自我保护,谨防受骗上当。
适度游戏益脑,沉迷游戏伤身。合理安排时间,享受健康生活。