Redis内存占用过高怎么办?内存分析、淘汰策略与安全优化教程

Redis内存占用过高怎么办?内存分析、淘汰策略与安全优化教程

Redis以内存作为主要数据存储介质,进程占用较多内存本身并不异常。真正需要处理的是:内存持续逼近服务器上限、写入开始返回OOM错误、系统频繁使用Swap、Redis被操作系统终止,或者在执行RDB、AOF重写和主从同步时出现明显的内存峰值。

Redis内存问题也不能只看top中的进程占用。数据集、键和值的元数据、内存碎片、客户端缓冲区、复制积压缓冲区、Lua脚本、模块、持久化写时复制等,都可能影响Redis实际占用。

本文介绍如何使用INFO MEMORYMEMORY STATSMEMORY USAGEredis-cli --bigkeys--keystats定位内存来源,并说明过期键、淘汰策略、碎片整理、持久化峰值和客户端缓冲区的安全优化方法。

示例主要适用于Redis Open Source 7.x及常见Linux环境。不同版本、托管Redis产品和兼容数据库的命令、指标与默认配置可能不同,修改前应先确认实际版本。

一、先判断Redis是否真的存在内存风险

查看系统内存:

free -h

持续观察换页:

vmstat 1 10

重点关注:

  • available:系统还能提供给进程使用的内存。
  • Swap:交换空间是否已使用。
  • siso:是否持续发生换入和换出。
  • Redis进程RSS是否仍在增长。

查看Redis进程:

ps -eo pid,%mem,rss,vsz,etime,comm,args --sort=-rss | head -n 20

不要把VSZ直接当成实际物理内存。判断Redis当前驻留内存时,RSS更有参考价值;判断Redis内部数据和分配情况,则需要继续查询Redis自身指标。

连接Redis:

redis-cli

如果启用了ACL或密码,应使用安全的认证方式。避免直接把密码写进Shell历史或进程参数。

二、使用INFO MEMORY查看核心指标

执行:

INFO MEMORY

也可以在Shell中执行:

redis-cli INFO MEMORY

常见核心指标包括:

指标含义排查重点
used_memoryRedis分配器统计的内存数据与内部结构的主要参考
used_memory_human易读格式的已用内存便于快速查看
used_memory_rss操作系统看到的Redis驻留内存与进程RSS接近
used_memory_peakRedis历史内存峰值判断是否经历过高峰
used_memory_dataset数据集占用判断业务数据本身大小
used_memory_overhead非数据集开销键元数据、客户端、复制等
mem_fragmentation_ratioRSS与内部内存的比例指标仅用于辅助判断碎片
mem_not_counted_for_evict未计入淘汰判断的部分内存复制和持久化缓冲等
maxmemory配置的内存上限0通常表示未设置
maxmemory_policy达到上限后的策略决定报错或淘汰方式

先筛选重点指标:

redis-cli INFO MEMORY |
  grep -E 'used_memory:|used_memory_rss:|used_memory_peak:|used_memory_dataset:|used_memory_overhead:|mem_fragmentation_ratio:|mem_not_counted_for_evict:|maxmemory:|maxmemory_policy:'

如果maxmemory为0,自建Redis通常会继续使用可用内存,直到受到系统或容器限制。生产环境一般应根据业务和持久化需求设置明确上限,并为操作系统、fork写时复制、客户端缓冲区和其他服务保留余量。

三、used_memory与RSS为什么不同

used_memory主要反映Redis通过内存分配器使用的内存,used_memory_rss则反映操作系统为Redis进程保留在物理内存中的页面。

删除大量键后,可能出现:

used_memory明显下降
used_memory_rss变化不大

这不一定是内存泄漏。分配器可能保留已经释放的内存块,等待Redis后续写入时复用,而不是立即归还操作系统。

Redis官方文档指出,数据删除后RSS可能保持在历史高位;后续重新写入相近规模的数据时,分配器通常会优先复用这些空闲块。因此,容量规划不能只参考当前低谷,也应考虑历史峰值。

判断时应结合:

  • used_memory是否持续增长。
  • 键数量是否同步增长。
  • used_memory_dataset是否增长。
  • used_memory_rss是否在稳定业务下无限上升。
  • 是否刚删除过大量键。
  • 是否刚结束RDB、AOF重写或主从同步。
  • 内存碎片整理是否启用。

四、使用MEMORY STATS拆解内存来源

执行:

MEMORY STATS

该命令会返回更细的内存统计,例如:

  • 数据集内存。
  • 键和值的开销。
  • 客户端内存。
  • 复制积压缓冲区。
  • AOF缓冲区。
  • Lua相关内存。
  • 内存碎片与分配器统计。

