☰
Redis 缓存清理实战:从底层原理到安全操作指南
2026/10/13 12:31:49 网站建设 项目流程

你有没有遇到过这种情况:Redis 用着用着,内存快满了,业务也开始卡了,打开客户端一看,一堆乱七八糟的 key,不知道该删哪些;或者干脆执行了FLUSHALL,结果把不该清的数据全部干掉,线上直接告警。我自己在维护 Redis 的这几年里,清理缓存这个看似简单的操作,其实踩过不少坑。很多人以为清缓存就是删 key,但实际涉及内存淘汰策略、过期键处理、数据类型差异、序列化方式,甚至分布式锁的安全性。这篇内容我会结合自己真实操作过的场景,把 redis 清理缓存这件事从原理到实战彻底讲透,包括命令怎么选、批量怎么删、哪些缓存不能乱清、用什么工具更高效,以及我在生产上遇到的问题和排查思路。不管你是刚接触 Redis 的运维,还是写业务代码的开发者,看完应该都能少走弯路。

1. 清理 Redis 缓存前必须搞懂的底层逻辑

先说个扎心的现实:如果你不知道缓存是怎么存进去的,那你大概率也不知道怎么安全地把它清掉。Redis 的缓存清理本质上就是操作内存中的 key-value 数据,但 Redis 里 key 的组织方式、过期机制、内存淘汰策略,都直接影响你应该用哪种清理方式。

1.1 缓存到底存在哪:从 key 的空间结构说起

Redis 的每个数据库实例里,数据都是存在一个全局字典里的,你可以把它想象成一个大号的 HashMap,key 是对应的键,value 是具体的值。Redis 默认有 16 个逻辑数据库,从 0 到 15,我们平时用的默认库是 0。清理缓存前,你得先确认自己连的是哪个库,很多人用客户端工具登录后默认连的是 db0,但业务可能把缓存写在了 db1、db2 里面,结果你在 db0 里执行FLUSHALL,看着好像清干净了,实际上业务缓存纹丝不动。

另外,key 名是有层级结构的,我强烈建议团队从一开始就按业务模块来命名,比如user:info:1001、order:detail:20240101。这样你做清理的时候,只需要按前缀匹配就能精准定位,而不是靠肉眼在一堆无规则的 key 里面翻找。

还有一点很多人忽略:Redis 里存储的 key 数量很大时,KEYS命令会直接阻塞主线程。比如线上有上千万个 key,你执行KEYS user:*,Redis 会疯狂扫描整个字典,持续几秒钟甚至更久,期间所有读写请求全部卡住。所以清理缓存不是简单敲个命令,你得搞清楚数据规模、阻塞风险、还有业务对缓存的依赖程度。

1.2 为什么不能一把梭:过期键、内存淘汰与惰性删除

很多新手清理缓存的思路是“删掉所有 key”,但 Redis 的过期键并不是一到时间就立刻消失的。它有两种删除机制:惰性删除和周期删除。惰性删除是指当某个 key 被访问时,如果发现已经过期,顺手删掉;周期删除是后台每 100ms 抽查一部分过期 key 来清理。这意味着一个已经过期的 key,只要没人访问它,它可能还会继续占着内存一段时间。

这有什么影响呢?就是你清理缓存的时候,用DBSIZE看到的内存占用和 key 数量,可能不准确。你以为清了,实际旧数据还赖在内存里。这时候应该主动用清理命令来处理。

内存淘汰策略也要搞明白。Redis 默认是noeviction,也就是内存满了不淘汰任何数据,直接报错。但如果配置成allkeys-lru或volatile-lru,Redis 会按照近期的访问情况自动淘汰 key。这里有个很容易踩的坑:如果你用的是allkeys-lru,那么当你执行FLUSHALL清掉所有 key 后,内存并不会马上完全释放到操作系统,因为 Redis 的内存分配器(比如 jemalloc)有内存碎片,你看到 RSS 可能还是很高。这不是没清干净,而是内存碎片问题,需要重启实例或者用MEMORY PURGE手动整理(注意这只对部分分配器有效)。

从工程角度看,清理缓存应该是一种有计划的治理行为,而不是等到内存爆了才去救火。你需要知道自己每个业务的 key 大概多大、命中率多少、允许丢失的容忍度是多少。比如用户会话缓存丢了,顶多让人重新登录;但库存缓存如果被一把梭清掉,瞬间把数据库打挂,那就是事故了。所以清理之前,先评估业务影响,这是底线。

