'maxmemory',淘汰策略如果配" />

Redis内存占用过高怎么办?内存分析与淘汰策略配置

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 逐个处理。