易读查看:

redis-cli --raw MEMORY STATS

还可以查看诊断建议:

MEMORY DOCTOR

MEMORY DOCTOR适合辅助发现明显碎片或数据结构问题,但它不是完整诊断工具。数据量较小或场景不典型时,输出可能无法给出明确建议。

五、先确认键数量和过期情况

查看键空间:

INFO KEYSPACE

输出可能类似:

db0:keys=1200000,expires=850000,avg_ttl=3600000

其中:

  • keys:数据库中的键总数。
  • expires:设置了过期时间的键数量。
  • avg_ttl:设置过期键的平均剩余生命周期。

如果Redis主要用作缓存,但大量键没有TTL,数据集可能只增不减。

抽查键的TTL:

TTL your:key

返回值:

  • 正数:剩余秒数。
  • -1:该键没有过期时间。
  • -2:该键不存在。

不要在大实例上直接执行:

KEYS *

KEYS需要遍历整个键空间,可能阻塞Redis。生产环境应使用SCAN渐进式扫描:

SCAN 0 COUNT 1000

COUNT只是每次扫描的工作量提示,不保证准确返回指定数量。

六、定位大键和高内存键

Redis CLI提供多种扫描工具。

使用–bigkeys

redis-cli --bigkeys

它会使用SCAN遍历键空间,并按数据类型寻找元素数量或字符串长度较大的键。

如果实例压力较高,可以降低扫描速度:

redis-cli --bigkeys -i 0.1

-i 0.1表示每批扫描之间短暂休眠,以降低对线上实例的影响。

使用–memkeys

部分新版redis-cli支持:

redis-cli --memkeys

它根据MEMORY USAGE寻找占用内存较大的键,比只看元素数量更接近实际内存。

使用–keystats

新版Redis CLI还可使用:

redis-cli --keystats

该模式结合键大小和分布统计帮助定位异常键。是否支持取决于客户端版本,可先执行:

redis-cli --help | grep -E 'bigkeys|memkeys|keystats'

查看指定键

MEMORY USAGE your:key

对大型集合类型可指定抽样数量:

MEMORY USAGE your:key SAMPLES 100

返回值是估算该键及其值占用的内存字节数。集合抽样越多,估算越准确,但命令开销也会增加。

七、常见导致Redis内存过高的键

1. 缓存键没有设置TTL

典型问题:

  • 写缓存时漏掉EXPX
  • 更新缓存时覆盖了原来的过期设置。
  • 代码只写入不删除。
  • 业务将Redis当作永久数据库使用,却没有容量规划。

写入时直接设置过期时间:

SET cache:user:123 value EX 3600

已有键补充TTL:

EXPIRE cache:user:123 3600

不要对未知业务键批量设置统一TTL。会话、锁、验证码、排行榜和持久数据的生命周期完全不同。

2. 单个Hash、List、Set或Sorted Set过大

大型集合会带来:

  • 内存占用集中。
  • 删除操作延迟。
  • 网络响应体过大。
  • 主从同步压力。
  • 迁移和备份时间增加。

可检查:

HLEN your:hash
LLEN your:list
SCARD your:set
ZCARD your:zset
XLEN your:stream

优化方向包括:

  • 按用户、日期或业务分片。
  • 删除过期成员。
  • 为Stream设置长度策略。
  • 避免把无限增长日志放在List中。
  • 对排行榜只保留业务需要的范围。

3. 键名过长且数量巨大

Redis需要同时保存键名和值。少量长键影响不大,但数千万个长键名会形成明显开销。

键名应保持可读且适度精简。不要为了节省少量内存把所有键名压缩成无法维护的乱码,也不要在键名中重复保存完整URL、大段JSON或无必要的路径。

4. 重复保存JSON和冗余字段

大量小对象使用独立String键保存完整JSON时,键元数据和文本字段名可能占用较多空间。

可以根据访问方式评估:

  • 使用Hash保存对象字段。
  • 合并相关小对象。
  • 删除不需要的字段。
  • 使用更紧凑的序列化格式。
  • 对可计算字段不重复存储。

Redis对较小的Hash、List、Set和Sorted Set可以使用紧凑编码。调整紧凑编码阈值属于CPU与内存之间的权衡,不能简单调大,修改前应在真实数据上进行压测。

八、过期键为什么没有立即消失

Redis通过惰性删除和主动过期两种方式处理过期键:

  • 惰性删除:客户端访问已过期键时删除。
  • 主动过期:后台周期性抽样并删除已过期键。