2. 最常用的清理命令实战与踩坑

Redis 提供了多个删除命令,每个命令都有它的脾气。我这里直接按照生产环境的使用频率来拆解,并附上我实际用过的场景。

2.1 DEL、UNLINK、FLUSHDB、FLUSHALL 怎么选

先看一张对比表,看完你就知道为什么不能无脑用 DEL 了。

命令作用阻塞风险推荐场景
DEL key1 key2同步删除 key如果 value 很大(比如大 List 或大 Hash),删除时释放内存可能导致阻塞小 key,key 数量少
UNLINK key异步删除,先取消链接,后台回收内存不阻塞大 key,批量删除
FLUSHDB清空当前库所有 key全量删除可能阻塞测试环境、可接受全清的业务
FLUSHALL清空所有库所有 key同上极度危险,生产禁用

我见过有同事在线上执行DEL删一个包含几百万元素的 Set,结果 Redis 直接卡了差不多 3 秒,监控曲线瞬间掉到底。原因就是DEL是同步操作,它需要遍历所有元素并释放内存,而大对象释放内存是耗时操作。后来我们用UNLINK替代了DEL,因为UNLINK先把 key 从全局字典中摘掉,然后后台线程慢慢回收内存,主线程不会被卡住。

那FLUSHDB和FLUSHALL呢?我个人的原则是测试环境随便用,生产环境只有一种情况能用:这个 Redis 实例是专门给某个非核心功能做缓存用的,而且业务上允许短暂全量丢失,比如某些展示用的配置项,丢了可以从数据库重新加载。但凡涉及订单、支付、库存,绝对禁止在生产上执行FLUSHALL,哪怕是从库也要谨慎,因为从库同步数据也会受影响。

2.2 按前缀批量清理的正确姿势

生产环境下,我们经常需要按业务前缀清理缓存,比如清理所有promotion:*开头的营销活动缓存。这里有两种主流方案。

第一种是先用SCAN找出匹配的 key,再用UNLINK删除。SCAN命令是游标式遍历,每次返回一部分结果,不会像KEYS那样阻塞,但仍然会占用一点 CPU。我写过一个常用的 Lua 脚本,循环调用SCAN并删除匹配的 key,但 Lua 脚本在 Redis Cluster 模式下对 key 有严格的限制,脚本里必须传递同一个 key,否则会报错。所以更简单的办法是在客户端层面用 Python 或 Shell 脚本,循环执行SCAN和UNLINK批量处理。

第二种是直接维护一张“可清理的 key 前缀清单”,在写缓存的时候就把 key 按前缀隔离,清理的时候直接点名。比如定义一个batch_delete接口,传入前缀列表,服务端用SCAN配合匹配模式执行删除。我还用过redis-cli --scan --pattern 'user:logout:*' | xargs redis-cli UNLINK这种命令行组合,但要注意一次 xargs 传入的 key 数量不要太多,否则命令行过长。

另外,在 Redis 6.0 以上,还有CLIENT PAUSE命令可以和清理配合使用,但它不适合在线业务,只适合维护窗口。我的经验是批量清理最好放在流量低峰期,比如凌晨两点,并且分批次执行,每次清理一部分,观察一下 Redis CPU 和延迟没有异常,再继续下一批。

2.3 定时清理与过期时间设置的工程实践

清理缓存不应该全靠手动,更多时候应该靠合理的过期时间来自动完成。我在不少团队里看到,业务方设置过期时间时特别喜欢用 24 小时,大家觉得这样可以减轻数据库压力。但问题是,如果大量 key 都在同一时间点过期,就会产生“缓存雪崩”效应:同一瞬间大量请求全部穿透到数据库,数据库压力瞬间飙升。

我的做法是让过期时间带上随机偏差。比如基础过期时间是 86400 秒,那我在写缓存时会额外加一个随机数,范围在 0 到 3600 秒之间。这样分散了过期时间,降低了雪崩概率。对于一些重要数据,用两级的缓存策略,比如本地缓存配合 Redis 缓存,Redis 过期之后还有本地缓存兜底,不至于立刻击穿。

