1. 存算分离到底动了监控的哪块蛋糕先说一个我自己的判断存算分离不是简单的“存储换个地方”它是把过去几十年大数据集群默认的“本地计算 本地落盘”铁律给拆掉了。拆完之后整个集群的故障模型、资源模型、成本模型全部变了。你要是还拿以前那套监控思路去盯存算分离集群大概率会踩进几个很隐蔽的坑里。1.1 很多人对存算分离的理解还停留在“存储上云”我接触过的团队里一说存算分离第一反应就是“HDFS换成了对象存储”或者“数据都放到云上去”。这话对了一半。真正落地后你会发现存算分离是一个分层解耦的架构计算层跑 Spark、Flink、Trino、Presto 这些引擎的计算节点它是无状态的可以随时扩缩容节点挂了不影响数据。存储层HDFS、Ceph、MinIO、云对象存储或者湖仓一体的 Iceberg、Hudi 这种表格式存储数据在这里长期驻留。元数据层Hive Metastore、内嵌 catalog、各类 schema registry管理着表结构、分区、权限这些“数据的数据”。调度与接入层YARN、K8s 之上的资源调度以及各类查询网关。计算和存储分开之后最直观的变化就是数据本地性data locality没了。以前一个 Spark 任务读 HDFS 的 block调度器会优先把 task 分配到数据所在节点减少网络传输。现在数据在远端对象存储或者独立的存储集群里计算节点只能通过网络去拉数据。所有数据访问都变成了一次网络 IO网络成了整个集群最大的瓶颈也成了监控里最不能忽视的指标。1.2 三个“以前没事现在出事”的监控盲区我参与过好几套存算分离集群的监控改造最常见的盲区有三个都是因为沿用老思路导致的第一看 CPU 和内存但不看网络带宽。传统 HDFS 集群里任务是本地读盘为主网络压力集中在 shuffle 阶段。存算分离之后任务启动、中间结果刷盘、远端数据读取全要走网络。我一个客户当时只盯着 YARN 队列的使用率结果量大了之后任务全部卡在数据读取阶段队列空闲得像没跑任务一样问题却迟迟定位不到。后来加上了节点出入流量、重传率和 TCP 连接数指标一秒定位。第二存储侧只盯容量不盯 IO 延迟分位数。对象存储这类系统它的延迟不是恒定的对桶内前缀的请求多了会产生分区热点延迟抖动非常明显。你在监控面板上看平均延迟永远是绿的但它实际的 P99 可能已经从 20ms 飙到了 2 秒。不建分位数监控这种“平缓的恶化”根本看不见。第三任务失败重试归因不清。存算分离之后计算任务失败的原因往往不在计算侧。比如 Spark 拉取远端数据时偶发超时这个错误会以 Executor 丢失、stage 重试的形式表现出来。你要是只看计算任务日志会觉得是节点不稳定其实根子在存储侧那一两秒的连接抖动。没有链路级的指标关联排障就只能靠猜。1.3 监控对象从“集群”变成了“三张网”这是我在一套监控体系设计里自己总结的框架传统监控盯的是节点和进程存算分离必须盯“三张网”。数据网络计算节点和存储节点之间的数据通道核心指标是带宽、延迟、重传、丢包。调度网络任务和资源之间的耦合关系。存算分离下计算集群可以动态扩缩容你需要看队列当前容量、等待中的任务数、调度延迟而不是单纯看一个集群总负载。元数据网络所有引擎都要先访问元数据服务这个服务的 QPS、RPC 延迟、锁竞争、缓存命中率直接决定了任务能不能快速启动。后面我讲的指标分层、告警和排障案例都是基于“三张网”这个框架展开的。你记住一点存算分离集群里几乎没有一个故障是“单点原因”造成的全是网络链路、存储抖动、调度策略交织出来的结果所以监控体系的第一任务就是把这三张网各自的信号拆清楚。2. 监控指标分层计算层和存储层得分开建指标体系很多人上来就铺采集 Agent把 CPU、内存、磁盘、网络一股脑全部收上来然后堆一个大盘。这种“全家桶”式监控在存算分离架构下基本归零——指标太多等于没有指标故障时找信号的时间比恢复的时间还长。我的做法是分层建指标体系每一层只保留该层真正能反映问题的信号。2.1 计算层别再只盯 CPU任务级指标更重要计算层是用户直接感知性能的地方Spark 任务跑得慢、Flink 作业处理不过来这些都是计算层问题。但这层的指标和传统集群有个很大区别计算节点本身是无状态的节点挂了直接替换所以节点硬件指标CPU、内存反而要降级关注取而代之的是“任务生命周期指标”。我实际在用的计算层指标分三组资源使用组队列/组剩余 CPU、内存配额当前活跃任务数、等待任务数任务调度时延提交到启动的间隔计算节点实际分配率分配的核数 / 可用核数任务执行组Spark/Flink job 平均执行时长、P95 执行时长Shuffle 读写量大 shuffle 场景容易引发计算节点负载假象Executor GC 时间占比P99 GC 5% 就要关注各 stage 的输入数据量这个很关键数据量异常膨胀往往意味着上游存储了脏数据连接与拉取组计算节点到存储节点的活动连接数数据拉取失败率按存储节点维度拆分数据拉取平均耗时、P95 耗时我把第三组单独拎出来是因为存算分离下很多计算任务的“慢”本质是数据拉取慢但大多数人还是习惯性地去调计算参数调了半天没效果最后发现是网络或存储拖后腿。有了这组指标至少能把问题快速分流到正确的一侧。2.2 存储层容量、延迟、分区热点一个都不能少存储层的监控是整个体系里最容易被低估的。大家习惯了看容量使用率超过 80% 才报警但在存算分离架构里存储侧的延迟抖动比容量问题更容易引发雪崩。我一般把存储层指标拆成四块指标类别具体指标报警参考阈值说明容量桶/目录总容量、剩余容量、文件数、小文件数容量 85%小文件数 100万容量不够和元数据压力是两个维度的风险延迟平均读延迟、P95/P99 读延迟、写延迟P99 超过基线 2 倍只看平均延迟会掩盖分区热点带宽读带宽、写带宽、当前 IOPS带宽 70% 瓶颈线存算分离下带宽是最稀缺资源分区热度单前缀请求 QPS、热点分区数单前缀 QPS 持续 5000对象存储单前缀性能上限比很多人想象的低我特别想强调小文件数这个指标。存算分离之后计算引擎直接从对象存储读数据如果表下面堆了几十万个小文件任务启动要频繁发起请求元数据服务压力大数据拉取也慢。这种问题你在任务日志里看到的是“启动慢”但根因在存储侧的文件布局。没有小文件数这个指标根本没法主动预警。2.3 元数据层的隐性指标QPS、RPC 延迟、锁等待存算分离架构下元数据服务的地位比传统 HDFS 时代更高了。因为所有数据访问请求都要先过元数据服务它的性能直接影响任务启动速度和查询解析效率。我遇到过不止一次“排查半天以为计算引擎出了问题结果发现是 HMS 的数据库连接池打满”的情况。元数据层需要监控这些指标元数据服务 QPS请求量是否接近服务上限。RPC 平均延迟与 P99 延迟很多元数据服务是 Java 写的GC 一停顿 RPC 延迟就飙升。连接数与连接池占用率HMS 背的数据库连接池、Thrift 连接数。锁等待时间并发 DDL 导致的元数据锁竞争。缓存命中率HMS 的 partition 缓存、catalog 缓存命中率低了延迟会成倍增加。有一个很反直觉的经验元数据层的监控阈值应该比计算层和存储层更保守。因为元数据服务一旦抖动影响是扇形的所有引擎、所有任务都会一起变慢直接感受就是“集群整体卡死”。这种故障的恢复时间通常很长所以要更早预警。2.4 网络链路指标带宽、重传、连接数前面说了“三张网”对应到指标上就是网络层的核心监控项。存算分离集群对网络的要求和传统集群完全不同传统集群网络压力集中在 shuffle日常使用峰值有规律存算分离集群的数据读取是无时无刻不在进行的网络指标更像一个“生命线”随时可能逼近上限。我实际在用的网络层指标节点出入带宽区分内部流量和外部流量TCP 重传率0.5% 就需要排查连接建立失败率计算节点到存储节点的平均 RTT网络队列溢出丢包数这个指标能反映交换机或网卡是否过载这些指标采集本身不复杂但要注意一点不要只采集节点维度的平均值要按“计算节点 → 存储节点”这个方向拆。存算分离下网络流量是有方向性的比如上行读流量大、下行写流量小单独看某一个节点的总带宽可能看不出问题但拆开方向后就会发现读取方向已经打满了。3. 自动化告警策略从“拍脑袋阈值”到“动态基线 收敛抑制”指标建好之后下一个问题就是什么情况需要报警报警怎么发才能不把人吵死我见过太多监控项目死在“告警风暴”上——一天推几千条通知运维同学直接屏蔽掉整个群真出故障的时候反而没人看见。告警策略是存算分离集群自动化监控里最需要花心思设计的地方因为它的故障形态比传统集群复杂得多。3.1 静态阈值告警只适合“硬边界”指标先讲静态阈值。虽然很多人觉得静态阈值老土但在某些场景它依然是最可靠的方案因为它不依赖历史数据判断逻辑简单不容易误报。我保留静态阈值的指标只有三类容量类磁盘使用率、对象存储桶容量、元数据服务连接数上限。硬性资源节点 CPU 使用率 95%、内存使用率 95%、带宽 85%。可用性检查探活失败、进程退出、端口不可达。这些指标的特点是“到了阈值就是真的出了问题”不需要智能判断。而像任务执行时长、读写延迟这类随时间波动明显的指标简单定一个固定阈值很容易翻车白天业务高峰延迟自然高固定阈值报警太频繁凌晨低峰延迟轻微上涨可能已经预警了真正的故障但固定阈值还没到。所以凡是“跟业务节奏相关”的指标我建议一律走动态基线。3.2 动态基线告警怎么用 EWMA 和周期同比做智能判断动态基线的核心思想是不搞一个固定阈值而是基于历史数据为每个指标计算一个“当前时刻的期望区间”实际值超出期望区间的幅度达到一定程度才报警。这样白天高峰告警阈值自动提高凌晨低峰告警阈值自动降低。在实际工程里我实践过两种算法都不复杂EWMA指数加权移动平均对实时指标做平滑公式是EWMA_t α * value_t (1 - α) * EWMA_{t-1}α 一般取 0.1~0.3值越大对近期变化越敏感。然后用 EWMA 和实际值的差做判定。比如实际值超过EWMA n * 滚动标准差时触发告警。这个算法实现简单适合没有明显周期性的指标比如节点的生成错误日志数量。周期同比季节性的环比大数据集群普遍有“白天忙、晚上闲、周末更闲”的规律所以按周期对比更准。做法是按天/周存储指标的历史序列用“当前时刻的值”和“过去 7 天同一时刻的值”做对比计算偏差率(当前值 - 历史中位数) / 历史标准差超过设定倍数就告警。这个思路很像“我们和上周同一天同一时间比”能天然消除白天晚上的周期性波动也避免了周末误报。我实际使用下来周期同比在存算分离集群里效果很好因为这个架构下的指标波动主要来自业务负载变化而业务负载的周期性非常强。提示动态基线不是一上来就全量适配要有一个“学习期”。我习惯先把指标接入采集跑 7~14 天的数据确认数据完整后再开启基线告警。如果数据缺口太大基线会算出一个失真区间反而疯狂误报。3.3 告警收敛与抑制避免“故障放大效应”存算分离集群有个很讨厌的特性一个底层问题会被多层级放大。比如对象存储某个分区出现热点直接的影响是存储侧延迟上升然后计算层的任务拉取数据变慢Spark 任务时长增加再往上报用户查询界面变慢业务侧告警也触发。如果每一层都各自告警一条根因可能瞬间产生几十条上下游告警运维根本分不清哪条是源头。我在实践中用的收敛抑制手段主要有三种聚合收敛。同一个告警规则在 10 分钟内多次触发只发一条并附带更新次数。层级抑制。如果存储层已经告警那么计算层在这段时间因为存储问题触发的衍生告警自动抑制。实现方式是给告警规则打标签存储层动因为storage_issue计算层规则声明“当存在storage_issue告警时自动等待”或“降级成信息级别”。通知频率限制。每条告警规则独立设置通知冷却时间。比如存储延迟 P99 超过阈值第一次立即通知后续如果持续超过每隔 15 分钟才提醒一次防止持续输出。另外还有一个实用技巧变更窗口抑制。如果运维同学在执行扩容、缩容、版本升级等变更窗口期间告警全部自动静默仅记录日志。这能有效避免“正在操作的时候被告警轰炸”的尴尬情况。3.4 告警路由按团队分工和故障影响面分发存算分离集群涉及的团队通常不止一个负责计算引擎的、负责存储底座的、负责网络的、负责元数据服务的。告警如果不做路由全部发给一个大群噪音太大全发给一个人又容易漏重要告警。我建议按两层来分发第一层按故障域分发。计算层指标告警发给计算团队存储层指标告警发给存储团队元数据层告警发给数据平台团队。这样各团队只需要关注自己负责的层。第二层按严重级别升级。P0集群不可用级别的告警除了分发给负责人同时抄送值班群和技术主管P1服务降级级别发给当班响应人P2/P3 走工单系统不直接打扰。这里有个经验跨层级的告警必须有一条“总联调”通道。存算分离的故障往往跨团队排查如果 A 团队收到告警说“对我们存储确实有问题”B 团队也收到告警说“我们在拉的存储数据慢”两边各看各的没人把因果串起来恢复时间会非常难看。所以我会建一个“根因关联看板”把同一时间窗内上下游的告警事件聚合成一个 session自动标记可能的因果关系这个在后面排障案例里会详细讲。4. 一次真实的排障实录任务集体变慢根因却在存储热点光讲理论和配置太干了我拿一个之前帮客户排查的实战案例来复盘。这个案例在存算分离架构下非常典型故障的表象在计算层根因在存储层而这次用什么链路把它定位出来是有可复现价值的。4.1 故障表象Spark 任务 stage 重试率突然飙升那天下午 3 点左右值班群开始有人反馈“跑数作业变慢了好几个之前 20 分钟跑完的任务现在 50 分钟还没跑完。”看计算层监控面板YARN 队列还有资源CPU 也不高任务本身没有报错但大量 Spark 任务的 stage 出现了重试重试原因写的是“FetchFailedException”。这里有个容易误导人的细节FetchFailedException这个名字非常容易让人以为是 shuffle 数据拉取失败也就是计算节点之间的问题。值班同事一开始也确实往这个方向查把重试之间的网络情况都翻了一遍没啥异常。4.2 排查链路从计算层到存储层的下钻我介入之后先调出了“三张网”的联合视图把时间轴对齐到 3:00~3:30计算层任务执行时长的 P95 从正常 20 分钟涨到 47 分钟同时段网络流量并没有异常下降。网络层计算节点到存储节点的 TCP 重传率从 0.1% 升到了 0.8%不太高不至于导致大规模失败。存储层关键信号出现了——对象存储服务端监控里某个前缀分区的请求 QPS 在 15:02 开始从 3000 冲到 8500读写延迟 P99 从 20ms 飙到 1.8 秒。看到这个数据问题基本就有了方向存储侧某个分区过热导致数据读取延迟剧增计算侧拿不到数据只能重试重试又加剧了分区压力形成了正反馈。之所以表现为 FetchFailed 而不是读超时是因为 Spark 对远端读取有自己的超时和重试机制底层不断的连接超时沉淀到上层就是 stage 失败。继续往下钻这个热点分区下挂的是一张每天新增大量小文件的业务表那天的数据生成任务因为上游处理逻辑变更写出来的文件数量是平时的 8 倍每个文件又特别小。对象存储对该分区前缀的处理能力被小文件请求直接打穿单点瓶颈就出现了。4.3 根因确认与修复文件布局优化 读取策略调整根因确认后修复分了两步第一步短期止血。让数据生成任务把这些小文件在写入端先合并压缩到合理大小具体做法是对同一分区先写临时文件再合并提交同时把任务读取时对文件列表的扫描改成分区级预取减少对元数据的重复请求。这一步执行后存储侧热点 QPS 在一个小时内降到 4000 以下延迟恢复正常任务时长回落到 22 分钟左右。第二步长期整改。在存储侧增加“小文件数量”和“单前缀 QPS”两个维度的监控指标并对读写延迟建立动态基线告警阈值用周期同比方式自动计算。同时规定新增业务表必须设置分区大小约束写文件数超过阈值自动拦截并告警。这次排障给我的触动很大。如果沿用传统监控思路只盯计算层任务指标我们可能还在 NodeManager 日志里大海捞针。而把指标按计算层、存储层、网络层拆开以后故障信号从哪个层面来一眼就能定位。4.4 这次排障暴露的三个监控盲区复盘这次故障我发现自己之前的监控体系也有三个盲区后来都做了补强一是没有按“前缀/分区”维度拆存储指标。整个桶级别的指标全部正常掩盖了单分区的热点。现在我对对象存储都是按前缀做 QPS 和延迟的聚合至少要做到 TopN 前缀能持续追踪。二是没有把任务级重试和存储指标做关联。Spark 的 FetchFailed 本身很容易引起误判所以我后来专门做了数据拉取失败率和存储节点各自延迟的关联看板一旦某个存储节点上拉取失败率升高自动展示该节点的 IO 延迟状态。三是缺少“变更事件”的时间叠加。之前只看指标曲线不看变更时间线。这次搜索故障时发现上游数据生成逻辑在一个小时前刚变更过但监控系统没有同步这个事件排查初期少了一条重要线索。现在我会把部署发布、配置修改、参数调整这些事件也接入监控系统的时间轴和指标曲线叠加展示经常能一眼看到“变更之后症状开始出现”排障效率提升很多。5. 落地部署的实操经验与值得注意的细节最后聊一些落地层面的实操内容。这部分看起来不如告警算法那么“高大上”但往往是最影响项目成败的地方。我按自己的踩坑经验来写希望帮你少走一点弯路。5.1 监控采集器的选型与部署节奏存算分离集群的监控采集目前主流的方案还是 Prometheus 全家桶加上各类 exporter节点指标node_exporter采集 CPU、内存、网络计算引擎Spark 用SparkExporter或通过 REST API 拉取Flink 用官方flink-metrics-prometheus存储层HDFS 用NameNode JMX对象存储一般通过服务商 API 或网关 exporter 拉取元数据层HMS 可以开启 Thrift 指标写一个轻量 exporter 把 JMX 转成 Prometheus 格式。部署节奏上我的建议是分阶段第一周先把节点级和基础网络指标跑通确保数据链路稳定第二周接计算引擎任务级指标第三周接存储层和元数据层指标之后进入告警规则配置和调优。不要试图一天把所有指标都接完。我见过不少团队上来就接了几千个指标结果采集器之间互相打架、存储监控数据的时序库直接被写爆最后项目烂尾。小步快跑每一层数据都确认质量后再往下走反而更快。5.2 监控数据本身的“存算分离”思路一个很有意思的坑监控系统本身也要处理海量时序数据时序数据库的计算和存储也会成为瓶颈。我这里有一个偷懒但很实用的思路——监控数据也可以做存算分离。Prometheus 的本地存储保留时间窗口不要设太长建议 15 天左右。超过这个时间窗口的监控数据通过Thanos或VictoriaMetrics这类组件把数据同步到对象存储里长期保存。这样本地节点只需要处理热数据的查询历史数据从对象存储拉到查询层运算成本低、容量几乎无限。具体配置上我用过 Thanos 的sidecar store gateway模式Prometheus 本地跑热数据sidecar 定期把历史 block 上传到对象存储store gateway 统一查询。查询时如果数据不在本地会从对象存储拉取。这套方案的好处是不需要额外扩容计算节点监控数据的历史查询能力也和集群规模基本解耦了。5.3 几个容易忽视的日常运维点最后写几个不亲自落地很难知道的小细节第一指标命名规范一定要从一开始定。存算分离集群涉及的组件多每个团队习惯可能不一样如果不清洗规范后面对接告警和看板的时候会花大量时间做映射。我用的规范是层级_组件_指标类型_指标名比如storage_hdfs_read_latency_p99、compute_spark_task_duration_p95。第二动态基线告警要定期校准。业务形态会变比如某天上了一个大促活动历史基线可能不再适合当前负载。我的习惯是每个月出一份“告警命中率报告”观察哪些规则频繁误报、哪些规则长期不触发及时调整算法参数或干脆关掉。第三告警记录本身也要可回溯。存算分离集群的故障往往不是单点事后复盘时你需要快速还原“当时发生了什么”。我会把告警事件、指标曲线、变更事件、操作记录四类信息统一存到一个案件系统里每次故障处理完自动生成一份时间线。对这个系统的投入很值得因为同样的故障模式很可能会以不同表象再次出现下次处理就快多了。第四不要忽略“监控自身的监控”。采集器挂了、时序库写满了、告警通道断开这些都是监控体系自己的故障如果没人发现等于整个集群在裸奔。我给自己定的底线是至少要有两条独立的告警通道一条依赖集群内部监控一条走外部拨测哪怕内部全挂外部还能兜底喊人。