一、Redis 内存分配原理与数据存储特性

要解决内存满的问题,首先需理解 Redis 如何占用内存 —— 其内存消耗并非仅用于存储键值对,而是由多模块构成,且数据类型的编码特性直接影响内存效率。

1.1 内存结构组成(按占用比例排序)

Redis 的内存消耗主要分为 4 个部分,其中数据区是核心:

  • 数据区(~80%-90%):存储键值对数据,包含键(Key)、值(Value)及数据结构元数据(如哈希表桶、链表节点、跳表索引等)。

    • 例:一个Hash类型键,若采用ziplist编码,会存储字段 / 值的紧凑数组;若转成hashtable,则需额外存储哈希桶、链表指针等元数据,内存占用显著增加。
  • 缓冲区(~5%-15%):包括三类关键缓冲,易被忽视但可能引发内存溢出:

    • 客户端缓冲:每个客户端连接的输入 / 输出缓冲(默认无上限,大量闲置连接会导致缓冲堆积);
    • 复制缓冲:主从同步时的repl-backlog-buffer(默认 1MB,若同步延迟高会扩容);
    • AOF 缓冲:AOF 持久化时的写缓冲(临时存储待写入磁盘的命令)。
  • 进程开销(~1%-5%):Redis 进程自身的内存消耗,如代码段、栈空间、全局变量(固定大小,通常几 MB 到几十 MB)。

  • 内存碎片(动态变化):由内存分配器的 “分配粒度不匹配” 导致(如申请 8 字节内存,分配器最小分配 16 字节),碎片率过高会导致 “物理内存满但逻辑数据未达上限”。

1.2 数据类型的编码优化特性

Redis 会根据数据量和值大小自动切换编码格式,不合理的编码会导致内存浪费,加速内存满的发生:

数据类型 高效编码(内存友好) 低效编码(内存占用高) 切换触发条件(默认配置)
String embstr(紧凑存储) raw(独立存储) 字符串长度 > 44 字节
Hash ziplist(压缩列表) hashtable(哈希表) 字段数 > 512 或 字段值 > 64 字节
List quicklist(混合列表) linkedlist(链表) 节点数 > 1024 或 节点值 > 64 字节
Set intset(整数集合) hashtable(哈希表) 元素非整数 或 元素数 > 512
ZSet ziplist(压缩列表) skiplist(跳表) 元素数 > 128 或 元素值 > 64 字节

例:若将 1000 个整数存储为Set,用intset编码仅需几 KB;若误存为String(每个整数一个键),则需额外存储 1000 个键名和元数据,内存占用增加 10 倍以上。

二、Redis 内存满的故障表现与核心参数影响

当 Redis 内存达到maxmemory上限时,故障表现由内存淘汰策略决定,不同场景下的危害差异极大。

2.1 典型故障表现(按严重程度排序)

写操作拒绝(OOM 报错)

  • 当策略为noeviction(默认策略)时,Redis 会拒绝所有写操作(如 SET、HSET、LPUSH),返回OOM command not allowed when used memory > 'maxmemory',但读操作(GET、HGET)正常;

  • 影响:业务写入中断,如缓存更新失败、会话存储失败。

性能骤降(CPU 高 + 延迟飙升)

  • 当策略为allkeys-lru/volatile-lfu等淘汰策略时,若内存持续接近上限,Redis 会频繁执行 “筛选淘汰键” 操作(需遍历键空间或采样),导致 CPU 使用率飙升至 80% 以上;

  • 同时,淘汰过程会阻塞主线程(Redis 单线程模型),响应延迟从毫秒级增至秒级,甚至出现 “连接超时”。

Swap 滥用(性能雪崩)

  • 若未限制 Redis 使用 Swap(系统默认允许),当物理内存满时,系统会将 Redis 的部分内存页交换到磁盘(Swap 分区);

  • 磁盘 IO 速度比内存慢 1000 倍以上,导致 Redis 操作延迟从毫秒级增至秒级,服务近乎不可用。

进程被 OOM Killer 杀死(服务中断)

  • 若未设置maxmemory(默认maxmemory=0,不限制内存),Redis 会持续占用系统内存,直至系统内存耗尽;

  • 此时 Linux 内核的 OOM Killer 会根据进程优先级(oom_score)杀死 Redis(默认 Redis 的oom_score较高),导致服务中断且可能丢失未持久化数据。

2.2 核心参数对内存满的影响

3 个关键参数直接决定内存满的触发时机和故障模式:

参数名 作用说明 风险配置示例 推荐配置(缓存场景)
maxmemory 限制 Redis 最大使用内存(字节),0 表示不限制 maxmemory=0(默认,必改) 设为系统内存的 70%-80%(如 8G 内存设6G)
maxmemory-policy 内存满时的淘汰策略 noeviction(默认,写操作拒绝) allkeys-lfu(优先淘汰不常用键)
maxmemory-samples LRU/LFU 策略的采样数(平衡准确性与 CPU) maxmemory-samples=1(采样少,淘汰不准) maxmemory-samples=10(兼顾准确与性能)

