Redis 是内存数据库,内存是它唯一的"容量"指标,也是最容易出事故的地方。内存用满之后的表现很吓人:写入开始报 OOM command not allowed when used memory > 'maxmemory',淘汰策略如果配的是 noeviction 就完全写不进去;更糟的是操作系统层面的 OOM Killer 可能直接把 Redis 进程杀掉,导致数据全丢(如果还没落盘)。
这篇文章讲清楚怎么分析 Redis 的内存到底花在哪:used_memory 和 used_memory_rss 的区别、碎片比怎么解读、--bigkeys 和 MEMORY USAGE 怎么找大 key、八种淘汰策略各自的适用场景。最后给出一套从"缩短 key 名"到"拆分大 key"的可执行优化手段,以及客户端缓冲区的监控配置。文中会反复强调一个原则:涉及清数据的操作必须先确认影响范围,FLUSHALL/FLUSHDB 是最高危的命令,没有之一。
一、先看清楚:内存到底用在哪
INFO memory 全貌
redis-cli info memory
输出的关键字段:
| 字段 | 含义 |
|---|---|
used_memory |
Redis 分配器实际使用的内存(数据 + 内部开销),这是 Redis 自己认为用了多少 |
used_memory_human |
同上,人类可读格式 |
used_memory_rss |
从操作系统角度看到的进程常驻内存(Resident Set Size),这是实际占用物理内存 |
used_memory_peak |
历史峰值 |
used_memory_lua |
Lua 脚本引擎占用 |
used_memory_dataset |
数据集部分(used_memory 减去各种 buffer 和开销后的估算) |
mem_fragmentation_ratio |
碎片率 = used_memory_rss / used_memory |
mem_allocator |
内存分配器,通常是 jemalloc 或 libc |
maxmemory |
配置的上限,0 表示不限制 |
maxmemory_policy |
淘汰策略 |
想一次看到最关键的几个:
redis-cli info memory | grep -E 'used_memory_human|used_memory_rss_human|used_memory_peak_human|mem_fragmentation_ratio|maxmemory_human|maxmemory_policy'
还有一个全局视角:
redis-cli info keyspace # 每个库的 key 数量与带过期的数量
redis-cli dbsize
内存构成的拆解
Redis 的内存大致由这几部分组成:
数据本身。 键和值的实际内容,这是主体。
客户端缓冲区。 包括输入缓冲(客户端发来的命令)和输出缓冲(要返回给客户端但还没发完的数据)。这部分经常是"内存莫名涨了很多"的元凶,详见第七节。
复制缓冲区。 主从复制时的 backlog 以及发给从节点的输出缓冲。从节点断线重连时,如果积压太多,主节点可能因为缓冲区超限而主动断开从节点,触发全量同步,形成恶性循环。
AOF 缓冲区。 AOF 刷盘前的缓冲区,以及 AOF rewrite 期间的缓冲区。
内部开销。 数据结构本身的元数据(robj 头、dict 结构、SDS 头等)、以及 jemalloc 分配器产生的碎片。
一个重要的认识:存一个小 key 的实际内存开销远大于 value 本身。 一个 SET a 1 这样的键值对,实际占用可能是几十到上百字节——键名、SDS 头、robj 对象头(约 16 字节)、dictEntry(约 24 字节)、以及 jemalloc 按大小类分配导致的取整浪费。这也是为什么"缩短 key 名"在海量小 key 场景下效果显著。
二、used_memory 与 used_memory_rss:碎片比怎么读
这是最容易被误解的一组指标。
used_memory 是 Redis 向分配器申请并实际使用的字节数(jemalloc 视角)。它包含数据、缓冲区、以及分配器内部的元数据,但不包含分配器持有的空闲但未归还给操作系统的内存。
used_memory_rss 是操作系统看到的进程实际占用的物理内存(包含共享库、代码段、以及未能归还的空闲页)。
碎片率 = used_memory_rss / used_memory,这个值的含义很容易被搞反:
| 碎片率 | 含义 | 处理方向 |
|---|---|---|
| 略大于 1(1.0 到 1.5) | 正常,有一定碎片 | 无需处理 |
| 1.5 以上 | 碎片较严重 | 大量更新/删除导致的内存碎片,可考虑重启或开启主动碎片整理 |
| 远大于 1(比如 3 以上) | 碎片非常严重 | 通常是频繁写入不同大小的 value、大量删除造成的 |
| 小于 1 | Redis 的部分内存被换出到 swap | 严重问题,性能会断崖式下降,必须立刻排查 |
碎片率小于 1 是最危险的信号,它意味着操作系统把 Redis 的内存页换到了磁盘上。数据库用 swap 基本等于不可用(每次访问都可能触发一次磁盘 IO)。处理办法:
free -h # 看是否用了 swap
cat /proc/$(pidof redis-server)/smaps | grep -i swap # 看该进程换出多少
确认后要立即降低内存占用或迁移实例,并确保系统 swappiness 设置合理(数据库服务器建议设 1 或更低,甚至关闭 swap):
sysctl vm.swappiness
sudo sysctl -w vm.swappiness=1 # 临时生效
echo 'vm.swappiness=1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p # 永久生效
碎片整理
Redis 4.0 之后支持在线碎片整理(需要 jemalloc):
activedefrag yes
active-defrag-ignore-bytes 100mb # 碎片超过这个量才开始整理
active-defrag-threshold-lower 10 # 碎片率超过 10% 才开始
active-defrag-threshold-upper 100 # 碎片率超过 100% 时尽全力整理
active-defrag-cycle-min 5 # 整理占用的 CPU 时间下限(百分比)
active-defrag-cycle-max 75
redis-cli config set activedefrag yes
redis-cli info memory | grep -E 'active_defrag|frag_bytes'
主动碎片整理会消耗 CPU,要根据业务负载调节 cycle-min 和 cycle-max。 也可以在低峰期直接用 MEMORY PURGE(jemalloc 下归还空闲内存给操作系统):
redis-cli MEMORY PURGE
另一个更彻底的办法是重启实例(当然要配合持久化和主从切换)。重启后内存从干净状态重建,碎片率回到 1 附近。注意:重启前必须确认持久化文件完整、且有可用的备份,并确保重启后能正确加载数据。
三、找出大 key
大 key 是内存问题的常见根因,也是性能杀手——前面说过 Redis 单线程,操作一个大 key 会阻塞后面所有命令。
redis-cli --bigkeys
redis-cli --bigkeys
redis-cli --bigkeys -i 0.1 # 每次扫描后 sleep 0.1 秒,降低对线上的影响
它会用 SCAN 遍历键空间(不会像 KEYS 那样阻塞),按数据类型统计最大的 key。注意它只统计每种类型的"元素个数最多"的那个 key,不是按内存占用排序,所以一个元素少但 value 很长的 String 可能不是它报出来的那个。
输出示例解读:
Biggest string found 'article:1001:content' has 500000 bytes
Biggest hash found 'user:profile:all' has 100000 fields
Biggest zset found 'leaderboard:global' has 200000 members
--bigkeys 有一定性能开销(遍历全部键),建议在低峰期执行,或加 -i 参数限速。
MEMORY USAGE 看具体某个 key
redis-cli MEMORY USAGE user:1001
redis-cli MEMORY USAGE user:1001 SAMPLES 10 # 对集合类型,采样越多越准但越慢
它返回的是该 key 及其 value 占用的字节数估算。对 Hash、List、Set、ZSet 这类聚合类型,默认只采样 5 个元素来估算,加 SAMPLES 能提高准确度。
MEMORY DOCTOR 与 MEMORY STATS
redis-cli MEMORY DOCTOR # 给出内存问题的诊断建议(文字描述)
redis-cli MEMORY STATS # 详细的内存分配统计
MEMORY DOCTOR 会直接提示碎片率是否异常、是否有大量从节点缓冲等问题,是个不错的起点。
用脚本扫描出真正占内存最大的 key
官方的 --bigkeys 不够用时,可以用 --memkeys(Redis 7+ 支持按内存排序):
redis-cli --memkeys
redis-cli --memkeys -i 0.1
老版本可以用 SCAN 加 MEMORY USAGE 的方式(有开销,低峰执行):
redis-cli --scan --count 1000 | while read key; do
echo "$(redis-cli memory usage "$key") $key"
done | sort -rn | head -20
风险提示:这个脚本会对每个 key 执行一次 MEMORY USAGE,在百万级 key 的实例上耗时很长、且会产生大量命令请求。务必在从节点或低峰期执行,不要在主节点上跑。
大 key 的标准
经验上的参考线:
- String:value 超过 10 KB 就要关注,超过 100 KB 就算大 key
- Hash / List / Set / ZSet:元素个数超过 5000 到 10000 就算大 key(具体取决于单个元素的大小)
真正的判断标准不是绝对大小,而是操作耗时:如果一个 key 的读取或删除耗时超过几毫秒,它就会对线上产生影响。可以用 SLOWLOG 验证:
redis-cli slowlog get 10 # 慢日志,单位是微秒
redis-cli slowlog len
redis-cli config get slowlog-log-slower-than # 默认 10000 微秒(10 毫秒)
四、maxmemory 设置
redis-cli config get maxmemory
redis-cli config set maxmemory 8gb # 在线设置
# 配置文件
maxmemory 8gb
必须设置 maxmemory。 不设(默认 0)意味着 Redis 可以无限使用内存,直到把系统内存吃光,然后被 OOM Killer 杀掉或者开始用 swap——两种结果都比"拒绝写入"糟糕得多。
建议留出 20% 到 30% 的余量。 也就是:如果机器内存 16G,Redis 的 maxmemory 建议设 10G 到 12G。理由:
- 主从复制、AOF rewrite、RDB fork 期间会有额外的内存开销(fork 的写时复制在写入密集时可能让内存显著上升)
- 客户端缓冲区、复制缓冲区不计入数据内存但真实占用 RSS
- 碎片会让 RSS 高于 used_memory
- Redis 之外还有监控 agent、日志、系统进程要占用内存
如果同一台机器上还跑了别的服务,要先把它们的内存扣掉再算。
另外,maxmemory 限制的是 used_memory(数据相关部分),不包括 used_memory_rss 中的碎片和部分缓冲区。所以实际 RSS 可能超过 maxmemory。这也是留余量的原因之一。
五、八种淘汰策略怎么选
当 used_memory 达到 maxmemory 时,Redis 按 maxmemory-policy 决定怎么做。
| 策略 | 淘汰范围 | 算法 | 适用场景 |
|---|---|---|---|
noeviction |
不淘汰 | — | 数据不能丢;写满后新写入报错。必须配合监控和扩容 |
allkeys-lru |
所有 key | LRU(最近最少使用) | 纯缓存首选,最常用 |
volatile-lru |
只淘汰设了过期时间的 key | LRU | 一部分数据要常驻(不设 TTL)、一部分可淘汰 |
allkeys-lfu |
所有 key | LFU(最不经常使用) | 有明确冷热区分、希望保留高频访问数据 |
volatile-lfu |
只淘汰设了过期时间的 key | LFU | 同上,但需保留无 TTL 的数据 |
allkeys-random |
所有 key | 随机 | 所有 key 被访问概率差不多,极少用 |
volatile-random |
只淘汰设了过期时间的 key | 随机 | 同上,且需保留无 TTL 数据 |
volatile-ttl |
只淘汰设了过期时间的 key | 剩余 TTL 最短优先 | 希望尽快清理即将过期的数据 |
redis-cli config get maxmemory-policy
redis-cli config set maxmemory-policy allkeys-lru
LRU 与 LFU 的区别
LRU 看的是"最近什么时候被访问过",适合"热点会随时间推移"的场景(新闻、活动页)。
LFU 看的是"被访问的频率",适合"热点相对稳定"的场景(热门商品、基础配置)。LFU 能避免一个问题:一个高频访问的热 key 因为偶然一段时间没被访问(比如凌晨)而被 LRU 错误淘汰。
Redis 的 LRU 是近似 LRU:它随机采样一批 key(默认 5 个),淘汰其中最久未访问的那个,而不是维护全局链表。这样避免了大量内存开销。采样数可以调:
maxmemory-samples 10 # 越大越接近真实 LRU,但 CPU 开销更高
LFU 相关配置:
lfu-log-factor 10 # 计数器的增长速率,越大越慢达到高计数
lfu-decay-time 1 # 计数器的衰减时间(分钟)
选型建议
- 纯缓存(数据丢了可以从数据库重建):
allkeys-lru,这是最安全也最通用的选择 - 缓存 + 少量常驻数据(比如配置、字典):
volatile-lru,常驻数据不设 TTL - 有明显冷热区分、且热点稳定:
allkeys-lfu - 做分布式锁、会话、或者任何不能丢的数据:
noeviction+ 严格监控 + 提前扩容。但要清楚,写满后所有写入都会失败,这是个硬故障,不是解决方案 allkeys-random/volatile-random:几乎不主动使用
一个常见错误:把 Redis 当持久存储用,配了 allkeys-lru,结果分布式锁或者会话被淘汰了。 记住:只要用了 LRU/LFU 类策略,就要接受"任何 key 都可能被淘汰"。 不能丢的数据不要和缓存放在同一个实例里,应该拆分。
六、优化手段(从易到难)
1. 缩短 key 名
海量小 key 场景下效果显著。比如把 user:profile:basic:1001 改成 u:p:1001,每个 key 省十几个字节,一亿个 key 就是 1G 多。
代价是可读性下降。 折中做法是用统一的命名规范,保留必要的业务前缀,去掉冗余单词。可以用 Hash 结构把一类对象聚合起来,进一步减少 key 数量:
# 不要为每个字段建一个 key
SET user:1001:name "张三"
SET user:1001:age "20"
# 用一个 Hash
HSET user:1001 name "张三" age "20"
Hash 结构在元素少且值小时会用紧凑编码(ziplist / listpack),内存效率远高于多个独立 String。
2. 利用紧凑编码(ziplist / listpack)
Redis 对小规模的 Hash、List、Set、ZSet 使用紧凑编码,能省下大量指针开销。相关配置:
# Hash
hash-max-listpack-entries 512
hash-max-listpack-value 64
# List(Redis 7 用 listpack 替代 ziplist)
list-max-listpack-size -2
# Set(整数集合)
set-max-intset-entries 512
# ZSet
zset-max-listpack-entries 128
zset-max-listpack-value 64
含义是:当 Hash 的字段数不超过 512 且每个字段名/值的长度不超过 64 字节时,使用 listpack 紧凑编码。
注意这里有个权衡:阈值调大能省内存,但紧凑编码是连续内存,元素多了之后每次修改都要整体重排,CPU 开销会上升。默认值对大多数场景是合适的,除非你明确知道自己的数据特征,否则不要大幅调整。
判断某个 key 用了什么编码:
redis-cli OBJECT ENCODING user:1001
redis-cli DEBUG OBJECT user:1001 # 注意:DEBUG 命令有风险,仅在排查时用
DEBUG 命令在高版本上有安全限制,且部分子命令有副作用,只在测试或确认安全的排查场景使用。
3. 避免大 key 与热 key
大 key 的处理:拆分。
# 一个大 Hash 拆成多个小 Hash
# 原来:HSET big:hash field1 v1 ... field100000 v100000
# 拆成:按 field 哈希分到 100 个小 Hash
HSET big:hash:00 field1 v1
HSET big:hash:01 field2 v2
...
# 一个大 List 拆成多个
# 一个大 ZSet 按分数区间拆
拆分的原则是:让单个 key 的操作耗时降到毫秒以下。 代价是业务逻辑变复杂(要自己算分片),所以更好的做法是在设计阶段就避免产生大 key。
删除大 key 也有讲究。 Redis 4.0 之前删除大集合是同步操作,会阻塞;4.0 之后支持 UNLINK(异步删除,后台线程慢慢释放):
redis-cli UNLINK big:key
redis-cli --bigkeys # 找到大 key 后用 UNLINK 而不是 DEL
也可以配置让 Redis 在后台自动异步释放:
lazyfree-lazy-eviction yes
lazyfree-lazy-expire yes
lazyfree-lazy-server-del yes
replica-lazy-flush yes
热 key 的处理: 热 key 是指被极高频率访问的单个 key(比如秒杀商品的库存)。它会打满单实例的 CPU 和网络。解决办法:
- 本地缓存:应用服务器本地缓存一份,定期从 Redis 刷新
- 拆分成多个 key 加随机后缀:
hot:stock:1到hot:stock:10,访问时随机选一个,把压力分散(读场景可行,写场景要小心) - 读写分离:用从节点分担读压力
4. 设置过期时间,并打散
所有缓存 key 都必须设 TTL。 不设 TTL 的 key 会永久累积,是内存缓慢增长直至打满的典型原因。
检查有多少 key 没有过期时间:
redis-cli info keyspace # 看 db0 的 keys 和 expires 差值
扫描出没有 TTL 的 key(低峰在从节点执行):
redis-cli --scan --count 1000 | while read key; do
ttl=$(redis-cli ttl "$key")
[ "$ttl" = "-1" ] && echo "$key"
done | head -50
-1 表示没有设置过期时间,-2 表示 key 已不存在。
TTL 要加随机抖动,避免大批 key 同一时刻过期造成数据库压力尖峰(雪崩):
redis-cli SETEX cache:item:1001 $((3600 + RANDOM % 600)) '...'
5. 定期清理与归档
对于长期不访问的数据,可以定期扫描并删除。用 OBJECT IDLETIME 看空闲时间:
redis-cli OBJECT IDLETIME user:1001 # 单位秒
注意 OBJECT IDLETIME 会重置该 key 的 LRU 时钟(在某些版本上),频繁调用会影响 LRU 淘汰的准确性,谨慎使用。
七、客户端缓冲区:最容易被忽略的内存大户
Redis 的 maxmemory 只限制数据部分,客户端输出缓冲区不计入 maxmemory。这意味着即使 used_memory 远低于上限,进程的实际 RSS 也可能很高。
常见场景:
- 客户端用
MONITOR命令(会把所有命令返回给客户端,输出缓冲区暴涨) - 慢客户端(消费者消费慢)导致发布订阅的输出堆积
- 一次
HGETALL一个几十万字段的 Hash,结果集很大 - 主从复制时从节点长时间断开,主节点积压了大量复制数据
监控
redis-cli info clients
# 关键字段:connected_clients、client_recent_max_input_buffer、client_recent_max_output_buffer
redis-cli client list # 每个客户端的详细状态
redis-cli client list | awk '{print $1, $12, $13}' # 粗看 id 与缓冲
client list 输出里关注 obl(输出缓冲区长度)、omem(输出缓冲区内存占用)、idle(空闲秒数)。找出 omem 大的客户端:
redis-cli client list | grep -oE 'id=[0-9]+ .*omem=[0-9]+' | sort -t= -k3 -rn | head
限制
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
三个参数的含义是:硬性限制(超过立即断开)、软性限制、软性限制的持续秒数。超过软限并持续指定秒数也会断开。
normal普通客户端,默认不限制(0 表示不限)replica从节点,默认 256MB 硬限、64MB 软限持续 60 秒pubsub发布订阅客户端,默认 32MB 硬限、8MB 软限持续 60 秒
redis-cli config get client-output-buffer-limit
redis-cli config set client-output-buffer-limit "pubsub 64mb 16mb 60"
给 normal 也设一个上限是保护自己的好习惯(比如 client-output-buffer-limit normal 256mb 64mb 60),防止某个异常客户端(比如误开了 MONITOR)把内存吃光。但要注意:如果业务本身有合理的大结果集查询,设太小会导致正常请求被断开。设置前先观察 client_recent_max_output_buffer 的实际峰值。
从节点缓冲区
redis-cli info replication
# 看 slave0 的 offset 与主库 master_repl_offset 的差,以及 repl_backlog_size
从节点长时间落后会导致主节点积压。repl-backlog-size 默认 1MB,写入量大的实例建议调大到几十 MB 甚至上百 MB:
repl-backlog-size 64mb
repl-backlog-ttl 3600
八、内存打满时的应急处理
按风险从低到高排列
1. 先确认是不是真的满了,以及是什么在涨:
redis-cli info memory | grep -E 'used_memory_human|maxmemory_human|mem_fragmentation_ratio'
redis-cli info clients | grep -E 'connected_clients|output_buffer'
redis-cli --bigkeys -i 0.1
2. 淘汰策略是 noeviction 且是纯缓存场景 → 改成 allkeys-lru:
redis-cli config set maxmemory-policy allkeys-lru
redis-cli config rewrite # 把当前配置写回配置文件,避免重启丢失
config rewrite 很重要,否则重启后回到旧值。
3. 清理可以丢弃的大 key(用 UNLINK 而不是 DEL):
redis-cli UNLINK some:big:key
执行前必须确认这个 key 的业务含义,别只看名字想当然。
4. 扩容或拆分。 加内存、迁移到更大实例、或者按业务拆成多个 Redis 实例。
5. 重启释放碎片。 只在碎片率极高且其他手段无效时,配合持久化和主从切换在低峰执行。
关于 FLUSHALL 和 FLUSHDB 的严重警告
FLUSHALL(清空所有库)和 FLUSHDB(清空当前库)是不可逆的破坏性操作,会立即删除全部数据且不经过确认。
必须记住的几点:
- 生产环境永远不要手动执行,除非你明确知道自己在清一个可以完全重建的缓存实例,并且已经确认没有别的服务在用这个实例
- 执行前必须确认连接的是哪个实例:连错实例(比如把生产的当成测试的)是最常见的事故原因。执行前先跑
redis-cli info server | grep -E 'run_id|config_file'和redis-cli dbsize确认 - 先做快照:如果数据有任何价值,先
BGSAVE并把 RDB 文件拷走 - 异步版本:Redis 4.0+ 提供了
FLUSHALL ASYNC/FLUSHDB ASYNC,在后台线程释放内存,避免主线程阻塞。但它同样会删掉所有数据,只是不阻塞而已 - 建议在配置文件里重命名禁用:
rename-command FLUSHALL ""
rename-command FLUSHDB ""
- 从节点也会同步清空:在主节点执行
FLUSHALL,从节点的数据也会一起没了 - 如果是 AOF 持久化,FLUSHALL 会被记录到 AOF 文件里,恢复时会重放这个清空操作——也就是说 AOF 救不了你,只能靠之前备份的 RDB
一句话:想清数据时,先问自己"这个数据没了会怎样",然后问"备份在哪",最后再动手。
常见问题
Redis 内存一直在缓慢上涨,是怎么回事?
最常见的是三种:新写入的 key 没设 TTL(用 info keyspace 对比 keys 与 expires 的差值确认);有大量过期 key 但还没被惰性删除或定期删除清理掉;或者是客户端缓冲区在涨(用 client list 看 omem)。按这个顺序排查,基本都能定位。
used_memory 不高但系统内存快满了,为什么?
看 used_memory_rss。RSS 高于 used_memory 的部分主要是:内存碎片、客户端输出缓冲区、复制缓冲区、以及 fork 写时复制带来的开销。碎片率超过 1.5 就要考虑碎片问题,同时记得 maxmemory 不限制这些额外开销,所以必须给 RSS 留余量。
碎片率高一定要重启吗?
不一定。先试 MEMORY PURGE(jemalloc 下归还空闲内存),或者开启 activedefrag 让 Redis 在线整理。重启是最彻底的手段,但要配合持久化和主从切换,代价最大,放到最后。
淘汰策略选了 allkeys-lru,为什么内存还是涨到上限?
这是正常的。LRU 是在达到上限时才淘汰,不是提前清理。Redis 会一直用到 maxmemory,然后边淘汰边写入,稳定在上限附近。只要没有大量写入报错(OOM command not allowed),就说明淘汰机制在工作。想降低占用要从源头减少数据量。
大 key 删除为什么会卡住?
Redis 释放内存是同步操作(除非用 UNLINK 或开启 lazyfree)。一个几百万元素的集合,释放时要逐个回收,可能耗时几百毫秒到几秒,期间所有命令都排队。用 UNLINK 替代 DEL,或者配置 lazyfree-lazy-server-del yes。
为什么 maxmemory 设了 8G,实际进程占了 11G?
maxmemory 限制的是数据部分。多出来的主要是碎片、客户端输出缓冲区、复制缓冲区、以及 fork 期间的内存开销。这正是建议 maxmemory 只设为机器内存 70% 到 80% 的原因。 如果 RSS 远超预期,先查碎片率和 client list 的 omem。
分布式锁的 key 被淘汰了怎么办?
这是设计问题,不是参数问题。如果用了 LRU/LFU 类淘汰策略,任何 key 都有被淘汰的可能。不能丢的数据(锁、会话、配置)必须放在独立的、用 noeviction 策略且有充足容量的实例上,不要和缓存混在一起。 这是最稳妥的做法。
总结
- 监控从
INFO memory开始:重点看used_memory、used_memory_rss、mem_fragmentation_ratio。碎片率小于 1 意味着内存被换出到 swap,是最危险的信号,要先查swappiness和实际内存压力;碎片率 1.5 以上考虑MEMORY PURGE或activedefrag。 maxmemory必须设置,并留 20% 到 30% 余量:它只限制数据部分,不包含碎片、客户端缓冲、复制缓冲和 fork 开销,所以实际 RSS 会超过它。机器 16G 时设 10G 到 12G 比较稳妥。- 八种淘汰策略按场景选:纯缓存选
allkeys-lru;缓存加常驻数据选volatile-lru;热点稳定选allkeys-lfu;不能丢数据选noeviction(但要清楚写满即硬故障)。只要用了 LRU/LFU,就要接受任何 key 都可能被淘汰——锁和会话不要与缓存共用实例。 - 大 key 用
--bigkeys或--memkeys找,用MEMORY USAGE量,用UNLINK删;判断标准不是绝对大小而是操作耗时(用SLOWLOG验证)。大 key 要拆,热 key 要分散或加本地缓存。扫描类操作一律在从节点或低峰执行。 - 内存优化从五处入手:缩短 key 名、用 Hash 聚合减少 key 数量、利用 listpack/intset 紧凑编码(不要盲目调大阈值)、所有缓存都设 TTL 并加随机抖动、定期扫描清理无 TTL 和长期空闲的 key。
- 客户端缓冲区是被忽略的内存大户:用
client list看omem,给client-output-buffer-limit的normal也设上限防止异常客户端。MONITOR命令会在生产上引爆输出缓冲,不要用。 - FLUSHALL / FLUSHDB 是不可逆的高危命令:执行前必须确认实例身份(
info server+dbsize)、先BGSAVE备份、清楚从节点会同步清空且 AOF 会记录该操作。建议在配置里rename-command禁用它们,正常清数据用UNLINK逐个处理。
企业QQ咨询




