SCAN易抛NoSuchElementException因旧版ScanCursor.next()未校验游标空状态,须手动加hasNext()双重判断;count()非硬限制,建议设500–1000;禁用逐个del,应批量或Lua封装;MATCH仅支持glob通配符。

直接用 SCAN 替换 KEYS 不行,必须改客户端逻辑——Spring Boot 里光调一次 scan() 方法,漏数据、抛异常、卡死连接都是常态。
为什么 RedisTemplate.scan() 容易抛 NoSuchElementException
老版本(如 spring-data-redis 1.6.x)的 ScanCursor.next() 在游标已空时仍被调用,就会炸出 NoSuchElementException。这不是你代码写错,是底层游标没做防御性判断。
- 新版(2.0+)已修复,但若项目锁死在 1.8.x 或更低,得手动加
hasNext()双重校验 - 别信
cursor.forEachRemaining()——它内部没兜底,遇到空结果或网络抖动照样崩 - 真实场景中,某次
SCAN返回空数组但游标非零(比如匹配不到 key),此时必须继续下一轮,否则漏扫
ScanOptions.count() 不是 limit,别设 Integer.MAX_VALUE
COUNT 是提示值,不是硬限制。设成 Integer.MAX_VALUE 或 Long.MAX_VALUE 等价于不设,Redis 会按默认 10 来返回,但客户端可能误以为“一次扫完”,导致后续游标没流转。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 生产建议值:500–1000,平衡 RTT 次数和单次耗时
- 太小(如 10):几万 key 要发上千次命令,网络开销压垮应用线程
- 太大(如 10000):单次扫描哈希桶过多,可能触发慢日志甚至超时
- 别用
count(Integer.MAX_VALUE)去“假装全量”——SCAN 机制根本不支持这个语义
删除前缀 key 时,别在 scan 循环里直接 del
每扫到一个 key 就立刻 connection.del(key),等于把 O(N) 操作拆成 N 次网络往返 + N 次 Redis 执行,吞吐极低,还放大连接池压力。
- 正确做法:攒批,每 100–200 个 key 合并一次
connection.del(keys) - 注意
del批量上限:Redis 单次命令参数不能超 1024 个(实际建议 ≤500) - 别用
redisTemplate.delete(keys)——它对每个 key 单独发 DEL,退化回逐个删 - 更优方案:用 Lua 脚本封装 SCAN + DEL,但需确认 Redis 配置未禁用 EVAL(云厂商常禁)
Pattern 匹配只支持 glob,不是正则
MATCH user:*:v2 可以,MATCH user:\d+:v2 不行——SCAN 的 MATCH 只认 *、?、[abc] 这三类 glob 通配符,后端不走正则引擎。
- 想实现“user:数字:v2”,只能扫
user:*:v2,再在 Java 层用String.matches("\d+")过滤 - 别在
MATCH里写user:[0-9]*:v2——这会被当字面量,匹配不到任何 key - 大小写敏感:MATCH
User:*≠user:*,线上环境 key 命名不统一时容易漏
SCAN 的游标是状态机,不是页码;它的弱一致性不是 bug,是设计取舍。真正难的不是写对第一次调用,而是处理好游标中断恢复、重复 key 去重、以及高并发下 key 动态增删带来的边界情况——这些细节不压测根本暴露不出来。

















