恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

Redis内存与性能调优实战:从内存账本到线上故障排查

  • 首页
  • 资讯中心
  • /
  • Redis内存与性能调优实战:从内存账本到线上故障排查

相关资讯

Java抽奖功能从零到高并发:随机算法、库存扣减与数据一致性的工程实践 2026/9/30 11:41:13
Git从入门到实践:快照思维、分支合并与疑难排查全攻略 2026/9/30 11:36:12
本地部署开源大模型实现情绪化文案批量生成:从模型选型到API封装实战 2026/9/30 11:36:12

最新资讯

前端异步竞态问题详解:循环组件接口赋值失败的四套解决方案
CL_ABAP_PARALLEL 实战:SAP ABAP 批处理并行优化、配额与超时治理
混合云API调用成本与速度矛盾?三种架构设计路径全解析
OpenClaw 小龙虾 AI|Windows3.1.0 一键部署本地 AI 智能体实操教程
维度建模与第三范式:数仓分层中的分工与配合
高项备考失败三次,换对老师后一次通关的全程复盘

今日推荐

模型优化器实战:从FP32到INT8的推理加速与精度平衡
LangGraph+FastAPI构建可审计AI编码助手
基于图像预处理与几何特征的人脸脸型发型搭配系统实现

本周热门

从像素到笔画:srt-whiteboard-animation骨架笔迹追踪实现(Zhang-Suen细化+8邻接追踪)
网站建设的英语怎么说?别只背单词,看完这套安全完整流程才敢上线
新手入门看这篇:建设网站加盟避坑指南与SEO实操

本月精选

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

Redis内存与性能调优实战:从内存账本到线上故障排查