如果确实需要在某个时间点清理一批 key,比如零点活动结束后清理活动缓存,可以用EXPIREAT结合任务调度器(比如 Linux 的 crontab 或者云上的定时任务)来设置统一过期时间点。但要注意,EXPIREAT设置的是时间戳,如果业务服务器时钟不同步,可能导致过期时间不准。我一般建议所有服务器统一启用 NTP 时间同步,这点在缓存清理场景里也算是冷门但实用的细节。

3. 不同类型缓存的清理方案

Redis 不只是简单的 String,还有 Hash、List、Set、ZSet,每种数据类型的清理细节不一样。另外我们经常用 Redis 实现分布式锁,锁缓存清理更是一个安全红线,稍不注意就会出现并发事故。

3.1 字符串、Hash、Set、ZSet、List 的清理差异

先说最常用的 String 类型,直接DEL或者UNLINK就行,没有额外逻辑。但 Hash 类型要注意,如果你只想清掉某个字段,比如HDEL user:1001 name,那是局部删除。但如果你要删掉整个 Hash,DEL和UNLINK都行。Hash 还有一个特殊场景:如果 Hash 里的 field 非常多,比如有几十万个字段,删除整个 key 时DEL也会阻塞,同样建议用UNLINK。

Set 和 ZSet 的内部编码会根据元素数量变化。当元素较少时,Redis 使用压缩列表,删除很快;但元素多了会转为哈希表和跳表,删除大 Key 的耗时就会上涨。我遇到过一个线上问题,有个 Set 存了上百万个用户 ID,因为某个运营脚本 bug 一直往里面加数据,导致内存爆炸。当时我先用了SREM尝试移除部分元素,发现太慢了,最后直接UNLINK整个 key,然后重建数据。

List 类型稍微特殊一点。如果 List 很长(几百万个元素),DEL也会阻塞。但 List 通常用于消息队列或最新动态列表,删除整个 key 前要确认下游消费者是否已经消费完,否则会丢数据。另外还可以用LTRIM来裁剪列表,保留指定的范围,这在清理历史数据时非常有用。比如LTRIM mylist 0 99,表示只保留前 100 个元素,后面的全部删除。这种方式不会阻塞,按需裁掉旧数据,很丝滑。

3.2 分布式锁缓存清理的特殊性

Redis 实现分布式锁最常用的是SET key value NX PX 30000,这本质上也是一个缓存 key。清理分布式锁的坑点在于:你很可能分不清这个 key 是不是锁,以及持有锁的任务是不是已经结束了。如果你傻乎乎地用FLUSHALL把所有 key 都清掉,等于把锁全释放了,多个业务就会同时进入临界区,造成数据错乱。

所以清理分布式锁的安全姿势是:只删除自己持有的锁,并且要校验 value。我每次释放锁都会用 Lua 脚本,先判断 key 的 value 是否等于当前线程的唯一标识,相等才删除,这个操作是原子性的。如果你用 Redis 的哨兵或 Cluster 架构,还要考虑主从切换导致的锁失效问题,不过这属于另一个话题。我想提醒的是,清理 Redis 缓存前,一定要留意 key 名字里有没有lock、mutex这类关键字,这些是红线,绝不能批量清。

另外,如果你用 Redisson 这类客户端实现的锁,它的 key 可能带一些内部结构,比如 Redisson 的锁底层是用 Hash 类型存的,里面包含线程信息和重入次数。这种锁如果被误删,重入机制就会失效,其他线程也无法正常获取到锁。我在排查一个死锁问题时,发现就是清理脚本把锁的 hash key 删掉了,导致锁的 watchdog 续期逻辑找不到 key,然后其他线程也获取不到锁,因为 Redisson 的看门狗看到 key 不存在会自动认为锁被释放,但实际上业务持有锁的逻辑已经乱了。这个坑让我意识到,任何清理工具都需要配置“白名单”和“黑名单”,锁 key 必须进入黑名单,绝对不参与批量清理。

3.3 序列化方式对清理的影响

很多人没意识到,不同的序列化方式会让 Redis key 在客户端工具中显示成完全不一样的内容。比如你用 JDK 原生序列化,key 前面会带一串类似\xAC\xED\x00\x05t\x00...的二进制前缀,用字符串匹配时根本匹配不到;如果用 Hessian、JSON、Protobuf,key 的形态又各不相同。

