
Redis以内存作为主要数据存储介质,进程占用较多内存本身并不异常。真正需要处理的是:内存持续逼近服务器上限、写入开始返回OOM错误、系统频繁使用Swap、Redis被操作系统终止,或者在执行RDB、AOF重写和主从同步时出现明显的内存峰值。
Redis内存问题也不能只看top中的进程占用。数据集、键和值的元数据、内存碎片、客户端缓冲区、复制积压缓冲区、Lua脚本、模块、持久化写时复制等,都可能影响Redis实际占用。
本文介绍如何使用INFO MEMORY、MEMORY STATS、MEMORY USAGE、redis-cli --bigkeys和--keystats定位内存来源,并说明过期键、淘汰策略、碎片整理、持久化峰值和客户端缓冲区的安全优化方法。
示例主要适用于Redis Open Source 7.x及常见Linux环境。不同版本、托管Redis产品和兼容数据库的命令、指标与默认配置可能不同,修改前应先确认实际版本。
一、先判断Redis是否真的存在内存风险
查看系统内存:
free -h
持续观察换页:
vmstat 1 10
重点关注:
available:系统还能提供给进程使用的内存。Swap:交换空间是否已使用。si和so:是否持续发生换入和换出。- 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_memory | Redis分配器统计的内存 | 数据与内部结构的主要参考 |
used_memory_human | 易读格式的已用内存 | 便于快速查看 |
used_memory_rss | 操作系统看到的Redis驻留内存 | 与进程RSS接近 |
used_memory_peak | Redis历史内存峰值 | 判断是否经历过高峰 |
used_memory_dataset | 数据集占用 | 判断业务数据本身大小 |
used_memory_overhead | 非数据集开销 | 键元数据、客户端、复制等 |
mem_fragmentation_ratio | RSS与内部内存的比例指标 | 仅用于辅助判断碎片 |
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
典型问题:
- 写缓存时漏掉
EX或PX。 - 更新缓存时覆盖了原来的过期设置。
- 代码只写入不删除。
- 业务将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_peak、current_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-lru或allkeys-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:查询缓冲区空闲部分。obl、oll:输出缓冲区状态。age和idle:连接存活和空闲时间。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_memory和used_memory_rss。used_memory_peak。used_memory_dataset与used_memory_overhead。maxmemory使用率。mem_fragmentation_ratio。mem_not_counted_for_evict。evicted_keys和expired_keys。- 键数量及增长速度。
- 客户端数量和客户端内存。
current_cow_peak。- RDB、AOF与复制状态。
- 系统可用内存和Swap。
- Redis延迟和命令错误率。
只设置“Redis内存超过80%”并不够。缓存实例接近maxmemory后持续淘汰可能是设计行为,但淘汰率突然升高、命中率下降和数据库负载上升则需要告警。
对noeviction实例,还应监控写命令错误,因为达到上限后业务可能直接失败。
十九、推荐的完整排查顺序
遇到Redis内存占用过高时,可以按照下面的顺序操作:
- 使用
free -h和vmstat确认系统可用内存与Swap状态。 - 使用
INFO MEMORY对比used_memory、RSS、峰值和上限。 - 使用
MEMORY STATS拆解数据集、客户端与复制开销。 - 使用
INFO KEYSPACE检查键数量和过期键比例。 - 使用
--bigkeys、--memkeys或--keystats定位异常键。 - 使用
MEMORY USAGE核查具体键的内存。 - 检查没有TTL的缓存和无限增长集合。
- 检查
maxmemory与淘汰策略是否符合业务。 - 检查客户端输出缓冲区和复制内存。
- 检查RDB、AOF重写及写时复制峰值。
- 判断RSS偏高是历史峰值、碎片还是持续增长。
- 修复写入来源和数据模型后,再清理或迁移数据。
- 根据真实峰值扩容、拆分实例或使用集群。
总结
Redis内存占用过高不一定只是数据太多。过期策略缺失、大键、冗余数据、内存碎片、慢客户端、复制缓冲区,以及RDB和AOF重写期间的写时复制,都可能导致进程内存增加。
排查时应先区分used_memory与RSS,使用INFO MEMORY和MEMORY STATS确认内存来源,再通过--bigkeys、--memkeys和MEMORY USAGE定位具体键。
生产环境应设置合理的maxmemory,但不能把它设置为服务器全部内存。淘汰策略必须与业务数据属性一致:缓存可以选择LRU或LFU类策略,不能丢失的数据则应使用明确的写入保护和容量告警。
最终解决方案不是频繁重启Redis,而是建立清晰的TTL、数据结构、客户端限制、持久化余量和监控体系,并根据真实增长速度及时拆分或扩容。