发布时间:2026/9/30 11:41:13
Redis内存与性能调优实战:从内存账本到线上故障排查 Redis的Day 6来了。前五天我们把数据类型、持久化、主从复制、哨兵和高可用都过了一遍今天聊一个平时最容易出问题、也最能体现运维功力的环节内存和性能调优。很多朋友Redis用着用着就卡了、内存报警了、或者明明数据不多却占了好几个G一查发现压根不知道问题出在哪。这篇文章我会从内存账本讲起先搞清楚内存到底花在哪再讲淘汰策略、过期机制、数据结构怎么省内存然后是性能排查的思路和实操命令最后分享一个我自己线上踩过的内存告警案例。内容会比较干建议对照着你们的Redis实例边看边试。1. 内存问题的根源先搞懂Redis的内存账本很多人以为Redis的内存占用就等于数据量大小这是最大的误解。Redis的内存开销远不止key-value本身它包括了数据的内存、Redis自身的运行时开销、以及操作过程中产生的临时内存。如果不把账算清楚后面所有调优都是瞎猜。1.1 数据本身只占一部分内存开销从哪来我经常跟团队里的小朋友打比方Redis存一条数据不只是把“钥匙”和“锁”放进柜子里柜子本身的隔板、标签、登记本都是要占地方的。具体来看主要有这么几块。第一块是key和value本身的开销。每个key都有一个字典条目dictEntry这个条目在64位系统下大约要占64字节左右包含了key的指针、value的指针、next指针、以及过期时间等元信息。如果你存的key很短比如只有几个字符那这个dictEntry的开销甚至比key本身还大。第二块是Redis对象头的开销。每个value都是一个redisObject包含type、encoding、ptr等信息在64位系统上大约占16字节。也就是说哪怕你存一个整数1除了int本身8字节还有16字节的redisObject包装。第三块是内存碎片的损耗。Redis默认使用jemalloc分配器它的特点是快、多线程安全、碎片少但并不是没有碎片。频繁的申请和释放会造成内存碎片导致used_memory_rss进程实际占用的物理内存明显大于used_memory实际使用的内存。注意这里我在下面会专门展开讲。还有一块容易被忽略的是客户端输出缓冲区。每个连接到Redis的客户端都有一个query buffer和output buffer如果某个客户端执行了类似于keys *或者一次性读取大集合的操作输出缓冲区瞬间膨胀会直接把内存顶到警戒线。1.2 三个关键指标used_memory、used_memory_rss、mem_fragmentation_ratio排查内存问题第一步永远是用redis-cli info memory看内存的台账。很多人上来就盯着used_memory看其实不够。我建议重点看这三个值。used_memory是Redis分配器分配出去的内存总量包含数据和所有的运行时开销相当于“账面内存”。used_memory_rss是操作系统视角下Redis进程实际占用的物理内存相当于“实际占内存”。第三个是mem_fragmentation_ratio也就是used_memory_rss除以used_memory的比值用来衡量内存碎片情况。这个比值怎么解读我总结了一个经验区间比值小于1说明发生了swap也就是物理内存不够Redis的一部分内存被交换到了磁盘上性能会急剧下降这是最危险的信号。比值在1到1.5之间正常区间。Redis刚启动、数据量小、或者jemalloc分配比较整齐的时候比值接近1。比值大于1.5碎片率偏高。内存碎片意味着分配器向操作系统申请了很多内存但实际分配给数据的只有一部分剩余的空间暂时无法利用。这时候要考虑重启实例主从切换后用slave先数据同步再提升、或者用memory purge只对jemalloc有效来缓解。注意memory purge这个命令不是所有版本都有而且它只能清理一部分碎片不是万能药。真正严重的碎片问题通常需要从数据模型设计上解决比如避免频繁修改大对象、避免大量相同前缀的key反复创建删除。2. 内存优化三板斧淘汰、过期与数据结构改造账算清楚了接下来才是动手优化。内存优化没有银弹但有三板斧在绝大多数场景下都能见效设定上限和淘汰策略、处理过期key、改造数据结构。2.1 maxmemory与淘汰策略的选择逻辑不管你的机器内存有多大我都建议给Redis设置maxmemory。不设置上限Redis就会像一个没有节制的消费狂直到把系统内存吃光然后触发OOM killer把Redis进程杀掉或者疯狂swap导致性能雪崩。设置maxmemory的时候要留出给操作系统和持久化的余量。比如机器是8G内存建议Redis最多用5-6G剩下的给OS的page cache、给fork子进程做COWCopy On Write留出空间。特别要注意的是开启RDB持久化时bgsave会fork出一个子进程虽然子进程共享内存页但如果后续有写操作触发COW内存会短暂上涨如果maxmemory设得太满很容易踩到fork失败或者内存不足。设置完了还要选对淘汰策略这个很多人随便选其实里面门道不少。Redis 4.0以后有8种策略日常主要用这三种allkeys-lru全体key按LRU最近最少使用淘汰。适合缓存场景不区分key是否设置过期时间谁最近没被用就淘汰谁。volatile-lru只在设置了过期时间的key里按LRU淘汰。适合某些key需要永久保留、某些key可以丢的场景。allkeys-lfu按LFU最不经常使用淘汰。适合访问频率极度不均衡的场景比如热点数据特别集中用LFU比LRU更精准。我在线上遇到最多的问题是有人把maxmemory-policy设成noeviction默认值然后开启持久化、数据量超过内存写入直接报OOM command not allowed when used memory maxmemory。这不是bug而是策略使然。如果用Redis做缓存且允许丢数据老老实实改成allkeys-lru如果做存储且不能丢那就得扩容或者优化数据模型了别指望Redis帮你兜底。2.2 过期key的延迟释放陷阱这是内存优化里最隐蔽的问题之一。你给key设置了过期时间以为到了时间内存就自动释放了但Redis的过期删除其实是被动的。具体来说Redis的过期key删除靠两种机制惰性删除和定期删除。惰性删除是当你访问一个key时才发现它过期了顺手删除定期删除是Redis每隔一段时间默认每秒10次随机抽一批设置了过期时间的key删除其中过期的部分。这两种机制配合既保证了效率又避免了集中删除造成阻塞。但问题来了如果一个key过期之后一直没人访问而且定期删除的抽样池子里又没抽到它那这个key就会一直占着内存。这种情况在key量大、过期时间分散的场景下特别明显。我见过最夸张的案例是一台机器上亿个key里有过期时间的占了40%但实际上被删除的只有一少部分内存占用一直下不来。排查方法很简单用redis-cli --scan --pattern *看一下已过期但未删除的key数量或者用redis-cli --stat观察keyspace_hits和keyspace_misses的比值。如果确认大量过期key没有及时清理有两个方向可以处理。临时方案是用redis-cli --scan --pattern 要清理的前缀* | xargs -L 1 redis-cli del批量删除注意这个命令在大库上会阻塞建议在从库上干或者低峰期干或者用unlink替代del来异步释放内存。长期方案是检查业务上是不是有过期时间设置不合理的场景或者主动把过期key的TTL集中在偏短的时间窗口减少分散。2.3 用更省内存的数据结构hash、intset、ziplist的实战取舍有经验的Redis开发者都知道Redis内存优化最大的空间不在Redis本身而在你选择的数据结构编码方式上。Redis对同一个数据类型有不止一种底层编码小数据量时用紧凑编码大数据量时切换为常规编码。以hash为例当hash里的字段数小于512默认hash-max-ziplist-entries并且每个字段名和值的长度都小于64字节默认hash-max-ziplist-value时Redis用ziplist这种紧凑的连续内存结构存储开销量非常小。一旦超过阈值就切换成hashtable每个字段的独立性增强但内存开销变大。同理list在元素较少且都是整数时用intset编码小list用quicklist内部也采用了ziplist节点set在元素少且为整数时用intset。这就是为什么我经常建议把多个小key合并成一个hash能极大降低内存占用。举个例子如果业务里有100万个用户状态每个用户有十几个字段。你如果按user:10001:name、user:10001:age这样的方式拆成多个字符串key每个key都要算一遍dictEntry和redisObject的固定开销100万用户的十几个字段就是几千万个key内存瞬间爆炸。但如果用hash每个用户一个key内部字段打包在ziplist里固定开销被摊薄了。实测下来同样的数据量用hash组织比拆散成string可以省一半以上内存尤其是字段短、key多的时候效果更明显。不过要注意ziplist省内存的代价是读写时定位元素需要遍历如果单个hash里的字段太多反而会导致性能下降。所以设计规则是hash里的字段控制在1000以内字段名和值保持短小不要为了省内存把一个hash塞成超级大对象。修改hash-max-ziplist-entries可以调大阈值但我不建议设得太大否则长字段的遍历会成为性能陷阱。3. 性能调优从头到尾的链路排查说完了内存再讲性能。性能问题从来不是单点的它可能出在连接层、命令执行层、持久化层、甚至网络层。以下是我在实际排查中总结的比较完整的优化链路。3.1 配置参数里的隐形杀手timeout、tcp-keepalive、tcp-backlog很多人的Redis配置文件是默认的改都没改过这里面就有不少隐藏问题。第一个是timeout。默认值是0表示客户端连接空闲多久之后Redis主动断开——0表示永不断开。如果你的客户端没有做连接池复用每次用完就丢这会造成大量空闲连接一直挂着每个连接都要占内存作为输出缓冲区。建议设置timeout 300让空闲超过5分钟的连接自动断开。第二个是tcp-keepalive。默认是300表示Redis每隔300秒向客户端发送一个keepalive探测包确认连接是否还活着。在客户端异常断电、网络断开的情况下这个值决定了Redis多久能发现连接失效并清理。如果你的应用有大量短连接可以把这个值调小到60-120加速失效连接的回收。第三个是tcp-backlog。它决定了Redis的TCP accept队列长度。默认值511如果并发连接数很高而应用层来不及accept内核的accept队列会溢出导致部分客户端连接被拒或者握手超时。在高并发场景下建议调整系统参数net.core.somaxconn和net.ipv4.tcp_max_syn_backlog并把tcp-backlog设置成和它们匹配的值比如1024或更高。还有maxclients默认10000一般够用但如果你用的是低配机器连接数上来后fd不够用Redis会报max number of clients reached。这个需要结合系统的ulimit -n来调先把文件描述符限制放宽再调大maxclients。3.2 持久化对性能的影响RDB快照与AOF重写的取舍持久化是Redis性能的一大干扰源。RDB做bgsave时会fork子进程fork本身是很快的现代Linux用的是写时复制但在大内存实例上fork瞬间会因为要复制页表而短暂卡顿。我见过一个12G内存的实例fork一次导致主线程卡了将近1秒线上直接报警。经验值是单个Redis实例的数据量不要超过物理内存的一半能有效降低fork的代价。AOF的影响则体现在append和rewrite上。AOF每次写操作都要追加到文件如果appendfsync设置成always每次写都要刷盘性能会降到惨不忍睹只能用在数据安全性极高的场景everysec是推荐的折中方案每秒刷一次盘no交给操作系统决定刷盘时机性能最好但丢失数据的窗口最大。AOF重写rewrite虽然是在子进程里做的但重写期间主进程如果有大量写操作会积压AOF缓冲和重写缓冲导致内存上涨和IO压力。我的建议是如果业务对数据丢失容忍度足够高纯缓存场景直接关掉持久化能省掉一大部分性能开销如果必须持久化优先RDB恢复快、影响小AOF只在数据变更比较重要且机器IO有余力的时候开。3.3 慢查询日志定位拖后腿的元凶性能问题排查我建议先看慢查询日志再看monitor最后才是抓栈。Redis的slowlog机制很简单超过阈值的命令会被记录下来用slowlog get可以查看。配置有两个关键参数slowlog-log-slower-than微秒和slowlog-max-len条数。注意10000微秒等于10毫秒我一般把这个阈值设成50005毫秒也就是超过5毫秒的命令都记录下来因为Redis单线程模型下一个命令超过5毫秒就会让整个实例的吞吐量有明显波动。slowlog get的输出里有几个关键信息命令名、执行参数、执行耗时、时间戳。排查慢查询时逐条分析是不是有keys *、hgetall大hash、smembers大set、zrange大zset这些危险命令。如果是就要从业务层改成分批读取或者用scan系列替代keys *。需要特别提一句的是KEYS命令。很多人为了调试方便直接在线上执行keys *这个命令会遍历全库的所有key数据量大了以后阻塞时间是以秒计的。线上排查key请用scan它每次只返回一小部分配合游标可以无阻塞地遍历。3.4 大key与小key不要小看网络耗时另一个常见的性能杀手是大key。Redis是单线程的一个命令执行期间其它命令都排队等待如果你有个value是几MB的字符串或者一个hash里有几万字段那么读取它的命令会在主线程里跑很久直接拖垮整个实例的响应能力。排查大key可以用redis-cli --bigkeys它会扫描实例并输出最大的若干key。不过要注意--bigkeys这个命令本身在线上扫全库也是有一定开销的建议低峰期跑。扫出来之后策略就是拆分大hash可以拆成多个小hash大list可以拆成多个list大string可以通过压缩或者分片存储。另外很多人忽略的一点是网络耗时。Redis的一次操作数据在客户端和服务器之间传输的时间往往比Redis执行命令本身还要长。如果客户端和服务器之间网络延迟是5ms那一秒最多也就200次请求再多也是排队。所以Redis的调用方要尽量和Redis同机房部署用内网而不是公网连接这比任何SQL优化都有用。4. 线上实战一次内存告警的排查实录理论讲了这么多来一个真实案例。这个案例是我之前带的一个项目的经历排查过程挺典型的。4.1 现象used_memory不高但物理内存告警有一个Redis实例8G的机器maxmemory设了5G数据量大概在2G左右。某天运维报警说物理内存使用率超过90%进程有被OOM kill的风险。我上去一看info memory发现used_memory才2.1G但used_memory_rss已经5.8G了mem_fragmentation_ratio高达2.7。账面上只用了2G实际却占了5.8G中间差了3.7G这就是典型的严重碎片化问题。4.2 排查从INFO到--bigkeys第一步我先确认了时间线这台机器是三个月前从2G数据升级上来的中间经历过一次全量数据迁移之后每天都有大量短生命周期key的频繁写入删除。结合mem_fragmentation_ratio持续高于2基本可以锁定内存碎片积累。第二步我用redis-cli --bigkeys扫描了一遍确认没有单个大key导致数据分布不均的问题各个类型里最大的key也就几百KB不足以解释3.7G的差距。第三步我看了info stats里的keyspace_hits和expired_keys发现过期key数量不少但expired_keys增量并不大说明大量设置了过期时间的key并没有被主动访问触发删除而是在定期删除的随机抽样中漏掉了一直在占内存。过期key加碎片效应叠加才让内存账本彻底失控。4.3 结论碎片与过期key的叠加效应解决方案分两步走。第一步处理过期key我在低峰期用scan配合unlink批量清掉了那些已过期但未删除的key这一步释放了大概800M内存。第二步处理碎片最彻底的办法是重启实例让内存重新整块分配但生产环境不能直接重启丢数据。我采用的做法是在主从复制环境下先用从库顶上来把主库重启清掉碎片再切回来全程零停机。重启后mem_fragmentation_ratio降到了1.1左右内存告警消失了。但这还没有完我在代码层面还做了一件事把原来大量拆散的短生命周期字符串key合并成了若干个hash用ziplist编码存储从根本上降低了dictEntry的固定开销和碎片的产生源。三个月后回头看同样的数据规模used_memory_rss只有之前的40%。4.4 常见问题速查表一条命令看清内存状态内存排查我整理了一个速查表直接收藏就好现象可能原因直接命令used_memory低但RSS高内存碎片化info memory看mem_fragmentation_ratio内存持续增长但数据量没涨过期key堆积info stats看expired_keysscan扫疑似过期keymaxmemory没到却写不进去maxmemory-policy为noevictionconfig get maxmemory-policy单条命令卡顿数秒大key或keys *slowlog get连接数暴增导致OOM客户端未复用连接info clients看connected_clients重启后内存没降多少AOF或RDB缓冲未释放等持久化完成再看或memory doctormemory doctor这个命令是Redis 4.0引入的它会自动检查常见的内存问题并给出报告排查时可以先跑一下当个参考。5. 调优避坑指南别把调优做成负优化调优这件事很多时候不是不会调而是调过头了。下面这几条都是我用真金白银换来的教训。5.1 配置改动的负优化清单很多人一拿到调优文章就照着抄结果越调越差。比如过度调大ziplist阈值。把hash-max-ziplist-entries从512调到10240确实内存降了但hash内字段一多哈希查找退化成了遍历CPU飙升。ziplist越小越省内存是有限度的需要在内存和CPU之间求平衡。盲目关闭AOF。有的场景数据其实不能丢为了性能关了AOF结果机器一重启丢了几分钟数据业务事故。关持久化之前先确认你的业务真的能接受数据丢失。maxmemory设得太满。总觉得既然机器有8G就把maxmemory设成7G结果RDB fork的时候COW需要额外内存触发OOM。我建议maxmemory最多占物理内存的70%-75%给操作系统和fork留足空间。连接池配得太大。客户端连接池大小不是越大越好连接数太多会让Redis的CPU花在处理连接请求上而不是处理命令。单个实例的客户端连接数在几百到一两千就很充裕了没必要配上万。5.2 调优后的监控与自检调优不是一次性动作后续的监控更重要。我建议至少盯这几个指标做成图表每天看一眼used_memory和used_memory_rss的走势以及mem_fragmentation_ratio。keyspace_hits和keyspace_misses命中率掉到90%以下说明缓存设计有问题大量请求穿透到数据库了。slowlog的条数和耗时分布持续出现慢查询就要追根因。connected_clients的变化曲线异常突增多半是客户端没做好连接复用。rejected_connections被拒绝的连接数大于0时要么maxclients太低要么TCP backlog满了。还有一个容易被忽略的自检项版本升级。Redis 5和Redis 6在内存管理、过期机制、阻塞命令处理上都有很大改进。如果还在用老版本很多调优手段压根用不上。比如Redis 4.0才有的memory命令和LFU淘汰Redis 5.0引入的client pause和动态调整hash-max-ziplist-entries参数Redis 6.0引入的多线程IO处理注意只是网络IO多线程命令执行还是单线程。版本太老调优天花板就低。我个人在实际排查中还有个习惯每次调优都记录before和after的指标快照哪怕只是改了maxmemory-policy也要把info memory的几个关键值记下来。不然时间一长你就会忘记当初为什么改这个参数、改完是变好了还是变差了。很多坑恰恰是“不知道改了什么参数”造成的。最后分享一个小技巧如果决定要动生产Redis的配置先把config get的结果备份下来用config set在线修改后观察一段时间确认没问题再写入redis.conf。万一调坏了config set改回来也就是一条命令的事没必要直接改配置文件然后重启那是自己给自己找麻烦。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号