线上作业的 Checkpoint 突然从 30 秒涨到 4 分钟第一反应肯定是翻日志可日志里干干净净连个 WARN 都没有。这种时候唯一能救场的就是 Flink 的监控体系——Metrics、Reporter、反压指标、Checkpoint 统计这套东西平时不起眼出事时是唯一的信息来源。《Flink 官方文档 - Operations - Monitoring》这一页看着像配置项清单实际上它是整个可观测性的地基metric 类型怎么分层、scope 怎么拼、reporter 怎么接、哪些指标能翻译成排障语言。这篇笔记我按自己实际搭监控链路的顺序重新梳理了一遍把文档里没写透的取舍和踩过的坑都补上适合正在给 Flink 作业搭监控、或者已经搭了但看板一直不准的同学对照着看。1. 监控之前先认清坐标系Metrics 类型、Scope 层级与命名规则很多人接监控的第一步是抄一份 Prometheus 配置贴进 flink-conf然后打开页面发现指标一大堆但根本对不上号。问题不在 reporter在于没搞清楚 Flink 的指标是用什么规则命名的。这一层不理解后面所有的看板、告警、排障都是在猜。1.1 四种 Metric 类型与它们各自适合承载什么Flink 的 Metric 接口下面挂了几个实现官方文档里列得比较简略但对排障来说差别巨大。Counter是单调递增的计数器典型代表是numRecordsIn、numRecordsOut、numberOfCompletedCheckpoints。它的特点是只增不减除非作业重启归零。这一点在实际用的时候必须记住如果你在看板上直接画 Counter 的绝对值得到的是一条永远向上的斜线毫无意义你要么画它的导数Prometheus 里用rate()要么直接看它对应的 Meter 版本。Gauge是瞬时值最灵活也最容易被滥用。lastCheckpointDuration、currentInputWatermark、堆内存占用这些都属于 Gauge。Gauge 的问题是它只在被采集的那一刻有意义两次采集之间的抖动完全丢失。所以判断一个 Gauge 是否异常绝对不能看单点必须看持续性——我的习惯是任何基于 Gauge 的告警都加一个持续 N 分钟的条件单点超阈值的告警基本都是噪音。Meter是速率统计Flink 内部用它算每秒记录数暴露出来的是numRecordsInPerSecond这类指标。它内部维护了多个时间窗口的滑动平均好处是平滑坏处是迟钝——流量真的掉零之后Meter 还会带着历史均值缓慢下滑好几分钟所以判断作业是否卡死不要用 Meter用 Counter 的rate()更干脆。Histogram是分布统计端到端延迟、状态大小这类需要看长尾的场景才用它。Histogram 的坑最多后面 6.3 会专门讲它在 Prometheus 侧的形态问题。一句话总结选型想统计总量用 Counter想知道此刻状态用 Gauge想看速度用 Meter想看长尾和分位数用 Histogram。选错了类型看板画出来就是错的而且错得很隐蔽。1.2 Scope 是怎么一层层拼出来的为什么它会决定你查不查得到数Flink 的每个指标都带一个 ScopeScope 是一个用分隔符连接的层级字符串。默认情况下TaskManager 上的任务级指标大致长这样host.taskmanager.tm_id.job_name.task_name.subtask_index.numRecordsInJobManager 侧则是host.jobmanager.job_id...这种形态。这套拼接规则由一组配置项控制metrics.scope.jm、metrics.scope.tm、metrics.scope.task、metrics.scope.operator分别定义四个层级的模板模板里可以用${Variable}这样的占位符。理解这一点之后很多玄学问题就有解释了。比如为什么同一个numRecordsIn在存储里会有成百上千条时间序列因为每个 subtask 都有一条并行度 200 的作业就是 200 条。再比如为什么重构时给算子改了个名字历史曲线就断成两截因为 scope 里带的是task_name/operator_name名字一变时间序列的名字就变了存储那边视作两条全新的序列。注意scope 里拼接的是算子的名字不是uid()。给算子设置 uid 能保证状态恢复时的兼容性但救不了指标断链。想让看板稳定要么提前把算子命名规范定死要么在看板层做一层名字映射。另外还有一个容易被忽略的点metrics.scope.task和metrics.scope.operator是两个独立的模板同一个逻辑位置可能同时产出两套指标一套以算子链里的 task 名命名一套以 operator 名命名。并行度上来之后指标量直接翻倍这也是后面讲基数治理时要处理的对象。1.3 Variables 与默认过滤指标消失的第一类原因配好 reporter 之后最常见的抱怨是文档里说有这个指标我这里查不到。绝大多数情况不是 bug是被默认配置过滤掉了。第一个高频例子是主机层面的 CPU、内存指标。这类系统资源指标默认是关的需要显式打开对应开关否则你只能看到 JVM 堆看不到容器实际吃了多少内存误判率极高。第二个例子是 Job 状态类的 Gauge它们受一个单独开关控制某些版本默认只暴露部分状态想让所有状态都参与告警就得手动调整。第三个例子更隐蔽scope 的变量集是可以扩展的。部分版本支持往 scope 里注入额外变量比如从系统属性里取值这样你能给不同集群、不同环境打上标签。但键名在不同大版本之间有过调整我不建议背直接用的时候去 Configuration 页面搜scope关键词以你手上那个版本的说明为准五分钟的事。还有一类是 reporter 级别的过滤。较新版本给 reporter 提供了 include / exclude 形式的过滤能力可以按指标名前缀裁剪老版本没有这个能力只能在采集端或者聚合层做减法。这一条直接决定了你的监控系统会不会在半年后被指标量压垮。2. Reporter 选型的真实取舍JMX、Slf4j 与 Prometheus 的边界文档里列了一长串 reporter看着像多选题实际上每个都有明确的使用场景。选错了不是不能跑是会在关键时刻掉链子。2.1 几种常见 Reporter 的能力对照Reporter数据流向适合场景明显短板JMX进程内 MBean单机本地排查、临时看某个指标远程连接配置繁琐无长期存储Slf4j / 输出到日志写日志文件快速验证某个指标是否存在高频写入污染日志无法聚合PrometheusHTTP 端点被拉取生产环境长期监控需要处理端口分配与抓取配置时序数据库类推送型主动上报没有抓取通道的网络环境需要额外维护写入链路我自己的习惯是本地起单机调试时用 JMX因为它零依赖JConsole 连上去就能看一旦进入联调环境立刻换成 Prometheus 形态哪怕只是单机因为能被curl出来的指标和必须开图形客户端才能看的指标在排障效率上差着一个数量级。凌晨三点出问题的时候你不会想打开 JConsole。JMX 还有一个隐形成本远程访问需要开一堆启动参数而且 RMI 会占用不止一个端口。在容器化环境里这意味着要额外开放端口、处理主机名解析稍有不慎就连不上。这不是不能做是性价比太低。2.2 Prometheus Reporter 的配置项逐条拆解生产上主流方案是让 Flink 暴露一个 HTTP 端点由采集侧定期拉取。配置大致长这样# 新版用 factory.class老版是 class升级时最容易在这里翻车 metrics.reporter.prom.factory.class: org.apache.flink.metrics.prometheus.PrometheusReporterFactory # 端口支持区间写法多实例同机部署时必须用区间 metrics.reporter.prom.port: 9249-9269 # 采集间隔默认 10s不要无脑调小 metrics.reporter.prom.interval: 10 s逐条说下为什么这么配。port用区间而不是固定值是踩过大坑的。同一台机器上跑多个 TaskManager 是常态如果都配 9249第二个进程的 reporter 会因为端口被占而启动失败而这个失败往往只打一条不显眼的日志等你发现某个 TM 完全没指标的时候作业可能已经跑了半天。用区间写法让每个实例自己找空位虽然端口不固定会给抓取配置带来麻烦但至少不会静默丢数据。interval的默认 10 秒是有道理的。这个值决定了两件事一是采集的精度二是指标系统自身的开销。把间隔调到 1 秒指标量不变但写入频率翻十倍在高并行度作业上会让 reporter 线程吃掉可观的 CPU。我一般保持 10 秒只有在定点排查某个瞬时问题时才临时调到 2 秒排查完立刻改回去。如果你的版本支持标签提取能力可以把 scope 里的某一级变量提成一个稳定的 label同时充当过滤条件。这个功能很好用但要小心正则写错了不是少几个标签而是那批指标整条消失。配完之后一定要打开/metrics端点确认输出里有你想要的东西别跳过这一步。另一个经常被忽略的关键点是factory.class和老的class两种写法不能混用新版如果不写factory.class会直接报找不到 reporter。升级大版本时配置文件里那些看着还能用的旧键未必还生效。2.3 插件 jar 放哪里、怎么验证真的生效了reporter 的实现类不是默认加载的得把对应的 jar 放到能被加载的位置。发行包里通常在opt/目录下躺着一个需要你手动挪到plugins/metrics/或者lib/。两种方式都能跑我更倾向plugins/类加载器隔离更干净不会和用户代码里的依赖打架。验证是否生效有个三步法我每次都按这个顺序走启动后直接curl http://tm_host:9249/metrics有输出说明端点起来了如果 404 或者连接拒绝去 TaskManager 日志里搜 report 相关的 WARN通常是类没找到端点通了但输出为空说明 reporter 起来了但没匹配到指标回头检查过滤配置和 scope 模板。第三步最容易被跳过。端点返回 200 但内容为空很多人以为是指标还没产生实际是过滤条件把全部指标干掉了。等了一晚上才发现是配置问题这种教训一次就够了。3. 把官方指标清单翻译成排障语言官方文档给了完整的指标清单但清单本身不会告诉你这个数变大意味着什么。这一节是我自己踩坑之后整理出来的翻译表按排障场景组织。3.1 Checkpoint 三兄弟次数、时长与大小Checkpoint 相关指标永远是第一优先级。核心看三个维度成功与失败的次数比、最近一次耗时、最近一次状态大小。判断逻辑是这样的如果lastCheckpointDuration持续上涨但lastCheckpointSize基本稳定八成不是状态变大而是 barrier 在链路里走不快——要么是某个算子反压严重barrier 排在数据后面进不去要么是状态后端写入慢比如远端存储抖动。这两种原因的处理方式完全相反一个要扩容或优化算子一个要找存储侧。所以必须两个指标一起看。如果lastCheckpointSize出现跳变式增长那就要怀疑状态本身出了问题key 里带了时间戳导致状态无限累积、State TTL 配了但没真正生效、某个ListState只增不清。这类问题不看趋势图是发现不了的因为作业本身可能跑得好好的只是状态在慢慢吃掉磁盘和网络带宽。还有一个很实用的组合失败次数除以总次数。单次失败在长周期作业里是常态网络抖动、存储超时但如果这个比值在爬升说明是系统性问题而不是偶发。我一般会把连续失败次数作为告警条件比总失败数大于零精确得多。观察到的现象首选指标交叉验证常见根因耗时上涨、大小稳定最近一次耗时反压时长占比barrier 被反压阻塞或存储写入慢大小跳变状态大小各算子状态大小分布状态无界增长、TTL 失效失败率爬升失败/总次数比异常日志存储侧不稳定、超时配置过紧恢复后耗时异常恢复时间戳恢复后的首个小 checkpoint 耗时冷启动、状态反序列化开销恢复之后的第一两个 checkpoint 通常偏慢这是状态预热和 JIT 还没跑热的正常现象不要在这个窗口里报警否则每次重启都会误报一次。3.2 反压的三种观测口径与各自的局限反压是 Flink 排障里最容易看错的东西。至少有三套口径在看同一件事每套的结论都可能不一样。第一套是任务级别的缓冲池占用率也就是输入、输出两个池子的使用率。判断逻辑很直观某个任务的输入池占用率高说明它的输入队列堆满了它自己处理不过来瓶颈在它输出池占用率高说明它的输出发不出去瓶颈在下游。这套指标的好处是能快速定位瓶颈在哪个算子的哪一侧缺点是它只是水位看不出持续时间。第二套是忙闲统计也就是每秒内被反压的时间、真正忙的时间、空闲的时间。这三个值加起来约等于 1000 毫秒每个采集周期所以你可以直接用比例来判断。被反压时间占比长期高于 50%基本可以确定这个算子是受害者而不是元凶——它想干活但下游不给机会。反过来如果一个算子忙时间接近满格、反压时间很低那它很可能就是瓶颈本身。第三套是线程栈采样老版本的 Web UI 里那个反压页面就是干这个的。它在若干次采样里看任务线程停在哪个位置停在缓冲区写入上的比例高就判定为反压。这套方式的优点是能直接看到卡在哪个方法缺点是需要采样时间对瞬时抖动不敏感而且在高版本里这套机制已经被前两套替代。注意这三套口径的采样周期和计算方式都不一样同一时刻得出的结论可能互相矛盾。我的做法是固定用第二套做主判断第一套做交叉验证第三套只在需要定位到具体代码位置时才用。还有一个细节忙闲统计在旧版本里的指标名带accumulated前缀新版本去掉了这个前缀语义也做了调整。如果你的看板是跨版本复用的这一处一定会出问题后面 5.2 会专门讲。3.3 网络缓冲与 detailed metrics 的开启代价Flink 的网络栈内部是有缓冲池的缓冲池的容量、每个通道分到多少都会直接影响上面那套输入输出池占用率的基线。同样是 0.6 的占用率在缓冲充足和缓冲紧张两种配置下代表的风险完全不同。所以看这类指标时必须知道当前作业的缓冲配置不能拿一个绝对值横着比所有作业。有些版本提供了网络层的详细指标开关打开之后能看到每个通道级别的水位、排队长度等细粒度信息。这在定位到底哪个下游通道堵了时非常有用但代价很大指标数量会从几百条暴涨到几万条甚至更多采集和存储压力陡增指标系统自己可能先扛不住。我自己的规则是这个开关默认关只在明确怀疑网络层有问题、并且能限定时间窗口的时候打开抓到数据立刻关掉。把它当成长开的监控项是在给自己挖坑。3.4 端到端延迟与水位滞后延迟指标是通过在数据流里打标记记录实现的默认是关闭的。打开需要配两个东西采集间隔和粒度。# 每隔 60 秒打一批标记记录0 表示关闭 metrics.latency.interval: 60000 # 粒度single / operator / subtask粒度越细指标越多 metrics.latency.granularity: operator这里有个很多人没意识到的点标记记录是沿着某一条具体路径走的如果中间有按 key 分区的操作它只会走其中一个分支。所以延迟指标反映的是抽样路径的耗时不是全量数据的真实分布。在数据倾斜严重的作业里这个采样值可能比真实的最坏情况乐观得多。用它做趋势判断可以用它做SLA 承诺要谨慎。粒度选择上subtask级别会把指标数量乘以并行度除非你在排查某个具体 sub-task 的长尾否则用operator级别就够了。水位滞后是另一个经常被忽略的信号。把当前的输入水位和系统时间做差得到的就是数据落后了多少。这个差值是判断作业是否在健康推进的最直接证据——比吞吐量更可靠因为吞吐量可能因为数据源本身没数据而降下来水位滞后则直接告诉你处理进度。做法上把水位作为一个 Gauge 采出来在看板里用采集时刻减去它即可。源端如果能看到输出水位还能进一步判断是源头就没数据还是数据进来了但没推进。4. 绕开 Metrics 的第二条通路REST API 与 Web UIMetrics 是给长期监控用的REST API 是给我现在就要知道用的。两者不是替代关系是互补关系。有些信息只有 REST 接口能给比如 checkpoint 的历史列表、异常堆栈、算子的拓扑结构。4.1 常用端点与调用姿势端点拿什么典型用途/overview集群概况快速确认 TM 数量、槽位使用/jobs/overview所有作业的简要状态巡检脚本的第一站/jobs/:jobid单个作业详情与算子列表拿到 vertex id 做后续查询/jobs/:jobid/metrics该作业可用的指标名清单升级前后做指标名 diff/jobs/:jobid/checkpoints历史 checkpoint 统计定位是哪一次开始变慢/jobs/:jobid/checkpoints/configcheckpoint 配置确认间隔、超时、模式/jobs/:jobid/exceptions异常与根因重启原因溯源/jobs/:jobid/vertices/:vid/subtasks/:idx/metrics单 subtask 指标定位倾斜其中/jobs/:jobid/metrics不带参数时会返回可用指标名列表这个能力在做版本升级评估时特别值钱——升级前 dump 一份升级后 dump 一份直接 diff哪些指标改名了、哪些没了一目了然比翻 changelog 快得多。/jobs/:jobid/checkpoints返回的是结构化的历史记录包含已完成、失败、进行中的次数和最近几次的详细数据。想回答是从哪一次开始变慢的这种问题这个接口比时序数据库更好用因为它保留了每一次的记录。4.2 用 REST 做巡检脚本长期跑着的作业靠人眼看板是不现实的。写个几十行的脚本每天定时跑一遍比任何看板都可靠。#!/usr/bin/env bash JMhttp://jm-host:8081 # 1. 列出所有非运行态作业 curl -s $JM/jobs/overview \ | jq -r .jobs[] | select(.state ! RUNNING) | [.jid, .name, .state] | tsv # 2. 检查某个作业最近的 checkpoint 情况 JOBjob-id curl -s $JM/jobs/$JOB/checkpoints \ | jq {completed: .counts.completed, failed: .counts.failed, latest_duration: .latest.completed.end_to_end_duration, latest_size: .latest.completed.state_size}第一段脚本回答有没有作业挂了第二段回答checkpoint 还正常吗。两段拼起来就是最小可用的巡检。真要落地的话我会再加两条一是对比最近三次 checkpoint 耗时的趋势二是检查失败计数在最近一小时内有没有增长。趋势比绝对值重要这一点在巡检脚本里同样成立。这类脚本还有个额外好处它强迫你把什么样的状态算异常想清楚。写不出来的判断条件说明你对业务还不够了解这时候上告警系统只会制造噪音。4.3 火焰图定位哪个算子慢的最后一步当指标告诉你某个算子反压严重、忙时间接近满格接下来的问题是它到底在干什么。这时候火焰图是唯一能给出答案的工具。相关配置项大致是采样次数、采样间隔、栈深度这几个开关在部分版本里默认关闭需要显式打开。请求某个算子的火焰图端点之后服务端会连续采样若干次线程栈然后按调用链聚合成一棵树返回。读火焰图的方法横向宽度代表该栈帧出现的比例越宽说明线程停在这里的时间越多。你不能指望它告诉你这里有 bug但它能明确区分几种情况卡在序列化上、卡在用户代码的循环里、卡在等待网络写入、卡在 GC。这四类的处理方向完全不同。采样期间是有性能损耗的别在业务高峰期对一个高并行度作业全量抓火焰图。我的习惯是先在指标里锁定一两个可疑的 sub-task只对它们抓采样次数控制在百次以内抓完立刻关闭。5. 告警规则设计与升级过程中的指标改名监控搭起来不等于有用一堆没有阈值的曲线只是装饰。真正产生价值的是告警规则而告警规则最大的敌人是版本升级。5.1 一份能落地的告警规则清单我自己的规则集大概是这样几条按优先级排告警项判断思路阈值建议备注作业不在运行态状态查询或状态 Gauge持续 1 分钟最高优先级先打电话Checkpoint 连续失败失败计数增量连续 3 次比失败数大于零精确Checkpoint 耗时逼近间隔耗时 / 间隔大于 0.6 倍留出余量别等超时才发现持续反压反压时间占比大于 500ms/秒持续 5 分钟短时抖动不报吞吐归零输入速率等于 0持续 3 分钟需排除源头无数据的情况水位停滞当前水位与系统时间差持续增长 10 分钟比吞吐更可靠TM 数量不足实例数小于期望值防止静默掉节点GC 时间占比过高GC 时间增量大于 20%内存压力的早期信号老年代占用高堆内存使用率大于 85%持续 10 分钟配合 GC 指标一起看阈值不是抄来的是根据作业的 checkpoint 间隔、SLA 要求、历史波动范围算出来的。比如 checkpoint 间隔 3 分钟那耗时告警的阈值就该设在 100 到 120 秒之间而不是笼统的5 分钟。这一条我吃过亏早期用了一个拍脑袋的绝对阈值结果大促期间每次流量上升都报报了两周之后所有人都开始忽略告警最后真出事那天没人看。还有一个经验告警要分级。作业挂了、连续 checkpoint 失败属于 P0立刻通知反压持续、GC 偏高属于 P1工作时间处理指标轻微漂移属于 P2进日报就行。全都设成 P0 的结果等于全都没有。5.2 版本升级导致的指标重命名这是整个监控体系里最容易在无声中失效的一环。指标名变了看板不会报错只是曲线变成一条平线或者直接消失而告警规则因为查不到数据也安静下来——这不是没问题是监控瞎了。我印象最深的几处变化第一反压相关指标从带accumulated前缀的版本改成了不带前缀的版本同时语义从累计值调整成了每秒窗口内的占比。名字变了、单位也变了直接平移会把阈值全部搞错。第二早期那个布尔型的是否反压Gauge 在新版本里不再是推荐做法甚至被移除。有人的看板还在查它查到的永远是空。第三对齐相关的指标在非对齐检查点普及之后语义发生了变化部分指标被移除或替换。如果你的作业开启了对齐优化这些指标的存在与否会直接影响你对 checkpoint 慢的判断路径。第四reporter 的配置键从class迁移到factory.class。这一条在升级时几乎必然要改不改的话 reporter 直接不启动而且报错信息不够直白。应对办法没什么捷径升级前用前面提到的指标名清单接口做一次全量 dump和升级后做 diff把消失和改名的指标逐条映射到新的看板表达式里。这个过程枯燥但比出事之后发现没数据要划算得多。5.3 在采集层做聚合别把复杂度留给告警表达式指标名字很长、维度很多直接在告警表达式里写sum by (...) (rate(flink_taskmanager_job_task_operator_numRecordsIn[5m]))这种写一次两次还行写到第十次就开始出错而且改一个地方要改十处。更合理的做法是在采集侧定义一层中间指标把原始的长名字收敛成业务语义的短名字比如作业级别的每分钟输入记录数。下游的看板和告警都只引用这层中间指标。这样做的价值不只是好看当底层指标改名时你只需要改一处映射上面所有引用都不用动。代价是多一层维护而且中间指标的数量要控制住别把原始指标的所有维度原样带上来——那就等于没做聚合。6. 采样开销、标签基数与我踩过的坑这一节是纯经验都是那种文档里不会写、但配完就能感觉到的东西。6.1 采样周期与精度、开销的三角关系默认 10 秒的采集间隔是经过权衡的。想更精确代价是指标系统自身的开销线性增长。这个开销体现在三个地方应用侧的指标收集与序列化、抓取侧的连接与解析、存储侧的写入与压缩。高并行度作业上指标收集不是免费的。每个 subtask 都在往共享的指标注册表里写采集时要把整张表遍历一遍并格式化输出。并行度上千的时候这个遍历本身的耗时就不能忽略了。所以我的建议是分档核心大盘保持 10 到 15 秒排查期临时调到 2 到 5 秒并且用过滤把范围缩小到可疑的算子排查结束立刻恢复。不要图省事全局改成 1 秒那是给自己找麻烦。6.2 标签基数失控是监控系统崩掉的头号原因基数这个词听起来抽象换个说法时间序列的数量。一个numRecordsIn指标如果 scope 里带了主机名、TaskManager 标识、作业名、算子名、子任务编号五级那么时间序列的数量就是这些维度取值的笛卡尔积。一个中等规模的作业200 个算子、200 并行度、3 个 TaskManager光这一个指标就能产生 12 万条序列。再乘上几十个指标百万级很轻松。而每条序列在存储里都是独立的索引和块写入放大非常严重。给各项指标做减法比调整保留策略更有效。治理手段按效果排序采集端过滤只保留真正会看的指标去掉 subtask 维度的原始序列在采集层就按算子聚合掉需要定位倾斜时再临时打开缩短高基数指标的保留时间原始数据存 7 天聚合后的存 90 天关掉不必要的详细指标开关尤其是网络层的那个。第二条最有效也最容易忽略。绝大多数看板其实不需要 sub-task 粒度它们要的是这个算子整体怎么样。等到真要排查倾斜再从 REST 接口按需查单个 sub-task比常年存着几百万条序列划算得多。6.3 几个具体踩坑记录坑一jar 没放对位置端点 404日志里只有一条不起眼的 WARN。那次排查了两个小时一直在怀疑网络和端口最后发现 reporter 的类压根没加载。教训是配完 reporter 的第一件事永远是curl那个端口而不是去看板里找数据。坑二多个实例共用固定端口静默丢了一个节点的全部指标。端口被占之后 reporter 启动失败但作业本身跑得好好的所以完全没人发现。直到某天需要看那个节点的堆内存才发现查不到。后来统一改成端口区间写法才算根治。坑三算子改名之后看板断成两截。重构时觉得名字改得更语义化了是好事结果所有以算子名做维度的曲线全部重来历史的基线数据全废。后来定了个规矩算子的对外名称当成接口来管理改之前必须同步改看板映射。坑四打开了延迟统计却发现用不了分位数查询。原因是 Histogram 类型在 Prometheus 侧的暴露形式是摊平成一串带后缀的数值平均值、各个分位点等而不是原生的 bucket 结构所以常规的分位数聚合函数直接套上去会得到空结果。要么直接用已经算好的分位点数值要么自己在应用侧做统计。这个差异文档里提得比较隐蔽配之前最好先去端点上确认一下实际输出长什么样。坑五Job 级和 Task 级的同名指标搞混。状态大小这个指标在两个层级都有但含义完全不同一个是整个作业的状态总量一个是单个任务的状态量。看板上把两者画在一起或者告警引用了错误的层级得出的结论会完全相反。我现在配置任何指标之前都会先在指标名清单里搜一遍确认它到底挂在哪个 scope 下。坑六JMX 远程连接在容器环境里连不上。需要开一堆启动参数还涉及两个端口的映射和主机名解析容器里主机名往往是随机串RMI 回连直接失败。折腾了半天之后彻底放弃本地用 JConsole 看单机集群一律走 HTTP 端点。6.4 我现在的默认配置长什么样经过几轮折腾稳定下来的配置大概是这个形态生产环境只开 Prometheus reporter端口用区间采集间隔 10 秒延迟统计开着但粒度用 operator 级别间隔 60 秒网络详细指标关闭系统资源指标打开因为容器内存的限制和 JVM 堆的配置经常对不上没有这层数据根本判断不了是不是被容器 OOM 掉了火焰图开关打开但只在需要时请求。告警侧只保留五条 P0作业不在运行态、连续 checkpoint 失败、checkpoint 耗时逼近间隔、TM 数量不足、吞吐归零且水位停滞。其余全部进日报。这套配置撑过了几次流量高峰中间只调整过阈值没再动过结构。最后分享一个小技巧把指标名清单的 dump 做成定时任务每周存一份带日期的快照。哪天发现某个曲线莫名其妙变平了先把这周的清单和上周的 diff 一下十有八九能立刻定位到是哪个指标被改了名或者被过滤掉了比从头查快得多。