微服务架构的快速发展使得分布式链路追踪系统成为观测体系中越来越重要的组件。字节跳动的分布式链路追踪系统经历了数年的发展后,已覆盖了字节的绝大部分在线业务,完成了对数万微服务和数百万微服务实例的在线链路追踪。在经典的指标观测分析和单请求链路追踪的基础上,如何从浩瀚如海的分布式链路数据中进一步挖掘出更高层次的信息,为业务的架构优化、服务治理、成本优化等场景提供更高效的数据支持,成为了下一步亟待回答的问题。
本次分享主要介绍我们构建海量链路数据分析计算系统的实践经验,以及一些具体的落地场景。
为了方便读者更好的理解“链路分析”,首先浅聊一下什么是“可观测性”和“链路追踪”。对“可观测性”和“链路追踪”的概念已经熟悉的读者可以跳过本章节。
随着微服务架构的快速发展,软件系统正在从单体应用发展为由大量微服务节点构成的复杂应用。为了更好的管控复杂的软件系统,“可观测性”工具正在变得越来越重要。“可观测性”工具构建的基础是可观测性数据,可观测性数据一般包括如下部分:链路追踪 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 来分析一些具体问题,比如一个请求为什么慢,或者一个请求为什么出错了。
但是我们还面临着一些更高层级的问题,例如,面对一个不断变化的复杂微服务系统:
这些问题的答案靠人工去一条一条看 Trace 难以得到可靠结果。但是却可以从大量的 Trace 数据中自动化地计算出来,为最终决策提供可靠的数据支持。我们把这种对大量 Trace 的聚合计算叫做 链路分析。
链路分析的基本原理就是对大量的 Trace 进行聚合计算,一般遵循 MapReduce 计算模型,过程中可能会结合一些订阅规则和其他数据,得到计算结果,然后应用到具体的场景化应用中去。
适合对大量链路数据进行聚合计算的可选模式主要有三种,分别是从基于在线数据流的流式计算、从在线存储中查询有限条 Trace 然后进行的即兴(抽样)计算,以及基于离线数仓的离线计算。这三种计算模式的特性分析如下表所示。
计算方案 | 优点 | 缺点 |
---|---|---|
流式计算 | - 近实时的分析结果 - 数据完整性和准确度较高 - 多机房部署升级较为方便(自研未依赖 flink) | - 无法对任意时段任意条件的数据进行计算 - 受数据流量波动影响,维护成本相对较高 |
即兴(抽样)计算 | - 可对任意时段任意条件的数据发起分析并快速获取结果 - 最低的额外机器成本和运维成本 | 只能对抽样的有限条 Trace 进行计算,数据完整性较低 |
离线计算 | - 数据完整性和准确度高 - 稳定,机器成本和运维成本较低 | - 小时级或天级的延迟的分析结果 - 对大数据套件依赖较重,多区域的部署和升级成本较高 |
分析完了三种聚合计算模式的特性后,再分析下链路分析场景所面临的技术需求。
需求类型 | 描述 | 此类需求高的场景举例 | 合适的计算方案 |
---|---|---|---|
实时性需求 | 需要分析最新的数据吗还是可以接受一定的延迟? | 故障归因 | 即兴计算/流式计算 |
数据完整性需求 | 抽样一部分数据计算能否满足需求?还是必须对所有 Trace 计算才能得到结果? | 流量估算 | 离线计算/流式计算 |
需求即兴程度 | 任务是即兴发起且必须快速拿到结果的吗? | 故障归因 | 即兴计算 |
基于上述分析,我们认为并不存在一种计算模式就能解决所有问题,因此最终选择的技术方案是流式,即兴,离线一体化的技术方案:基于统一的基础数据模型和逻辑算子,支持三种不同的计算引擎,以实现不同的场景化需求。
在落地此方案的过程中,我们总结了一些有益的实践经验:
Trace 本身数据结构就较为复杂,其分析计算逻辑也往往有一定的复杂度,算子与引擎分离便于相同逻辑算子应用于不同计算引擎,可以较好地提升研发效率和代码可维护性。例如性能瓶颈分析相关算法,既可以用于即兴计算,也可以用于离线计算。
Trace 数据常常会存在一些数据不规范现象,例如超高维度的接口名,导致聚合后的数据维度远超预期,影响计算任务的稳定性。自动化的异常数据发现与封禁或降级机制能起到较好的保护作用。
尽量保留聚合分析结果对应的代表原始(极值)Trace 样本,可以提升分析结果的可解释性和用户信任度。
大数据量的计算成本较高,非基础功能可采用按需订阅模式以提升 ROI;建设对任务进行灵活降级的能力,资源紧张的情况下优先保障高频基础功能的高可用。
介绍完链路分析的底层技术架构后,我们来介绍一些具体的落地场景。
链路分析使用最高频最广泛的场景当属链路拓扑的计算。这里说的“链路拓扑”是指输入任意一个服务节点即可获取流经此节点的所有 Trace 的聚合路径,从而清晰的得知此服务节点的上下游依赖拓扑关系。
由于字节的微服务数目极多且有各类中台和基础服务,调用关系较为复杂,因此我们做拓扑计算的目标是:
举例说明什么是精准性需求:如下图所示,“抖音.X” 和 “火山.Y” 都调用了 “中台.Z”,但是对于 “抖音.X” 的流量 “中台.Z” 会使用 “Redis.抖音”,而对于 “火山.Y” 的流量 “中台.Z” 会使用 “Redis.火山”,因此实际上 “抖音.X” 和 “Redis.火山” 是没有直接依赖关系的。那么当我们检索 “抖音.X” 时希望得到的拓扑是 [“抖音.X”, “中台.Z”,“Redis.抖音”],而不要包含 “Redis.火山”。
举例说明什么是灵活性需求:如下图所示,不仅可以按照入口来检索拓扑,也可以按照中间节点 “中台.Z" 或者是存储组件节点 "Redis.抖音" 来检索拓扑;不仅可以按照服务+接口粒度来检索拓扑,也可以按照服务粒度、服务+集群粒度、服务+机房粒度等其他粒度来检索拓扑。
面对这样的技术需求,我们研究了业界现有的一些拓扑计算方案:
结合字节场景的实际需求,平衡精度、成本和检索速度,最终我们设计了一种新的方案。
方案三:为每个节点计算出一张拓扑图,存储载体选择图数据库。图数据库的一个点对应一个服务节点,图数据库的一条边对应一个["所属拓扑",“源节点”,“目标节点”]三元组。在上述例子中,有这样一些边:
所属拓扑 | 源节点 | 目标节点 | 上图中对应标注颜色 |
---|---|---|---|
抖音.X | 抖音.X | 中台.Z | 蓝色 |
抖音.X | 中台.Z | Redis.抖音 | 蓝色 |
火山.Y | 火山.Y | 中台.Z | 黄色 |
火山.Y | 中台.Z | Redis.火山 | 黄色 |
中台.Z | 抖音.X | 中台.Z | 绿色 |
中台.Z | 火山.Y | 中台.Z | 绿色 |
中台.Z | 中台.Z | Redis.抖音 | 绿色 |
中台.Z | 中台.Z | Redis.火山 | 绿色 |
精准链路拓扑的应用场景比较广,这里举一些具体的例子:
全链路实时观测:实时观测拓扑各节点的流量/延迟/错误率/资源使用率/告警/变更等,快速从全链路视角获取整体状态信息。
混沌演练:为故障演练提供链路依赖底图,生成演练计划,对全链路各个环节进行故障注入,收集与验证系统的反应得到演练报告。
压测准备: 提前梳理压测流量将会流经的节点,让相关节点做好压测准备。
全链路流量估算主要回答的问题是:
全链路流量估算是在精准拓扑计算的基础之上实现的,因此也采用了流式计算的方式,基于各路径的 Trace 数目以及 Trace 所对应的采样率数据进行估算。其计算结果格式如下图所示,每张拓扑中的每条边对应一个估算流量和流量比例。基于这样的数据,对于任意一个微服务接口,我们都可以给出上面两个问题的答案。
全链路流量估算的主要应用场景如下:
强弱依赖信息是服务稳定性治理场景的重要数据支撑,它也是可以通过线上的 Trace 数据自动化地计算出来的。
强依赖:异常发生时,影响核心业务流程,影响系统可用性的依赖称作强依赖。
弱依赖:异常发生时,不影响核心业务流程,不影响系统可用性的依赖称作弱依赖。
如下图所示,当 A 调用 B 失败时,如果 A 仍然能成功响应其 Client,则 B 是 A 的弱依赖;当 A 调用 B 失败时,如果 A 无法成功响应其 Client,则 B 是 A 的强依赖。
强弱依赖计算的技术目标包括:
为了尽可能满足数据的完整性和及时性需求,我们选择了流式计算模式,从数据流中选择带 Error 的 Trace 进行强弱依赖关系计算。需要注意短期的实时数据样本往往不够,需要结合历史累积数据共同判定再下结论。
强弱依赖分析的主要挑战:
强弱依赖分析的主要应用场景包括:
超时漏斗配置指导: 强弱依赖数据可以指导业务更合理的配置超时漏斗。如下图所示,弱依赖配置超时时间过长是不合理的,可能会导致不必要的慢请求;如果强依赖配置的超时时间过短也是不合理的,可能会导致不必要的失败请求影响用户体验。
辅助自动化故障归因: 准确的强弱依赖信息,对自动化故障归因可以起到较好的辅助作用。
在实践的过程中,我们观察到有一些非常典型的性能反模式问题是可以从 Trace 数据中自动化的计算发掘,常见的性能反模式包括:
性能反模式问题的发掘还有如下两个需求:
因此性能反模式的分析任务需要自动化发现最严重的那些反模式问题,给出极值样本,并且关联出这些问题所在路径的流量和入口优先级,从而帮助业务对服务进行延迟优化和成本优化,及早解决掉相关的潜在稳定性风险。
单请求的分布式 Trace 视图清晰直接,但是局限性在于观察者无法确定单请求所呈现出的 Trace Pattern 是普遍现象还是特殊现象。因此从大量的 Trace 数据中分析链路性能瓶颈,从而识别出整体性能 Pattern 和 worst case 样本也是链路分析的一个需求场景。
从批量 Trace 中聚合计算出链路性能 Pattern,既有即兴模式的需求,也有离线模式的需求。即兴模式下可以满足对任意时段、灵活条件(各类tag,耗时区间)筛选批量 Trace 并快速获取分析结果的需求。离线订阅模式下可以满足更完整的对全量 Trace 数据的整体性能模式分析需求,并观察长期变化趋势。因此我们会将链路性能分析聚合算子复用在即兴和离线两种计算模式下。
分析结果示例:
单条 Error Trace 可以观察出一条错误传播路径,但是观察者无法确认一条错误传播路径是否一定代表了普遍问题,也无法回答错误传播的影响面如何。因此聚合大量的 Error Trace 去分析整体的错误来源、传播路径和影响面也是链路分析的一个需求场景。
和链路性能分析类似,从批量 Trace 中聚合计算出错误传播路径,既有即兴模式的需求,也有离线模式的需求。我们也会将错误传播链分析算子应用在即兴和离线两种计算模式下。即兴模式可以满足对任意时段、灵活条件(各类tag)筛选批量 Error Trace 并快速获取聚合分析结果的需求。离线订阅模式则可以满足更完整的对全量 Error Trace 数据的聚合分析需求,并观察长期变化趋势,辅助业务进行长期的稳定性优化工作。
分析结果示例:
本文主要介绍了在完成了从零到一的链路追踪基础能力的构建之后,如何对海量的链路数据进行聚合分析来回答更高层面的场景化问题。分享了我们的具体技术选型过程和落地的技术架构,以及一些较为成功的落地案例。
最后分享后续的一些展望:
我们是字节跳动-基础架构-应用观测(服务端)团队,专注于 PB 级别海量数据的可观测性基础设施(Metrics、Tracing、Logging、Event、Profiling)和上层可观测性应用(E.g. 报警生命周期管理、异常检测、根因分析)的建设,为字节跳动整体业务的稳定性、性能优化、服务治理等方向保驾护航。