Redis分布式锁需满足互斥性、安全性、超时释放、原子性及可重入性;核心用SET key value NX EX t原子加锁,Lua脚本校验值后删锁防误删,Redisson提供自动续期等高级特性。

缓存未命中时多个请求同时查库,怎么只让一个去执行?
核心是“请求排队”:当 redis.get(key) 返回 null,不能直接放行所有请求去查数据库,必须用某种机制让其余请求暂停,等第一个请求完成并写入缓存后,再统一返回结果。
Node.js 单线程特性反而帮了忙——不需要处理多线程锁的可见性问题,但得小心 Promise 状态共享和竞态。常见错误是每个请求都新建一个 Promise 去查库,导致 10 个并发请求各自触发一次 DB 查询。
- 用一个全局
Map存储正在重建缓存的 Promise:pendingPromises.set(key, promise) - 后续同 key 请求先查这个 Map,命中就直接
await它,不新建 Promise - Promise resolve 后立即
pendingPromises.delete(key),避免内存泄漏 - 务必加
catch并主动delete,否则失败后该 key 会永远卡在 pending 状态
用 Redis SETNX 实现分布式锁,为什么不能只靠它?
单纯用 SET key value NX EX 30 加锁,在 Node.js 中容易漏掉两个关键环节:锁释放时机不可控、锁被其他实例误删。比如请求 A 拿到锁,查库花了 35 秒,锁自动过期;此时请求 B 成功加锁,而请求 A 在写缓存时仍会执行 DEL key,把 B 的锁干掉了。
安全做法是用 Lua 脚本保证“判断+删除”原子性,且锁值必须是唯一标识(如 uuidv4()),不能写死。
- 加锁:用
client.set(key, randomValue, 'NX', 'EX', 30),不是SETNX命令(ioredis 已弃用) - 解锁:必须用 Lua 脚本,检查 value 相等才删,示例:
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end - 别在
try/finally里无条件DEL,那是典型误删来源 - 锁超时时间要明显大于 DB 查询 + 缓存写入耗时,建议设为后者 2–3 倍
为什么用 EventEmitter 实现等待比纯 Promise Map 更稳?
当服务重启或进程崩溃,Map 里的 pending Promise 全丢,新请求又得重来一遍;而 EventEmitter 的 once 是事件驱动模型,天然支持“发布-订阅”,只要事件名一致,哪怕中间有延迟,监听者也能收到通知。
使用一条命令部署ProbeChain Rydberg测试网代理节点。自动注册为Agent(NodeType=1),免gas,支持macOS/Linux/Windows。触发词:/r
不过要注意:事件名必须带业务上下文,比如 cache:rebuild:user:123,不能只用 rebuild,否则不同 key 的重建会互相干扰。
- 首次请求触发
emitter.emit(key, result),之后所有emitter.once(key)都能拿到结果 - 必须在 Promise resolve 后立刻 emit,reject 时也 emit(传 error 或 null)
- Node.js 的
EventEmitter默认最大监听数 10,高并发下需调用emitter.setMaxListeners(0) - 不用手动清理监听器,
once自动移除,比 Map 的 delete 更省心
本地内存锁(Mutex)在集群部署下完全失效
如果你用 const mutex = new Mutex() 这类纯内存锁,它只在当前 Node 进程内有效。一旦部署多实例(PM2 cluster、K8s 多 Pod),每个实例都有自己的 mutex 实例,根本起不到互斥作用——10 个实例各放行一个请求,还是 10 次 DB 查询。
真正的分布式场景,锁必须落在共享存储上。Redis 是最直接选择,但要注意:单点 Redis 故障会导致全部请求阻塞,生产环境建议搭配降级策略(如允许少量击穿,或 fallback 到本地 LRU 缓存)。
- 不要在开发环境用内存锁测试,然后上线换 Redis 锁——行为差异太大
- ioredis 的
lock插件(如 redlock)比手写 SETNX 更可靠,内置租约续期和故障转移逻辑 - 如果 Redis 不可用,mutex 应快速失败(比如
acquire(timeout=100)),而不是无限等待 - 锁粒度宁细勿粗,按业务 key 分,别用一个全局锁锁住所有缓存重建
真正难的不是写对一行 SETNX,而是想清楚“谁在等谁”“等多久”“等不到怎么办”。很多线上事故,都出在锁释放逻辑没覆盖异常分支,或者降级开关压根没测过。

















