凌晨一点,Redis 内存告警响了。我爬起来看了一眼监控面板,used_memory 已经冲到 maxmemory 的 92%,再涨几分钟,所有写入请求都会直接报 OOM command not allowed。这种事干过运维和后台的人应该都不陌生——线上 redis 清理缓存,绝不是拿一条 FLUSHALL 糊弄过去就行,那种操作等于把生产环境当草稿纸,清完立刻见鬼:缓存雪崩、DB 被打爆、用户集体掉线,哪一个都比内存告警更难看。
所以这篇东西不写给 Redis 加一个 DELETE key 就算完的教程,而是把我这些年做 redis 缓存清理的完整方法论整理出来:什么时候真该清、怎么定位到底是谁占的内存、用哪种删除方式不会阻塞线上、清错了怎么补救,以及如何让缓存健康到不需要频繁"救火"。适合刚接手 Redis 的运维、写业务缓存的后端,以及所有被"Redis 内存又满了"折磨过的朋友。
1. 先别急着 FLUSHALL:判断 Redis 缓存该不该清的三个信号
遇到内存告警,第一反应不应该是清,而是先回答一个问题:这些内存里面,哪些是"该死的数据",哪些是"还没到该死时间的活数据"?把全部 key 清空是最笨的解法,因为你同时把热数据也干掉了。真正需要手动介入的,通常同时满足下面几个特征。
1.1 内存持续处于高水位,且不是瞬时抖动
所谓高水位,我的习惯是看 maxmemory 和 used_memory 的关系。如果 Redis 设置了 4GB 上限,used_memory 长期超过 80% 甚至 90%,并且持续了几个小时,而不是偶尔一次尖峰,那说明缓存数据本身膨胀了。注意这个"持续"很重要——有些业务晚上是大促,内存冲到 85% 几分钟又回落,这不算病,只是快照不够平滑;但如果你监控面板上那条内存曲线全天贴着上限走,那就不是健康状态了。
判断的时候别只看一个采样点。用 INFO memory 拿到的 used_memory_human 是最直观的,但最好配合 INFO stats 里的 expired_keys 和 evicted_keys。如果 evicted_keys 一直在涨,说明 Redis 已经在靠淘汰策略续命了,这时候你再不做点什么,业务端会开始看到奇怪的抖动——某些 key 突然没了、某些请求穿透到数据库,问题比内存高更隐蔽。
1.2 命中率下滑:缓存开始大量存了用不上的数据
缓存的意义是命中,如果内存占满了,命中的却是老数据,那这些老数据就该清理。我一般看两个计数器:keyspace_hits 和 keyspace_misses,两者不能直接看绝对值,要看趋势。比如实时的命中率:
code复制keyspace_hits / (keyspace_hits + keyspace_misses) * 100%
如果这个数字从 95% 掉到 80% 以下,同时内存还是满的,基本可以判断缓存池里塞了大量冷数据——不是你清理慢了,而是业务写入时缺少过期时间规划。这时候清缓存是给系统"腾地方",但腾完之后如果不用过期策略兜住,几周后还会复发。
1.3 写入已经开始报错:OOM 出现,说明已经到临界点
Redis 默认的 maxmemory-policy 是 noeviction,意思是内存写满之后,不淘汰任何已有 key,新写入请求直接拒绝,并返回 OOM command not allowed when used memory > 'maxmemory'。如果你在日志里见到这样的报错,说明 Redis 已经在"拒绝服务"了,这是最不该出现的信号。
出现 OOM 之后,留给你的操作窗口很短。清缓存不是"优化"而是"止血",这时候更要按下面的路径走:先诊断、再分类、后分批删除,而不是直接 FLUSHALL 把问题变成更大的问题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 定位元凶:大 Key 扫描与内存占用的精确诊断
在删任何 key 之前,我必须知道整个实例里到底有哪些东西在吃内存。Redis 不是关系型数据库,不能跑 SELECT * FROM ... ORDER BY size,但也有几个常规手段可以用,从轻量到重量我都列出来。
2.1 用 INFO memory 快速看水位和碎片
redis-cli INFO memory 是最先要看的。输出里有几个字段尤其重要:
| 字段 | 含义 | 我关心什么 |
|---|---|---|
used_memory |
实际存储数据占用的内存 | 和 maxmemory 对比,决定剩余空间 |
used_memory_rss |
进程实际占用的物理内存 | 看是否远大于 used_memory,判断碎片 |
mem_fragmentation_ratio |
used_memory_rss / used_memory | 大于 1.5 时存在内存碎片问题 |
maxmemory |
配置的内存上限 | 设置的是多少,决定了告警线 |
这里有个特别容易误导人的点:删完大 key 后,used_memory 下去了,但 used_memory_rss 可能一动不动。这是因为 Redis 底层用的内存分配器(默认 jemalloc)不太愿意把已经占用的内存归还给操作系统,而是先留在自己的内存池里。所以你判断清理效果要看 used_memory,而不是 free 命令,也不要因为系统 RSS 没降就认为删除无效。
2.2 用 --bigkeys 扫描大 Key:区分"大容量"和"多成员"
--bigkeys 是 redis-cli 自带的分析命令,用起来非常简单:
bash复制redis-cli -h 127.0.0.1 -p 6379 -a 'yourpassword' --bigkeys -i 0.1
它会遍历整个实例,统计每种数据类型中最大的 key。老版本的 --bigkeys 统计的是"成员数量最多"的 key,比如一个集合有 100 万个元素,但每个元素只有 4 个字节,它在集合类型里可能排第一,可实际上总共才 4MB,不算真正的大块头。Redis 6.0 之后的 redis-cli --memkeys 会更准确,它按估算内存排序。日常我两个都用:先 --bigkeys 看有没有"元素数量惊人的对象",再用 --memkeys 看真正的内存大户。扫描时加 -i 0.1 表示每扫描完 100 个 key 暂停 0.1 秒,是避免给主线程造成压力。
如果你已经怀疑某个具体的 key 有问题,直接用 MEMORY USAGE:
bash复制redis-cli MEMORY USAGE user:session:123456
返回的是该 key 加上它的数据结构体消耗的内存字节数。这招适合在清理前给一个 key 做"体检",确认它到底值不值得删。
2.3 全量分析:用 RDB 离线统计最靠谱
线上 Redis 实例可能有很多个 key,--bigkeys 只列了"最大的几个",但你要清理的是"所有垃圾 key 的总量"。这种情况下我建议直接拉一份 RDB 出来,在本地离线分析。
做法很简单:在低峰期执行 BGSAVE,让它后台生成 RDB 文件,然后把 dump.rdb 拷贝到一台独立的机器上,用 redis-rdb-tools 这类工具解析:
bash复制rdb -c memory dump.rdb
它会按内存占用排序输出所有 key 的列表,还会标出每个 key 的过期时间、数据类型和具体大小。这个文件比 --bigkeys 精确得多,并且不碰线上实例,完全零风险。我通常在内存告警之后的第一时间,不管三七二十一先做一次 BGSAVE,一方面是为了备份,另一方面也是为分析备好素材。线上执行 BGSAVE 会 fork 子进程,内存占用瞬间翻倍的风险存在,所以不要在内存已经 99% 的时候才做——这也是为什么我建议平时就把监控线设在 80%。
2.4 从业务维度给 key 分类
工具只能告诉你"谁大",不能告诉你"谁该删"。真正决定该不该删的是业务规则。我一般分成三类:
- 临时数据:验证码、一次性 token、未支付的会话状态。这类 key 应该带着 TTL 写入,如果没带,就是清理的首要目标。
- 可重建数据:商品详情缓存、配置缓存、用户摘要。删掉之后可以从数据库或者上游接口重新拉取,只是会有一阵子数据库压力变大。这类数据适合作为第二梯队。
- 不可重建或重建代价极高:排行榜、计数器、分布式锁状态、正在进行的流程实例。删了就不是缓存雪崩的问题,是直接丢业务状态,必须谨慎。
这个分类是整个清理方案的基础,后面每一步都建立在这个判断之上。
3. 清理缓存的四种手段,分别适合什么场景
大多数人一提到 redis 清理缓存,脑子里只有 DEL 和 FLUSHALL。其实 Redis 提供了好几类清理路径,每一种的阻塞风险、使用成本、适用场景都不一样。这一节我把它们拆开讲清楚。
3.1 DEL:适合小 key,大 key 会卡断主线程
DEL key 是最直接的删除方式,但很多人不知道它删除对象时是同步的。Redis 是单线程模型,如果删除一个包含 200 万个元素的 list,主线程要逐一把这些元素释放掉,期间所有读写命令都得排队等着。我一个客户曾经在生产环境上 DEL 一个几千万成员的 set,Redis 直接阻塞了十几秒,这十几秒里所有业务请求全部超时,最后落在了数据库上,数据库也跟着报警。
所以我的原则是:只有确认 key 的对象特别小(几十 KB 以内)才用 DEL。只要没把握,一律用下一招。
3.2 UNLINK:异步删除,4.0 之后的最优解
Redis 4.0 引入了 UNLINK,它做的事情和 DEL 一样,把 key 从键空间里摘除,但真正的内存回收放到后台线程去做。主线程只是 O(1) 地把 key 断开连接,然后立即返回。删除大 key 时,这个差别是决定生死的。
bash复制redis-cli UNLINK user:token:4038291
返回 (integer) 1 代表 key 存在且已摘除,返回 0 说明 key 本来就不存在。注意 UNLINK 不保证一定异步:如果 key 很小,后台线程的成本和同步删除差不多,Redis 可能直接同步删除;如果是超大 key,异步优势就能体现出来。不管怎样,它都不会造成长时间阻塞,所以安全得多。
批量删除时我也推荐把 DEL 换成 UNLINK,这是最不容易踩坑的改动。唯一要注意的是 Redis 版本必须 4.0 以上,低于 4.0 的实例上没有这个命令,要先升级或者说放弃直接大 key 删除,改用渐进式截断。
3.3 过期时间:让 Redis 自己清理,而不是你手动删
最优雅的清理,其实是根本不手动删。给每个 key 设置合理的 TTL,让它到期后自动被 Redis 回收。但这里要补充一下:Redis 对过期 key 的回收不是"时间一到立刻删",而是两种机制配合。
一种是惰性删除:当客户端访问一个已经过期的 key 时,Redis 发现它过期了,就先删掉再返回空。另一种是定期删除:Redis 每 100ms 随机抽取一批设置了过期时间的 key,检查是否到期,到期就删。这两种方式决定了:即使你给 key 设置了 TTL,过期了之后,它也可能不会立刻从内存里消失,而是要等下一次抽样或者访问才被真正回收。所以如果内存很紧张,你不能干等那批过期 key 自动消失,还是得按 TTL 排序主动处理。
处理方式也不复杂,用批量扫描把所有快到期的 key 提前 UNLINK 掉,或者直接依靠淘汰策略让 Redis 按规则清理。不过更实际的做法,是清理之前把"写入时没设置 TTL"的口子堵上。我这里说的不是玄学,是很多团队真实的情况:开发时图省事 set key value,忘了加 EX,于是 Redis 变成了永久内存数据库,数据越攒越多,只能靠手动清理续命。
3.4 内存淘汰策略:没有手动清,满员时 Redis 按策略开刀
如果你设置了 maxmemory 并且用的是非 noeviction 策略,Redis 会在内存写满时自动淘汰 key。策略通过配置项 maxmemory-policy 控制,常见几种如下:
| 策略 | 作用范围 | 适用场景 |
|---|---|---|
noeviction |
不淘汰 | 写入报错,适合无法接受缓存丢失的业务 |
volatile-lru |
仅设置过期时间的 key | 混合存储,推荐 |
allkeys-lru |
所有 key | 纯缓存场景,最省心 |
volatile-ttl |
仅设置过期时间的 key | 优先淘汰即将过期的 key |
volatile-random / allkeys-random |
范围随机 | 冷热不明显的场景 |
我见过很多团队既设了 maxmemory 又用的是默认 noeviction,于是内存满了就报错。这其实不算错,只是把"清理"的责任完全推给了运维人员。如果你的 Redis 定位纯粹是缓存——丢了可以从数据库重建,那我建议用 allkeys-lru,让它自动清理最久没访问的 key。如果 Redis 里面还存了分布式锁、消息队列 offset、点赞计数这些丢了会出问题的数据,就别用 allkeys-lru,这些 key 可能被当成"冷数据"淘汰掉。稳妥方案是 volatile-lru:只对设置了 TTL 的缓存 key 做淘汰,没设 TTL 的业务状态数据就保住了。
3.5 FLUSHDB / FLUSHALL:最干净也最危险的一招
清空整个库或者整个实例,用:
bash复制redis-cli FLUSHDB
redis-cli FLUSHALL
Redis 4.0 之后还支持异步清空:
bash复制redis-cli FLUSHALL ASYNC
FLUSHDB 清当前库,FLUSHALL 清所有库。我看到很多出事故的案例,就是因为误把 FLUSHALL 当成"清缓存"用了。它把每个 key 都干掉,包括那些不可重建的业务状态。就算用 ASYNC,也只是把阻塞问题解决了,数据丢失问题依然在。
我的态度是:FLUSHALL 只允许在两种场景出现。一是测试环境,二是你确认整个实例里的数据全部都是可重建缓存,并且对"删除瞬间的缓存雪崩"有预案(比如下游数据库能扛住瞬时全量流量)。生产环境真要全量清,也得在业务低峰期、提前备份、通知相关团队,然后执行完立即观察数据库负载。大多数情况下,你需要的不是全量清空,而是把临时数据和冷数据按前缀匹配删除。
4. 安全清理的标准操作流程:从备份到分批删除
把工具和手段搞清楚之后,真正动手时的流程才是保命的关键。我总结了一套适合自己的操作顺序,每次线上清缓存都按这套走,基本没有再出过大事故。
4.1 第一步:先备份,清理前务必留后路
在任何删除操作之前,先执行一次后台备份:
bash复制redis-cli BGSAVE
然后检查 INFO persistence,确认 rdb_bgsave_in_progress 变成 0,说明备份已经完成。接着把生成的 dump.rdb 文件拷贝到一个独立的存储位置。这一步的作用是:如果后面误删了不可重建的 key,你还有机会从 RDB 里恢复。
如果你开了 AOF 持久化,清理命令本身也会追加进 AOF。所以我建议清理结束后,如果删除了大量 key,让它自然触发 AOF rewrite,不然 AOF 文件会被删除命令撑大。也可以用 BGREWRITEAOF 手动触发,同样建议在低峰期做。
4.2 第二步:写清理清单,而不是边删边想
这一步看起来有点笨,但它能救命。根据前面 RDB 离线分析的结果,生成一份清单,至少包含三列:key 名称、预估大小、清理原因(临时数据/冷数据/超过保存期限)。清理的标准不是"我瞅着它没用",而是有明确的规则。
最常见的规则是按 key 前缀匹配,比如 temp:*、cache:verify:*、session:tmp:*。这种规则最容易用 SCAN 实现,也最不容易误伤。按 --bigkeys 列出的大 key 单独建表,一个 key 一条记录,因为大 key 在删除时要特殊照顾,不能和普通 key 混在一起批量扫。
4.3 第三步:用 SCAN 而非 KEYS 匹配 key
这是新手最容易踩的坑。网上不少教程会写:
bash复制redis-cli KEYS "temp:*"
在本地小数据量上跑没问题,但生产环境如果有几十万、上百万 key,KEYS 命令会导致 Redis 主线程长时间阻塞,因为它需要全表扫描匹配所有 key,期间无法处理其他命令。正确写法是用 SCAN 做游标迭代,每次只返回一小批,不阻塞主线程:
bash复制redis-cli --scan --pattern "temp:*" --count 500
--count 500 只是给服务端的扫描步长建议,不是每次固定返回 500 个 key,但足够让每次迭代的压力可控。实际清理时我习惯用脚本完成,因为还要配合删除。
4.4 第四步:分批删除 + Pipeline 提升效率
如果只是几十个 key,命令行直接删没问题。但你清缓存往往面对的是几十万个 key,必须走脚本。我用 Python 的 redis-py 写了一个通用的清理脚本,核心逻辑是 SCAN 游标循环 + UNLINK 批量执行:
python复制import redis
import time
r = redis.Redis(host="127.0.0.1", port=6379, password="yourpassword", decode_responses=True)
def batch_unlink_by_prefix(prefix, batch_size=500, sleep_seconds=0.05):
cursor = 0
total_deleted = 0
while True:
cursor, keys = r.scan(cursor, match=prefix, count=batch_size)
if keys:
# 使用 pipeline 减少 RTT,一次发多个 UNLINK
pipe = r.pipeline(transaction=False)
for key in keys:
pipe.unlink(key)
pipe.execute()
total_deleted += len(keys)
print(f"deleted {len(keys)} keys, total: {total_deleted}")
if cursor == 0:
break
return total_deleted
if __name__ == "__main__":
# 清理临时验证码和临时会话
total = batch_unlink_by_prefix("temp:*", batch_size=500)
print(f"cleanup finished, total deleted: {total}")
脚本里三个细节很重要:
- 用
SCAN而不是KEYS,避免阻塞主线程。 - 用
pipeline(transaction=False)把每批 500 个UNLINK一次性发送,减少网络往返次数。同步删除和异步删除只是 Redis 主线程是否等待,pipeline 是减少客户端与 Redis 的通信压力。 - 每批之间
sleep(0.05)让主线程歇口气。即使UNLINK本身不阻塞,删除后的内存回收和同步压力也需要缓冲。
删除大 key 不能简单用上面的脚本因为 UNLINK 一次只处理一个 key,如果这一个 key 特别大,Redis 4.0 的异步回收虽然在后台进行,但后续操作期间内存分配器大量释放也可能短暂影响性能。不过相比 DEL 已经优雅太多了。
4.5 分类型的渐进式删除:针对大对象的各种结构
对不同类型的超大 key,单靠 UNLINK 一步到位虽然不阻塞,但如果你希望"低峰期把大对象慢慢瘦身"而不是一次性释放大量内存,也可以采用渐进式删除,比如:
- 大 Hash:
HSCAN批次取出字段名,然后HDEL一批批删。 - 大 List:用
LTRIM key 0 -1清掉所有元素,或者反复LPOP/RPOP。 - 大 Set:
SSCAN取成员,SREM批量删。 - 大 ZSet:
ZREMRANGEBYRANK key 0 -1直接清空有序集合,或按 score 区间散弹删除。
渐进式删除的好处是每一步只释放一小块内存,对主线程的冲击很小,代价是删除时间长,要持续监控。通常在内存告警、服务已经飘红时,我没有耐心做渐进删除,直接 UNLINK 一步到位;只有在"内存还好,但我想优化大 key"时才会用渐进式。
4.6 清理后的验证:看内存、看命中、看数据库
所有删除命令执行完,不能关掉终端就完事。重点看三个指标:
INFO memory里的used_memory是否明显下降,目标是可以降到 maxmemory 的 70% 以下,留出安全缓冲。INFO stats里的keyspace_misses是否在清理后的几分钟内激增。如果 miss 暴涨,说明你把热 key 也删了,要立刻准备局部回填。- 数据库的 QPS 和延迟是否异常。缓存穿透到数据库通常会有 5 到 30 分钟的延迟显现,所以清理后的半小时内要盯紧数据库监控。
我见过最惨的一次事故就是清理脚本的前缀写成了 user:*,把用户会话全删了。当时用户重新登录会产生大量数据库查询,幸好是凌晨,数据库扛住了,但如果发生在白天,就是个 P0 事故。所以验证不是可选项,是必选项。
5. 清理过程中踩过的坑:阻塞、雪崩、误删,一个都没少见
这一节写几个真实踩过的情况,当作给后来者的路标。Redis 清缓存这件事看着简单,坑全在细节里。
5.1 KEYS 命令的代价:一次全表扫描卡死了整个实例
有一回在某老项目里,团队为了排查故障,直接在线上执行了 KEYS "*"。当时实例里有大约 400 万个 key,KEYS 命令在主线程上跑了 18 秒。这 18 秒内所有读写请求全在排队,最直观的表现是 Redis 的 blocked_clients 激增、客户端连接大量超时。事后我们做复盘,定位到这是一个纯粹的操作习惯问题——诊断线上问题应该默认使用 SCAN,只有本地调试或者明确知道 key 数量极小时才允许 KEYS。
这条我现在仍然屡次强调,因为总有新人带着大学课本里的 KEYS 命令走进生产环境。Redis 的文档其实也写了:KEYS 复杂度 O(N),生产环境慎用。
5.2 DEL 大 Key 导致的主线程阻塞
前面提到过 DEL 大 key 会阻塞,但可能还有人低估这个阻塞的严重性。真实案例:有个 list 类型的 key 存了大约 5000 万个元素,执行 DEL 后 Redis 阻塞了接近 90 秒。这 90 秒里,所有依赖缓存的请求全部雪崩式打到数据库,数据库连接池瞬间被打穿,最后快照显示直接影响了整条链路。
如果当时用的是 UNLINK,主线程完成摘除后立刻返回,后台慢慢释放这 5000 万元素,整个过程对外无感。那次之后,我把所有清理脚本里的 DEL 全部替换成了 UNLINK,同时也把 Redis 版本从 3.x 升到了 6.x,因为低版本不支持这个命令。如果你还在用 3.x,建议优先考虑升级,这比研究任何删除技巧都重要。
5.3 清完缓存引发雪崩:范围太大,局部热 key 全灭
缓存雪崩不一定是大面积过期造成的,也可能是"你手动把它清了"。我有一次清缓存时图省事,把 shop:* 前缀的所有 key 全部清理,结果这些 key 里包含了几个每天被访问几十万次的爆款商品缓存。删除命令执行完,紧接着商品服务大量回源数据库,数据库 CPU 直接飙到 100%,差点宕机。
这次教训让我养成了两个习惯:一是清理前必须把清单按访问热度分级,热 key 要单独处理,不能和冷数据一起批量删;二是清理过程要分批、限速,给下游数据库留出应对冲击的时间。比如删 10 万个 key,不要一个脚本 10 秒跑完,可以拆成 10 轮,每轮间隔 30 秒,让数据库逐步消化回源压力。为了进一步避免雪崩,我还会在应用侧加一层兜底:热点数据缓存 miss 时走分布式锁回填,同一时刻只允许一个请求回源。
5.4 误删数据的恢复:从 RDB 中挖回被删的 key
即使有备份,误删后的恢复也没那么简单。我第一次误删 session 时,第一反应是直接把那份 RDB 覆盖回生产实例,结果发现这个动作等于"全量回滚",会把 RDB 生成之后所有新的写入都覆盖掉,影响范围反而更大。正确的恢复方式是:把 RDB 文件加载到一个临时的 Redis 实例上,然后用 SCAN 找到被误删的 key,再用 DUMP / RESTORE 命令把它们单独导回生产实例。
bash复制# 在临时实例上导出 key
redis-cli -p 6380 DUMP user:session:123456
# 在临时实例上查看完整 key
redis-cli -p 6380 --scan --pattern "user:session:*"
DUMP 会返回一个序列化的值,把它记下来,在生产实例上执行 RESTORE:
bash复制redis-cli -p 6379 RESTORE user:session:123456 0 <serialized-value>
RESTORE 后面的 0 是存活的毫秒数,0 表示永久不过期,也可以填一个 TTL 毫秒值。这套"隔离 + 定向恢复"的方式,比我一开始的"整份 RDB 覆盖回去"靠谱得多,恢复粒度小,对生产环境干扰也小。
5.5 删除后内存并没有真的释放
最后提一个很多人会被吓到的现象:删掉几个 GB 的数据,top 里看到 Redis 的 RSS 没掉多少,于是以为清除无效。实际上这是内存分配器在发挥作用。jemalloc 不会把释放的小块内存立刻还给操作系统,而是留着给后续分配用。所以判断清理是否成功,请以 INFO memory 的 used_memory 为准,RSS 需要较长一段时间才会逐步回落,或者你配置了 activedefrag(Redis 4.0+ 可以开,但也会消耗 CPU)才能加速整理。
6. 让缓存保持健康:减少"清理缓存"这件事发生的频率
讲了这么多清理技巧,其实我心里真正的答案是:清理缓存应该是一个低频动作,而不是日常作业。如果一个系统每隔几周就要手动清一次缓存,那说明缓存治理出问题了。
6.1 所有缓存必须带 TTL,而且要用随机抖动
这是最基础也是最有效的一条规约。任何写入 Redis 的缓存数据,都要设置过期时间,哪怕只是 1 小时。但如果你给所有 key 都设置同样的 TTL,大量 key 会在同一时刻过期,导致缓存雪崩。解决办法是给 TTL 加随机抖动:
python复制import random
ttl = 3600 + random.randint(0, 600)
r.setex(key, ttl, value)
或者在写入框架层统一处理。这样即使所有 key 的基础过期时间相同,实际过期时刻也会被拉开,数据库不会在某一个瞬间被集中冲击。
6.2 设置 maxmemory 并匹配正确的淘汰策略
手动清理永远赶不上自动淘汰的反应速度。我会建议任何生产 Redis 都设置 maxmemory,并且根据业务模式把 maxmemory-policy 配好。纯缓存场景就用 allkeys-lru 最省心,缓存数据被淘汰了可以由上层回源。混合业务场景用 volatile-lru,只淘汰带 TTL 的数据。同时配合 maxmemory-samples(默认是 5)控制 LRU 抽样的精度,样本数越大越精确,但 CPU 开销也会增加。
还有一个容易忽略的点:maxmemory 不要设成物理内存的 100%。Redis 在数据量很大的时候,如果同时开启 AOF 重写或 RDB 快照,fork 子进程会额外占内存,所以我的话一般只用到物理内存的 60% ~ 70%,留出的余量是给操作系统、fork 和碎片用的。
6.3 上线一套覆盖"缓存健康"的监控
清理缓存不能只靠人肉盯监控,要配置好告警。用 Prometheus + redis_exporter,或者云厂商的监控服务都行,至少要盯这些指标:
redis_memory_used_bytes和redis_memory_max_bytes,超过 maxmemory 的 80% 告警。redis_memory_fragmentation_ratio,超过 1.5 告警。redis_keyspace_hits_total和redis_keyspace_misses_total,换算成命中率,低于阈值告警。redis_evicted_keys_total,如果持续增长说明淘汰策略在工作,也说明内存可能长期吃紧。redis_connected_clients和blocked_clients,连接数异常暴增通常伴随阻塞或雪崩。
告警只是第一步,收到告警后要能快速定位。最好给每个 Redis 实例标记业务归属,这样告警一响,你能直接找到对应的业务负责人,而不是一群人排查半天。
6.4 定期巡检:每季度做一次大 Key 摸底
即使没有内存告警,我也建议每个季度做一次大 key 扫描和 RDB 离线分析。很多内存问题不是一天形成的,等你看到监控告警的时候,数据已经膨胀得很厉害了。定期巡检能找到那些"没有设置过期时间"的漏网之鱼,也能发现某些业务逻辑在不断追加数据到同一个集合里,导致单个 key 无限制增长。
巡检之后输出一份报告,列出可以清理的 key 清单以及建议的 TTL 值,发给业务团队确认。这个流程走完,下一次内存告警可能一年之后才会来。
最后再分享一个小技巧,也是我每次清理缓存时的习惯:清理前把所有删除操作记录到日志里,包括 key 前缀、删除数量、耗时。不要小看这一行日志,出了事故要恢复,它就是你唯一的线索。我的经验是,线上 Redis 清理工作,做得好是无人察觉的维护,做不好就是删库跑路的教科书,区别往往就在删除之前的那一个备份和多看一眼日志。