因此,键到达TTL后不一定在同一毫秒立刻从内存中移除。大量键在同一时间集中到期时,回收过程可能持续一段时间。

检查过期统计:

redis-cli INFO STATS |
  grep -E 'expired_keys|expired_stale_perc|expire_cycle_cpu_milliseconds|evicted_keys'

如果大量缓存使用完全相同的TTL,可能形成集中失效和回源高峰。可以在合理范围内为TTL加入随机抖动,例如基础一小时再随机增加若干分钟。

不要依赖过期机制替代业务删除。业务明确失效的数据应及时删除,TTL用于兜底和生命周期控制。

九、maxmemory应该怎么设置

查看当前配置:

CONFIG GET maxmemory
CONFIG GET maxmemory-policy

临时设置为4GiB:

CONFIG SET maxmemory 4gb

配置文件示例:

maxmemory 4gb
maxmemory-policy allkeys-lru

是否可以通过CONFIG SET持久保存取决于部署方式和权限。自建Redis应将最终配置写入实际加载的配置文件;托管Redis应使用云控制台或厂商接口。

上限不能等于服务器全部内存

假设服务器有8GiB内存,不应直接将maxmemory设置为8GiB。还要为以下内容预留空间:

  • 操作系统和文件缓存。
  • Redis进程基础开销。
  • 客户端输入与输出缓冲区。
  • 复制积压缓冲区。
  • RDB或AOF重写期间的写时复制。
  • 监控和安全程序。
  • 同机运行的其他服务。

写入频繁、数据集较大且启用持久化时,需要保留更多余量。具体比例不能机械套用,应根据used_memory_peakcurrent_cow_peak和实际压测确定。

十、淘汰策略怎么选择

Redis达到maxmemory后,会根据maxmemory-policy决定返回错误还是淘汰键。

常见策略包括:

策略行为适合场景
noeviction不自动删除键,写命令返回错误数据不能被自动丢弃
allkeys-lru从所有键中近似淘汰最近最少使用的键通用缓存
allkeys-lfu从所有键中近似淘汰访问频率较低的键热点长期稳定的缓存
allkeys-random从所有键中随机淘汰访问分布接近或特殊场景
volatile-lru只从有TTL的键中按LRU淘汰同实例混合缓存与持久键
volatile-lfu只从有TTL的键中按LFU淘汰有TTL的热点缓存
volatile-ttl优先淘汰剩余TTL较短的键过期时间能反映价值
volatile-random在有TTL的键中随机淘汰特殊缓存场景

如果使用volatile-*策略,但没有足够的键设置TTL,Redis可能仍然无法释放内存并返回写入错误。

缓存实例

通常可以考虑allkeys-lruallkeys-lfu。选择应根据访问模式压测:

  • 最近访问更能代表价值:偏向LRU。
  • 长期访问频率更能代表价值:偏向LFU。

持久数据实例

如果数据不能被自动丢弃,使用noeviction更明确。达到上限后应用会收到错误,因此还必须配置监控、容量告警和写入失败处理。

不要在同一个Redis实例中无规划地混合“绝不能丢的数据”和“随时可淘汰的缓存”。拆分实例通常更容易设置清晰的淘汰与持久化策略。

十一、内存碎片如何判断和处理

查看:

INFO MEMORY

重点参考:

mem_fragmentation_ratio
allocator_frag_ratio
allocator_rss_ratio

碎片比率高并不一定意味着当前存在严重问题。Redis曾经达到较高内存峰值,随后删除大量键时,RSS可能保持较高,导致比例上升。

先判断:

  • 当前数据量是否远低于历史峰值。
  • Redis是否仍能复用已释放内存。
  • 服务器是否真的缺少可用内存。
  • RSS是否持续不受控增长。
  • 是否刚进行大规模删除或数据迁移。

Redis可支持主动碎片整理。查看配置:

CONFIG GET activedefrag

启用示例:

CONFIG SET activedefrag yes

主动碎片整理会消耗CPU,并且是否可用与Redis构建及内存分配器有关。生产环境应先评估CPU余量,逐步调整相关阈值,而不是遇到RSS较高就立即开启最高强度整理。

十二、RDB和AOF为什么会造成内存峰值

Redis创建RDB或执行AOF重写时,通常会fork()子进程。父子进程最初共享内存页;在后台任务期间,如果父进程继续修改数据,相关内存页会通过写时复制产生额外占用。

写入越频繁,后台保存时间越长,写时复制峰值可能越明显。

查看持久化状态:

INFO PERSISTENCE

重点指标包括:

  • rdb_bgsave_in_progress
  • aof_rewrite_in_progress
  • current_cow_size
  • current_cow_peak
  • rdb_last_bgsave_status
  • aof_last_bgrewrite_status

筛选查看:

redis-cli INFO PERSISTENCE |
  grep -E 'rdb_bgsave_in_progress|aof_rewrite_in_progress|current_cow_size|current_cow_peak|rdb_last_bgsave_status|aof_last_bgrewrite_status'

优化方向:

  • 避免在内存接近系统极限时执行后台保存。
  • 将AOF重写和备份安排在相对低峰期。
  • 提升磁盘性能,缩短后台任务时间。
  • 控制大规模写入和批量删除。
  • 根据真实current_cow_peak预留内存。
  • 检查透明大页、内核和持久化相关配置。

不要为了降低峰值就直接关闭持久化。Redis是否只是可丢弃缓存、能接受多少数据损失,应先由业务恢复目标决定。

十三、客户端缓冲区也可能吃掉大量内存

Redis为每个客户端维护输入、输出和其他中间缓冲区。以下场景容易使输出缓冲区增长:

  • 客户端发送命令后读取响应过慢。
  • Pub/Sub消费者处理速度低于消息产生速度。
  • 查询一次返回大量数据。
  • 副本同步速度跟不上主节点。
  • 网络带宽或延迟异常。

查看客户端:

CLIENT LIST

重点关注:

  • omem:客户端输出缓冲区内存。
  • qbuf:查询缓冲区。
  • qbuf-free:查询缓冲区空闲部分。
  • obloll:输出缓冲区状态。
  • ageidle:连接存活和空闲时间。
  • cmd:最近执行的命令。

按输出内存排序:

redis-cli CLIENT LIST |
  tr ' ' '\n' |
  grep '^omem='

更适合分析的方式是把CLIENT LIST输出交给脚本或监控系统按连接解析,不要只依赖简单文本筛选。

Redis提供不同客户端类型的输出缓冲区限制:

CONFIG GET client-output-buffer-limit

达到硬限制,或在规定时间持续超过软限制时,Redis会关闭相应客户端。修改限制前应确认正常业务的消息峰值,避免误断开订阅者或副本。

新版Redis还支持通过maxmemory-clients限制所有客户端连接合计使用的内存:

CONFIG GET maxmemory-clients

十四、复制也需要额外内存

主从复制相关内存包括:

  • 复制积压缓冲区。
  • 副本客户端输出缓冲区。
  • 全量同步期间的RDB与缓冲。
  • 无盘复制相关缓冲。

查看复制状态:

INFO REPLICATION

主节点达到maxmemory不代表副本进程一定不会超过同样的物理内存。Redis官方文档指出,副本默认忽略maxmemory淘汰行为,并可能因缓冲区和内部结构使用更多内存,因此主从节点都应单独监控并预留资源。

频繁全量同步通常不只是内存问题,还可能说明:

  • 网络不稳定。
  • 复制积压缓冲区过小。
  • 副本长时间断线。
  • 主节点重启或复制ID变化。
  • 磁盘和同步速度不足。

十五、删除大键要注意什么

直接执行:

DEL very:large:key

删除大型集合可能在主线程上产生较长阻塞。

Redis支持异步释放:

UNLINK very:large:key

UNLINK会先从键空间移除键,再由后台线程回收大部分内存,通常更适合删除大键。

查看异步释放队列:

INFO MEMORY

关注:

lazyfree_pending_objects

不要看到大键就立即删除。先确认:

  • 是否是核心业务数据。
  • 是否有持久化或副本。
  • 应用是否会瞬间回填导致缓存击穿。
  • 删除是否会触发大量数据库访问。
  • 是否可以先限流或分批迁移。

十六、Redis达到内存上限后的应急处理

当Redis已经开始返回OOM或系统内存接近耗尽时,可以按以下顺序处理。

1. 保存现场

执行并保存:

date
free -h
vmstat 1 5
redis-cli INFO MEMORY
redis-cli INFO KEYSPACE
redis-cli INFO STATS
redis-cli INFO PERSISTENCE
redis-cli INFO REPLICATION

2. 确认是否正在持久化或同步

如果内存峰值来自RDB、AOF重写或全量同步,不要急于重启。先判断任务是否即将完成,以及系统是否仍有足够余量。

3. 停止异常写入来源

如果某个应用、任务或爬虫正在无限写入,优先限制写入入口。只在Redis端删除键,而应用继续高速回填,内存很快会再次占满。

4. 删除明确可丢弃的数据

对确认可删除的大键优先使用UNLINK。对缓存键可以按业务前缀和生命周期分批清理,但必须使用SCAN,不要使用KEYS *全量阻塞扫描。

5. 谨慎调整maxmemory

如果服务器仍有足够可用内存,可以临时小幅提高上限争取处理时间;如果系统已经换页或接近OOM,提高maxmemory会让风险更大。

6. 最后才考虑重启

重启可能暂时降低RSS,却会清空无持久化数据、触发RDB或AOF加载,并造成缓存集中回源。主从和集群环境还可能发生故障转移。必须先确认持久化、复制、业务影响和恢复路径。

十七、如何从根本上优化Redis内存

数据设计

  • 为缓存设置合理TTL。
  • 删除不需要的字段和重复数据。
  • 拆分无限增长的大集合。
  • 使用适合业务的数据结构。
  • 避免海量超长键名。
  • 对位状态考虑Bitmap等紧凑结构。
  • 对近似统计考虑HyperLogLog等结构。

应用治理

  • 防止连接泄漏和无限流水线。
  • 限制单次查询返回数量。
  • 为缓存回填设置并发控制。
  • 避免缓存穿透和击穿。
  • 为批量写入设置速率限制。
  • 将持久数据与可淘汰缓存拆分。

实例与架构

  • 设置合理maxmemory与淘汰策略。
  • 为持久化和复制保留内存。
  • 按业务类型拆分Redis实例。
  • 数据量超过单机能力时使用分片或集群。
  • 使用副本提高读取能力,但不要把副本当成备份。
  • 对关键数据保留独立备份和恢复测试。

十八、建立监控和告警

建议持续记录:

  • used_memoryused_memory_rss
  • used_memory_peak
  • used_memory_datasetused_memory_overhead
  • maxmemory使用率。
  • mem_fragmentation_ratio
  • mem_not_counted_for_evict
  • evicted_keysexpired_keys
  • 键数量及增长速度。
  • 客户端数量和客户端内存。
  • current_cow_peak
  • RDB、AOF与复制状态。
  • 系统可用内存和Swap。
  • Redis延迟和命令错误率。

只设置“Redis内存超过80%”并不够。缓存实例接近maxmemory后持续淘汰可能是设计行为,但淘汰率突然升高、命中率下降和数据库负载上升则需要告警。

noeviction实例,还应监控写命令错误,因为达到上限后业务可能直接失败。

十九、推荐的完整排查顺序

遇到Redis内存占用过高时,可以按照下面的顺序操作:

  1. 使用free -hvmstat确认系统可用内存与Swap状态。
  2. 使用INFO MEMORY对比used_memory、RSS、峰值和上限。
  3. 使用MEMORY STATS拆解数据集、客户端与复制开销。
  4. 使用INFO KEYSPACE检查键数量和过期键比例。
  5. 使用--bigkeys--memkeys--keystats定位异常键。
  6. 使用MEMORY USAGE核查具体键的内存。
  7. 检查没有TTL的缓存和无限增长集合。
  8. 检查maxmemory与淘汰策略是否符合业务。
  9. 检查客户端输出缓冲区和复制内存。
  10. 检查RDB、AOF重写及写时复制峰值。
  11. 判断RSS偏高是历史峰值、碎片还是持续增长。
  12. 修复写入来源和数据模型后,再清理或迁移数据。
  13. 根据真实峰值扩容、拆分实例或使用集群。

总结

Redis内存占用过高不一定只是数据太多。过期策略缺失、大键、冗余数据、内存碎片、慢客户端、复制缓冲区,以及RDB和AOF重写期间的写时复制,都可能导致进程内存增加。

排查时应先区分used_memory与RSS,使用INFO MEMORYMEMORY STATS确认内存来源,再通过--bigkeys--memkeysMEMORY USAGE定位具体键。

生产环境应设置合理的maxmemory,但不能把它设置为服务器全部内存。淘汰策略必须与业务数据属性一致:缓存可以选择LRU或LFU类策略,不能丢失的数据则应使用明确的写入保护和容量告警。

最终解决方案不是频繁重启Redis,而是建立清晰的TTL、数据结构、客户端限制、持久化余量和监控体系,并根据真实增长速度及时拆分或扩容。

知识库

Nginx出现504 Gateway Time-out怎么办?超时原因排查与配置优化教程

2026-8-11 14:27:09

知识库

服务器虚拟化技术的发展趋势与实践应用(深度分析)

2025-3-21 12:05:17