资讯动态

Redis缓冲区内存膨胀与连接超限排查调优指南

发布时间:2026/10/27 16:31:45 来源:尧图企业网站定制
1. 缓冲区为什么是Redis里最容易“背刺”你的一块内存晚高峰刚过一半监控平台上某业务系统的Redis实例内存使用率从四十多个百分点一路爬到八十以上可数据量没涨、key数量没变、慢查询列表也干干净净。与此同时客户端连接数却在往下掉实例日志里反复出现输出缓冲区超限、连接被强制关闭的提示。这种“内存涨了、连接掉了、数据却没多”的组合排查时特别容易把人带偏——先去怀疑大key再去看内存碎片率接着又翻持久化配置兜一大圈才回到真正的原因缓冲区在偷偷吃内存。Redis缓冲区、客户端输入缓冲区、客户端输出缓冲区、主从复制缓冲区、repl_backlog_buffer、client-output-buffer-limit这几个词平时在文档里看着不起眼一旦在线上出问题表现往往非常吓人实例内存被顶到接近上限、写入开始报错、从库反复触发全量同步、订阅客户端被踢下线。更麻烦的是缓冲区占用的内存不像普通键值那样能靠淘汰策略释放maxmemory 到了也拿它没办法只能从连接和配置层面动手。这篇笔记适合两类人看一类是已经把 Redis 用起来、但还没认真研究过缓冲区细节的开发者另一类是被“内存莫名上涨”“从库老是全量同步”折腾过的运维同学。下面我会把几类缓冲区拆开讲清楚它们分别在什么条件下膨胀、膨胀之后会引发什么连锁反应、排查时看哪几个指标、参数到底怎么估。命令和配置都是可以直接复制去试的踩过的坑也会一并写出来。1.1 一次内存曲线抬头带来的三个疑问先还原一下现场。某业务系统的缓存实例在晚高峰出现内存快速上涨运维同学第一反应是查大key用扫描工具跑了一遍最大的 key 也就几百 KB占比很低第二反应是查慢查询慢日志里只有零星几条耗时不长第三反应是看持久化AOF 重写刚做完不久也不像是重写缓冲区的问题。三条路都走不通问题就卡住了。真正把方向掰回来的是三个疑问。第一个疑问used_memory涨了但used_memory_dataset涨幅很小多出来的内存到底记在哪个账上第二个疑问为什么连接数在下降而且下降的时机和内存上涨的时机高度重合第三个疑问maxmemory明明设了为什么没有触发淘汰反而开始拒绝写入这三个疑问指向同一个答案内存被客户端连接相关的缓冲区占用了而连接因为缓冲区超限被服务端主动断开。连接断开之后它占用的缓冲区内存会被释放所以内存曲线呈现出“涨上去、掉下来、再涨上去”的锯齿状看起来像抖动实际是同一批慢客户端在反复重连、反复超限。把这个逻辑串起来之后后面的排查就顺了。1.2 Redis里到底有几块缓冲区各自归谁管先把概念理清楚。Redis 里叫“缓冲区”的东西不止一个它们归属不同、触发条件不同、限制参数也不同混在一起讲很容易乱。下面这张表是我自己整理的口径排查时按这张表对号入座基本不会跑偏。缓冲区类型归属主要触发场景默认限制排查入口客户端输入缓冲区每个客户端连接一份客户端发送大命令、命令发送速度超过处理速度client-query-buffer-limit默认 1gbclient list的qbuf、qbuf-free客户端输出缓冲区每个客户端连接一份返回结果过大、客户端读取过慢、Pub/Sub 消费慢client-output-buffer-limit分类配置client list的obl、oll、omem复制缓冲区主库为每个从库各分配一份全量同步期间积累写命令归入replica类输出缓冲区限制client list中flagsS的连接复制积压缓冲区主库全局一份环形结构主从断连后的增量同步repl-backlog-size默认 1mbinfo replication的偏移量差值AOF 缓冲区全局一份每次写命令追加到 AOF 前的暂存无独立上限受刷盘策略影响info persistence、aof_buf相关指标这五块里前两块是“每个连接一份”最容易随着连接数放大第三块和第四块跟主从架构绑定单机部署可以暂时不看第五块跟持久化绑定出问题的表现通常是写延迟抖动而不是内存暴涨。后面的章节会按这个顺序展开先从最常见的客户端输入缓冲区讲起。注意client-output-buffer-limit在较新版本里把从库类别写作replica老版本写作slave两种写法功能一致改配置时别因为写法不同以为参数没生效。2. 客户端输入缓冲区命令还没执行内存先被占住了输入缓冲区是 Redis 为每个客户端连接准备的一块内存用来暂存“已经从 socket 读进来、但还没被解析执行”的命令数据。打个比方它像是餐馆前台的点单台客人把菜名报上来服务员先记在单子上后厨再按单子做。正常情况下单子写完就撕掉占用很小但如果客人一口气报了一百道菜或者后厨出菜速度突然变慢点单台就会堆满单子。这块内存的特点是它统计在used_memory里但不属于数据键淘汰策略管不到它它的上限由client-query-buffer-limit控制默认是 1gb看着很宽松实际上一旦有连接顶到这个值说明客户端行为已经严重异常了。更隐蔽的是输入缓冲区在执行完命令后不会立刻缩回去Redis 会保留一段时间的峰值容量所以你在client list里看到的qbuf可能比当前正在执行的命令大不少这是正常的不要一看到qbuf有几 KB 就紧张。2.1 输入缓冲区是怎么长出来的从一次网络读取说起。Redis 的事件循环发现某个连接的 socket 可读就会把数据读进输入缓冲区然后尝试按 RESP 协议解析出完整命令。解析出一条命令就执行一条执行完继续解析缓冲区里剩下的数据直到解析不出完整命令为止。如果客户端发过来的数据不完整比如一条命令只发了一半剩下的半条就会留在缓冲区里等下一次读取。这个机制本身没问题问题出在两个地方。第一客户端发送速度超过服务端处理速度时缓冲区里的数据会越积越多因为读进来的速度比消费掉的速度快。第二单条命令本身就特别大时缓冲区需要一次性容纳整条命令才能开始解析比如一个几 MB 的SET命令或者一个参数特别多的MSET在解析完成之前全部躺在输入缓冲区里。还有一个容易被忽略的细节Redis 在读取数据时是按批读的一次事件循环可能读进来多条命令。如果客户端用了 pipeline 一次性发了几万条命令这一批数据会全部进入输入缓冲区然后逐条执行。pipeline 本身是提升吞吐的好手段但批次大小失控时输入缓冲区就会被瞬间顶高。2.2 哪些操作最容易把它撑爆按我实际遇到的情况排个序最容易把输入缓冲区撑大的操作有这么几类。第一类是超大 value 的写入命令。单条命令的参数字节数直接决定输入缓冲区的峰值一个 10MB 的 value 写入输入缓冲区至少要能容纳 10MB 左右的数据才能完成解析。如果业务里存在这种大对象又恰好并发写入几个连接同时来输入缓冲区占用就会非常可观。第二类是失控的 pipeline。有些客户端为了追求吞吐把几千甚至几万条命令攒成一批发出去服务端一次读取就要吃下整批数据输入缓冲区峰值跟着批次大小走。批次越大峰值越高而且这批命令执行时间也长期间缓冲区一直占着。第三类是MONITOR命令。这个命令会让 Redis 把执行的每一条命令都推送给监控连接它影响的不只是输出缓冲区监控连接本身如果读取慢输出缓冲区也会堆积反过来又会拖慢整体。线上实例我一般建议直接禁用或者严格限制使用时长。第四类是协议异常或客户端 bug。比如某些客户端在连接复用、断线重连时把半条命令留在了缓冲区里服务端一直等后续数据这块内存就一直占着。这种情况从client list里能看到qbuf很大但cmd字段是空的或者一直不变。2.3 用client list把qbuf和qbuf-free读明白排查输入缓冲区最直接的工具就是client list。输出字段比较多刚开始看容易眼花重点盯住这几个id、addr、name、age、idle、qbuf、qbuf-free、cmd。qbuf表示当前输入缓冲区已分配的总容量单位是字节qbuf-free表示这块缓冲区里还空着多少容量。两者相加大致就是缓冲区当前的分配上限。判断有没有异常不能只看qbuf的绝对值要结合cmd和idle一起看如果某个连接的qbuf持续很大cmd一直停在某条命令上idle又很小说明它一直在发数据但处理不完或者命令本身太大。# 按 qbuf 倒序找出输入缓冲区占用最大的前十个连接 redis-cli -h 实例地址 -p 端口 client list \ | awk { for (i 1; i NF; i) { if ($i ~ /^qbuf/) { split($i, a, ); qbuf a[2] } if ($i ~ /^id/) { split($i, b, ); id b[2] } if ($i ~ /^cmd/) { split($i, c, ); cmd c[2] } if ($i ~ /^addr/) { split($i, d, ); addr d[2] } } print qbuf, id, addr, cmd } \ | sort -rn | head -n 10除了client listinfo clients里还有两个很有用的指标client_recent_max_input_buffer和client_recent_max_output_buffer。前者记录的是最近一段时间内所有连接中出现过的最大输入缓冲区后者对应输出缓冲区。这两个值不需要你一个个连接去翻直接看汇总就能判断“最近有没有出现过异常大的缓冲区”做监控告警非常合适。如果发现某个连接确实异常可以用client kill精确干掉它而不是重启实例。命令写法上按 ID 杀最稳# 按连接 ID 精确关闭 redis-cli -h 实例地址 -p 端口 client kill id 连接ID2.4 处理套路与配置建议输入缓冲区的治理思路说到底是两句话把单条命令变小把批次控制住。client-query-buffer-limit默认 1gb我一般不建议直接调小到很小的值因为它限制的是单条命令解析所需的峰值容量调得太小会误伤正常的批量写入。比较稳妥的做法是先通过监控确认峰值水平再把上限设成“正常峰值的一点五到两倍”比如业务里最大的单条命令在 2MB 左右上限设成 8mb 到 16mb 就够用既留了余量又能在异常时及时把连接掐掉避免一块内存被长期占着。配置可以动态改不用重启# 动态调整输入缓冲区上限示例值按实际业务调整 redis-cli -h 实例地址 -p 端口 config set client-query-buffer-limit 16mbpipeline 批次这块我自己的经验是单批控制在 500 到 1000 条以内同时单批总字节数控制在一个明确的上限比如 1MB。只限条数不限字节是不够的因为一条大命令抵得上一千条小命令。客户端封装的时候两个维度都要卡。注意输入缓冲区达到上限后Redis 会记录类似Closing client that reached max query buffer length的日志并关闭该连接客户端侧看到的是连接被重置。排查时不要只盯着客户端日志服务端这份日志才是根因。3. 客户端输出缓冲区慢消费者拖垮快服务端如果说输入缓冲区是“客人点单太快”那输出缓冲区就是“后厨出菜太快、客人吃得太慢”。Redis 把命令执行结果写进输出缓冲区等待 socket 可写时再发出去。服务端处理一条命令可能只要几十微秒但网络发送受限于客户端读取速度、网络带宽、客户端处理逻辑一旦发不出去结果就堆在输出缓冲区里。这块内存的危险程度比输入缓冲区更高原因是它没有统一的 1gb 上限而是按连接类别分别配置normal类默认甚至是完全不限制。很多实例出问题就是因为普通客户端这一类配的是0 0 0也就是硬限制、软限制、软限制时长全部为零等于不设防。等到业务里出现一个超大返回值的查询输出缓冲区可以一路涨到把实例内存顶穿。3.1 三类输出缓冲区与各自的默认阈值Redis 把客户端按用途分成三类分别配置输出缓冲区上限。配置项的格式是client-output-buffer-limit 类别 硬限制 软限制 软限制秒数硬限制和软限制的单位都是 mb 或 gb写 0 表示不限制。类别适用连接常见默认值说明normal普通读写客户端0 0 0默认不限制风险最高建议显式配置replica旧版写slave主从复制连接256mb 64mb 60全量同步期间积累写命令pubsub发布订阅客户端32mb 8mb 60订阅者消费慢时容易堆积三类里我最担心的是normal。replica和pubsub至少有默认值兜底normal一旦不设限任何一条返回大量数据的命令都可能成为导火索。比如误用了全量查询类的命令、某个接口一次性返回了几十万条记录、某个导出任务直接扫了一个大集合结果全部写进输出缓冲区等网络发送。3.2 软限制与硬限制的触发逻辑这两个概念经常被混淆实际行为差别很大。硬限制是“一碰就死”。输出缓冲区一旦超过硬限制的字节数Redis 立即把这个连接标记为待关闭日志里会出现scheduled to be closed ASAP for overcoming of output buffer limits这样的提示连接被断开缓冲区内存释放。软限制是“持续超标才死”。输出缓冲区超过软限制之后Redis 不会立刻动手而是开始计时只有持续超过软限制达到配置的秒数才会关闭连接。这个设计是为了容忍短时间的突发比如一次批量返回导致的瞬间高峰只要很快回落就不会被误杀。理解了这个逻辑配置就有章法了。硬限制设成“绝对不能超过的红线”软限制设成“可以短暂触碰但要尽快回落的水位”软限制秒数设成“能容忍的持续时间”。比如normal 512mb 128mb 30意思是任何时候超过 512mb 直接断超过 128mb 且持续 30 秒也断中间这段突发是允许的。3.3 参数怎么定从业务侧估一个合理值拍脑袋配参数是排查时最常见的二次伤害。我的做法是从业务侧倒推具体分三步。第一步确认单次返回的最大数据量。翻一遍业务里的查询接口找出返回结果最大的那类请求估算它的序列化后字节数。假设最大的单次返回在 2MB 左右那么单连接的输出缓冲区至少要能容纳这个量级否则正常请求都会被误杀。第二步确认单实例的并发连接规模。如果实例上有 2000 个活跃连接理论上不可能所有连接同时返回 2MB但要有兜底。按“正常峰值连接的百分之五同时返回最大结果”估算2000 × 5% × 2MB ≈ 200MB。这个数字是粗略的但比拍脑袋强。第三步结合实例总内存定上限。输出缓冲区的总和不能吃掉太多内存预算一般控制在实例内存的 10% 到 20% 以内比较安全。如果实例是 8GB那所有缓冲区加起来控制在 1GB 左右单个连接的硬限制设成 256mb 到 512mb 是合理区间。综合下来一个比较通用的起点是# 普通客户端硬限制 512mb软限制 128mb持续 30 秒触发 # 从库连接硬限制 512mb软限制 128mb持续 60 秒触发 # 订阅客户端硬限制 64mb软限制 16mb持续 60 秒触发 redis-cli -h 实例地址 -p 端口 config set client-output-buffer-limit normal 512mb 128mb 30 redis-cli -h 实例地址 -p 端口 config set client-output-buffer-limit replica 512mb 128mb 60 redis-cli -h 实例地址 -p 端口 config set client-output-buffer-limit pubsub 64mb 16mb 60注意config set这个参数的值是一个完整字符串三类要分三次设每次只写一类不要试图一次写三段格式会解析失败。设完之后用config get client-output-buffer-limit确认再写进配置文件避免重启后丢失。3.4 线上排查演练obl、oll、omem怎么用client list里跟输出缓冲区相关的字段有三个obl、oll、omem。obl是输出缓冲区的字节长度oll是输出缓冲区里链表对象的个数omem是这两者占用的总内存。看的时候重点看omem它才是真正计入used_memory的数字。# 找出输出缓冲区内存占用最大的前十个连接 redis-cli -h 实例地址 -p 端口 client list \ | awk { for (i 1; i NF; i) { if ($i ~ /^omem/) { split($i, a, ); omem a[2] } if ($i ~ /^id/) { split($i, b, ); id b[2] } if ($i ~ /^cmd/) { split($i, c, ); cmd c[2] } if ($i ~ /^addr/) { split($i, d, ); addr d[2] } } print omem, id, addr, cmd } \ | sort -rn | head -n 10实测下来omem排在前面的连接cmd字段往往能直接告诉你问题出在哪。如果cmd是一条全量查询类的命令那就是返回值太大如果cmd是subscribe或者psubscribe那就是订阅消费慢如果cmd是monitor那基本可以确定是监控连接在拖后腿。还有一个辅助手段是看实例层面的汇总。info clients里的client_recent_max_output_buffer记录了最近出现过的最大输出缓冲区如果这个值一直在涨说明有连接反复顶高即使当前已经被断开问题还会复现。3.5 客户端限流与连接治理光靠服务端断开连接是治标不治本。连接被断开后客户端通常会重连、重试同一个慢查询再来一次缓冲区再涨一次形成循环。真正的解法在客户端侧。第一限制单次返回的数据量。查询接口加明确的分页上限禁止无上限的全量拉取。需要导出大数据量时走离线任务不要在在线实例上做。第二订阅客户端要独立。Pub/Sub 的消费逻辑如果和普通业务混在同一个连接池里一个慢消费者会影响整个池子。我倾向于给订阅单独分配连接并且订阅客户端只做轻量转发真正的处理逻辑放到下游异步做。第三客户端读取要跟上。有些客户端框架在收到响应后处理逻辑很重导致 socket 读取变慢服务端发送不出去。这种情况要么把处理逻辑异步化要么给这类客户端单独限流。第四对慢客户端做识别和隔离。可以通过定期采样client list把omem持续偏高的连接标记出来结合业务侧确认是哪个调用方然后针对性治理。这个动作做成定时任务比出事之后临时排查效率高得多。注意normal类不设限是很多实例的默认状态也是最容易被忽略的风险点。上线新实例时把三类限制都显式写进配置文件别依赖默认值。4. 主从复制缓冲区一次全量同步背后的内存账主从架构下缓冲区的故事更复杂一些。主库要给每个从库维护一份复制缓冲区用来在全量同步期间积累写命令同时主库还有一个全局的复制积压缓冲区用来支持断连后的增量同步。这两个东西名字很像作用完全不同混着讲很容易把排查方向带错。先说结论复制缓冲区影响的是“全量同步期间的内存峰值”复制积压缓冲区影响的是“断连后能不能增量同步”。前者出问题的表现是从库连接被断开、全量同步反复重来后者出问题的表现是从库每次都做全量同步、主库压力周期性飙升。4.1 replication buffer 与 repl_backlog_buffer 不是一回事复制缓冲区是主库为每个从库单独分配的从库连接在主库上表现为一个特殊客户端client list里能看到它的flags带S。全量同步时主库先 fork 出子进程生成 RDB这期间新进来的写命令不能丢就暂存在这个复制缓冲区里等 RDB 传完、从库加载完再把缓冲区里的命令补发过去。这个缓冲区的大小受replica类输出缓冲区限制约束超过限制主库会断开从库连接全量同步前功尽弃从库重连后又要从头来一遍。复制积压缓冲区是主库全局唯一的环形缓冲区所有从库共享。主库执行写命令时除了发给从库还会往这个环形缓冲区里写一份。从库断连重连后会带着自己最后处理到的偏移量来找主库如果这个偏移量还在环形缓冲区覆盖的范围内主库就把缺失的部分补发过去这就是增量同步如果偏移量已经被新数据覆盖了就只能退化成全量同步。用一个类比复制缓冲区像是给每个学生单独准备的补课讲义全量同步期间落下的课先记下来回头单独补复制积压缓冲区像是教室后面那块公共黑板所有学生都能看但黑板容量有限写满了就把最早的擦掉来晚的学生就看不到前面的内容了。4.2 repl-backlog-size 到底该给多大这个参数默认 1mb很多实例一直没改过。1mb 在写入量小的场景够用但在写入频繁的实例上从库只要稍微抖动一下偏移量就超出覆盖范围直接触发全量同步。估算方法不复杂核心是三个变量主库的峰值写入速率、能容忍的最长断连时间、安全系数。所需积压缓冲区 ≥ 主库峰值写入速率字节/秒 × 最长断连时间秒 × 安全系数举个具体例子。某业务主库峰值写入速率大约是 2MB/s网络抖动或者从库重启从断开到恢复平均需要 60 秒安全系数取 2那么所需容量是 2MB × 60 × 2 240MB配置成 256mb 比较合适。如果峰值写入速率不好估可以用另一个口径看info replication里master_repl_offset的增长速度。隔一段时间采样两次算出每秒增长的字节数这个就是实际的写入速率比拍脑袋准得多。# 采样两次 master_repl_offset间隔 10 秒估算写入速率 redis-cli -h 主库地址 -p 端口 info replication | grep master_repl_offset sleep 10 redis-cli -h 主库地址 -p 端口 info replication | grep master_repl_offset需要注意的是积压缓冲区配得越大占用的内存越多而且这部分内存是实打实常驻的。我的经验是不要一步配到几个 G先从 64mb 或 128mb 起步观察一段时间全量同步的发生频率再决定要不要往上加。配得过大反而会掩盖网络或从库本身的稳定性问题。4.3 复制缓冲区溢出后的连锁反应复制缓冲区溢出的表现很有辨识度从库反复处于同步中状态主库的 fork 次数明显增多rdb_bgsave_in_progress频繁为 1磁盘上 RDB 文件反复生成网络带宽周期性打满。业务侧感受到的是主库周期性抖动写延迟出现尖刺。原因链条是这样的全量同步期间主库 fork 生成 RDB同时把新写入的命令攒在复制缓冲区里。如果 RDB 生成时间长、传输慢或者这期间写入量特别大缓冲区就会顶到replica类的硬限制主库断开从库连接。从库重连后发现自己需要的偏移量已经不在积压缓冲区里了只能再请求一次全量同步于是同样的过程再来一遍形成循环。这个循环一旦形成主库的负载会持续偏高因为 fork 和 RDB 传输本身就要消耗 CPU、内存和带宽。更糟的是如果从库一直同步不上业务读请求全部压到主库压力进一步放大。处理这个问题的顺序是先确认是不是缓冲区限制导致断开再看全量同步为什么慢最后决定是调大缓冲区还是优化同步过程。只调大缓冲区而不解决同步慢的问题相当于把症状压下去根因还在。4.4 实操从info replication读出蛛丝马迹排查主从复制问题info replication是主要入口。重点看几个偏移量字段master_repl_offset是主库当前的写偏移量slave_repl_offset是从库已经处理到的偏移量slave_read_repl_offset是从库从主库读到的偏移量。三个值的关系能说明很多问题。如果master_repl_offset和slave_read_repl_offset差距很大说明网络传输跟不上或者主库发送被阻塞如果slave_read_repl_offset和slave_repl_offset差距很大说明从库收到了但处理不过来通常是从库自身负载高或者执行了慢命令。# 主库侧确认从库连接状态和偏移量 redis-cli -h 主库地址 -p 端口 info replication # 从库侧确认同步状态、偏移量、是否处于全量同步 redis-cli -h 从库地址 -p 端口 info replication判断积压缓冲区是否够用核心是看两个偏移量的差值有没有超过repl-backlog-size。info replication里有一个repl_backlog_size字段配合偏移量差值就能算出余量。如果差值经常逼近上限说明缓冲区偏小如果差值长期很小却仍然频繁全量同步那问题不在缓冲区要去看网络或者从库的repl-timeout配置。还有一种情况容易被忽略从库的repl-timeout设得太小网络稍有抖动就被判定为超时断开断开一次就可能导致偏移量超出覆盖范围。这个参数默认 60 秒如果跨机房同步延迟较高可以适当放宽。注意调整repl-backlog-size之后环形缓冲区会在下一次复制相关操作时按新大小重新分配老数据不会迁移。调大之后建议观察一到两个全量同步周期确认效果再固化到配置文件。5. AOF缓冲区与持久化链路里的内存波动AOF 缓冲区不像前面几类那么容易单独出问题它更多是作为持久化链路的一环参与进来。但它的影响不小AOF 缓冲区里的数据在刷盘之前是实打实占着内存的如果刷盘策略配置不当或者写入量突然放大这块内存会明显上涨同时伴随写延迟抖动。5.1 aof_buf 的工作机制每一条写命令执行完之后Redis 会把它以 RESP 格式追加到 AOF 缓冲区aof_buf里然后根据appendfsync配置决定什么时候真正写到磁盘。写到磁盘之后缓冲区里的这部分数据才能释放。所以aof_buf的大小取决于两个因素写入速率和刷盘频率。appendfsync有三个取值行为差别很大。always表示每条命令都同步刷盘最安全但性能最差缓冲区几乎不会堆积everysec表示每秒刷一次是性能和安全性的折中也是最常用的配置正常情况下缓冲区最多堆积一秒的写入量no表示交给操作系统决定性能最好但缓冲区可能堆积较久且故障时丢数据风险最高。实际排查中aof_buf异常增长通常伴随两个现象aof_rewrite_in_progress为 1或者磁盘 IO 出现明显瓶颈。AOF 重写期间主进程的写入会同时进旧的 AOF 缓冲区和重写缓冲区内存会有额外的峰值磁盘 IO 跟不上时aof_buf里的数据刷不下去越积越多。5.2 三种刷盘策略的取舍选哪种刷盘策略本质是在“丢数据容忍度”和“写入性能”之间做选择没有标准答案。always适合数据不容有失的场景但代价是每条命令都要等磁盘吞吐会掉得很厉害除非写入量本来就很小否则不建议。everysec是绝大多数场景的默认选择最多丢一秒的数据性能影响可控。no适合对数据完整性要求不高、但对吞吐要求高的场景比如纯缓存。这里有个容易被误读的细节everysec模式下如果上一秒的刷盘还没完成主线程会跳过本次刷盘等下一次再做。这意味着在某些磁盘压力大的时段实际刷盘间隔可能超过一秒aof_buf会堆积更多。这个行为可以在日志里看到相关提示排查时别只盯着配置值要看实际刷盘耗时。5.3 大事务与大pipeline对aof_buf的冲击AOF 缓冲区堆积还有一个来源是写入批次过大。一个包含几千条命令的事务或者一个超大的 pipeline执行期间所有命令的 AOF 记录会在短时间内集中写入aof_buf如果恰好碰上刷盘这一批数据要一次性落盘耗时会被放大缓冲区占用也会出现尖峰。我踩过的一个坑是某个批量导入任务用了很大的 pipeline导入期间 AOF 缓冲区持续偏高同时aof_buf的刷盘耗时从毫秒级涨到几十毫秒业务侧的写请求开始超时。后来把 pipeline 拆成小批每批之间留一点间隔问题就消失了。不是配置的问题是写入形态的问题。所以遇到 AOF 相关的内存和延迟问题先看写入模式再看配置。批量任务、定时任务、数据迁移这类操作尽量安排在低峰期或者拆成小批执行避免和正常业务抢 IO。6. 常见问题速查表与踩坑清单缓冲区相关的问题现象往往很相似但根因可能完全不同。下面这张表是我自己排查时用的对照表按现象找根因能省不少时间。6.1 排查速查表现象优先检查项常用命令典型根因内存上涨但数据量不变info clients的最大缓冲区指标info clients、client list输出缓冲区被慢客户端占满连接数突然下降服务端日志里的缓冲区超限提示查看实例日志、client list输出缓冲区触发硬限制连接被断开写入报 OOM 但淘汰没生效used_memory与used_memory_dataset差值info memory缓冲区内存不可淘汰顶穿了内存预算从库反复全量同步偏移量差值与repl-backlog-sizeinfo replication积压缓冲区太小增量同步退化为全量主库周期性抖动rdb_bgsave_in_progress、fork 次数info stats、info persistence全量同步循环fork 和传输压力叠加订阅客户端频繁掉线pubsub类输出缓冲区限制client list、info clients消费速度跟不上发布速度写延迟周期性尖刺AOF 刷盘耗时、aof_buf占用info persistence磁盘 IO 瓶颈导致刷盘变慢查表的时候有个顺序建议先看info clients的两个最大值指标确认是不是缓冲区问题再按连接类型定位到具体是哪一类缓冲区最后才去调参数。反过来先调参数再看现象很容易把真正的问题盖住。6.2 几条踩过的坑第一条别把缓冲区内存当成可淘汰内存。used_memory超过maxmemory时淘汰策略只对数据键生效缓冲区占用的部分淘汰不掉。所以看到used_memory超标但evicted_keys没怎么涨第一反应应该是去看缓冲区而不是怀疑淘汰策略没配。第二条client list的输出要定期采样不能只看当下。缓冲区问题是动态的出事那一刻的快照可能已经错过了峰值。我的做法是每分钟采样一次把omem和qbuf排在前面的连接记下来连续几次都排在前面的基本就是问题连接。第三条MONITOR命令要当成高危操作对待。它不只是输出缓冲区的问题本身还会带来明显的性能开销。线上实在需要抓包排查也要限定在很短的窗口内抓完立刻断开不要挂着不管。第四条输出缓冲区限制不要只配normal一类。三类都要配尤其是主从架构下replica类不配全量同步期间的行为完全不可控。配置写进配置文件不要只靠config set否则一次重启就回到解放前。第五条调参之前先确认写入形态。很多缓冲区问题不是参数配错了而是业务侧突然上了大 batch、大返回、慢消费。参数只是兜底写入形态才是根因。我在实际处理这类问题时通常会把“最近有没有上线新的批量任务”作为第一个要问的问题命中率比翻配置高得多。第六条监控指标里把client_recent_max_input_buffer和client_recent_max_output_buffer加上告警。这两个指标是现成的不需要额外开发设个阈值就能提前发现异常比等到内存告警再去排查主动得多。阈值怎么定可以先跑一周观察基线再按基线的两到三倍设置。第七条处理慢客户端要有隔离思路不能只靠杀连接。把订阅连接、批量任务连接、普通业务连接分到不同的连接池甚至不同的实例一类出问题不至于拖垮全部。这个改造成本不高但收益很明显尤其是实例规模上去之后。最后分享一个我自己常用的小习惯每次调整完缓冲区相关参数隔一天回看一次info clients和info memory对比调整前后的峰值变化。参数调整的效果不是立刻能看出来的要经过一个完整的业务高峰才能验证。只调不看等于没调。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