KEYS命令会导致Redis卡住,因其单线程下需遍历全库O(N)时间复杂度,彻底阻塞其他命令;Spring Boot中redisTemplate.keys()会透传该命令,引发超时异常;SCAN分批游标遍历才是安全替代方案,需手动管理游标并关闭,且集群下需遍历各节点。

Keys命令为什么会让Redis卡住
因为Redis是单线程处理命令的,KEYS会遍历整个数据库所有key,时间复杂度是O(N)。只要它一执行,其他所有命令就得排队等——不是慢,是彻底阻塞。哪怕你只查KEYS user:*,只要库里有50万key,它就得挨个比对,期间客户端发来的GET、INCR、甚至PING全被挂起。
Spring Boot里调用Keys的实际表现
在Spring Boot中,如果你用redisTemplate.keys("pattern")或stringRedisTemplate.keys("pattern"),底层就是发KEYS命令。问题不在于Java侧,而在于这个调用会直接透传到Redis服务端。常见触发场景包括:
- 缓存清理脚本里写
redisTemplate.keys("cache:*")再逐个delete - 监控工具或健康检查主动扫描key分布
- 开发环境调试时随手在Controller里加了keys查询
这些操作在本地小数据量下没问题,一上生产,Redis响应延迟立刻飙升,连带Lettuce报RedisCommandTimeoutException: Command timed out after 10 second(s)——其实是KEYS自己还没跑完,客户端先等不及超时了。
Scan才是唯一安全的替代方案
SCAN用游标分批遍历,每次只查一小块,不阻塞主线程。但要注意:它不保证一次扫完,也不保证结果不重复,所以不能简单替换为keys()。Spring Boot里要用redisTemplate.scan(),且必须自己管理游标循环:
Cursor<String> cursor = redisTemplate.scan(ScanOptions.scanOptions()
.match("user:*")
.count(100)
.build());
while (cursor.hasNext()) {
String key = cursor.next();
// 处理单个key,比如删除或统计
}
cursor.close();
几个关键点:
-
count只是提示,不是硬限制,实际返回数量可能更少 - 必须显式调用
cursor.close(),否则连接泄漏 - 集群模式下
SCAN只作用于当前节点,需遍历所有slot(Redisson或自定义分片逻辑) - 别在事务里用
SCAN,它不支持MULTI/EXEC上下文
真正该禁掉Keys的地方
光靠开发自觉不靠谱。最稳妥的是从源头掐断:redis.conf里加rename-command KEYS "",让Redis直接拒绝该命令。重启后,任何KEYS调用都会返回(error) ERR unknown command `KEYS`。Spring Boot侧如果还留着keys()调用,会立刻抛RedisSystemException,反而能提前暴露问题。
这个配置不能只写在开发环境——很多线上事故,就是测试没跑通SCAN逻辑,上线后KEYS一执行,整个Redis就“假死”两分钟,连INFO都查不出来。


















