不能直接用 keys 因其阻塞主线程,易致服务卡顿甚至雪崩;应改用 scan 分批扫描,配合 lua 脚本实现前缀匹配与渐进式删除,客户端控制节奏、监控指标并动态调整。

为什么不能直接用 KEYS 扫描前缀再删
Redis 的 KEYS 命令会阻塞主线程,单次扫描上万 key 就可能卡住几百毫秒,线上服务直接受影响。尤其在内存快满、淘汰策略已触发时,再加一个阻塞操作,容易引发雪崩。官方明确建议生产环境禁用 KEYS,改用 SCAN —— 它是渐进式、非阻塞的,但代价是需要客户端配合多次迭代。
如何用 Lua 脚本安全实现前缀批量删除
Lua 脚本在 Redis 中原子执行,但注意:它仍运行在单线程里,脚本内不能有耗时操作(比如循环上万次不做中断)。所以不能把整个前缀扫描+删除写死在一个脚本里。正确做法是分两步:先用 SCAN 拿一批 key,再用 DEL 批量删——这两步拆开,由客户端控制节奏。
- 脚本只做「单次 SCAN + 匹配 + 返回匹配到的 key 列表」,不执行
DEL - 匹配逻辑用
string.match(key, "^" ..ARGV[1]..".*"),避免正则过度消耗(ARGV[1]是传入的前缀) - 返回结果必须是数组(Lua table),Redis 才能转成 RESP 数组给客户端
- 示例脚本:
local prefix = ARGV[1] local cursor = tonumber(ARGV[2]) local count = tonumber(ARGV[3]) or 100 local keys = {} local new_cursor, batch = redis.call("SCAN", cursor, "MATCH", prefix.."*", "COUNT", count) for _, key in ipairs(batch) do if string.sub(key, 1, #prefix) == prefix then table.insert(keys, key) end end return {new_cursor, keys}
客户端怎么配合实现“手动干预内存淘汰”
所谓“手动干预”,本质是绕过 Redis 自动淘汰机制(如 allkeys-lru),主动清理已知无用数据。这要求你清楚哪些前缀属于可删范围(比如 cache:temp:、session:old:),且删除动作要可控、可中断、可监控。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 每次调用脚本传入当前游标(初始为 0)、前缀、单次 scan 数量(建议 50–200)
- 检查返回的
new_cursor:若为 "0",说明扫完;否则继续下一轮 - 对每批返回的 key 列表,用
DEL批量删除(一次最多 1000 个,避免命令包过大) - 每次操作后 sleep 几毫秒(如 5ms),减轻 Redis 压力,也方便随时中止
- 记录删除总数和耗时,用于判断是否需调整扫描节奏
容易被忽略的边界问题
实际跑起来常踩的坑不在语法,而在语义和部署细节:
-
SCAN不保证一次返回所有匹配 key,即使MATCH写对了,也可能漏掉——因为它是基于哈希桶的抽样扫描,必须循环到cursor == 0才算真正结束 - 脚本里用
string.sub而不用string.match做前缀判断,前者性能高一个数量级,且不会因特殊字符(如.、*)误匹配 - 如果目标 key 有设置过期时间(TTL),用
DEL删除后,内存释放不是立即可见的——Redis 采用惰性删除+定期删除结合策略,INFO memory中的used_memory可能延迟几秒才下降 - 集群模式下,Lua 脚本中的 key 必须落在同一 slot,否则报
CROSSSLOT错误;此时需确保前缀本身能哈希到固定 slot(如加{xxx}包裹),或改用客户端分片处理
真正难的不是写对脚本,而是判断“现在删多少、间隔多久、从哪开始停”——这些得结合当前 mem_used、evicted_keys、expired_keys 等指标动态决定,没法一劳永逸。










