redis单线程不解决缓存击穿,仅序列化命令执行;击穿防护依赖setnx、set...nx px等原子命令或分布式锁,而非本地synchronized。

Redis单线程模型本身不解决缓存击穿,也不能“减少竞争”——它只是让“竞争”在服务端被序列化了,而真正的并发控制必须由应用层或Redis命令级原子操作来承担。
Redis单线程如何影响缓存击穿场景
当多个请求同时发现 user:1001 缓存过期,它们都会执行“查缓存→没命中→查DB→写缓存”流程。Redis的单线程特性意味着:所有对Redis的读写命令(如 GET、SET、DEL)是串行执行的,但关键点在于——GET 和 SET 之间存在时间窗口,且这个窗口完全暴露给客户端逻辑。
也就是说,单线程只保证“命令执行不交错”,不保证“业务逻辑原子性”。两个并发请求都执行了 GET user:1001(返回 null),然后各自去查数据库,再各自执行 SET user:1001 ... —— 这就是典型的击穿,单线程对此毫无防护能力。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
真正起作用的是带条件的原子命令
避免击穿的核心不是靠Redis单线程,而是用具备“判断+设置”原子语义的命令,在一个不可分割的操作中完成“锁占位”或“空值兜底”。常见手段包括:
-
SETNX key value:仅当key不存在时才设值,返回1表示抢锁成功,可用于实现互斥锁;注意它不支持直接设置过期时间(老版本需配合EXPIRE),Redis 2.6.12+ 可用SET key value NX EX seconds -
GETSET key value:获取旧值并设置新值,原子执行,适合做“空值占位”(先GETSET user:1001 "NULL",再查DB,成功后覆盖为真实数据) -
SET key value NX PX milliseconds:最推荐,一条命令完成“加锁 + 设置过期”,避免死锁风险
为什么不用 synchronized 或本地锁
在分布式系统中,单机 synchronized 或 ReentrantLock 完全无效——不同实例上的线程互不可见。你看到的“双重检查+本地锁”代码(比如 synchronized(lockHelp))只在单JVM内生效,一旦服务横向扩容到多节点,击穿立刻重现。
典型错误现象:
- 本地锁看似阻塞了同进程内的并发,但其他机器上的请求照常打库
- lockHelp 是静态变量,但在K8s或负载均衡下,每个Pod都有独立JVM,锁毫无意义
- 日志里可能看到“同一秒内数据库被查了27次”,就是这个原因
击穿防护的关键不在Redis线程模型,而在调用链路设计
单线程模型确实带来一个隐性好处:它让Redis内部状态始终可预测,所以基于它的原子命令(如 SETNX、EVAL 脚本)能稳定工作。但击穿是否发生,最终取决于你有没有在“缓存未命中”之后、访问数据库之前,插入一道跨进程的协调机制。
容易被忽略的点:
- SETNX 成功后,如果业务逻辑抛异常或网络超时,没执行 DEL 或后续 SET,会导致锁长期残留 → 必须配过期时间(PX)
- 空值缓存(SET user:999 "NULL" EX 300)要和正常值区分,否则反序列化会失败 → 建议用固定字符串如 "__NULL__",而非JSON null
- Lua脚本虽能封装复杂逻辑,但执行时间过长会阻塞整个Redis事件循环 → 击穿场景下不建议在脚本里查DB










