动手点关注
干货不迷路
微服务架构的快速发展使得分布式链路追踪系统成为观测体系中越来越重要的组件。字节跳动的分布式链路追踪系统经历了数年的发展后,已覆盖了字节的绝大部分在线业务,完成了对数万微服务和数百万微服务实例的在线链路追踪。在经典的指标观测分析和单请求链路追踪的基础上,如何从浩瀚如海的分布式链路数据中进一步挖掘出更高层次的信息,为业务的架构优化、服务治理、成本优化等场景提供更高效的数据支持,成为了下一步亟待回答的问题。
本次分享主要介绍我们构建海量链路数据分析计算系统的实践经验,以及一些具体的落地场景。
为了方便读者更好的理解“链路分析”,首先浅聊一下什么是“可观测性”和“链路追踪”。对“可观测性”和“链路追踪”的概念已经熟悉的读者可以跳过本章节。
随着微服务架构的快速发展,软件系统正在从单体应用发展为由大量微服务节点构成的复杂应用。为了更好的管控复杂的软件系统,“可观测性”工具正在变得越来越重要。“可观测性”工具构建的基础是可观测性数据,可观测性数据一般包括如下部分:链路追踪 Trace、日志 Logging、时序 Metrics、代码级 Profiling、事件 Event 和 元数据相关的 CMDB 等。
为了帮助大家对可观测性工具有一个更直观的感受,这里用一个例子来阐述如何基于可观测性工具来解决工作中的实际问题:某值班人员收到告警通知服务的失败率正在上升,点击关联到错误指标对应的 Trace,在 Trace 中定位到错误的源头,在源头查看到关键的异常日志和代码栈,并发现源头报错服务正在执行一个变更操作,于是基本定位到此变更很可能就是导致此故障的原因。有了高质量的可观测性数据和工具,一个对此系统并不是非常熟悉的值班人员,就可能快速地完成此次故障的排查与止损。
分布式链路追踪(Trace) 是可观测性系统的其中一个组件。狭义上讲 Trace 是对单次请求的明细追踪,记录请求在各环节上的调用关系,耗时,以及各类明细标签与事件。同时 Trace 还有一个角色是各类可观测性数据的链接纽带,即同一个 Request Context 的数据载体,分布式请求上的各类信息(Metrics/Logs..)通过 Trace 实现了可靠关联,进而可以构建各类可观测性数据的上卷下钻的跳转功能。
字节链路追踪系统经历了数年的发展,现已覆盖公司绝大部分在线业务。整体发展历程如下:
2019: Trace 1.0 完成了 Trace 组件能力的构建。
2020: Trace 2.0 实现了 Metrics/Trace/Log 的埋点一体化,对数据协议与技术架构进行了升级,并开始构建一站式观测平台 Argos。
2021: 与字节绝大部分主流框架组件实现了默认集成,全司各业务线微服务覆盖度 > 80%。
2022: 构建与探索场景化、智能化的场景,例如本分享聊的“链路分析”。
字节链路追踪系统当前的现状数据(2022 年 10 月):
覆盖面: 5 万+ 微服务 /FAAS 数,300 万+ 容器实例数。
使用量: 日 UV 6 千+ ,日 PV 4 万+。
吞吐量: Span 数 2 千万+/s (默认 0.1% 采样)。
资源配比: 100+ CPU cores 支撑 100 万 Span/s。
SDK 性能: 单线程 40w Span/s。
查询性能: TraceID 点查 P50 < 100 ms, P99 < 500 ms。
数据产生到可检索时延: AVG < 1 分钟,P99 < 2 分钟。
存储资源: 10 PB (3 副本 TTL 15 天)。
字节链路追踪系统从数据接入侧、消费存储侧、到查询平台侧的整体架构如下图所示,更多细节可阅读之前的分享,上一次分享较为详细的介绍了如何从零到一构建一个分布式链路追踪系统。本文主要聊链路追踪系统中的链路聚合计算分析部分。
经常使用 Trace 的同学对 Trace 的印象可能是通过 TraceID 或者某些 Tag 检索出一条或者一些 Trace,然后从 Trace 数据中仔细查看明细的调用轨迹和各种 Tag 来分析一些具体问题,比如一个请求为什么慢,或者一个请求为什么出错了。
但是我们还面临着一些更高层级的问题,例如,面对一个不断变化的复杂微服务系统:
稳定性:紧急状况时,哪些服务可以被降级,哪些服务必须被保障?高风险的易故障点在哪里?
流量/容量:如果某页面的 PV 要增加10倍,哪些服务需要扩容?需要扩容多少?
成本性能:哪里存在明显的性能反模式?哪里存在架构不合理带来的性能浪费?
这些问题的答案靠人工去一条一条看 Trace 难以得到可靠结果。但是却可以从大量的 Trace 数据中自动化地计算出来,为最终决策提供可靠的数据支持。我们把这种对大量 Trace 的聚合计算叫做 链路分析。
链路分析的基本原理就是对大量的 Trace 进行聚合计算,一般遵循 MapReduce 计算模型,过程中可能会结合一些订阅规则和其他数据,得到计算结果,然后应用到具体的场景化应用中去。
适合对大量链路数据进行聚合计算的可选模式主要有三种,分别是从基于在线数据流的流式计算、从在线存储中查询有限条 Trace 然后进行的即兴(抽样)计算,以及基于离线数仓的离线计算。这三种计算模式的特性分析如下表所示。
计算方案 | 优点 | 缺点 |
---|---|---|
流式计算 | - 近实时的分析结果 - 数据完整性和准确度较高 - 多机房部署升级较为方便(自研未依赖 flink) |
- 无法对任意时段任意条件的数据进行计算 - 受数据流量波动影响,维护成本相对较高 |
即兴(抽样)计算 | - 可对任意时段任意条件的数据发起分析并快速获取结果 - 最低的额外机器成本和运维成本 |
只能对抽样的有限条 Trace 进行计算,数据完整性较低 |
离线计算 | - 数据完整性和准确度高 - 稳定,机器成本和运维成本较低 |
- 小时级或天级的延迟的分析结果 - 对大数据套件依赖较重,多区域的部署和升级成本较高 |
分析完了三种聚合计算模式的特性后,再分析下链路分析场景所面临的技术需求。
需求类型 | 描述 | 此类需求高的场景举例 | 合适的计算方案 |
---|---|---|---|
实时性需求 | 需要分析最新的数据吗还是可以接受一定的延迟? | 故障归因 | 即兴计算/流式计算 |
数据完整性需求 | 抽样一部分数据计算能否满足需求?还是必须对所有 Trace 计算才能得到结果? | 流量估算 | 离线计算/流式计算 |
需求即兴程度 | 任务是即兴发起且必须快速拿到结果的吗? | 故障归因 | 即兴计算 |
基于上述分析,我们认为并不存在一种计算模式就能解决所有问题,因此最终选择的技术方案是流式,即兴,离线一体化的技术方案:基于统一的基础数据模型和逻辑算子,支持三种不同的计算引擎,以实现不同的场景化需求。
在落地此方案的过程中,我们总结了一些有益的实践经验:
逻辑算子与计算引擎分离
Trace 本身数据结构就较为复杂,其分析计算逻辑也往往有一定的复杂度,算子与引擎分离便于相同逻辑算子应用于不同计算引擎,可以较好地提升研发效率和代码可维护性。例如性能瓶颈分析相关算法,既可以用于即兴计算,也可以用于离线计算。
自动化异常数据检测与封禁
Trace 数据常常会存在一些数据不规范现象,例如超高维度的接口名,导致聚合后的数据维度远超预期,影响计算任务的稳定性。自动化的异常数据发现与封禁或降级机制能起到较好的保护作用。
保留原始样本提高可解释性
尽量保留聚合分析结果对应的代表原始(极值)Trace 样本,可以提升分析结果的可解释性和用户信任度。
配备订阅和降级机制
大数据量的计算成本较高,非基础功能可采用按需订阅模式以提升 ROI;建设对任务进行灵活降级的能力,资源紧张的情况下优先保障高频基础功能的高可用。
介绍完链路分析的底层技术架构后,我们来介绍一些具体的落地场景。
链路分析使用最高频最广泛的场景当属链路拓扑的计算。这里说的“链路拓扑”是指输入任意一个服务节点即可获取流经此节点的所有 Trace 的聚合路径,从而清晰的得知此服务节点的上下游依赖拓扑关系。
由于字节的微服务数目极多且有各类中台和基础服务,调用关系较为复杂,因此我们做拓扑计算的目标是:
精准:只会检索到与被检索节点有直接流量的拓扑节点
灵活:可以按照任意节点(非仅入口),不同深度,不同粒度(服务/接口/集群/机房)进行拓扑检索
实时:nice to have
举例说明什么是精准性需求:如下图所示,“抖音.X” 和 “火山.Y” 都调用了 “中台.Z”,但是对于 “抖音.X” 的流量 “中台.Z” 会使用 “Redis.抖音”,而对于 “火山.Y” 的流量 “中台.Z” 会使用 “Redis.火山”,因此实际上 “抖音.X” 和 “Redis.火山” 是没有直接依赖关系的。那么当我们检索 “抖音.X” 时希望得到的拓扑是 [“抖音.X”, “中台.Z”,“Redis.抖音”],而不要包含 “Redis.火山”。
举例说明什么是灵活性需求:如下图所示,不仅可以按照入口来检索拓扑,也可以按照中间节点 “中台.Z" 或者是存储组件节点 "Redis.抖音" 来检索拓扑;不仅可以按照服务+接口粒度来检索拓扑,也可以按照服务粒度、服务+集群粒度、服务+机房粒度等其他粒度来检索拓扑。
面对这样的技术需求,我们研究了业界现有的一些拓扑计算方案:
方案一:也是开源主流方案,聚合单跳调用关系,检索拓扑时将单跳的调用关系进行组装。此方案简单,低成本,但是精度较低,对于中小型的微服务系统是可以的,但是对于字节场景,例如上述例子中希望用 “抖音.X” 检索拓扑时希望不要看到 “Redis.火山” 的需求则无法满足。
方案二:由复旦 & Ebay 分享,将 Trace 按照 Path 进行分组聚合,每个服务节点对应一组 Path。检索时首先检索到服务对应的 PathID 列表,然后再根据 PathID 检索出 Path 路径聚合成拓扑。此方案精度最高,可以清晰的梳理出每个服务节点对应的所有调用路径,但是在字节的实际场景中,我们发现 Path 膨胀非常严重,一个服务节点动辄成千上万的 Path,成本高检索慢,难以满足字节场景的实际需求。
结合字节场景的实际需求,平衡精度、成本和检索速度,最终我们设计了一种新的方案。
方案三:为每个节点计算出一张拓扑图,存储载体选择图数据库。图数据库的一个点对应一个服务节点,图数据库的一条边对应一个["所属拓扑",“源节点”,“目标节点”]三元组。在上述例子中,有这样一些边:
所属拓扑 | 源节点 | 目标节点 | 上图中对应标注颜色 |
---|---|---|---|
抖音.X | 抖音.X | 中台.Z | 蓝色 |
抖音.X | 中台.Z | Redis.抖音 | 蓝色 |
火山.Y | 火山.Y | 中台.Z | 黄色 |
火山.Y | 中台.Z | Redis.火山 | 黄色 |
中台.Z | 抖音.X | 中台.Z | 绿色 |
中台.Z | 火山.Y | 中台.Z | 绿色 |
中台.Z | 中台.Z | Redis.抖音 | 绿色 |
中台.Z | 中台.Z | Redis.火山 | 绿色 |
当需要检索“抖音.X”对应拓扑时,首先定位到图数据库中“抖音.X”对应的点,然后以此为起点发起图的遍历搜索,找到所有满足“所属拓扑=抖音.X”的边(即图中所有蓝色的边),得到了“抖音.X”所对应的拓扑为["抖音.X", "中台.Z", "Redis.抖音"]。
这个方案精度能够满足需求,成本相对折中,同时很好的利用了图数据库的特性,可以实现非常高效的拓扑查询。同时,由于链路拓扑的数据完整性需求较高,对实时性有一定的需求,因此我们使用了流式计算引擎来支持链路拓扑的计算,大致流程如下图所示。
精准链路拓扑的应用场景比较广,这里举一些具体的例子:
全链路实时观测:实时观测拓扑各节点的流量/延迟/错误率/资源使用率/告警/变更等,快速从全链路视角获取整体状态信息。
混沌演练:为故障演练提供链路依赖底图,生成演练计划,对全链路各个环节进行故障注入,收集与验证系统的反应得到演练报告。
压测准备: 提前梳理压测流量将会流经的节点,让相关节点做好压测准备。
服务架构治理: 为服务架构治理场景提供上下游依赖梳理依据。
故障归因: 为故障归因提供上下游依赖遍历的底图数据。
全链路流量估算主要回答的问题是:
流量流向哪些下游?如果当前节点的流量增加了 N,那么这些下游节点的流量会增加多少?
流量来自哪些上游?如果当前节点的流量增加了 N,那么这些流量是由谁带来的?占比如何?
全链路流量估算是在精准拓扑计算的基础之上实现的,因此也采用了流式计算的方式,基于各路径的 Trace 数目以及 Trace 所对应的采样率数据进行估算。其计算结果格式如下图所示,每张拓扑中的每条边对应一个估算流量和流量比例。基于这样的数据,对于任意一个微服务接口,我们都可以给出上面两个问题的答案。
全链路流量估算的主要应用场景如下:
业务活动扩容估算: 业务在进行大促活动前,往往先由产品估算出 DAU 的增量,然后将 DAU 增量转换为一组入口服务接口的 QPS 增量,进而再根据全链路流量比例估算出全链路各环节的 QPS 增量和扩容需求。可靠的全链路流量比例数据可以为此场景提供很好的辅助支持。
成本容量治理: 准确的全链路流量估算数据,有助于业务构建更加精细的成本容量规划,促进降本增效。
流量变化根因分析: 当流量发生预期外的波动变化时,全链路流量估算数据可以帮助快速分析出波动的根因与来源。
强弱依赖信息是服务稳定性治理场景的重要数据支撑,它也是可以通过线上的 Trace 数据自动化地计算出来的。
强依赖:异常发生时,影响核心业务流程,影响系统可用性的依赖称作强依赖。
弱依赖:异常发生时,不影响核心业务流程,不影响系统可用性的依赖称作弱依赖。
如下图所示,当 A 调用 B 失败时,如果 A 仍然能成功响应其 Client,则 B 是 A 的弱依赖;当 A 调用 B 失败时,如果 A 无法成功响应其 Client,则 B 是 A 的强依赖。
强弱依赖计算的技术目标包括:
准确性: 尽可能高,尤其是减少“强误判弱”,提供判定依据样本
覆盖度: 尽可能高,尽可能多的为线上存在的调用关系给出强弱依赖数据
粒度: <调用方服务接口,被调方服务接口>、<场景,调用方服务接口,被调方服务接口>
实时性: nice to have
为了尽可能满足数据的完整性和及时性需求,我们选择了流式计算模式,从数据流中选择带 Error 的 Trace 进行强弱依赖关系计算。需要注意短期的实时数据样本往往不够,需要结合历史累积数据共同判定再下结论。
强弱依赖分析的主要挑战:
准确率: 不同业务线判定业务稳态的规则较难统一,需要推动业务完善数据标记或规则录入。
覆盖率: 部分路径线上常态化错误率极低,较难收集到足够的错误样本,需要配合混沌演练进行补充。
强弱依赖分析的主要应用场景包括:
限流降级预案配置指导: 强弱依赖数据可以回答紧急状况时哪些服务可以被降级,哪些服务必须被保障的问题。弱依赖可以进行限流降级,而强依赖则应当尽可能的保障高可用。
超时漏斗配置指导: 强弱依赖数据可以指导业务更合理的配置超时漏斗。如下图所示,弱依赖配置超时时间过长是不合理的,可能会导致不必要的慢请求;如果强依赖配置的超时时间过短也是不合理的,可能会导致不必要的失败请求影响用户体验。
辅助自动化故障归因: 准确的强弱依赖信息,对自动化故障归因可以起到较好的辅助作用。
服务架构治理: 准确的强弱依赖信息,可以协助业务优化架构,治理不符合预期的强依赖,提前准备灾备方案,提升整体的稳定性和高可用。
在实践的过程中,我们观察到有一些非常典型的性能反模式问题是可以从 Trace 数据中自动化的计算发掘,常见的性能反模式包括:
调用放大: 单请求中,大量调用同一服务接口。如果在线链路中出现了调用放大比例极高的 case,往往不仅暴露出性能问题,还很可能会造成稳定性风险,需要及早治理掉。
重复调用: 单请求中,多处使用相同 Request 调用同一个服务接口。这种情况常见于基础信息的重复获取,例如多处重复去请求 user info 或者 device info,是可以进行优化的。
读写放大: 单请求中,从底层服务获取的数据量远大于最终返回给client的数据量。这种情况常见于滥用重接口获取轻信息,例如调用重接口请求了 user 的所有信息,却只取了 user name 一个字段。
串行循环: 串行循环特征较为明显,其形态一般如下图所示,一般可优化为并行或批量调用。
性能反模式问题的发掘还有如下两个需求:
优先发现极值问题并提供 worst case 样本
优先发现高流量+核心链路上的反模式问题
因此性能反模式的分析任务需要自动化发现最严重的那些反模式问题,给出极值样本,并且关联出这些问题所在路径的流量和入口优先级,从而帮助业务对服务进行延迟优化和成本优化,及早解决掉相关的潜在稳定性风险。
单请求的分布式 Trace 视图清晰直接,但是局限性在于观察者无法确定单请求所呈现出的 Trace Pattern 是普遍现象还是特殊现象。因此从大量的 Trace 数据中分析链路性能瓶颈,从而识别出整体性能 Pattern 和 worst case 样本也是链路分析的一个需求场景。
从批量 Trace 中聚合计算出链路性能 Pattern,既有即兴模式的需求,也有离线模式的需求。即兴模式下可以满足对任意时段、灵活条件(各类tag,耗时区间)筛选批量 Trace 并快速获取分析结果的需求。离线订阅模式下可以满足更完整的对全量 Trace 数据的整体性能模式分析需求,并观察长期变化趋势。因此我们会将链路性能分析聚合算子复用在即兴和离线两种计算模式下。
分析结果示例:
单条 Error Trace 可以观察出一条错误传播路径,但是观察者无法确认一条错误传播路径是否一定代表了普遍问题,也无法回答错误传播的影响面如何。因此聚合大量的 Error Trace 去分析整体的错误来源、传播路径和影响面也是链路分析的一个需求场景。
和链路性能分析类似,从批量 Trace 中聚合计算出错误传播路径,既有即兴模式的需求,也有离线模式的需求。我们也会将错误传播链分析算子应用在即兴和离线两种计算模式下。即兴模式可以满足对任意时段、灵活条件(各类tag)筛选批量 Error Trace 并快速获取聚合分析结果的需求。离线订阅模式则可以满足更完整的对全量 Error Trace 数据的聚合分析需求,并观察长期变化趋势,辅助业务进行长期的稳定性优化工作。
分析结果示例:
本文主要介绍了在完成了从零到一的链路追踪基础能力的构建之后,如何对海量的链路数据进行聚合分析来回答更高层面的场景化问题。分享了我们的具体技术选型过程和落地的技术架构,以及一些较为成功的落地案例。
最后分享后续的一些展望:
数据质量持续建设: 数据质量对于链路数据分析的结果优劣起到极其重要的作用。不断完善业务语义规范,推进各层面接入覆盖会是我们后续持续投入的工作。
场景化: 开放能力持续建设,沉淀业务知识和专家经验,打磨业务最佳实践。
智能化: 基于数据+知识+算法,持续提升应对故障归因、稳定性优化、性能成本优化等场景的能效。
拥抱云原生 : 完善 OpenTelemetry 接入,打磨基础组件,更好的适应各类云上场景。
我们是字节跳动-基础架构-应用观测(服务端)团队,专注于 PB 级别海量数据的可观测性基础设施(Metrics、Tracing、Logging、Event、Profiling)和上层可观测性应用(E.g. 报警生命周期管理、异常检测、根因分析)的建设,为字节跳动整体业务的稳定性、性能优化、服务治理等方向保驾护航。
● 深度解析字节跳动开源数据集成引擎
● 【专访】Chrome HEVC 硬解背后的字节开源者
● Android 插件化中资源错乱的解决方案
● 【字节跳动技术沙龙】抖音 Android 基础技术大揭秘!