ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

记一次生产环境 Redis 大 Key 导致主从切换的排查过程

记一次生产环境 Redis 大 Key 导致主从切换的排查过程 上周四下午快下班的时候运维群里突然 我说线上某个服务的 Redis 主节点挂了已经自动切到从节点让我看看是不是代码侧有什么异常操作。说实话第一反应是懵的。这个服务上线大半年了一直跑得很稳Redis 用的是单主单从的哨兵模式之前从来没出过这种事。先看了 Redis 的日志发现主节点在下午 4:23 左右突然和哨兵断开了连接哨兵判定它 down 掉之后触发了故障转移。1234:X 28 Aug 2026 16:23:17.892 # Connection with master lost. 1234:C 28 Aug 2026 16:23:18.105 # Master seems to be down, starting failover.然后去看应用侧的慢查询日志发现 4:22 左右有一个KEYS *的操作耗时 3.8 秒。当时我就知道问题出在哪了。定位根因翻了一下代码发现是同事新加的一个数据清理定时任务逻辑大概是这样的// 清理过期的临时token SetString keys redisTemplate.keys(temp_token:*); if (keys ! null !keys.isEmpty()) { redisTemplate.delete(keys); }看着好像没啥问题对吧但实际上这个 key 的数量已经涨到了40 多万。KEYS命令的时间复杂度是 O(N)而且它会阻塞整个 Redis 主线程。40 多万个 key 扫一遍直接把主节点卡死了哨兵连续几个心跳没收到响应就认为主节点挂了。更离谱的是这个定时任务配的是每 5 分钟执行一次。也就是说每隔 5 分钟Redis 就要被卡一次。之前数据量小的时候没感觉随着业务增长key 越攒越多终于在某一次执行的时候把主节点彻底卡崩了。修复方案改了两个地方第一把KEYS换成SCAN// 用 SCAN 替代 KEYS避免阻塞 CursorString cursor redisTemplate.scan( ScanOptions.scanOptions().match(temp_token:*).count(200).build() ); ListString batchKeys new ArrayList(); while (cursor.hasNext()) { batchKeys.add(cursor.next()); if (batchKeys.size() 200) { redisTemplate.delete(batchKeys); batchKeys.clear(); } }SCAN是增量式迭代不会一次性遍历所有 key每次只返回一小批对主线程的阻塞可以忽略不计。第二给 key 加上过期时间其实最根本的问题是这些临时 token 在写入的时候就没设 TTL全靠定时任务去清理。改成写入时就设过期时间redisTemplate.opsForValue().set(key, value, 30, TimeUnit.MINUTES);这样即使定时任务偶尔漏执行Redis 自己也会把过期 key 清掉不会无限堆积。回过头来看这个问题其实很低级但确实很典型。总结几个教训永远不要在生产环境用KEYS命令哪怕你觉得数据量不大。数据量是动态增长的今天 1000 个 key 没事半年后可能就是 100 万个。大 Key 要提前防范。写代码的时候就要想清楚这个 key 的 value 有多大、同类 key 的数量会增长到什么量级。定时任务要做监控。这个任务跑了大半年从来没人关注过它的执行耗时。如果一开始就配了慢查询告警问题在数据量刚涨起来的时候就能被发现。一点延伸其实 Redis 大 Key 的问题远不止KEYS命令这一种场景。比如一个 Hash 类型的 key 里面塞了几十万个 fieldHGETALL一样会卡住。之前还见过有人把一个几 MB 的 JSON 字符串直接塞进一个 String key 里读取的时候网络 IO 直接打满。关于大 Key 的治理后续有时间我再单独写一篇包括怎么用redis-cli --bigkeys做扫描、怎么拆分大 Key、以及 pipeline 批量操作的最佳实践。
返回列表