不能直接用keys删除千万级key,因其全量阻塞扫描会卡住redis主线程,导致请求堆积、超时熔断甚至雪崩;生产环境必须禁用。

为什么不能直接用 KEYS 删除千万级 key
因为 KEYS 是全量阻塞式扫描,会卡住整个 Redis 实例的主线程,集群中其他请求全部堆积,超时、熔断、雪崩风险极高。生产环境必须禁用。即使加了 --scan 参数的 redis-cli --scan --pattern "user:123:*",也只是客户端分批拉取,删除仍需额外发 DEL 命令——非原子、不可控、网络开销大、容易漏删或重复删。
EVAL 执行 Lua 脚本实现原子化 SCAN + DEL
Lua 脚本在 Redis 服务端原子执行,避免网络往返和并发干扰;配合 SCAN 游标分批遍历,不阻塞主线程。关键点在于:脚本内不能用 KEYS,必须用 SCAN 迭代;每次最多处理一批(如 1000 个),防止单次执行超时;删除必须用 redis.call("DEL", ...) 批量调用,而非循环逐个 DEL(性能差且可能触发慢日志)。
示例脚本(保存为 del_by_pattern.lua):
local pattern = ARGV[1]
local count = tonumber(ARGV[2]) or 1000
local cursor = tonumber(ARGV[3]) or 0
local keys = redis.call("SCAN", cursor, "MATCH", pattern, "COUNT", count)
local deleted = redis.call("DEL", unpack(keys[2]))
return {keys[1], deleted}
调用方式:
redis-cli -c -h node1 -p 7001 --eval del_by_pattern.lua , "user:123:*" 1000 0
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
注意:--eval 后第一个逗号是分隔符,ARGV 从逗号后开始;-c 表示集群模式,但该脚本只作用于当前节点(Redis 集群中 key 槽位固定,需按 slot 分别执行)。
如何安全覆盖所有 master 节点并避免重复/遗漏
Redis 集群中,匹配 user:123:* 的 key 可能分布在多个 slot,必须对每个 master 节点单独执行脚本,并确保只扫本节点负责的 slot 范围。最稳妥做法是先用 redis-cli -c ... cluster nodes 解析出所有 master 地址和 slot 分配,再对每个 master 执行一次脚本(pattern 不变,但游标需从 0 开始重扫)。
常见错误包括:
- 误用
redis-cli --cluster工具链直接跑脚本(它不支持--eval) - 脚本里硬编码
SCAN的COUNT过大(>5000),导致单次执行 >100ms,被 slowlog 记录甚至触发 client-output-buffer-limit 限制 - 没检查返回游标是否为 "0",就认为扫完了(实际可能还有数据,需循环调用直到游标归零)
- 在 slave 节点执行(只读,
DEL报错(error) READONLY You can't write against a read only replica.)
线上执行前必须验证的三件事
千万级 key 删除不可逆,务必前置验证:
- 用
SCAN 0 MATCH "user:123:*" COUNT 100在目标节点手动试扫,确认 pattern 匹配逻辑和 key 数量级 - 在低峰期用小范围 pattern(如
"user:123:abc*")完整跑通一次脚本,观察INFO stats中的expired_keys、evicted_keys、total_commands_processed是否突增异常 - 确认目标节点未开启
protected-mode yes且账号有DEL和SCAN权限(ACL 中需包含~* +scan +del)
真正麻烦的不是脚本怎么写,而是你怎么确认删的是对的节点、对的 key、且没影响其他业务——游标没清零、节点选错、ACL 权限不足,这三个点最容易在线上卡住几小时。










