内存、淘汰与碎片

used_memory 是 Redis 自己记账的内存,used_memory_rss 是操作系统实际给这个进程的内存,两者之间的差额就是碎片,或者已经释放但没有归还给系统的内存。本章按“涨得快、涨得满、涨得虚”三种情况分别给出判读口径与处置顺序。

先看清这几个数

redis-cli info memory
redis-cli info memory | grep -E 'used_memory_human|used_memory_rss_human|used_memory_peak_human|maxmemory_human|maxmemory_policy|mem_fragmentation_ratio|mem_clients_normal'
字段含义判读要点
used_memory分配器记账的数据 + 内部开销持续单向增长且业务量平稳时,优先怀疑内存泄漏
used_memory_rss操作系统视角的常驻内存与 used_memory 差距过大说明碎片或未归还
used_memory_peak历史峰值峰值远高于当前值时,说明曾有大批量写入或溢出
mem_fragmentation_ratiorss / used_memory1.0 ~ 1.5 正常;大于 1.5 有碎片;小于 1 说明用了 swap
mem_clients_normal普通客户端缓冲占用突然升高说明慢客户端或大 value 写入
mem_not_counted_for_evict不计入淘汰的内存(如 AOF 缓冲)内存满了却淘汰不动时,来看这一项
allocator_frag_ratio分配器自身的碎片率用于区分“分配器碎片”与“操作系统未回收”

情况一:涨得快——先分清是数据还是缓冲

redis-cli info memory
redis-cli info clients
redis-cli client list | sed 's/ /\n/g' | grep '^omem=' | sort -t= -k2 -nr | head -10
redis-cli --bigkeys -i 0.1
redis-cli memory doctor

MEMORY DOCTOR 给出的是提示性建议,比如“峰值内存远大于当前内存”“碎片率偏高”“大量内存被客户端缓冲占用”,它不是精确诊断,要顺着提示再去验证。判读顺序:客户端缓冲暴涨 → 查慢客户端;--bigkeys 有异常 → 走第 24 章的拆分;都没异常但曲线持续上升 → 怀疑 key 只增不删(缺少 TTL)。

情况二:涨得满——淘汰策略怎么选

策略淘汰范围适合场景风险
noeviction不淘汰,写入直接返回 OOM 错误当作持久存储、绝不允许丢数据的场景内存满后写入全部失败,是硬故障
allkeys-lru所有 key 按最近最少使用淘汰纯缓存场景的默认选择长时间未被访问但重要的数据会被删
allkeys-lfu所有 key 按访问频率淘汰热点集中、有长尾访问的业务新 key 需要累积计数,短期可能被误删
allkeys-random所有 key 随机淘汰几乎不用命中率随机波动
volatile-lru / volatile-lfu只淘汰设置了过期时间的 key同一实例混存持久数据与缓存可丢数据若没设 TTL,会退化成 noeviction
volatile-random带 TTL 的 key 随机淘汰极少使用同上
volatile-ttl优先淘汰剩余存活时间最短的TTL 语义本身就代表重要性的场景TTL 设计不合理时淘汰效果差

选择建议:纯缓存用 allkeys-lfu(热点明显时优于 LRU),混存场景用 volatile-lfu 且缓存类 key 必须带 TTL。不要用 noeviction 当缓存——那等于把 Redis 变成一个随时会写失败的存储。

evicted_keys 的曲线判读:

evicted_keys 曲线含义动作
稳定为 0容量充足保持巡检
偶发小幅增长已接近水位观察命中率,准备扩容
持续增长且命中率下降缓存正在删掉自己有用的数据立即扩容,同时压缩 value、缩短 TTL、拆分实例
持续增长但命中率不变淘汰的是冷数据仍要规划容量,不要当成正常现象

情况三:涨得虚——碎片与操作系统行为

