一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

如何通过Redis Lua脚本实现分布式信号量控制

时间:2026-07-16 08:14:48 编辑:袖梨 来源:一聚教程网

不能直接用 INCR + EXPIRE 实现信号量,因为二者非原子执行,中间可能中断导致 key 有值无过期或设了过期却未自增,引发超卖;必须用 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])endlocal current = tonumber(redis.call('GET', KEYS[1]))if current and current < tonumber(ARGV[1]) then  redis.call('INCR', KEYS[1])  return 1else  return 0end

EVALSHA 替代 EVAL 有什么坑?

脚本 SHA1 值需要提前 SCRIPT LOAD,否则 EVALSHA 会返回 NOSCRIPT 错误。更关键的是:集群模式下,所有 KEYS 必须落在同一个 slot,否则 Redis Cluster 会拒绝执行——这意味着信号量 key 必须带固定 hash tag,比如 semaphore:{order},否则 EVALSHA 在集群中直接失败。

容易忽略的点:

  • 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)不能设太长,也不能太短——要略大于最长业务耗时。

热门栏目