这意味着你准备按前缀批量清理的时候,必须知道业务写入时用的序列化器。我见过一个 case:一个 Java 项目用StringRedisTemplate存 value,key 是普通的cache:user:1001;另一个项目用RedisTemplate的默认 JdkSerializationRedisSerializer,key 也经过了序列化,结果通过客户端看到的就是一堆乱码二进制。清理的时候只匹配到了前者,后者一直没清掉,内存不断上涨。

所以清理前,建议先在客户端工具里选中一个 key,右键查看它的原始 Redis key 和 type,确认 key 的真实形态。如果你看到的是\xAC\xED开头,那说明它是 Java 序列化后的产物,你需要用SCAN配合十六进制模式或者直接在代码里删除。另外,客户端工具比如 Redis Desktop Manager,也支持显示二进制 key,但你需要临时修改 key 编码或者用命令行调试。最稳妥的方案是:在业务代码里预留一个清理接口,不直接在运营工具里乱删。

4. 可视化工具与脚本化清理实战

命令行再强大,很多人平时还是喜欢用可视化客户端。这里我把我常用的工具和一些脚本方案整理一下。

4.1 Redis Desktop Manager / Another Redis Desktop Manager 使用要点

如果你下载过 Redis Desktop Manager,会发现新版已经改名为 Another Redis Desktop Manager(简称 ARDM),它是跨平台的,支持 Windows、Mac、Linux。用它清理缓存时,我建议先连接 Redis 实例,然后在 key 树里按前缀输入过滤条件,比如输入order:*,它就会列出所有匹配的 key。这时候不要全选直接按 Delete 键,因为 ARDM 的批量删除本质上是逐个发送 DEL 命令,如果匹配到的 key 数量很大,速度会比较慢,而且可能造成阻塞。你可以勾选多个 key 后选择“删除”,但此时工具不一定自动帮你改成 UNLINK,所以如果有大 key,建议还是去命令行操作。

另外,ARDM 还有一个很好的功能是查看内存分析和 key 的信息,比如可以看到每个 key 的大小、过期时间、编码格式。我经常用它来定位哪些 key 占用内存最高,然后有针对性的清理。这个功能在旧版 RDM 里叫“Memory Usage”,通过MEMORY USAGE key实现,注意这个命令本身也会消耗一定性能。

如果你用的是 Mac,我推荐直接用 brew 安装 Redis CLI,然后在终端里操作,因为 Mac 下图形客户端有时候会卡,特别是 key 数量到百万级别时,树形列表加载就很慢。而用 Redis CLI 加管道批量清理,效率高得多。Windows 用户可以去 Redis 官网下载 Windows 版或者使用 WSL,注意 Windows 版本并非官方原生支持,功能上有一些兼容差异,我在实际使用中发现SCAN的游标类型在 Windows 版上表现一致,但部分命令会有细微差别。

4.2 Linux/Windows 下清理缓存的脚本

我在 Linux 环境最常用的清理脚本是一个 bash 函数,大致逻辑就是循环 SCAN,然后用 UNLINK 删除。为了避免一次删太多导致主从同步延迟,我会控制每次删除的 key 数量,比如每批删除 1000 个 key,然后 sleep 0.1 秒。具体写法很简单,核心代码如下:

#!/bin/bash # 按前缀清理 Redis 缓存,避免阻塞 REDIS_CLI="redis-cli -h 127.0.0.1 -p 6379 -a 你的密码" PATTERN="$1" if [ -z "$PATTERN" ]; then echo "Usage: $0 'user:*'" exit 1 fi cursor=0 while true do result=$($REDIS_CLI -c --scan --pattern "$PATTERN" --count 1000) if [ -z "$result" ]; then break fi echo "$result" | xargs -n 100 $REDIS_CLI UNLINK cursor=$(echo "$result" | tail -1) # 这个写法不严谨,仅示意 sleep 0.1 done

这里要注意,redis-cli --scan命令本身就支持游标,但它内部已经帮你循环了,所以如果你的 Redis 没有密码并且不需要处理集群,可以直接用redis-cli --scan --pattern 'user:*' | xargs -n 100 redis-cli UNLINK这样的一行命令。但 xargs 这种方式有个小坑:如果 key 里有空格,会出问题,所以最好在代码中使用真正的 SCAN 命令。

如果是 Windows,我一般写一个批处理脚本,用 Python 来做删除逻辑,因为 bat 里处理编码和特殊字符太痛了。比如在电脑上装个 Python,然后写一个简单的爬虫式脚本,用 redis-py 连接 Redis,循环 scan_iter 然后 unlink。这不麻烦,而且能复用很多通用的清理逻辑。很多人问“清理电脑缓存的 bat”和这个没关系,那是操作系统层面的临时文件,和 Redis 不是一个东西,别混了。

