尧图精选

打造全局可观测性系统:从数据接入到关联引擎的实践指南

🕒 发布时间:2026/9/14 20:19:22 📁 来源:尧图网络
1. 为什么看得见所有不等于看得懂现场上帝视角的起点大概三年前我在一家业务增速很快的互联网公司负责平台稳定性。那会儿我们已经有完整的监控体系主机有node_exporter应用有SkyWalking日志走ELK链路有Jaeger告警走Prometheus Alertmanager。听起来很齐全是吧但真出了线上事故第一反应不是去查监控而是拉群、问人、翻文档——因为根本不知道该从哪个系统开始看。最典型的场景是半夜收到一条CPU使用率超阈值的告警你打开Grafana发现确实有一台机器CPU飙到了90%但这台机器上跑了八个应用实例到底是哪个应用导致的这个应用影响了哪些上下游依赖下游的消费者是不是已经堆积了大量消息这些问题在单点监控里完全回答不了。我当时就在想我们需要的是一个能将所有可观测性数据聚合成一个全局视图的系统一个真正的gods eye view。它不只是一个新的大屏工具而是一种视角转换从逐台机器、逐个指标地排查变成先看到一张全局拓扑再主动下钻到具体链路。这个系统后来被我实现成了一个内部平台跑了将近两年支撑了所有核心业务线的故障排查、容量评估和架构治理。这篇文章就是把整个设计和落地过程完整拆出来从数据接入、实时关联到可视化取舍再到三处让我印象深刻的性能瓶颈都讲清楚。2. 数据接入层把五类异构数据揉进同一张时序表2.1 先定边界上帝视图到底需要哪些数据很多人在做全景监控时最容易犯的错误是贪多什么数据都想接进来。我一开始也这样想着把日志全文、Trace的Span、Metrics的原始样本全部塞进一个系统结果就是存储成本失控、查询性能崩塌。后来我重新定义了这个系统的边界gods eye view只需要四类数据任何不属于这四类的都留在各自的源系统里需要时按需跳转。第一类是基础资源指标包括CPU、内存、磁盘、网络IO粒度是15秒一个点只保留最近7天的原始数据再往前就降采样到1分钟和5分钟。第二类是应用运行指标包括QPS、错误率、响应时间P99、线程池活跃度、JVM堆内存粒度同样15秒。第三类是调用链路数据但不是全量Trace而是经过采样的Span聚合结果——按服务、接口、状态码三个维度聚合出的调用关系和耗时分布。第四类是事件日志包括发布事件、配置变更、告警触发、扩缩容操作这些不是常规日志而是与系统状态变化相关的关键事件。为什么不接全量日志和全量Trace因为从全局视角来看你需要的不是某一行日志的内容而是哪个服务在什么时间点出现了错误激增这个事实。至于具体错误信息应该从全局视图下钻到ELK或Jaeger去查看。这个设计原则叫视图存事实详情留原处后面所有的架构决策都围绕它展开。2.2 采集端改造统一模型比统一协议更重要数据源五花八门Prometheus暴露的是Metrics格式SkyWalking上报的是gRPC协议Nginx日志走Filebeat发布系统提供的是HTTP回调。如果每个接入方都用自己的数据模型到了存储层会非常痛苦。我的做法是设计了一个中间层模型叫UnifiedEvent。它长这样message UnifiedEvent { string event_id 1; int64 timestamp_ms 2; string source_type 3; // metric | trace | event string scope 4; // service | host | cluster string scope_id 5; // 具体实例标识 string metric_name 6; double value 7; mapstring, string tags 8; string detail_link 9; // 跳转回源系统的URL }也就是说不管原始数据是Prometheus的Counter、Histogram还是Trace的Span耗时接入层最终都会转换成一条或者多条UniformEvent落到ClickHouse里。这样带来的直接好处是查询端只需要写一套SQL就能同时查指标、查链路聚合、查事件记录。采集端的具体实现是用自研的Agent从各个源拉取或接收数据。比如对于Prometheus写了一个scraper定时拉取/metrics接口做协议转换后写入Kafka对于应用链路直接用OpenTelemetry Collector的Kafka exporter把Span数据导出到Kafka再由统一消费程序做聚合和转换。每个接入通道都是独立的管道互不阻塞某一路挂了不影响其他路。2.3 存储模型用标签思想替代多表设计存储层我选了ClickHouse原因很直接它处理按时间范围标签过滤数值聚合这类查询性能远优于常规关系型数据库。表结构只建了一张统一的events表步骤如下CREATE TABLE events ( ts DateTime64(3), event_id String, source_type LowCardinality(String), scope LowCardinality(String), scope_id String, metric_name String, value Float64, tags Map(String, String), detail_link String ) ENGINE MergeTree() PARTITION BY toYYYYMMDD(ts) ORDER BY (ts, source_type, scope, metric_name);关键设计在ORDER BY上把ts放在最前面保证时间范围查询走主键索引。source_type和scope用LowCardinality优化因为它们的枚举值很少压缩比高、查询快。tags用Map类型存所有可选的维度标签比如instance、method、status_code、deploy_version这样不需要为每个新维度加列。这套模型上线后有个很明显的效果原本在Prometheus里跨服务聚合指标要写PromQL在SkyWalking里查调用链要单独开控制台现在一条SQL就能出来。比如查所有Java服务过去5分钟的P99响应时间按服务分组就是SELECT scope_id AS service, quantile(0.99)(value) FROM events WHERE ts now() - INTERVAL 5 MINUTE AND source_type trace AND metric_name rpc_cost_ms AND tags[type] server GROUP BY scope_id;这类查询在ClickHouse上几十亿行数据的量级下响应时间基本都在几百毫秒以内完全满足交互式分析的需求。3. 实时关联引擎从数据随手查到事件自动找上门3.1 告警风暴的根源是只见树木不见森林数据接进来之后如果只是做展示那它顶多算个高级Grafana。真正让gods eye view从工具变成视角的是实时关联分析能力。在旧体系里一个核心服务抖动通常会触发十几条甚至几十条告警Pod重启告警、CPU飙高告警、下游调用失败告警、网关5xx告警。这些告警虽然本质上是同一次故障的不同表现但因为分散在Prometheus和各类平台的告警规则里值班同学要一条条看、一条条判断非常浪费时间。我的目标很明确关联引擎要把同一根因产生的多条告警聚合成一个故障事件只通知一次并且自动附带一个从全局视角分析出来的可能性排名。3.2 关联规则基于拓扑传播而不是时间戳巧合市面上很多所谓的AI告警关联本质就是时间窗口内按告警数量做聚类谁俩挨得近就算谁俩有关联。这种方案的准确率我实测下来很一般因为业务高峰期的并发告警本来就密集很容易把两件不相干的事聚到一起。我换了个思路基于依赖拓扑推算故障传播路径。具体分四步第一步从Trace聚合数据构建服务依赖图。我每天凌晨跑一次离线任务读取过去24小时的调用链数据抽取出服务节点和调用边边上标注平均QPS、平均耗时、错误率。这张依赖图就是后续所有关联分析的地图。第二步告警产生时先做源头定位找出所有告警中拓扑层级最深、且被其他告警服务依赖的那一个。比如支付服务挂了会导致订单服务调用失败、商品服务超时、网关5xx那么支付服务就是源头。第三步从源头出发沿依赖图正向遍历把2分钟内告警列表中所有与源头直接或间接关联依赖路径长度不超过3跳的告警合并进同一个故障事件。第四步计算每个故障事件的影响面包括受影响的服务数、下游调用失败的估算请求量、持续时长按影响面打分排序。这套规则上线后我们的告警条数减少了大概78%但真正重要的故障一次都没漏过。有一次数据库主库抖动关联引擎把二十多条外围告警合并成了一条数据库主库异常导致下游十八个服务连锁故障的事件值班同学看一眼就知道处理方向了。3.3 在线实时计算Flink作业的拓扑缓存关联引擎的在线部分是跑在Flink上的。消费Kafka里的告警事件流完成拓扑匹配后输出聚合后的故障事件。Flink作业的核心难点是实时获取依赖拓扑。我本来想直接查ClickHouse但考虑到拓扑匹配的命中率完全取决于是否拿到最新依赖关系于是加了一层Redis缓存离线任务每5分钟更新一次依赖图写入RedisFlink作业从Redis加载到本地内存。具体实现上用Flink的KeyedProcessFunction按scope_id服务名做KeyBy在状态里维护该服务最近5分钟内的告警计数和状态变化。当收到新的告警时先检查该告警所属服务是否有活跃的故障事件上下文如果没有就启动一个30秒的窗口等待关联告警然后执行拓扑匹配逻辑。这里有个细节值得注意窗口时间不能太长否则故障事件产生得不够实时也不能太短太短会导致部分路径较长的关联告警来不及汇入。我压测下来30秒是平衡值。如果你和我一样是从零开始建议先用10秒后续根据实际链路深度调整。4. 可视化端的设计取舍全局态势图不是简单画个拓扑4.1 三张视图分别回答是什么、为什么、怎么办数据接入和关联引擎都在后端但用户真正感受到的上帝视角其实是可视化的能力。我在设计前端视图时贯彻了一个原则不同角色对上帝视角的诉求是不同的。值班同学需要快速知道现在哪里在冒烟架构师需要理解当前系统的全局形态管理层需要了解故障影响了多少用户和金额。所以我没有做一张包罗万象的大屏而是做了三张视图让用户按需切换。第一张是全局热力地图按机房和可用区展示每台物理机、每个Kubernetes节点的资源水位和健康状态颜色从绿色到红色渐变。这张图解决的是哪里在冒烟的问题适合值班室墙上挂的大屏。第二张是服务依赖拓扑图就是上一节说到的依赖图但叠加了实时状态信息每个服务的节点颜色对应该服务当前错误率节点大小对应当前调用量边上标注实时P99耗时。这张图是为什么的入口点任何一个节点就能下钻到该服务的详细面板。第三张是时间轴事件流以分钟为粒度展示发布、配置变更、告警触发、扩缩容等事件叠加关键指标曲线。排查故障时可以把时间轴和指标曲线联动比如一眼就能看出服务错误率升高发生在发布操作之后三分钟基本等于直接锁定了根因方向。4.2 拓扑布局算法力导向是方便但要克制服务依赖图的布局我踩过不少坑。一开始直接用了开源的力导向布局Force-Directed数据量小的时候效果不错节点之间谁跟谁关系紧密、谁离谁远一目了然。但服务数量超过两百个之后力导向图会变成一个毛线球节点互相遮挡连线杂乱无章别说找故障了连看清拓扑结构都费劲。后来我改为分层布局先识别出拓扑中的入口层、中间层和存储层把层级作为纵轴约束同一层内再用最小交叉算法排列节点。这样无论服务数量多大图都能保持清晰的上下游方向感。节点渲染上做了LODLevel of Detail控制缩放级别高的时候显示服务名和关键指标缩小到全局视野时只显示集群维度的聚合节点细节留给用户主动下钻。前端技术栈我用的ECharts 自研的Canvas渲染层。ECharts的graph类型负责基础布局和交互自定义的Canvas层负责三层以上的深度链路绘制避免大量DOM节点导致浏览器卡顿。4.3 下钻路径全局到局部不能断链做可视化最容易出现的问题是看起来很美点下去什么都没有。很多大屏系统全局视图做得极其震撼但点击某个节点想看详细数据要么跳不过去要么跳到另一个系统要重新登录。我在设计下钻链路时定了一条铁律任何可视化元素都必须可以点击进入更深一层最终一定可以触达到某个源系统且全程不需要重新登录。具体做法是这样全局拓扑图的节点点击后右侧弹出该服务的详情面板展示实时指标曲线、活跃告警列表、上下游调用列表。详情面板里的指标曲线可以框选时间范围点击查看链路跳转到Jaeger的Trace查询页面通过URL参数带上服务名和时间范围。告警列表里的每条告警都有原始详情链接跳转到Prometheus Alertmanager对应的告警详情或者跳转到ELK搜索对应的错误日志。这套全链路下钻设计最初是给值班同学用的他们遇到一个看起来异常的节点可以一路追问下去谁调的它它调了谁今天的发布是什么这段日志的原文是什么整个过程不用开第二个浏览器标签页。5. 性能优化记录三处关键瓶颈与改造实测5.1 ClickHouse查询热点的Rows扫描量失控系统上线后第一个明显的问题是某些全局聚合查询响应时间经常超过3秒甚至最慢的有8秒的。分析EXPLAIN后问题的根源在于ORDER BY的字段顺序。我最初的排序键是(ts, source_type, scope, metric_name)查询时如果只带source_type和metric_name条件不带ts范围限制ClickHouse还是会扫描整张表因为ts是排序键的第一列没有ts条件就无法裁剪数据分区。我们的很多场景恰恰是看当前分散在不同服务的所有指标天然不会限定具体时间点。比如查询当前所有服务的P99错误率这种全局视图实际上扫的是过去5分钟的数据但因为条件里落在了ts范围上索引裁剪是有效的。真正拖慢查询的其实是另一个查询模式查某服务过去24小时的所有指标变化。从全局视图下钻到服务详情一定会查这个服务在过去24小时的所有数据点数据量约等于24 * 60 * 4 * 指标数 数万行按理说不慢。但问题是ClickHouse的MergeTree在扫描多列宽表时如果查询的列太多IO放大效应很明显。优化方案是双管齐下。第一在events表基础上按(source_type, metric_name)建物化视图把指标分类存储减少单次查询的列扫描范围。第二把最热门的服务级查询拆到独立的按服务分区的表上每天凌晨把前一天的数据冷热分离热表只有今天和昨天的数据扫描量小了两个数量级。改造后同类查询的响应时间从秒级降到了200毫秒左右。5.2 Flink作业的反压和状态膨胀关联引擎上线两周后Flink作业的Kafka消费延迟开始持续上升从最初的秒级延迟扩大到了分钟级。看监控发现KeyedProcessFunction里维护的服务告警状态是无界增长的——每个服务都有一个活跃告警集合没有清理机制状态越来越大StateBackend的磁盘占用和内存开销跟着膨胀。修复方法是在状态里加了一个TTL和数量上限。状态条目的TTL设为10分钟超过10分钟没有更新的告警上下文自动清理同时每个服务的活跃告警集最大保留50条超过阈值时丢弃最老的数据。另外把RocksDB的StateBackend配置从默认改成了开启增量检查点checkpoint的时间从45秒降到了10秒以内。有时候瓶颈不是Flink作业本身而是下游Sink。我们的下游是写入ClickHouse做故障事件存储最初是一条一条地写吞吐完全跟不上。后来改成攒批写入每200条或者每2秒flush一次配合ClickHouse的批量插入特性Sink的TPS从几百提高到上万。5.3 前端拓扑图的渲染抖动最后一个坑在前端。服务依赖图在数据刷新的瞬间整个画布会重新布局节点位置抖一下而且每次刷新都会闪。原因是我把拓扑图的状态管理和数据轮询放到了同一个React组件生命周期里每两秒来一次新数据就触发一次全量重渲染。解决办法是彻底拆分布局计算和数据更新。布局只在初始加载和用户手动拖拽时计算一次后续的数据更新只更新节点和连线的指标属性不触发节点位置的重排。同时用requestAnimationFrame包了一层更新逻辑保证高频率的数据刷新不会阻塞主线程。一个额外的小优化节点上的P99耗时和错误率只在值变化超过5%时才重新绘制避免闪烁和无意义的绘制开销。这个优化把浏览器长任务的卡顿时间从每次刷新平均120毫秒降到了不足20毫秒。6. 落地效果与可复用的扩展思路6.1 数据说话上线半年后的一些硬指标先列几个真实的落地数据供你判断这套方案在你的场景下值不值得复刻。告警合并效果关联引擎上线后每天推送的告警事件数从平均每天410条降到了约90条其中约60条是单点告警合并后无法归因30条是多告警合并的故障事件。故障排查时长核心业务线的平均故障定位时间从上线前的平均32分钟降到了11分钟。最典型的场景是某次数据库死锁引发的连锁故障值班同学在5分钟内就通过拓扑图定位到了源头。全局拓扑的容量评估架构治理团队利用依赖图做服务调用链梳理清理了37个调用超过三个月的死链服务网络流量直接节省了约12%。发布会风险的快速识别时间轴和指标曲线的联动视图让每一次发布会后五分钟内的异常率一目了然上线后的快速反馈循环缩短了很多。6.2 这个系统的边界在哪里做了两年的gods eye view我最大的体会是上帝视角不是万能神眼它有自己的边界。第一个边界是数据延迟。我们的数据链路从采集到ClickHouse可查存在约15秒的延迟。这意味着这个系统用于故障定位和趋势分析非常合适但不适合做秒级实时控制比如自动扩缩容或者流量切换。这类决策还是应该交给专门的自动控制系统。第二个边界是关联引擎的准确性依赖拓扑图的准确性。如果服务间采用了异步消息或事件驱动架构部分调用依赖在Trace数据里是不完整的导致拓扑图缺边关联分析会出现假阴性。我的应对方案是把消息队列系统中生产者和消费者的关系也手动纳入拓扑图作为补充数据源。第三个边界是异常检测本身没有做太复杂的事。我没有引入机器学习做异常检测而是用阈值和同比环比规则。原因很直接在已有的监控体系里异常检测已经由Prometheus和各类算法在做gods eye view的定位是聚合与关联做好这一件事就行。6.3 如果要复刻这个项目我的建议顺序如果你也想做一个类似的全局视角系统我建议不要一上来就铺太大摊子。最好的路径是先把数据接进来做出第一张服务依赖拓扑图然后逐步叠加实时状态、关联分析、事件流最后才是性能优化和告警降噪。第一步先把统一事件模型定下来。很多后续的灵活性都受这一步约束模型设计得越坏后面返工的代价越大。第二步接两类数据就够了资源指标和服务调用链。有了这两类就可以画出第一张带状态的拓扑图。第三步做拓扑图的自动化更新和下钻链路确保拓扑不是一张静态图。第四步才轮到关联引擎。关联规则也不用一开始就是拓扑传播可以先从时间窗口内告警聚合并起跑通了再看效果。第五步最后做前端可视化打磨。可视化是我的经验里最容易返工的部分因为一开始你不太清楚用户真正关注什么等系统跑起来收集到真实使用反馈再去迭代布局和交互投资回报率更高。我踩过的一个比较深刻的坑是提前花了两周时间做了一个非常炫酷的3D机房视图最后发现值班同学根本不用它大家真正高频使用的反而是最朴素的拓扑图和事件流。后来我把那个3D视图下线了。根据我的经验把80%的精力投入到数据接入、关联引擎和查询性能上剩下的20%给可视化已经是比较合理的比例了。可视化的酷炫如果不能服务于快速回答当前系统状态是什么在运维场景里意义非常有限。最后分享一个小技巧在开发这类全局系统时务必把每一个视图都加一个当前数据截止时间的角标。原因很现实——数据链路延迟导致后端数据可能已经有15秒的滞后用户如果不清楚这个滞后会在故障排查时用旧数据做判断。我加了这个角标之后误判类的问题几乎绝迹了。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →