theme: default themeName: "默认主题" title: "Redis内存优化实战:线上从8GB降到2GB的调优之路"
Redis内存优化实战:线上从8GB降到2GB的调优之路
前阵子线上告警炸了,Redis实例内存使用率冲到95%,眼看就要触发OOM。运维同事慌得一批,说加内存吧,我拦住了——8GB的Redis,你能加到多少?16GB?32GB?治标不治本。
花了一周时间排查优化,最终把内存从8GB压到2GB,稳稳当当跑到现在。整个过程踩了不少坑,记录下来给各位参考。
问题暴露:info memory先走一遍
出问题的第一时间,别急着改代码,先摸清家底。连上Redis执行:
redis-cli info memory
输出一堆指标,重点关注这几个:
used_memory:8589934592 # 8GB,实际分配的内存
used_memory_human:8.00G used_memory_rss:9126805504 # 操作系统视角的RSS,比used_memory还大 used_memory_peak:9126805504 mem_fragmentation_ratio:1.06 # 碎片率还算正常 maxmemory:0 # 没设上限! maxmemory-policy:noeviction # 没设淘汰策略!
两个致命问题直接暴露:没设maxmemory,没设淘汰策略。这就好比你家的水龙头没装阀门,水想流多少流多少。
再看key的分布情况:
redis-cli --scan --pattern "*" | head -1000
redis-cli dbsize
dbsize返回120万,key数量不算离谱,但内存却吃了8GB,说明要么有大Key,要么数据结构选型有问题。
第一刀:揪出大Key
Redis自带的`redis-cli --bigkeys`是个好东西,直接扫:
redis-cli --bigkeys -i 0.1
输出结果让我倒吸一口凉气:
Biggest string: 'user:cache:profile:batch_001' (524288000 bytes)
Biggest list: 'task:queue:failed' (342891 items) Biggest hash: 'config:all_settings' (8291 fields) Biggest set: 'tags:hot_keywords' (41203 members) Biggest zset: 'rank:daily:score' (28901 members)
一个String类型的Key占了500MB!追到业务代码一看,某位天才开发把整个用户画像批量序列化成一个JSON塞进了Redis,还设了24小时过期。这种Key有十几个,光这一项就干掉将近6GB。
处理方案很直接——拆:
# 之前:一个大Key搞定一切
redis.set("user:cache:profile:batch_001", json.dumps(huge_profile_list))
之后:按用户ID拆分,每个Key只存单条数据
for user_id, profile in profile_list: redis.set(f"user:cache:profile:{user_id}", json.dumps(profile), ex=3600)
拆完之后,500MB的大Key变成了几十KB的小Key,内存直接降到2.3GB。这一刀砍掉了最大的毒瘤。
对于那种确实需要批量获取的场景,用Pipeline或者MGET搞定,别把大象塞进一个Key里。
第二刀:List大Key的清理
`task:queue:failed`这个队列积压了34万条数据,全是失败任务的记录,业务方早就不用了但没人清理。直接DEL会把Redis卡死(后面会说为什么),用`HSCAN`类似的思路分批删:
# 每次弹出1000条,直到清空
while true; do count=$(redis-cli LLEN task:queue:failed) if [ "$count" -eq 0 ]; then break fi redis-cli LTRIM task:queue:failed 1000 -1 sleep 0.1 done
更优雅的做法是删Key时开启lazyfree(Redis 4.0+),后面细说。
第三刀:数据结构优化,ziplist立大功
内存降到2.3GB后还不满意,继续挖。Redis的Hash类型在小数据量时有个隐藏福利——当元素个数少于`hash-max-ziplist-entries`(默认512)且每个元素大小小于`hash-max-ziplist-value`(默认64字节)时,Redis会自动用ziplist编码存储,而不是hashtable。
ziplist是什么?一段连续内存的压缩列表,省掉了hashtable里指针和桶的开销。同样存100个field-value对,ziplist可能只要hashtable三分之一的空间。
问题是我们有些Hash Key的field数超过了512,被自动升级成了hashtable。检查方法:
redis-cli HLEN config:all_settings
8291个field,远超512阈值
优化思路:把大Hash拆成多个小Hash:
# 之前
redis.hset("config:all_settings", mapping=all_settings)
之后:按命名空间拆分
for namespace, settings in grouped_settings.items(): redis.hset(f"config:{namespace}", mapping=settings)
除了Hash,List和Sorted Set也有类似的ziplist优化:
- `list-max-ziplist-size -2`(默认值,每个节点8KB以内用ziplist)
- `zset-max-ziplist-entries 128`(默认128个元素以内用ziplist)
合理设计Key的粒度,让数据尽量落在ziplist区间内,内存能省一大截。
第四刀:过期策略和淘汰策略
回看最开始info memory的输出,`maxmemory-policy:noeviction`简直是在裸奔。内存满了直接拒绝写入,线上服务直接炸。
配置淘汰策略:
# redis.conf
maxmemory 4gb maxmemory-policy allkeys-lru
选`allkeys-lru`而不是`volatile-lru`是有原因的——我们的Key有些没设过期时间,`volatile-lru`只淘汰设了TTL的Key,那些不设TTL的冷数据反而赖着不走。`allkeys-lru`不管你设没设过期,最久没访问的就先淘汰,简单粗暴但管用。
同时检查过期Key的情况:
redis-cli info stats | grep expired
expired_keys:238471
24万个Key靠惰性删除+定期删除清理,效率不高。Redis 6.0引入了`lazyfree-lazy-expire`,让过期Key的删除放到后台线程:
# redis.conf
lazyfree-lazy-expire yes lazyfree-lazy-eviction yes lazyfree-lazy-del yes replica-lazy-flush yes
开启之后,淘汰和过期操作不再阻塞主线程,CPU占用率反而降了。
第五刀:UNLINK代替DEL
前面提到删除大Key会卡死Redis,传统DEL命令是同步阻塞的。Redis 4.0提供了`UNLINK`命令,后台异步删除:
# 别用这个
DEL task:queue:failed
用这个
UNLINK task:queue:failed
代码里也要改:
# Python redis-py
redis.delete("big_key") # 同步删除,阻塞 redis.unlink("big_key") # 异步删除,不阻塞
配合前面的`lazyfree-lazy-del yes`配置,即使不小心调了DEL,Redis也会尝试异步处理(仅在配置开启时)。
最终效果
全部优化落地后,再跑一次info memory:
优化前:
used_memory:8.00G used_memory_rss:8.50G mem_fragmentation_ratio:1.06 keys:1200000
优化后: used_memory:1.92G used_memory_rss:2.10G mem_fragmentation_ratio:1.09 keys:1380000
Key数量反而增加了(大Key拆小Key),内存却从8GB降到2GB,降幅超过75%。
几条经验总结
1. 大Key是内存杀手。 一个Key超过1MB就得警惕,超过10MB必须拆。用`redis-cli --bigkeys`定期巡检,别等OOM了才想起来看。 2. 数据结构选型比你想的重要。 同样的数据,用Hash的ziplist编码能比String省3-5倍空间。设计Key结构时就考虑好拆分粒度。 3. maxmemory和淘汰策略必须配。 不配就是给线上埋雷,内存疯涨时连个兜底都没有。 4. lazyfree全家桶都开上。 Redis 6.0的异步删除机制成熟了,线上跑了大半年没出过问题,主线程延迟明显下降。 5. 监控比优化更重要。 上了Grafana监控Redis内存使用率、Key增长趋势、命令延迟分布,异常提前发现,不用等告警了才手忙脚乱。内存优化没有银弹,每家业务情况不一样。但排查思路是通用的:先看内存分布,再找大Key,然后优化数据结构和淘汰策略,最后上监控形成闭环。照着这套流程走,8GB降到2GB不是什么神话,你也能做到。
【放心,我们兜底】
不管你是自己尝试修复,还是需要专业人员上门,易云城IT服务都给你托底。修不好不收费,修好了质保期内随时找我。
📞 服务热线:13708730161 💬 微信:eyc1689 📧 邮箱:service@eycit.com 🌐 https://www.eycit.com
您身边的IT专家。