4.3 结合监控的缓存治理方案

清理缓存不是一锤子买卖,而是需要结合监控的持续治理。比如用 Prometheus 采集 Redis 的used_memory、keyspace_hits、keyspace_misses、evicted_keys等指标。当命中率突然下降,或者内存飙升时,你能第一时间发现异常,然后通过监控定位大 key。

我之前负责的一个系统,上线了一个新功能,结果它把一个订单对象序列化后整个塞到 Redis 里,而且 key 没过期时间,短短两天就把 Redis 内存吃光了。当时监控面板上的效率曲线直接爆表,后来通过 ARDM 的内存分析定位到了几个特别大的 key,然后用脚本把对应的 key 删掉,同时让业务方增加了过期时间。这就是治理的价值。

我还建议每个 Redis 实例都设置一个慢日志阈值,比如slowlog-log-slower-than 10000(10ms)。如果清理缓存导致命令变慢,慢日志里会留下证据,方便复盘。再配合 Keyspace 通知(notify-keyspace-events),你还可以监听过期事件,做后置清理逻辑,比如当某个缓存的 key 过期通知发生时,异步重算缓存。

5. 常见问题排查与避坑清单

这里我把清理缓存时最容易遇到的问题整理成一张速查表,这是我在生产环境里真实遇到过并逐一解决过的。

问题现象可能原因解决方案
清理后内存没降下来内存碎片或大量过期内key未回收用MEMORY PURGE或重启实例;主动UNLINK大key
KEYS命令卡死 Redis生产环境错误使用KEYS全表扫描改用SCAN或客户端工具
清理后业务报错误删了锁或正在使用的缓存引入黑名单/白名单机制,禁止清理锁 key 和热点 key
批量删除时延迟突刺使用DEL删除大 key改为UNLINK,并分批操作
删除 key 时提示READONLY连接到了从库,从库不可写连接主库执行删除,或使用只读从库的REPLICAOF解除
客户端工具里看到乱码 key序列化方式不同用代码删除,或配置客户端显示模式
清理后缓存雪崩大量 key 同时过期设置随机过期时间,错开过期点
FLUSHALL误执行操作失误在生产环境禁用该命令,通过 ACL 限制权限
使用 pipeline 批量删除时命令丢失管道连接异常增加确认机制,逐批检查返回结果

5.1 如何安全配置权限避免手滑清空

上面表格里提到 ACL,Redis 6.0 之后可以针对用户配置命令白名单。比如创建一个只能执行部分命令的用户,禁止执行FLUSHALL、FLUSHDB、KEYS、CONFIG等危险命令。这样即使有人拿到连接凭据,也无法一把梭清空。我在团队里专门建了一个cache-cleaner用户,只给它分配SCAN、UNLINK、DEL、EXPIRE、TTL等必要的命令权限,这样清理工作既高效又不越界。

5.2 从清理缓存升级到缓存治理

最后想说一下,清理缓存这件事,最怕临时抱佛脚。如果你经常需要清理 Redis,说明缓存设计本身可能有问题。比如没设过期时间、key 不合理堆积、大 key 过多。我现在更愿意做的是在应用层面建立统一的缓存工具类,所有缓存的读写都通过这个类,里面强制设置过期时间,并且自动统计 key 的规模。清理的时候只需要调用工具类里的deleteByPrefix方法,它会自动判断 key 类型、大小,选择用 UNLINK 还是 DEL,同时能绕过锁 key。

根据我个人的经验,清理 Redis 缓存最大的价值不是释放多少内存,而是通过一次次清理,倒逼你把缓存治理的规范建立起来。比如每次清理前都先看一眼慢日志、内存碎片率、大 key 列表,清理后对比一下内存曲线和命中率,这样你就能逐渐摸清每个业务的缓存特征。我自己后来已经很少手动去清理缓存了,因为前置规范做好了,缓存过期时间合理了,自动淘汰机制也能兜底,大部分问题都不会到需要人工清库的地步。但如果某天真的需要手动清理,至少你会知道该用 UNLINK 而不用 DEL,该用 SCAN 而不用 KEYS,该避开锁 key 而不是一把梭。这些经验,就是我一直想分享给你的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询