结账页报支付超时。你打开日志搜索,筛 ERROR,输入 payment provider timeout,只命中一条。这一条是真的只超时了一次,还是只有这一条活着走到了索引?
日志搜索展示的是抵达索引的内容,不是应用写下的内容。两者之间隔着采集代理、解析器、过滤器、缓冲区和搜索集群,每一环都可能让一行日志延迟、被拒或被丢弃,而应用毫不知情。这正是要把日志聚合管道逐段讲清楚的原因:事故中搜索返回空结果时,你得判断是系统没事,还是证据根本没送到。
有一个日志聚合管道模拟器,可以让你一段一段看这趟旅程,再用三种方式把它弄坏。它按批次搬运日志,并在每一段计数,而不是追踪单行。该工具免费、在浏览器里运行、无需注册。
六段管道,一条日志
模拟器把管道画成六张卡片。每点一次 Step 前进一段,Run flow 大约每秒自动走一步,六步构成一个周期。
- 来源(应用与主机):原始事件,以及有多少在排队。
- 采集(Fluent Bit):代理跟踪文件并转发事件。它跟不上时,来源队列就会变长。
- 处理(解析与过滤):抽取字段、去掉噪音。两个控件在阶段检查器里:解析器选择(JSON 解析器或 Grok 模式)和“丢弃健康检查噪音”开关。改动任一项都会重新开始一轮。
- 缓冲区(持久缓冲):解析与索引之间的有界队列,以已用容量对总容量显示。
- 索引(搜索集群):被接受的事件落到三个主分片上,“分片健康”标签页显示均衡情况。模拟器轮流把事件分给分片,所以各场景下柱状条都保持齐平。
- 搜索(日志探索):一个带文本框和级别筛选的标签页。它保留每个索引步骤最多四条样本事件,且只留最新的 24 条,所以判断完整性要看计数器,不要看这个列表。
卡片下方有五个计数器记账:已生成、已缓冲、已索引、已拒绝、已丢弃。背后的引擎对每个事件守恒:生成的事件要么在队列里等待,要么被过滤、被拒绝、被索引或被丢弃。被过滤的噪音没有独立计数器,处理卡片的“已移除”数字把过滤和拒绝加在一起。把这个习惯带进真实系统:如果你说不清“发出”和“可搜索”之间的差额,就不能相信一次空搜索。
点任意卡片,阶段检查器会显示它的“运维信号”,也就是这一段最该盯的东西。对搜索段来说,这句话在事故中最要紧:只有被索引的日志才可搜索,被过滤、被拒绝、被丢弃的日志永远到不了这里。
先跑健康状态
从 Healthy flow 开始,走几个周期。每个周期来源发出 24 条日志,噪音过滤器移除 4 条,20 条被索引。队列排空,徽标显示“管道健康”,分片柱状条均匀增长。
然后分三种方式弄坏它。
流量尖峰。来源每周期发出 84 条,采集器只转发 38 条。第一步徽标就变成“背压正在累积”。到第三个周期:来源发出 84 条,26 条在采集前被丢弃。此时看缓冲区,它是空的。持久缓冲救不了还没到它那里就丢掉的日志。到第五个周期结束,共生成 420 条,索引 140 条,丢弃 118 条。采集卡片的“待解析”计数每周期还多涨 4:采集器发 38 条,处理器只接 34 条。
解析器不匹配。一次部署改了日志格式。每周期 30 条里,4 条作为噪音被过滤,11 条被解析器拒绝,只有 15 条被索引。徽标显示“检测到解析器拒绝”,且没有任何丢弃,所以只盯丢弃的看板会一直绿着。换成 Grok 解析器也没用:它每周期只处理 30 条中的 27 条,于是每周期多 3 条堵在处理器队列里,而这 27 条里它过滤 4 条、拒绝 10 条、索引 13 条。一个更慢、又仍然不匹配格式的解析器只会让情况更糟。模拟器里没有修解析器的按钮;现实中你要把解析器更新到新格式,或者回滚这次变更。
索引变慢。先把解析器切回 JSON:切换场景时模拟器会保留你的解析器选择。搜索集群每周期索引 9 条,而 32 条抵达缓冲区(到达 36 条,噪音过滤器移除 4 条)。缓冲区每周期涨 23 条,第三个周期出现警告,第五个周期:缓冲区达到容量,丢弃了 19 条最旧的日志。现在关掉“丢弃健康检查噪音”再跑一遍,第一次丢弃会早一个周期到来。在缓冲区之前过滤噪音并没有修好慢索引器,但它买到了时间。
把场景当值班演练
把每个场景当成一次 on-call 演练。假装你只有搜索标签页,然后用屏幕其余部分按顺序回答四个问题:
- 输入正常吗?把每周期生成量与健康状态的 24 对比。尖峰往往是真实事故的第一信号,它同时也在给管道加压。
- 哪个队列在涨?来源队列涨指向采集器,缓冲区涨指向索引器。动手之前先说出是哪一段。
- 有东西被拒绝吗?拒绝意味着日志存在,但不再匹配解析器。如果改格式的那次部署同时也弄坏了结账,你需要的错误行就在被拒绝的那堆里。
- 距离丢失还有多久?缓冲区在填就是在倒计时。剩余空间除以每周期净增长是个粗略且乐观的估计:这里缓冲区是在一批日志落地时溢出,而不是等索引排空。JSON 解析器加噪音过滤开启时,第四个周期后它装着 92 条、剩 13 个空位,下一批 32 条会丢掉 19 条。
缓解手段要对着信号来:尖峰加采集容量,格式不匹配修解析器,索引慢就加索引容量或削减噪音。
亲手复现一次解析器不匹配
用 jq 和三行日志就能复现解析器不匹配:
- 一行 JSON 格式的 INFO 请求完成日志;
- 一行 JSON 格式的 ERROR 支付超时日志;
- 一行纯文本的 ERROR 支付超时日志。
只认 JSON 的管道会把第一条命令的输出限定为那行 JSON 错误;第二条命令则打印出被拒的纯文本 ERROR 行。两条超时被记录,搜索只找到一条。把第二份输出计数并告警,你就有了一个解析器拒绝指标。真实工具并不总是把拒绝直接扔掉。在 Vector 的 remap 转换里解析失败时,默认会把原始未修改的事件继续往下送,于是这行以原始文本抵达索引,不带你的字段。
真实代理对另外两个场景也各有版本。Fluent Bit 的背压文档说明,当某个输入达到 mem_buf_limit,在仅内存缓冲下它会停止从该源插件缓冲数据,直到足够多的缓冲块被刷出,多数插件此时会记录 paused(mem buf overlimit)。同一页还警告,仅内存缓冲下某些输入可能丢数据,需要避免就改用文件系统缓冲。在断定应用安静之前,先去搜代理自己的日志找这条警告。用文件系统缓冲时,Fluent Bit 丢弃最旧的数据,就像慢索引场景里的缓冲区:达到输出的 storage.total_limit_size 时,队列里最旧的块会被丢弃。Vector 的缓冲区默认是阻塞的。
该对什么告警
模拟器页面列出了要一起盯的信号:输入速率、队列年龄与深度、解析器拒绝率、缓冲区利用率、丢弃事件数、索引延迟和分片均衡。在真实工具里,丢失信号是可以抓取的计数器:Fluent Bit 的 fluentbit_output_retries_failed_total 和 fluentbit_output_dropped_records_total,Vector 的 component_discarded_events_total,以及 Loki 的 loki_discarded_samples_total(按 reason 打标签)。
把三种场景跑到你能预判下一步的数字,然后再只开着搜索标签页当演练跑一遍。
热门跟贴