Redis内存设置全攻略:maxmemory配置、淘汰策略与监控实战 Redis内存设置这件事我见过太多人栽跟头。上周还有位读者来找我说线上的Redis突然假死业务写不进去重启后缓存全丢。我远程一看maxmemory根本没配数据一路涨到物理内存耗尽系统直接OOM把Redis进程杀了。这种情况真不是个例只要用Redis内存设置就是你绕不过去的一道坎。很多人把“配个maxmemory”理解成写一行配置就算完事但实际远远不够。内存上限设多少、淘汰策略怎么选、碎片率怎么看、线上怎么动态调整、大key怎么排查这些环节环环相扣任何一环没做好Redis都会在某个深夜给你上一课。这篇文章我按自己做过的排查和调优顺序把Redis内存设置从头到尾捋一遍适合拿Redis当缓存用、当数据库用、或者正在被内存告警折腾的读者。1. 动手之前先搞清楚Redis内存设置到底在解决什么问题1.1 不设上限的Redis会被操作系统“教做人”先说一个认知误区。很多人以为Redis自带内存回收机制存满了自己会清理。实际上Redis默认的maxmemory是0也就是不限制内存使用。数据持续写入、过期key没有被及时清理、没有设置淘汰策略内存占用就会一路攀升直到把整台物理机内存吃光。到了那一步操作系统会启动OOM killer挑选进程下手。Redis往往是机器上最占内存的进程几乎每次都被优先选中直接被kill掉。如果没有开启持久化缓存数据全部丢失就算开启了持久化重启后也要花大量时间恢复期间业务基本不可用。我见过太多“Redis被系统杀掉”的现场第一句都是“我也没干嘛啊怎么突然挂了”——实际上内存已经悄悄涨了几个星期。所以Redis内存设置首先是一个“预算管理”问题。你得告诉Redis你最多能用多少内存超出之后怎么处理。同时还得给自己留出足够余量让操作系统和旁边跑着的其他进程有活路。1.2 内存预算怎么估算别按value大小一拍脑袋设置maxmemory之前先要搞清楚你的数据到底占多大内存。很多人只看value觉得“我有100万条数据每条100字节那也就100MB”——这个估算在真实环境里严重低估。一个Redis key的开销包括SDS字符串头、dictEntry节点、RedisObject对象头再加上字典扩容余量单key额外消耗大概几十字节。我做过一个简单的压测写入100万个key每个value约128字节按纯数据算只要128MB但INFO memory里used_memory实际到200MB以上多出来的全是结构开销。所以保守估算法是“value总量乘以1.5到2”。还有几块容易被忽略的内存开销有过期时间的key要额外维护过期字典。开启AOF重写或RDB持久化时fork出来的子进程要复制页表如果父进程持续写入写时复制还会继续增加内存消耗。客户端缓冲区、复制积压缓冲区、AOF缓冲区在高并发写入时也能吃掉不少内存。综合这些因素我个人的经验值是把maxmemory设在物理内存的50%到70%。纯缓存场景、不落盘可以偏高点到70%开了RDB或AOF的场景必须保守一点建议不超过60%否则内存一满fork子进程会直接失败。这里一定要记住一个结论内存预算不是看数据量而是看你的Redis进程峰值占用。上线之前用压测工具模拟真实流量观察used_memory_peak再按峰值往上留30%余量才是靠谱的方式。2. 动起手来maxmemory怎么配置才不踩坑2.1 redis.conf里的“内存三件套”配置文件里和内存最相关的三个参数是maxmemory、maxmemory-policy、maxmemory-samples。我先把一段实际可用的配置贴出来maxmemory 2gb maxmemory-policy allkeys-lru maxmemory-samples 5maxmemory的单位可以是字节数、kb、mb、gb需要注意大小写不敏感但是拼写要正确。最坑的写法是直接写数字比如maxmemory 2048它代表2048字节而不是2GB有人就因为这个线上疯狂淘汰key。我建议一律带单位写成2gb或者2048mb一眼就能看懂。maxmemory-policy是内存达到上限之后的处理策略默认值是noeviction也就是不淘汰任何key直接给写命令报错。这个默认值很多人不知道结果线上Redis内存满后写入全部失败业务表现就是“服务能读不能写”排查半天才找到原因。maxmemory-samples是LRU/LFU淘汰算法抽取样本的数量。每次触发淘汰时Redis不会扫描全部key而是随机抽几个key从中挑一个最合适的淘汰。默认是5个样本越多淘汰越精确但代价是CPU开销变大。后面我会专门展开讲怎么选。2.2 线上临时调大内存别动不动就重启配置文件改完要重启才能生效但线上要求的是平滑调整。Redis提供了运行时动态修改的接口通过redis-cli就能完成redis-cli -h 127.0.0.1 -p 6379 CONFIG GET maxmemory redis-cli -h 127.0.0.1 -p 6379 CONFIG SET maxmemory 2gb redis-cli -h 127.0.0.1 -p 6379 CONFIG REWRITE第一行是查看当前配置第二行是直接把maxmemory改成2GB第三行是把最新配置写回redis.conf保证重启后不丢。很多运维同学喜欢“出了问题改配置文件再重启”其实完全没必要。CONFIG SET生效是即时的不会中断服务。我的习惯是先CONFIG GET看现状确认当前生效值再决定调大还是调小。如果只是临时救急调完先观察等到业务低峰期再改配置文件并重启一次防止机器重启后配置回滚。这里有个非常关键的坑CONFIG REWRITE只有在启动时指定的redis.conf文件可写时才有效。有些容器平台部署Redis时没有挂载配置文件CONFIG REWRITE会报错所以动态设置之后一定要确认一下配置有没有持久化成功别指望它一定写了进去。2.3 设置了maxmemory会立刻触发淘汰吗这个问题很多人问。答案是不会立刻触发而是在下一次写命令执行之前检查内存占用。如果你设置maxmemory的值比当前used_memory还小那么下一次写操作比如SET、LPUSH会先触发淘汰把内存压到上限以下再执行写入。这意味着两件事如果淘汰策略是noeviction内存已超限时所有写命令都会直接报错Redis会返回OOM command not allowed when used memory maxmemory。此时读操作正常但写不进去任何数据。如果淘汰策略是allkeys-lru这类允许淘汰的策略一旦内存超限Redis会优先逐出最久没被访问的key可能在短时间内大量淘汰缓存命中率瞬间暴跌底层数据库压力飙升。所以我特别提醒调小maxmemory比调大要危险得多。比如一个实例当前已经用了3GB你直接把maxmemory改成2GB等于命令一下去就逼Redis立刻淘汰大约1GB的数据。如果里面有大量不能丢的业务数据响应速度根本来不及阻止。3. 淘汰策略怎么选这决定了你的Redis会不会“丢数据”3.1 八种策略先记一张速查表Redis 4.0以后内存淘汰策略一共有八种。它们在“淘汰哪些key”和“按什么规则淘汰”两个维度上有差异策略允许淘汰范围淘汰规则典型场景noeviction不淘汰任何一个key写命令报错业务数据、分布式锁、幂等标记allkeys-lru所有key淘汰最久未使用通用缓存最常用volatile-lru设置了TTL的key淘汰最久未使用希望永久key不被淘汰的缓存allkeys-lfu所有key淘汰访问频率最低热点集中、冷热分明的缓存volatile-lfu设置了TTL的key淘汰访问频率最低有TTL且访问频率差异大allkeys-random所有key随机淘汰冷热不明显的场景volatile-random设置了TTL的key随机淘汰冷热不明显且有TTLvolatile-ttl设置了TTL的key淘汰剩余寿命最短优先淘汰即将过期的key3.2 选择前先回答一个灵魂拷问哪个key丢了能接受我选策略从来不看网上教程推荐什么而是先问自己实例里存的这些key丢了哪些是没关系的哪些是绝对不能丢的。如果所有key丢了都能从数据库重新回源那随便用allkeys-lru或者allkeys-lfu怎么方便怎么来。如果Redis里混着一部分“永久key”比如配置项、白名单、账号基础信息这些不能丢那一定要选volatile开头的策略。因为allkeys-lru会把永久key也当成淘汰候选一旦内存紧张永久key同样会被逐出。用volatile-lru就安全得多它只淘汰设置了过期时间的key永久key老老实实待在里面。如果Redis被你当成数据库在用比如存订单状态、用户积分、消息offset那我强烈建议用noeviction。内存满的时候让它报错让监控系统报警让值班同学去处理而不是默不作声地丢掉几条关键业务数据。数据丢失是最危险的事情远比一次服务不可用严重。LFU和LRU怎么选我讲个实际感受。LFU对热点集中的场景特别有效比如一个排行榜接口的缓存每天就那几百个key被疯狂访问LFU能精准保留高频key。但LFU要额外维护访问频次信息内存和CPU开销略大。如果业务访问模式比较分散没有明显的“头部热点”LRU就足够了。多数业务场景用LRU只有对冷热分化特别明显的数据才需要LFU。3.3 maxmemory-samples一个影响精度的小参数maxmemory-samples默认是5这个参数决定了LRU/LFU淘汰时的随机抽样数量。采样数量越大淘汰结果越接近全局最优但也意味着每次淘汰要比较更多key的访问时间CPU开销上升。实践中我的建议是默认5够用想提升淘汰精度可以设到10但不要超过20。我试过设到20淘汰行为更精准了但QPS高的时候CPU确实多花了一点。对于大多数业务来说5到10的性价比最高。注意这个参数对volatile-ttl策略也有影响但不像对LRU那么显著。4. 配置完了没结束内存监控才是长期护城河4.1 INFO memory 里那些指标都什么意思配置设好只是第一步你还需要随时知道内存正在发生什么。最直接的工具是INFO memory命令输出大概长这样# Memory used_memory:1073741824 used_memory_human:1.00G used_memory_rss:1284505600 used_memory_peak:2147483648 used_memory_peak_human:2.00G maxmemory:2147483648 maxmemory_human:2.00G maxmemory_policy:allkeys-lru mem_fragmentation_ratio:1.20 evicted_keys:0重点看这几个used_memoryRedis实际存储数据占用的内存字节数这是判断内存够不够的核心数字。used_memory_rssRedis进程向操作系统实际申请的内存包括内存碎片。used_memory_peak历史内存使用峰值用来评估业务增长趋势非常有用。mem_fragmentation_ratioRSS除以used_memory就是内存碎片率。evicted_keys因内存淘汰被逐出的key数量这个数字突然增长就是警报。4.2 内存碎片率怎么看什么情况下要处理碎片率这个指标很多人不会读。常用的判断标准是小于1说明Redis一部分内存被换到Swap了物理内存已经不够用这是极度危险的信号。1到1.5正常范围碎片影响可接受。大于1.5碎片占比偏高内存白白浪费需要考虑处理。碎片率高的典型场景是频繁写入删除大key或者大量key集中过期。Redis内存申请和释放之间的间隙会慢慢积累碎片。我处理过的案例里碎片率到2.0以上系统内存明明还够但Redis可用内存越来越少创建新key非常慢。解决办法有两个一是开启activedefrag yes让Redis在后台自动整理碎片不过它只在jemalloc编译时有效而且碎片整理期间会短暂影响性能二是低峰期执行重启一次性释放全部碎片。如果RSS比used_memory大好几个G重启是立竿见影的。4.3 用脚本把内存指标抓出来别等爆了再人工看手动输命令只能看某个瞬间要做的是持续采集。我给一个很简单的bash脚本思路放到crontab里每小时跑一次#!/bin/bash REDIS_HOST127.0.0.1 REDIS_PORT6379 LOG_FILE/var/log/redis_mem.log USED$(redis-cli -h $REDIS_HOST -p $REDIS_PORT info memory | grep ^used_memory_human: | cut -d: -f2) MAX$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET maxmemory | tail -1) EVICTED$(redis-cli -h $REDIS_HOST -p $REDIS_PORT info stats | grep ^evicted_keys: | cut -d: -f2) echo $(date %Y-%m-%d %H:%M:%S) used$USED max$MAX evicted$EVICTED $LOG_FILE有监控平台的话可以直接把used_memory、mem_fragmentation_ratio、evicted_keys推到Prometheus这类系统里规则设成内存使用率超过80%告警evicted_keys在短时间内增量超过1000也告警。我自己的经验是“80%告警”这条阈值最实用到了90%再处理往往已经很被动。4.4 大key排查与内存优化三板斧日常内存优化只盯着maxmemory调大小还不够还要把“浪费内存的数据结构”揪出来。最常用的命令是redis-cli --bigkeys这个命令会扫描整个实例按类型列出最大的key。它在线上跑会有一点性能消耗建议在低峰期执行。找到大key之后常用的优化手段有三个拆key一个超大Hash拆成多个小Hash比如把100万字段的Hash按用户ID取模拆成10个。换数据结构能用Hash的别用String能用Intset编码的别用Hashtable。压缩valueJSON换MessagePack或者Protobufvalue能小一半甚至更多。另外单个key内存占用可以用MEMORY USAGE精确查询redis-cli MEMORY USAGE product:10001返回的是这个key当前占用的字节数。上线新业务前我会用这个命令抽查几个代表性key反向推算整个实例需要多少内存比拍脑袋准得多。5. 内存相关的线上故障三个我踩过的真实坑5.1 坑一没有设置maxmemoryRedis成了内存黑洞有一年我负责一个内部运营系统Redis只跑了一个缓存业务数据量一直不大所以配置就很随意地留下了默认值。后来运营活动上线缓存key量翻了近10倍起初没人注意内存直到某天凌晨所有写请求超时系统日志显示OOM killer把Redis进程杀掉了。排查过程很典型先看free -h发现物理内存耗尽再看INFO memoryused_memory已经和机器内存接近。因为完全没有maxmemoryRedis根本没有自我约束机制数据持续增长直到把系统拖垮。那次之后我养成了习惯任何实例上线前必须先确认maxmemory有值并且监控面板能看到内存使用率趋势。5.2 坑二maxmemory设得太小缓存命中率直线跳水另一个项目里Redis的maxmemory一直沿用上线初期的512MB业务规模涨了也没人调整。某天开始数据库的读负载莫名飙升慢查询增多。我去看缓存命中率发现从原来的90%降到了40%左右再查INFO stats里的evicted_keys数量已经到几十万级别。原因很清楚内存上限太小新增的缓存key一写入就触发淘汰旧key还没来得及被访问就被逐出等于缓存完全失效压力全部打到底层数据库。那次采用的是动态调大maxmemory到2GB同时把value里的JSON字段精简了一遍命中率才逐渐恢复。这件事给我的教训是maxmemory不是“配一次就完事”它要跟着业务数据量动态走还要配上evicted_keys监控。5.3 坑三开了持久化后内存爆满fork直接失败还有一次我踩得比较深。一台8GB内存的机器我把maxmemory设成了7GB想着给Redis最大空间同时开着RDB。结果大促期间Redis日志疯狂刷Cant save in background: fork: Cannot allocate memoryRDB备份全部失败。原因是fork子进程时需要分配内存来复制页表虽然通常只占父进程RSS的几个百分点但在内存已经被Redis占了87%的机器上操作系统连这点内存都拿不出来于是fork失败持久化失效。更危险的是如果这一步发生在需要故障恢复的关键时刻你会发现自己根本没有可用的RDB备份。最终把maxmemory降到了5.5GB给系统留了2.5GB余量fork再也没失败过。从此以后凡是开持久化的Redis实例我执行“内存使用上限不超过物理内存60%”这条规则宁可少放点缓存也要保证后台保存能正常工作。5.4 内存问题速查表现象可能原因快速排查解决方案Redis被OOM杀掉未设置maxmemory内存被耗尽系统日志、INFO memory设置合理maxmemory并监控写命令报OOM错误noeviction且内存已满CONFIG GET maxmemory调大内存或换淘汰策略缓存命中率骤降淘汰太多key查看evicted_keys增量调大maxmemory、优化value大小内存碎片率长期偏高频繁增删大key查mem_fragmentation_ratio开启activedefrag或重启持久化报fork失败maxmemory设置过高查看Redis日志下调maxmemory预留内存Swap占用升高物理内存不足查看mem_fragmentation_ratio是否小于1扩容内存或优化数据写在最后的个人体会Redis内存设置做到最后我最大的体会就是四个字持续关注。它不是一个“配置完就忘掉”的参数而是一个需要跟着业务量、数据规模、访问模式动态调整的活指标。我一开始也被不设maxmemory、重启大法、拍脑袋定大小这些习惯坑过摔了几次之后才养成固定的习惯——每周看一次内存趋势每月审视一次缓存命中率和淘汰数量每次上线新业务前先压测估算内存峰值。如果evicted_keys开始持续增长那说明你离内存上限已经很近了趁早优化或者扩容别等到OOM找上门。最后再分享一个小技巧每次用CONFIG SET动态调完参数记得执行CONFIG REWRITE写回配置文件不然机器重启之后又回到旧值等于白调。这个事我踩过不止一次写出来希望大家别再交学费。