redis-cli info memory | grep -E 'mem_fragmentation_ratio|allocator_frag_ratio|active_defrag_running'
redis-cli config set activedefrag yes
redis-cli config set active-defrag-ignore-bytes 100mb
redis-cli config set active-defrag-threshold-lower 10
  • 碎片率持续大于 1.5 且 CPU 有余量时,开启 activedefrag 在线整理;它本身消耗 CPU,高负载实例上要谨慎,一次只在一个节点开。
  • 碎片率小于 1 通常意味着部分内存在 swap 里,这是性能杀手:先解决物理内存,再谈其他参数。
  • 负载突增导致的碎片,往往在业务回落后自然恢复;只有长期不降才需要主动整理或重启。
  • 重启是消灭碎片最彻底的方法,但会丢失未持久化数据并可能触发全量同步,必须放在低峰并有预案。

过期删除机制与 expired_keys

  • 惰性删除:key 被访问时才判断是否过期,过期则删除。
  • 定期删除:每秒若干次(受 hz 影响)随机抽取一批带 TTL 的 key,删除其中已过期的;若过期比例超过 25%,同一轮继续采样。
  • 结论:大量 key 同一时刻过期会形成“过期风暴”——删除动作与业务命令争抢单线程 CPU,且过期 key 在被删除前仍占内存。
  • 应对:TTL 加随机抖动(基准值 + 0 ~ 10% 随机偏移);批量预热时也把过期时间打散。

内存水位与扩容流程

水位(used_memory / maxmemory)处置
小于 70%正常,保持巡检
70% ~ 85%排查增长来源(大 key、无 TTL 的 key、客户端缓冲),准备扩容
大于 85%扩容;同时缩短非核心数据 TTL、清理无用 key、给 fork 留出余量
达到 100% 且策略为 noeviction写入已经失败,属于线上故障,立即扩容或紧急清理

扩容流程:新增节点 → 加入集群或建立主从 → 迁移槽位或切换主库 → 观察一周 → 下线旧节点。Cluster 用 redis-cli --cluster reshard,主从架构则新增从库后提升为主库并切换客户端地址。maxmemory 建议设为物理内存的 70% ~ 80%,因为 fork 期间的写时复制可能额外占用接近一份数据的内存。

内存暴涨的四种典型来源

来源特征验证命令处置
客户端输出缓冲mem_clients_normal 阶梯上升CLIENT LIST 看 omem限制输出缓冲并断开慢客户端
大 key 批量写入used_memory 呈台阶式上涨redis-cli --bigkeys -i 0.1拆分大 key,分批写入
缺少 TTL 的数据曲线单调上升,expired_keys 几乎不增长抽样检查 key 的 TTL补 TTL,清理无用 key
复制缓冲与 AOF 缓冲mem_not_counted_for_evict 上升,淘汰不动info replicationinfo persistencerepl-backlog-size,调整 AOF 策略
# 小样本抽查没有过期时间的 key(TTL 返回 -1 表示永不过期,命令较慢,只做抽样)
redis-cli --scan --pattern 'cache:*' | head -500 | while read -r k; do
  [ "$(redis-cli ttl "$k")" = "-1" ] && echo "$k"
done
redis-cli memory purge        # 仅把空闲内存还给操作系统,不释放任何数据

常见误判

误判事实
碎片率大于 1.5 就要重启业务回落后碎片常自行下降,先观察一两个周期
内存高就调大 maxmemory内存不足是物理资源问题,调参数只会把 OOM 推迟
evicted_keys 为 0 就没有内存问题noeviction 下不淘汰,但写入会直接失败
MEMORY PURGE 能解决内存不足它只归还空闲内存,数据占用一点没减

预防与巡检

  • 非核心数据必须有 TTL,禁止永久 key 无限堆积;每周对无 TTL 的 key 做一次抽查。
  • 监控 used_memoryused_memory_rssevicted_keysexpired_keys 与碎片率五条曲线,并做周同比。
  • 大促或压测前评估峰值内存,把 fork 与 AOF 重写的额外开销算进去,不要顶到 maxmemory 才扩容。
  • 内存增长异常时第一时间保留现场(info memory--bigkeys 结果、CLIENT LIST),再动手清理。

小结:把内存问题分成涨得快、涨得满、涨得虚三类分别处理——先分清是数据增长还是客户端缓冲,再按业务语义选淘汰策略并盯住 evicted_keys 曲线,最后才处理碎片与 swap;maxmemory 留出 20% ~ 30% 余量,过期时间加随机抖动避免过期风暴,内存水位超过 85% 就开始扩容而不是等到写入失败。

笔记加载中…