资讯动态

Flink监控体系实战:Metrics、Reporter与背压排查

发布时间:2026/10/28 3:12:37 来源:尧图企业网站定制
1. Web UI 之外的战场Flink 监控到底要盯哪些东西Flink 的 Monitoring 这块内容官方文档写得其实挺克制——它把 Metrics、Reporter、REST API、日志这几块并列摆出来每个参数都列了但很少告诉你“什么时候该看哪个、看到多少算异常”。真正上手之后你会发现Web UI 上那几个跳动的数字只够应付演示环境一旦作业跑到几十个 TaskManager、几百个 subtask光靠刷新页面根本定位不了问题你会需要一套能留痕、能对比、能告警的监控体系。这篇笔记就是围绕这块内容展开的Flink 的 Metrics 体系怎么组织、Reporter 怎么配、哪些指标真正值得盯、出问题时按什么顺序排查。适合已经能跑起一个 Flink 作业、但遇到“延迟在涨、Checkpoint 在超时、CPU 不高但就是慢”这类问题时不知道从哪下手的同学也适合正在给团队搭监控看板、需要把 Flink 指标接进现有 Prometheus 体系的人。文中涉及的配置以 1.14 到 1.18 这个版本区间为主个别版本差异我会单独标出来。2. Metric 体系拆解类型、命名与 Scope官方文档里 Metrics 这一节的第一屏信息量其实很大——它定义了 Metric 的类型、Scope 的拼接规则、以及每个 Reporter 如何消费这些指标。很多人配完 Reporter 能出数就停了但后面所有“指标对不上”“看板画不出来”“Prometheus 内存爆了”的问题根子几乎都在这一节。2.1 Counter、Gauge、Meter、Histogram 分别用来表达什么Flink 的 Metric 只有四类基础类型理解它们的语义差别比记住名字重要得多。Counter只增不减的累计值比如numRecordsIn、numBytesOut。它本身没有时间维度要看速率必须自己做rate()或者increase()。Gauge瞬时值某一刻的快照比如堆内存使用量、当前水位线、当前并行度。Gauge 是最容易误用的类型——很多人把“每秒处理条数”做成 Gauge结果上游一抖动曲线就像心电图。Meter平均速率Flink 内部用 EWMA指数加权移动平均算出来的numRecordsInPerSecond就是 Meter。它已经帮你把“每秒多少”算好了但代价是有平滑延迟突发的尖峰会被吃掉一部分。Histogram分布统计能拿到 count、min、max、mean以及配置了HistogramStatistics之后的 p50/p75/p95/p99。延迟类指标基本都是 Histogram。这里有个实操上的经验排障时优先看 Counter 算出来的速率而不是 Meter。Meter 的 EWMA 在流量骤降的时候会拖一条长尾巴容易让你误判“问题还在持续”。而在做容量规划、看长期趋势时Meter 反而更省事因为不用写 PromQL 聚合。2.2 Scope 的拼接规则与“指标名为什么这么长”Flink 的每个指标都有一个完整路径由 Scope 一段段拼起来。默认的 Scope 格式大概是这样不同小版本措辞略有出入但结构一致配置项默认形态含义metrics.scope.jmhost.jobmanagerJobManager 全局指标metrics.scope.jm.jobhost.jobmanager.job_nameJobManager 按作业维度metrics.scope.tmhost.taskmanager.tm_idTaskManager 全局指标metrics.scope.task...job_name.task_name.subtask_indexTask 级指标metrics.scope.operator...job_name.operator_name.subtask_index算子级指标所以你看到的flink_taskmanager_job_task_operator_numRecordsOutPerSecond其实是被 Prometheus Reporter 把点号换成下划线、再加了flink_前缀之后的结果。理解这一点很关键指标名字长不是设计失误而是它把维度信息编码进了名字里。注意Scope 里的host在容器化环境里通常是 Pod 名每次重启都会变。这意味着同一个算子重启十次Prometheus 里就会多出十个不同的序列。这是容器环境下指标基数失控最常见的原因之一。处理办法有两个方向一是直接把metrics.scope.task和metrics.scope.operator里的host去掉改成在采集端统一打标签二是确认你用的 Reporter 版本是否把 Scope 变量转成了 label1.13 之后的 Prometheus Reporter 在这块行为有过调整最靠谱的做法是配好之后curl一次/metrics接口直接看实际输出的名字和标签长什么样不要凭记忆写查询。2.3 自定义 Metric 的正确写法与线程安全陷阱框架自带的指标覆盖不到业务语义比如“解析失败了多少条”“当前积压队列有多深”这类东西只能自己埋点。写法本身不复杂但坑集中在生命周期管理上。public class OrderParseFunction extends RichFlatMapFunctionString, Order { private transient Counter badRecord; private transient Meter parseRate; Override public void open(Configuration parameters) { MetricGroup group getRuntimeContext() .getMetricGroup() .addGroup(orders, parser); badRecord group.counter(badRecord); parseRate group.meter(parseRate, new MeterView(60)); } Override public void flatMap(String value, CollectorOrder out) { Order order tryParse(value); if (order null) { badRecord.inc(); return; } parseRate.markEvent(); out.collect(order); } }几个必须注意的点getRuntimeContext().getMetricGroup()一定要在open()里调一次存成成员变量不要写在flatMap里。前者只是一次组装后者是每条记录一次链路查找在千万级 QPS 的场景下这部分开销能占到几个百分点属于典型的“埋点把作业拖慢”。addGroup()的层级不要随手乱加。addGroup(orders, parser)会在指标路径里插入两段如果你还传了动态变量比如addGroup(topic, topicName)那就等于给每个 topic 都开一组指标——topic 数量上百的时候指标序列数直接翻百倍。多线程场景要特别小心。Flink 的 Counter 实现理论上是线程安全的底层用了原子操作但你自己组合出来的“先读 Gauge 再判断再写”的逻辑不是原子的。如果业务里有独立的回调线程在往 Metric 里写数据老老实实用一个AtomicLong当数据源Gauge 只做读不要在里面做任何计算。提示自定义指标名建议用驼峰且不加下划线Reporter 转换规则各版本略有差异纯驼峰在 Prometheus 里转成下划线后不会出现连续双下划线这种难看的名字。2.4 指标基数真正把 Prometheus 打爆的东西这一条官方文档不会明说但踩过的人都懂Flink 的算子级指标是一个组合爆炸源。假设你有 50 个算子、每个算子 40 个并行度、每个算子带 20 个指标那就是 50 × 40 × 20 4 万个时间序列如果 Scope 里还带着动态的host重启几次就是十几万。Prometheus 单实例在几十万序列的时候就开始吃力了查询变慢、内存见涨最后往往是监控系统自己先挂。控制基数的几个手段按性价比排序第一调整 Scope。把metrics.scope.operator里的subtask_index去掉粒度就从“每个并行子任务”降到“每个算子”序列数直接除以并行度。代价是你失去了发现单点倾斜的能力——所以这个改动只建议用在对“是否倾斜”不敏感的场景。第二用 Reporter 的过滤器。Prometheus Reporter 提供metrics.reporter.prom.filter.includes和filter.excludes前者是白名单、后者是黑名单语义是包含任意一条 includes 且不匹配任何 excludes 才上报。排障期可以把 includes 设成几个关键指标前缀长期再放开。metrics.reporter.prom.filter.includes: *.numRecordsInPerSecond,*.numRecordsOutPerSecond,*.backPressuredTimeMsPerSecond,*.lastCheckpointDuration第三把 JVM 那堆指标单独评估。Status.JVM.ClassLoader、Status.JVM.GarbageCollector这些是 TaskManager 级的数量不多但Status.JVM.GarbageCollector会按收集器名字展开成好几条G1 下一台机器就有三四条几百台就是上千条。数量不算大但如果你在做多集群汇总这部分可以按需取舍。3. Reporter 实操从配置文件到 Prometheus 抓取链路Reporter 是 Metrics 体系里唯一“往外发”的组件。官方文档列了十来种实际工作中常用的就那么三四个选错的话后面调优会很别扭。3.1 Reporter 选型的三个判断维度我的判断逻辑基本是三问数据要不要留痕、由谁去拉、集群规模多大。如果只是本地开发调一个作业Slf4jReporter最省事它把指标按固定间隔打到日志里配合grep就能看不用起任何外部服务。缺点是日志会被撑爆绝对不能在生产环境默认打开。如果团队已经有 Prometheus Grafana 这套东西那PrometheusReporter基本是唯一答案。它是 pull 模式——Flink 在 JobManager 和各 TaskManager 上各起一个 HTTP 端口Prometheus 定期来抓。好处是 Flink 侧不需要知道 Prometheus 在哪扩缩容时只要目标列表跟着变就行。如果是传统的 JMX 监控体系或者你想临时用 JConsole 连上去看一眼JMXReporter更顺手它暴露的是标准 MBean任何 JMX 客户端都能连。至于 Graphite、InfluxDB、StatsD 这些 push 模式的 Reporter现在新项目里用得少了。它们的问题是 Flink 需要主动往外推网络分区的时候会积压、会丢数据而且推的频率和 Prometheus 的抓取频率在语义上不对等跨系统对齐的时候容易扯皮。3.2 PrometheusReporter 的完整配置与多实例端口分配以 1.15 之后的配置方式为准用factory.class1.13/1.14 用的是metrics.reporter.prom.class直接写 Reporter 类名metrics.reporter.prom.factory.class: org.apache.flink.metrics.prometheus.PrometheusReporterFactory metrics.reporter.prom.port: 9249 metrics.reporter.prom.filter.includes: *.numRecordsInPerSecond,*.numRecordsOutPerSecond,*.backPressuredTimeMsPerSecond,*.busyTimeMsPerSecond,*.lastCheckpointDuration,*.lastCheckpointSize这里最容易翻车的是端口。默认端口是 9249而flink-conf.yaml是全局配置JobManager 和所有 TaskManager 会读到同一份。如果一台机器上跑了多个 TaskManager 进程standalone 部署经常这样第二个进程启动时会因为端口占用直接失败而且报错是在 TaskManager 启动阶段很容易被当成别的问题。解决办法是把端口写成一个区间metrics.reporter.prom.port: 9249-9260Flink 会在区间内找一个可用端口绑定。但区间方案只解决了“起得来”的问题没解决“Prometheus 怎么知道它在哪个端口”的问题。所以生产上更推荐的做法是每台机器只跑一个 TaskManager或者用容器编排时把端口固定映射出来。Prometheus 侧的抓取配置scrape_configs: - job_name: flink scrape_interval: 15s static_configs: - targets: - jm-host:9249 - tm-host-1:9249 - tm-host-2:9249容器环境下不要手写 targets用基于 Pod 的服务发现。但要注意Flink 不会自动给自己的 Pod 打上prometheus.io/scrape这类注解需要你在部署模板里手动加上否则服务发现扫不到任何东西你会对着空白的 Grafana 发呆半小时。3.3 JMX 与 Slf4j适合调试期和兜底期的两种方案JMX Reporter 的配置极简metrics.reporter.jmx.factory.class: org.apache.flink.metrics.jmx.JMXReporterFactory metrics.reporter.jmx.port: 9020-9040端口同样建议写区间。它最大的价值在于临时深挖——Prometheus 里的指标是被过滤过的你想临时看一个没上报的指标用 JConsole 或jcmd连上去MBean树里能翻到全量的东西。我有过好几次是靠 JMX 里翻出一个没被 Reporter 上报的指标才定位到问题的。Slf4j Reporter 的关键参数是间隔和日志级别metrics.reporter.slf4j.factory.class: org.apache.flink.metrics.slf4j.Slf4jReporterFactory metrics.reporter.slf4j.interval: 60 SECONDS在log4j.properties里把对应的 logger 单独设成 INFO 并输出到独立文件否则它会和业务日志搅在一起。这个方案适合短时间几小时的密集观测不适合长期开着。3.4 采集间隔与抓取间隔的对齐计算这一块是配置里最容易被忽略的部分但直接影响你看到的数据准不准。Flink 侧有两个时间参数需要区分清楚metrics.reporter.name.intervalReporter 的推送间隔默认 10 秒。注意对 PrometheusReporter 基本无效因为它不推送。metrics.fetcher.update-interval指标缓存的刷新间隔默认 10000 毫秒。这个参数决定 Web UI 和 REST/metrics接口看到的值多久更新一次。Prometheus 侧是scrape_interval。这三个数字的配合有个简单的判断标准抓取间隔应该小于等于刷新间隔。如果你把scrape_interval设成 5 秒而update-interval是 10 秒那 Prometheus 会抓到两份一模一样的数据rate()算出来的曲线会出现台阶状的锯齿看着像业务抖动其实是采集配置的问题。反过来scrape_interval设得太长比如 60 秒短时间的背压尖峰会被完全漏掉。比较平衡的取值是scrape_interval: 15s配合update-interval: 10000ms。数据量和灵敏度兼顾一个背压周期一般也会持续几分钟以上15 秒的采样率足够还原出形状。还有一个参数值得单独说metrics.latency.interval默认是 0也就是端到端延迟指标默认关闭。打开它需要设置成一个非零的毫秒数比如 60000代价是 Flink 会周期性往数据流里注入 LatencyMarker这些 marker 会占用网络带宽、也会影响吞吐。在高峰值吞吐的作业上我一般只在排查阶段临时打开抓完数据就关掉不建议长期开启。4. 关键指标清单与阈值推导这一节是我认为整篇文档里最值得反复看的部分因为它决定了你后面画的看板有没有意义。指标不在多在于每个都对应一个明确的故障假设。4.1 Checkpoint 与状态相关指标Checkpoint 是 Flink 作业的“体检报告”几乎所有状态类问题最后都会在这里露馅。核心的几个指标都在 Job 维度指标名类型关注点lastCheckpointDurationGauge最近一次成功的耗时lastCheckpointSizeGauge最近一次的状态大小lastCheckpointFullSizeGauge含增量文件的总大小numberOfCompletedCheckpointsCounter成功次数numberOfFailedCheckpointsCounter失败次数lastCheckpointRestoreTimestampGauge上次恢复的时间点阈值不是拍出来的可以这样推设 Checkpoint 间隔为interval超时时间为checkpointTimeout。一次健康的 Checkpoint耗时应该稳定在interval的 50% 以内也就是如果 3 分钟做一次耗时最好别超过 90 秒。因为 Flink 的调度逻辑是“上一次做完、等minPauseBetweenCheckpoints之后再开始下一次”实际的有效间隔约等于有效间隔 ≈ max(checkpointInterval, lastCheckpointDuration minPauseBetweenCheckpoints)这条公式直接解释了很多人困惑的现象明明配了 1 分钟一次为什么看板上是 3 分钟一次因为 Checkpoint 本身就做得太久间隔被撑开了配置根本没有生效。lastCheckpointSize的突增也很说明问题。状态大小在一天内涨了 3 倍通常意味着要么有状态泄漏比如ListState只加不清要么是 key 的基数在膨胀。这时候光看总量不够得看算子级别的状态分布找出是哪个算子的状态在吃内存。注意开增量 Checkpoint 的时候lastCheckpointSize反映的是本次新增的数据量lastCheckpointFullSize才是全量。两个数字在增量模式下能差一个数量级别拿lastCheckpointSize去评估状态总量。4.2 背压、吞吐与网络缓冲背压指标从 1.13 开始换了一套命名主要是backPressuredTimeMsPerSecond、idleTimeMsPerSecond、busyTimeMsPerSecond这三个单位是“每秒中有多少毫秒处于该状态”。这三个值加起来大致等于 1000会有细微误差所以判断逻辑非常直观指标取值含义建议动作backPressuredTimeMsPerSecond 500该算子有一半以上时间在背压重点排查下游busyTimeMsPerSecond接近 1000 且无背压算子满负荷但能跟上关注是否接近瓶颈idleTimeMsPerSecond 800上游供数不足往数据源方向查三者都偏低总和远小于 1000指标异常或作业在等外部 IO查线程栈如果是 1.13 之前的版本指标名是backPressuredTimeMs、idleTimeMs这种累计毫秒数需要自己做差值。这也是为什么我说一定要先 curl 一次实际接口版本之间的名字差异真的会让人白写一堆 PromQL。吞吐指标相对简单numRecordsInPerSecond、numRecordsOutPerSecond、numBytesInPerSecond、numBytesOutPerSecond。它们的价值在于对比——上游numRecordsOut和下游numRecordsIn出现持续缺口要么是网络丢数据这种情况很少要么是中间有个算子在过滤或者聚合得对着算子链去看。网络缓冲这块需要额外开一个开关taskmanager.network.detailed-metrics: true打开之后才会有inPoolUsage、outPoolUsage、floatingBuffersUsage、exclusiveBuffersUsage这些指标。默认关闭是有原因的——它要额外统计每条连接的缓冲占用对性能有轻微影响。但排“吞吐上不去、延迟忽高忽低”这类问题时这几个指标几乎是唯一能直接指向网络层的证据。outPoolUsage长期在 0.8 以上说明下游消费不动本地输出缓冲快满了这是背压的物理证据。floatingBuffersUsage高而exclusiveBuffersUsage低说明流量分布不均某些 channel 吃掉了大部分浮动缓冲——这时候该往数据倾斜方向想。4.3 JVM、GC 与线程这部分指标默认就有不需要额外配置但很多人看了不知道重点在哪。我一般只盯四个flink_taskmanager_Status_JVM_Memory_Heap_Used除以Heap_Max得到堆使用率。但要注意这个值包含了 Flink 的 Managed Memory 之外的区域而 Managed Memory 是 off-heap 的所以堆使用率不能直接等同于“Flink 用了多少内存”。GC 时间用rate()算增长量过去 5 分钟里 GC 占用了多少时间。经验阈值是 GC 时间占比超过 10% 就要警惕超过 25% 基本可以确定 JVM 是瓶颈之一。同时要看 GC 次数的增速年轻代频繁回收但耗时短问题不大老年代回收次数多且耗时长那就是真的有内存压力了。线程数Status.JVM.Threads.Count是个被低估的指标。它突然涨到几千通常是线程泄漏——比如某个Sink每次open都创建一个连接池但close里没关。作业跑几天之后 OOM 或者响应变慢回头看这个指标是一条持续上升的直线基本可以确诊。CPU 相关指标需要打开metrics.system-resource: true采集间隔由metrics.system-resource-interval控制默认 2 秒。这是通过操作系统的系统调用采的不同系统上能拿到的字段略有差异。4.4 能直接用的 PromQL 与告警规则光有指标没用得能写出查询。下面几条是我在看板上长期保留的# 各算子背压情况只看超过 500ms/s 的 max by (job_name, task_name) ( flink_taskmanager_job_task_backPressuredTimeMsPerSecond ) 500 # Checkpoint 耗时与间隔的比值超过 0.5 就该看 flink_jobmanager_job_lastCheckpointDuration / 180000 # TaskManager 堆使用率 flink_taskmanager_Status_JVM_Memory_Heap_Used / flink_taskmanager_Status_JVM_Memory_Heap_Max # 老年代 GC 时间占比5 分钟窗口 rate(flink_taskmanager_Status_JVM_GarbageCollector_G1_Old_Generation_Time[5m]) # Checkpoint 失败次数增长 increase(flink_jobmanager_job_numberOfFailedCheckpoints[30m]) 0告警规则我配置得比较克制只留三条硬性的groups: - name: flink-job rules: - alert: FlinkCheckpointFailing expr: increase(flink_jobmanager_job_numberOfFailedCheckpoints[30m]) 2 for: 5m labels: severity: warning annotations: summary: 作业 Checkpoint 连续失败 - alert: FlinkBackpressureHigh expr: max by (job_name, task_name) (flink_taskmanager_job_task_backPressuredTimeMsPerSecond) 600 for: 10m labels: severity: warning annotations: summary: 算子 {{ $labels.task_name }} 持续背压 - alert: FlinkHeapPressure expr: flink_taskmanager_Status_JVM_Memory_Heap_Used / flink_taskmanager_Status_JVM_Memory_Heap_Max 0.85 for: 15m labels: severity: warning annotations: summary: TaskManager 堆使用率持续偏高for的取值很重要。背压告警如果设成 1 分钟每次数据高峰都会响久而久之就没人看了。10 分钟能过滤掉绝大多数瞬时抖动剩下的基本都是真问题。4.5 阈值不是拍脑袋几个可复用的推导方法“多少算高”这个问题没有通用答案但有几个推导思路可以复用。延迟类指标用分位数反推。如果你的业务 SLA 是 p99 在 500ms 以内那就把Histogram的 p99 曲线拉出来看它平时的波动区间。如果平时就在 400ms 上下浮动那留给你的余量只有 100ms任何超过这个幅度的抬升都该告警如果平时是 150ms那 400ms 就已经是明显异常了。资源类指标看趋势而不是绝对值。堆使用率 70% 本身不是问题问题是它是不是在缓慢爬升。用deriv()算一下 1 小时窗口的斜率持续为正且在加速比一个静态阈值有用得多。吞吐类指标看缺口。上游的numRecordsOutPerSecond和下游的numRecordsInPerSecond做减法缺口持续存在说明链路里有个环节在丢或者卡。5. 背压与延迟的定位流程实录指标告诉你“有问题”但不会告诉你“问题在哪”。这一节说流程。5.1 第一步永远是分辨背压和数据倾斜背压和数据倾斜在指标上长得非常像都是部分 subtask 慢、整体吞吐上不去。但处理方式完全相反——背压要往下游找瓶颈倾斜要调 keyBy 或者加盐。区分方法很简单看同一个算子的不同 subtask 的numRecordsInPerSecond。如果各 subtask 的输入速率差异在 10% 以内但backPressuredTimeMsPerSecond差异很大那是背压沿着链路传上来了得继续往下游看如果输入速率本身就是几倍的关系比如某一个 subtask 吃了 60% 的数据那就是倾斜。倾斜还有个更直接的证据numRecordsOut的分布在时间上高度不均匀某些 subtask 的曲线是尖峰状而另一些是平的。这时候去查 keyBy 的字段十有八九是某个默认值或者空值占了大头。5.2 线程栈采样与火焰图的正确打开方式确认了是背压下一个问题就是“卡在哪个方法里”。Flink 在 1.13 之后把 Web UI 的背压页面从“输出缓冲占用估算”改成了“线程栈采样”这个改动含义很深——它承认了前者只能告诉你有没有背压不能告诉你卡在哪。手工采样的方式是直接打线程栈# 连续采 5 次每次间隔 2 秒 for i in $(seq 1 5); do jstack taskmanager_pid /tmp/dump_$i.txt sleep 2 done然后统计同一个堆栈出现在多少次采样里。出现在 3 次以上的基本就是卡住的那个位置。这个方法的原理很简单如果一段代码在 10 秒内被抓到 4 次那它大概率占了 80% 以上的 CPU 时间。较新版本1.16 之后提供了火焰图通过 REST 接口触发curl -s http://jm-host:8081/jobs/jobId/vertices/vertexId/flamegraph flamegraph.html火焰图的好处是能一眼看出调用栈的宽度分布比人工统计栈方便得多。但要注意它采样的是当前瞬间如果问题有周期性得在问题发生时抓抓早了看不到东西。5.3 一个端到端的排查过程说一个我印象比较深的场景细节做了脱敏和简化。某订单处理作业逻辑是 Kafka 进、几条keyBy、最后写外部存储。现象是吞吐从稳定期的 8 万条/秒掉到 3 万延迟从 50ms 涨到 2 秒但 CPU 使用率只有 40%堆内存也正常。第一步看背压指标发现链路上倒数第二个算子backPressuredTimeMsPerSecond长期在 700 以上最后一个算子Sink的busyTimeMsPerSecond接近 900idleTimeMsPerSecond接近 0。这说明 Sink 一直在忙但忙得没效率。第二步看网络指标outPoolUsage在 Sink 的前一个算子那里长期 0.9 以上floatingBuffersUsage高exclusiveBuffersUsage低。到这里可以确认是 Sink 消费不过来导致的背压而且通道之间的流量分布不均。第三步上火焰图Sink 的调用栈里有超过 60% 的宽度集中在同步 HTTP 调用上。到这里原因就清楚了——Sink 用的是同步批写批大小 100 条每批一次网络往返。上游吞吐一上来每次往返的时间没变但批次数变多整体就卡住了。后续的处理是把同步写改成异步批量提交同时给 Sink 单独提高了并行度。改完吞吐回到 7 万以上outPoolUsage降到 0.3 左右。整个过程里最有价值的信息其实来自第二步的网络缓冲指标——它明确指向了“下游消费能力”这个方向让后面找火焰图有了目标而不是满屏乱翻。5.4 四种常见误判把 GC 停顿当成背压。两者都会让busyTime看起来正常但吞吐下降。区别是 GC 停顿在指标上表现为周期性的“断崖”而且GarbageCollector.*.Time会有同步的尖峰。如果背压指标平稳、GC 时间有规律的尖峰先调 GC 参数。把下游限流当成上游瓶颈。外部系统做了限流的时候Sink 会一直重试看起来像是 Sink 自己慢。这时候要看 Sink 的失败率指标和重试次数如果重试在涨那就是对端在拒绝扩并行度反而会加剧问题。把 Checkpoint 影响当成业务抖动。每次 Checkpoint 都让吞吐掉一截在曲线上看是周期性的锯齿。判断方法是把lastCheckpointDuration的时间点和吞吐下降的时间点对齐如果严丝合缝那就是同步阶段的阻塞或者状态太大导致的写放大。把指标缺失当成作业健康。这个坑我在容器环境下踩过好几次Prometheus 抓不到数据Grafana 上是一条平线看起来“很平稳”实际上是采集链路断了。所以一定要配一个“指标缺失”的告警up{jobflink} 0或者absent(flink_jobmanager_job_lastCheckpointDuration)。6. 常见问题速查与踩坑记录6.1 指标缺失先查这几处指标不出来按这个顺序查能解决八成问题。配置有没有生效。Flink 读的是conf/flink-conf.yaml容器环境下是FLINK_PROPERTIES环境变量或者--config传的目录。改了配置文件但没重启进程配置不会自动重载这个低级错误我见过不止一次。端口通不通。在 Flink 所在的机器上先curl localhost:9249/metrics如果不通就别去看 Prometheus 了。通了再到 Prometheus 那台机器上curl如果这边不通就是网络策略的问题。过滤器有没有把指标全排掉。filter.includes写了一个不存在的指标前缀结果就是一条数据都没有。这个参数不会报错只会静默地什么都不给。指标名对不对。版本之间名字会变比如背压指标在 1.13 前后完全是两套命名。写 PromQL 之前直接去/metrics输出里grep一下比查文档快。6.2 指标看着对但用不了语义层面的坑有些问题不是“没有数据”而是“数据不是你以为的那个意思”。numRecordsIn是 Counter直接画出来是一条单调上升的线没有任何诊断价值。必须rate()或者increase()。numRecordsInPerSecond是 Meter它已经在 Flink 侧做了平滑。再套一层rate()会得到完全错误的数值——对已经算好的速率再求变化率结果没有物理意义。lastCheckpointDuration是 Gauge反映的是“最近一次”不是平均值。想看趋势得自己在外面存历史或者用avg_over_time()做窗口聚合。用 Gauge 判断“是否在恶化”很容易被单次异常值误导。Histogram 类指标在 Prometheus 里会展开成_count、_sum和若干_bucket序列算平均值要用_sum / _count算分位数要用histogram_quantile()套在_bucket上。直接把_count当次数用没问题但当延迟看就错了。6.3 监控自身的开销这一条很少被提起但规模上去之后是真实存在的成本。指标采集本身要遍历所有 MetricGroup、做字符串拼接、序列化。TaskManager 数量上百、指标全开的时候这部分开销能占到几个百分点表现为“什么都不改加了个监控之后吞吐掉了 2%”。控制手段有三个调大metrics.fetcher.update-interval比如从 10 秒到 30 秒减少 Repoter 的指标数量用过滤器以及关闭不必要的高频指标。taskmanager.network.detailed-metrics就是个典型——排查期打开平时关掉。还有一个隐性成本在 Prometheus 那边。序列数上去之后Prometheus 的内存和查询延迟都会涨Grafana 打开一个看板要等十几秒。这种体验上的退化最终会导致没人看监控那监控就白做了。6.4 速查表现象优先查的指标常见原因吞吐下降、CPU 不高backPressuredTimeMsPerSecond、outPoolUsage下游消费慢、同步 IO 阻塞Checkpoint 频繁超时lastCheckpointDuration、lastCheckpointSize状态过大、同步阶段阻塞、后端写入慢部分 subtask 特别忙各 subtask 的numRecordsInPerSecond分布数据倾斜、key 分布不均延迟抖动、周期性变慢Status.JVM.GarbageCollector.*.TimeGC 停顿、堆内存不足内存持续上涨Heap_Used斜率、Threads.Count状态泄漏、线程泄漏、连接池未关闭指标完全空白up{jobflink}、Reporter 端口配置未生效、网络策略、过滤器写错作业重启后指标断档Scope 里的host、tm_id容器重建导致序列名变化我在实际使用中最大的体会是这套监控体系真正的门槛不在配置而在“知道该看哪个”。配置部分照抄一遍就能跑但同样是面对backPressuredTimeMsPerSecond这条曲线有经验的人三分钟就能判断往下游查还是往上排查没经验的人会盯着它看一天。所以每次排完故障我都习惯把当时的关键指标和判断依据记下来攒上十几条之后会发现绝大多数问题都落在上面那张表的范围里剩下的才是真正的新问题。

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

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

免费获取报价 →
↑