关键提醒:maxmemory必须设置!若不设置,Redis 会无限制占用内存,最终触发系统 OOM,风险极高。

三、Redis 内存回收策略的执行机制与差异

Redis 提供11 种内存回收策略,分为 “淘汰型” 和 “不淘汰型”,需根据业务场景选择(如缓存场景 vs 存储场景)。

3.1 策略分类与核心差异

所有策略按 “淘汰范围” 和 “筛选逻辑” 可分为 4 类:

策略类型 具体策略名 适用场景 核心执行逻辑
不淘汰(危险) noeviction 纯存储场景(不允许数据丢失) 拒绝所有写操作,仅允许读 / 删除操作
基于 LRU allkeys-lru 缓存场景(无过期键,淘汰最近最少用) 所有键中采样,淘汰最近最少使用的键
volatile-lru 混合场景(部分键有过期时间) 仅从有过期时间的键中,淘汰最近最少使用的键
基于 LFU allkeys-lfu 缓存场景(淘汰最不常使用,比 LRU 更精准) 所有键中采样,淘汰最近访问频率最低的键
volatile-lfu 混合场景(部分键有过期时间) 仅从有过期时间的键中,淘汰访问频率最低的键
基于 TTL volatile-ttl 存储场景(需优先保留即将过期的键?否) 仅从有过期时间的键中,淘汰 TTL 最短的键
随机淘汰 allkeys-random 特殊缓存场景(无访问偏好) 所有键中随机淘汰
volatile-random 特殊混合场景 仅从有过期时间的键中随机淘汰
从库专用 volatile-leastrecently-used等 主从复制场景(从库不主动淘汰,由主库同步) 从库仅在主库淘汰后同步删除,自身不触发淘汰

3.2 关键执行机制(避免认知误区)

LRU/LFU 是 “近似算法”,非精确

  • 精确 LRU 需维护 “访问时间链表”,每个键访问时都要调整链表,开销极大;

  • Redis 采用 “采样 LRU”:每次淘汰时,从键空间中随机采样maxmemory-samples个键,淘汰其中最符合条件的(如最近最少用);

  • 采样数越多(如 10),结果越接近精确 LRU,但 CPU 消耗越高(推荐 5-10)。

volatile 类策略的 “失效风险”

  • 若maxmemory-policy=volatile-lru,但所有键均无过期时间(expire未设置),则策略等同于noeviction,会拒绝写操作;

  • 场景示例:缓存业务中,若误将所有键设为 “永久有效”,则volatile-lru策略失效,直接触发 OOM。

淘汰触发时机

  • 被动触发:新写入 / 修改数据时,检测到内存超过maxmemory,立即执行淘汰(阻塞主线程,耗时取决于采样数);

  • 主动触发:Redis 定时任务(默认每 100ms 执行一次),若内存超过maxmemory,批量淘汰部分键(减少被动阻塞时间)。

四、故障排查流程(内存满时的应急处理)

当 Redis 出现内存满故障时,按以下步骤快速定位并解决:

第一步:判断内存满的原因

  • 执行info memory,查看:

    • used_memory > maxmemory:确认内存已达上限;
    • mem_fragmentation_ratio:若 > 2.0,是碎片问题;若 < 1.0,可能用了 Swap;
    • evicted_keys:若为 0,说明淘汰策略未生效(如noeviction或volatile策略无过期键)。

第二步:紧急缓解措施

  • 若写操作被拒:立即切换淘汰策略,config set maxmemory-policy allkeys-lfu(允许淘汰数据);

  • 若碎片率高:config set activedefrag yes(开启自动整理);

  • 若存在大 Key:用UNLINK删除大 Key(unlink bigkey:xxx),快速释放内存。

第三步:根因修复

  • 若淘汰策略不合理:持久化配置(config rewrite),避免重启后失效;

  • 若数据结构冗余:优化编码配置(如调整hash-max-ziplist-entries);

  • 若缓存命中率低:重新设计缓存键(如增加键的颗粒度),避免 “无效缓存” 占用内存。

总结

Redis 内存满的本质是 “内存分配效率不足” 或 “数据增长超过预期”,核心解决方案需围绕 3 点:

合理配置参数:必设maxmemory,选对maxmemory-policy,平衡淘汰准确性与 CPU 消耗;

优化数据存储:用高效编码、拆分大 Key、管理过期键,从源头降低内存占用;

建立监控预警:跟踪内存碎片率、淘汰数、命中率,提前发现风险,避免故障发生。