简介:Redis 用起来简单,踩起坑来却很致命。本文聚焦大 Key 阻塞、热 Key 打爆节点、缓存一致性、穿透击穿雪崩、分布式锁五类高频问题,剖析成因并给出可落地的规避方案,包括异步删除、Key 打散、过期时间加随机偏移、Lua 脚本释放锁等实践,帮助读者提前识别风险、设计更稳健的缓存架构。
Redis 以单线程、高性能著称,命令语义简单,接入成本极低。但正是这种“简单”,让很多团队在业务早期一路顺畅,直到流量上来、数据膨胀,才被几个隐蔽的问题打得措手不及。本文不讨论 Redis 的基础用法,而是聚焦生产中最常见的五类深坑:大 Key、热 Key、缓存一致性、穿透/击穿/雪崩,以及分布式锁。每一个都会给出成因分析与可落地的规避手段。
设想一个场景:某个 String 类型的 Key 存了 10MB 的 JSON,某个 Hash 类型的 Key 有 100 万个 field。某天业务需要清理这些数据,执行了一次 DEL,结果整个 Redis 实例卡住了,所有请求排队等待。
为什么会卡? 根源在于 Redis 的核心执行模型是单线程处理命令。删除一个大 Key 并不是“标记一下”那么简单,它需要释放该 Key 占用的所有内存,对于包含百万级 field 的 Hash,这个过程可能需要几百毫秒甚至更久。在这段时间内,后续所有命令都只能在队列里等待,表现为整个服务的响应时间出现尖刺。
更隐蔽的坑是大 Key 过期。 Redis 的过期清理策略中,有一部分是在主线程中执行的。当定期清理任务扫到那个大 Key 时,同样会触发一次同步释放,造成周期性的卡顿。很多团队会观察到“服务每隔一段时间抖一下”,排查很久才发现是这个原因。
规避方法:
UNLINK 而不是 DEL。UNLINK 会在后台线程中异步释放内存,主线程只做 Key 的摘除,不会阻塞。秒杀商品的库存、热搜话题、爆款直播间的在线人数——这些场景的共同点是:所有流量都打向同一个 Key。
Redis Cluster 按 Key 做分片,一个 Key 只会落在一个节点上。这意味着即使集群有 100 个节点,热 Key 所在的那个节点依然会被打爆,而其他节点几乎闲置。分片解决的是数据容量和整体吞吐,解决不了单 Key 的访问集中问题。
解决办法是打散:
hot_key 拆成 hot_key_1 到 hot_key_10,读的时候随机选一个。写的时候需要注意数据同步,适合读多写少的场景。“删缓存失败怎么办?”“延迟双删要延迟多久?”——这些问题在技术社区讨论了多年,结论是:没有完美方案,只有把不一致窗口缩短到业务可接受的范围。
常见的几种策略各有漏洞:
务实的做法是:
目标是缩短不一致窗口,而不是消灭它。想清楚这一点,方案选择会清晰很多。
这三个问题经常被放在一起讨论,但成因和对策完全不同。
缓存穿透:查询一个不存在的数据,缓存没有,数据库也没有,每次都打到 DB。恶意攻击常用这招,用大量不存在的 ID 消耗数据库资源。
缓存击穿:某个热 Key 在过期的那一瞬间,几万个请求同时打到数据库。
缓存雪崩:大量 Key 在同一时刻过期,或者 Redis 实例整体不可用。
用 Redis 做分布式锁,最基本的写法是 SET key value NX EX。但就是这短短一行,藏着不少坑。
坑一:锁没有过期时间。 进程拿到锁后崩溃了,锁永远不会释放,后续所有请求都被阻塞。所以必须设置过期时间。
坑二:锁过期了,业务还没执行完。 进程 A 拿到锁,过期时间 10 秒,但业务执行了 15 秒。第 10 秒锁自动释放,进程 B 拿到锁开始执行,此时两个进程同时操作共享资源。解决思路是引入锁续期机制(看门狗),在业务执行期间定期延长锁的过期时间。
坑三:释放锁时没判断是不是自己的锁。 进程 A 的锁过期后被 B 拿到,A 执行完后直接 DEL,把 B 的锁删了。正确做法是用 Lua 脚本,先判断 value 是否匹配再删除:
if redis.call("get", KEYS[1]) == ARGV[1] thenreturn redis.call("del", KEYS[1])elsereturn 0end
其中 value 使用 UUID 等唯一标识,确保只有锁的持有者才能释放锁。
完整的加锁与释放流程:
# 加锁SET lock_key <uuid> NX EX 10# 释放锁(Lua 脚本保证原子性)EVAL "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" 1 lock_key <uuid>
Redis 的坑大多源于对“单线程”“分片”“过期策略”等基础机制的忽略。大 Key 和热 Key 是容量与流量分布的工程问题;缓存一致性是业务取舍问题;穿透、击穿、雪崩是高并发下的防护设计问题;分布式锁则是并发正确性问题。每一类问题都有对应的成熟解法,关键在于提前识别、提前设计,而不是等到线上抖动再去救火。