SCAN无法按访问时间过滤,冷数据判定需业务层埋点;安全清理应分三步:SCAN分批拉取+TYPE校验类型+OBJECT IDLETIME判断空闲时长;删除必须用UNLINK批量异步执行并限速。

SCAN 命令本身不支持按访问时间过滤,必须靠外部标记
Redis 的 SCAN 只能遍历 key,不提供最后访问时间(LASTACCESS)或写入时间字段。官方没有 OBJECT IDLETIME 以外的活跃度指标,而 IDLETIME 仅对设置了过期时间的 key 有效(未设 TTL 的 key 永远返回 -1)。所以“冷数据”判定必须依赖业务层埋点:比如在写入时用 SET + EXPIRE 组合打上逻辑过期时间,或单独维护一个 last_access:{key} 的哈希表。
用 SCAN + TYPE + OBJECT IDLETIME 构建安全扫描脚本
直接 DEL 扫描出的 key 风险极高——可能误删正在使用的热 key。推荐分三步走:
- 先用
SCAN分批拉 key(避免阻塞),每次COUNT设为 100~500,配合MATCH过滤前缀(如user:*) - 对每个 key 调用
TYPE确认类型(跳过none或stream等不支持IDLETIME的类型) - 再调用
OBJECT IDLETIME获取空闲秒数;注意:返回-1表示无 TTL 或 key 不存在,-2表示 key 不存在,需区分处理
示例伪代码逻辑(Python + redis-py):
cursor = 0
while True:
cursor, keys = r.scan(cursor=cursor, match="user:*", count=200)
for key in keys:
if r.type(key) not in [b"string", b"hash", b"list", b"set", b"zset"]:
continue
idle = r.object("idletime", key)
if idle and idle > 86400 * 30: # 空闲超 30 天
pending_deletes.append(key)
if cursor == 0:
break
异步删除必须用 UNLINK 而非 DEL,且控制并发量
DEL 是同步阻塞操作,删除大 hash 或 zset 会卡主线程;UNLINK 把实际内存释放交给后台线程,更安全。但并发执行太多 UNLINK 仍会压垮 Redis 内存回收线程。建议:
- 批量提交:每 10~50 个 key 合并为一次
UNLINK命令(Redis 支持多 key 参数) - 加延迟:两次批量间
time.sleep(0.1),避免瞬时压力 - 监控
evicted_keys和expired_keys指标突增,说明淘汰压力过大
错误示范:r.delete(*pending_deletes) —— 万一 pending_deletes 有 10 万个 key,会触发 Redis 单次命令参数超限(默认 proto-max-bulk-len 为 512MB)。
真正难的是“废弃”的业务定义,不是技术实现
“无过期时间”和“冷”只是表象。有些 key 明明半年没访问,却是下游定时任务的配置开关;有些 key 每月只读一次,但缺了就导致账单错乱。最稳妥的方式是:把扫描结果写入临时 sorted set(如 to_check:abandoned),按 IDLETIME 排序,人工抽检 top 100,确认后再触发 UNLINK。别迷信自动化清理——Redis 里没被访问的 key,90% 都不是真废弃,而是你忘了它在哪被用。











