☰
存算分离架构下的大数据监控:指标分层与动态基线告警实践
2026/10/14 21:13:21 网站建设 项目流程

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 * 滚动标准差时触发告警。这个算法实现简单,适合没有明显周期性的指标,比如节点的生成错误日志数量。

周期同比(季节性的环比)

大数据集群普遍有“白天忙、晚上闲、周末更闲”的规律,所以按周期对比更准。做法是:

  1. 按天/周存储指标的历史序列;
  2. 用“当前时刻的值”和“过去 7 天同一时刻的值”做对比;
  3. 计算偏差率:(当前值 - 历史中位数) / 历史标准差,超过设定倍数就告警。

这个思路很像“我们和上周同一天同一时间比”,能天然消除白天晚上的周期性波动,也避免了周末误报。我实际使用下来,周期同比在存算分离集群里效果很好,因为这个架构下的指标波动主要来自业务负载变化,而业务负载的周期性非常强。

提示:动态基线不是一上来就全量适配,要有一个“学习期”。我习惯先把指标接入采集,跑 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 格式。

部署节奏上,我的建议是分阶段:

  1. 第一周先把节点级和基础网络指标跑通,确保数据链路稳定;
  2. 第二周接计算引擎任务级指标;
  3. 第三周接存储层和元数据层指标;
  4. 之后进入告警规则配置和调优。

不要试图一天把所有指标都接完。我见过不少团队上来就接了几千个指标,结果采集器之间互相打架、存储监控数据的时序库直接被写爆,最后项目烂尾。小步快跑,每一层数据都确认质量后再往下走,反而更快。

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。

第二,动态基线告警要定期校准。业务形态会变,比如某天上了一个大促活动,历史基线可能不再适合当前负载。我的习惯是每个月出一份“告警命中率报告”,观察哪些规则频繁误报、哪些规则长期不触发,及时调整算法参数或干脆关掉。

第三,告警记录本身也要可回溯。存算分离集群的故障往往不是单点,事后复盘时你需要快速还原“当时发生了什么”。我会把告警事件、指标曲线、变更事件、操作记录四类信息统一存到一个案件系统里,每次故障处理完自动生成一份时间线。对这个系统的投入很值得,因为同样的故障模式很可能会以不同表象再次出现,下次处理就快多了。

第四,不要忽略“监控自身的监控”。采集器挂了、时序库写满了、告警通道断开,这些都是监控体系自己的故障,如果没人发现,等于整个集群在裸奔。我给自己定的底线是:至少要有两条独立的告警通道,一条依赖集群内部监控,一条走外部拨测,哪怕内部全挂,外部还能兜底喊人。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询