分布式链路追踪在字节跳动的实践

共 9153字,需浏览 19分钟

 ·

2021-12-31 22:49

点击上方“服务端思维”,选择“设为星标

回复”669“获取独家整理的精选资料集

回复”加群“加入全国服务端高端社群「后端圈」


作者 | 智能运维团队
出品 | 字节跳动技术团队

综述

字节跳动在发展过程中,逐渐形成了十分复杂的超大规模微服务体系,对后端整体的可观测性解决方案提出了极高的要求。为了解决这个问题,基础架构智能运维团队自研链路追踪系统,将海量 Metrics/Trace/Log 数据进行整合与统一,并在此基础上实现了新一代的一站式全链路观测诊断平台,帮助业务解决监控排障、链路梳理、性能分析等问题。本文将会介绍字节跳动链路追踪系统的整体功能和技术架构,以及实践过程中我们的思考与总结。

什么是分布式链路追踪(Trace) ?

M T L 的关系

可观测性的三大基础数据是 Metrics / Log / Trace。说到这三大件,可能大家会想到当需要监控变化趋势和配置告警时就去用 Metrics;当需要细查问题时去查 log;对于微服务数量较多的系统,还得有 Trace,Trace 也可以看做一种标准结构化的 log,记录了很多固定字段,例如上下游调用关系和耗时,查看上下游调用关系或者请求耗时在链路各节点上的分布可以查看 Trace。

但是如果带着孤立的观点去用这些数据的话,数据的价值会大大降低。举例一个常见的场景,通过 Metrics 得知某服务 SLA 降低,错误率上升,怎么去排查根因呢?先去找错误日志吧,可是我看到的错误日志是不是真的和这个错误率上升有关系呢?得翻翻代码看看这些错误日志都是哪里打出来的,代表什么意思。再去找找有没有错误 Trace?找出来的 Trace 也不太确定是不是和这个错误率上升有关系,还是得看代码确认下。终于通过一行行的代码和数据比对,确认到这个错误是下一层服务返回给我的,把那个服务的负责人拉进来一起排查吧,然后这个群越拉越大,更多的人被拖进来一层一层地查下去,最终定位到是某个底层服务上线了一个变更导致 Panic,错误层层向上传播导致服务 SLA 降低。

这个过程很不美好,需要工程师理解每一项数据的底层逻辑,才能充分利用它们去解决具体问题。而在复杂的大规模微服务系统中,没有单个工程师能够做到熟悉每一个微服务的底层逻辑,因此复杂微服务系统的排障和观测往往是一项有挑战的困难工作。

Trace 是数据的链接纽带

如果所有微服务的监控数据都是遵循统一模型和语义规范并且天生高度关联的呢?

在软件系统中,每秒钟有无数的 Context 在流动。这些 Context 可能是一个实时在线请求,也可能是一个异步处理任务。每个 Context 都会在多个微服务节点中持续传播才能最终完成。所有的监控数据(包括 Metric, Log 等)都源自于某一个 Context。Trace 就是这个 Context 的数据载体,通过标准化的数据模型,记录 Context 在多个微服务中的全部执行过程,并沿途关联上此 Context 上发生的所有事件(包括 Metric, Log 等)。

再回到刚才那个 Case,当我们对某个 Metric 波动发生兴趣时,可以直接将造成此波动的 Trace 关联检索出来,然后查看这些 Trace 在各个微服务中的所有执行细节,发现是底层某个微服务在执行请求过程中发生了 Panic,这个错误不断向上传播导致了服务对外 SLA 下降。如果可观测平台做得更完善一些,将微服务的变更事件数据也呈现出来,那么一个工程师就可以快速完成整个排障和根因定位的过程,甚至不需要人,通过机器就可以自动完成整个排障和根因定位过程。

Trace 不仅仅是用来查看耗时分布甘特图的工具,也是海量监控数据的 Context 链接纽带。基于可靠关联的 Metric / Trace / Log 数据,也构建出强大的可观测性能力,回答监控排障、SLO 调优、架构梳理、流量估算、智能化故障归因等众多复杂问题。

Trace 的采集以及跨服务进程的 Context 传递一般是由微服务框架等基础设施自动完成的,但是要实现最佳效果也需要所有研发工程师的理解和配合。研发工程师在编码的过程中应当有意识地在所有代码执行过程中持续传递 Context。比如在 Golang 中,context.Context 需要在所有函数调用中作为参数持续传递;在 Java 中,一般默认用 Threadlocal 作为 Context 的存储载体,但是如果有多线程或者异步的场景,则需要开发者自行对 Context 进行显式的传递,否则上下文就断了,难以实现有效的追踪和监控。

字节链路追踪系统的挑战与机遇

字节跳动在发展过程中,逐渐形成了十分复杂的超大规模微服务体系,对后端整体的可观测性解决方案提出了极高的要求。

我们面临的挑战包括:

  • 线上流量巨大
  • 微服务数量巨大,调用关系复杂,迭代变化快
  • 研发团队庞大,分工复杂

目前字节跳动有巨大的流量,众多的活跃微服务、容器实例数,以及庞大的研发团队。一个复杂业务链路动辄涉及数百个微服务,有一线业务,有中台,也有基础设施,不同微服务由不同的研发团队开发,同时还有各类横向团队负责整体架构的质量、稳定性、安全和效率等相关工作。不同团队对链路追踪系统都会有不一样的诉求。

同时我们也有着难得的机遇:

  • 微服务框架高度统一
  • 微服务高度容器化,环境统一
  • 存储/中间件等基础设施高度统一

得益于长期的统一基建工作,字节全公司范围内的所有微服务使用的底层技术方案统一度较高。绝大部分微服务都部署在公司统一的容器平台上,采用统一的公司微服务框架和网格方案,使用公司统一提供的存储组件及相应 SDK。高度的一致性对于基础架构团队建设公司级别的统一链路追踪系统提供了有利的基础。

字节链路追踪系统的目标

面对这样的现状,字节链路追踪系统围绕着一些目标展开建设。我们的功能性目标主要包括这几个方面:

  • 统一数据模型与语义:统一数据模型和语义规范,对所有主流框架/组件进行默认埋点中间件的替换升级,建立 Metrics / Trace / Log 可靠关联关系。
  • 开放自定义:统一模型的基础上,充分开放自定义能力,满足不同业务场景的监控追踪需求。
  • 中心化配置管控:中心化动态管理采样、染色、熔断限流、索引、脱敏保密等各类策略。
  • 一站式观测平台:提供从 SDK 到采集、计算、存储、查询和产品化交互的完整解决方案,基于高质量基础数据,构建一站式观测平台,提升监控排障、SLO 调优、架构梳理、容量管理等场景的能效。

在功能性目标的背后,我们追求的技术目标主要围绕这几个方面:

  • 业务集成开销最小化:集成开销包括业务接入的改造成本和接入后带来的 Overhead 开销。大范围的链路追踪能够成功覆盖推广,必须保证将集成开销降到最低。
  • 平衡存储效率与检索需求:需要以有限的机器预算完成较大数据量的处理和存储,保证数据从产生到可被检索的延迟在分钟级以内,检索响应速度在秒级以内。
  • 多机房容灾完备性:需要优先考虑当发生断网或拥塞、机房宕机等灾难场景,业务急需观测线上状况时,保持可用。
  • 最小化架构与依赖复杂度:字节在海内外有众多机房,需尽可能最小化整体架构的复杂度和第三方依赖的复杂度,否则多机房的部署运维包括容灾完备性保障会非常困难。

字节链路追踪系统的实现

数据采集

数据模型

统一的数据模型是 Trace 的基础,字节链路追踪系统的数据模型设计借鉴了 opentracing 和 CAT 等优秀的开源解决方案,结合字节内部实际生态和使用习惯,使用如下数据模型:

  • Span: 一个有时间跨度的事件,例如一次 RPC 调用,一个函数执行。
  • Event: 一个没有时间跨度的事件,例如一条 log,一次 panic。
  • Metric: 一个带多维 tag 的数值,例如一个消息体的大小,一个订单的价格。
  • Trace: 一个请求上下文在多个分布式微服务节点的完整执行链路。
  • Transaction: 一条 Trace 在单个服务节点上的所有 Span / Event / Metric 对象构成的树形结构消息体。Transaction 是 Trace 数据的处理和存储的最小单位,紧凑的数据结构有利于节约成本和提高检索性能。

下图展示了使用字节链路追踪系统 SDK 埋 Trace 的代码示例。注意其中 Context 贯穿整个请求生命周期,在进程内和跨进程间持续传递,将数据串联起来。

继续这个示例,我们结合下图阐述一下如何基于这套模型将 Metric / Trace / Log 进行可靠关联的。

Metric 关联 Trace:

  • 每个 Span 会有内置的频次/耗时/失败率 Metric 统计时序,Rpc/Mq 场景的 Span 还会有 SendSize/RecvSize/MqLag 等内置统计时序。Span Tag 和 Metric Tag 一一对应,以此为依据可以将 Span 时序指标与 Trace 中的 Span 可靠关联。
  • 每个 Event 不仅会挂载在 Span 上,也会有内置的频次 Metric 统计时序。Event Tag 与 Metric Tag 一一对应,以此为依据可以将 Event 时序指标与 Trace 中的 Event 可靠关联。
  • 每个 Metric 不仅会挂载在 Span 上,也会按 Metric 类型输出 rate/timer/store 等各类统计时序,两边 Tag 一一对应,以此为依据可以将 Metric 时序指标与 Trace 中的 Metric 对象可靠关联。

Trace 关联 Log:

  • Log SDK 会将 Context 中的 TraceID 和 SpanID 写入日志头中,通过 TraceID 和 SpanID 与 Trace 建立可靠关联。

语义规范

仅有统一的抽象数据模型还不够。如果每个服务都五花八门的随意打 tag 没有统一标准,那么即使有统一抽象模型也很难建设高质量的观测平台。必须对 HTTP Server, RPC Server, RPC Client, MySQL Client, Redis Client, MQ Consumer, MQ Producer 等各类主流场景都进行统一的语义规范,确保不同语言不同框架在相同场景下上报的数据遵循统一语义规范,才能够真正获取高质量的可观测性数据。

语义规范没有唯一标准,下面给出字节内部目前使用的部分语义规范作为参考示例。

通用基础字段

字段名称描述
ServiceName服务名称
Framework服务所使用的框架组件
DC服务所在机房
ClusterTCE 上服务的逻辑集群
DeployStage部署阶段(小流量,单机房,全流量)
IPV4IPV4 地址
IPV6IPV6 地址
PodName容器唯一名称
Env服务部署的环境泳道

场景化语义规范示例:RPC Client 场景

字段名称含义
Method此 RPC Client 调用发起时所在的服务接口
ToService被调用的远端 Server 服务名
ToMethod被调用的远端 Server 接口名称
ToServiceType被调用的远端 Server 服务类型,例如 thrift/grpc
ToCluster被调用的远端 Server 集群名
ToDc被调用的远端 Server 机房
ToAddr被调用的远端 Server IP:Port 地址
StatusCode系统状态码
BusinessStatusCode业务状态码
IsError此字段标识请求是否失败,0: 成功 1: 失败,框架默认会按照是否发生系统层面错误设置此字段的值,也允许业务自行调整这个字段的值,此字段会用于默认的错误率监控告警等场景
SendSize发送数据大小(单位 Byte)
RecvSize收到数据大小(单位 Byte)
LatencyRPC 调用耗时(单位微秒)

采样策略

由于字节整体线上流量非常大,微服务数目众多,不同微服务的性能敏感度、成本敏感度和数据需求各有不同,例如有些服务涉及敏感数据,必须有非常完整的追踪数据;有些服务性能高度敏感,需要优先控制采样数最小化 Overhead;测试泳道、小流量灰度或者线上问题追查等场景会需要不同的采样策略;常规流量和发生异常的流量也需要不同的采样策略。因此灵活的采样策略以及调控手段非常必要。字节链路追踪系统主要提供了如下几种采样模式:

  • 固定概率采样+低流量接口兜底采样:默认以 Logid 作为采样种子,按固定概率进行采样。对于流量较低的接口,按固定概率采样难以命中的,SDK 会自动按一定的时间间隔进行兜底采样,确保低流量接口也有一定数目的请求被采集。
  • 自适应概率采样:按单位时间对每个接口采集一定数目的 Transaction 为目标,例如 100 条/min,SDK 自动根据当前 QPS 动态计算采样率进行采样。流量较低或不稳定的服务建议采取这种模式。
  • 染色采样:对特定的请求添加染色标记,SDK 检测到染色标对该请求进行强制采样。
  • PostTrace 后置采样: 当一个 Trace 一开始未命中采样,但在执行过程中发生了一些令人感兴趣的事(例如出错或时延毛刺)时,可以在 Trace 中间状态发起采样。相较于先全采再后置采样,此方案开销极低。PostTrace 是前置概率采样的一个重要补充,可以针对性地采集到异常链路,相比于先全采后 tail-based sampling 方案其开销是极小的。但 PostTrace 的缺点只能采集到 PostTrace 时刻尚未结束的 Span,因此数据完整性相较前置采样有一定损失。

我们结合一个示例来更好的理解什么是 PostTrace。左图是一个请求,按照阿拉伯数字标识的顺序在微服务间发生了调用,本来这条 trace 没有采样,但是在阶段 5 时发生了异常,触发了 posttrace,这个 posttrace 信息可以从 5 回传到 4,并传播给后续发生的 6 和 7,最后再回传到 1,最终可以采集到 1,4,5,6,7 这几个环节的数据,但是之前已经结束了的 2、3 环节则采集不到。右图是我们线上的一个实际的 posttrace 展示效果,错误层层向上传播最终采集到的链路的样子。PostTrace 对于错误链传播分析、强弱依赖分析等场景有很好的应用。

这些采样策略可以同时组合使用。需注意,采样不影响 Metrics 和 Log。Metrics 是全量数据的聚合计算结果,不受采样影响。业务日志也是全量采集,不受采样影响。

中心化配置管控

为了提高效率,方便不同团队高效工作,字节链路追踪系统提供了丰富的中心化配置管控能力,核心能力包括以下几个方面:

  • 采样策略:支持业务按照不同的集群、接口、机房、部署阶段设置不同的概率采样策略;也可以动态设置染色、PostTrace 触发条件。
  • 自定义索引:不同的框架和场景会有不同的默认索引字段,也支持业务按需在默认索引的基础上为自定义字段创建索引。
  • 熔断保护:SDK 默认配备多种熔断保护机制确保 Trace 采集不会占用过多资源影响主线功能,同时允许业务根据实际情况对相关的熔断参数进行动态调整。
  • 脱敏保密:业务可以按需对 Trace 数据进行脱敏和保密。

整体架构

整体架构

字节链路追踪系统从数据接入侧、消费存储到查询整体模块架构如上图所示。这里说一些技术细节:

  • 私有协议数据流,性能更极致:从 SDK 到最终写入存储,整体数据流采用私有协议,数据流中各环节仅需解码部分 header 即可完成处理,无需解码所有内容,可以节约大量的中间环节资源,降低时延。
  • 底层本高吞吐的字节自研日志存储:以较低的存储成本实现较高的写入速度和查询性能,用于支持各类 Trace 在线检索场景。
  • 单元化架构保障多机房容灾完备性:整体采用单元化架构,节约机房间网络带宽,在部分机房间网络故障或单机房宕机时保持高可用。
  • 精细灵活的中心化调控能力:统一的配置中心向整个数据流各阶段下发各类动态配置发并实时生效。
  • 兼顾在线实时查询与计算分析:如架构图所示,数据流主要分两条,一条负责数据的在线存储和实时查询,要求链路尽可能短,追求性能极致,压低时延,保证数据从产生到可检索要尽可能快+高可用;另一条是计算分析流,对延迟的要求相对较低,但是需要满足各类场景化的计算分析需求,与公司数仓平台有较好的集成。
  • 元数据采集与安全过期:从 Trace 数据流中可以采集到准确度和时效性很高的元数据,例如每个微服务有哪些活跃接口,使用了哪些框架组件等信息,为多个第三方系统例如监控告警和服务治理等平台提供支持。

多机房容灾完备性

前面讲目标时提到,链路追踪系统作为一个可观测性基础设施,需要优先考虑当发生断网或拥塞、机房宕机等灾难场景,业务急需观测线上状况时,保持可用。

字节链路追踪系统采用单元化部署机制,写入数据流上各机房间无通信,顶层查询模块部署在汇聚机房(同时在主机房部署备用查询节点),查询时向各机房发起检索并合并结果。

  • 写入流主机房间无数据通信,主机房间发生断网时,功能不受损。
  • 当发生单机房宕机或孤岛时,可执行预案在查询侧屏蔽掉故障机房,保证其他机房数据可用。
  • 当顶层查询模块所在的机房宕机或断网时,可执行预案将查询切到备用机房查询节点继续提供服务。

分析计算

除了基础的实时检索能力以外,场景化的数据聚合分析计算也是链路追踪系统的一个重要需求,目前字节链路追踪系统支持的分析计算场景主要包括:

  • 拓扑计算:为每个微服务(精确到接口/集群/机房粒度)计算上下游依赖链路拓扑骨架,满足业务架构梳理,全链路监控等场景需求。
  • 流量估算:聚合计算所有 Trace 并结合每条 Trace 的采样率估算出链路的原始流量及调用比例,满足活动扩容评估,流量来源分析,成本分摊计算等场景需求。
  • 错误链分析:聚合计算含有错误的 Trace 的错误传播路径,满足故障根因定位,错误影响面分析,易故障点优化等场景需求。
  • 链路性能分析:聚合计算满足特定条件的 Trace 并分析各环节的耗时及调用比例等数据,满足全链路性能优化,耗时增加根因定位等场景需求。

不同的需求场景可以选择不同的计算模式,目前字节链路追踪系统采用的计算模式主要有三种:

  • 近实时流式计算:从消息队列中消费数据按照时间窗口进行流式聚合计算,近实时地不断更新计算结果。例如拓扑计算主要采取此模式,以获取近实时的拓扑骨架。
  • 即兴抽样计算:即兴从在线存储中按照特定条件抽样检索出有限数目(例如数百条)的 Trace 进行聚合计算,快速获得计算结果。
  • 离线批计算:定时对离线数仓中的 Trace 进行 MapReduce 批计算,输出分析结果。

大部分场景的的 Trace 分析计算实质都是批量 Trace 的 MapReduce 计算,基础逻辑算子在不同的计算模式中可以复用。例如错误传播链分析计算,既可以在故障时刻进行即兴抽样计算快速得到分析结果,也可以通过离线批计算的方式进行长期订阅用于 SLO 持续优化。

现阶段实施效果

  • 吞吐量:Transaction 数 10 Million/秒(默认采样率 0.1%),索引数 300 Million/秒
  • 查询性能:TraceID 检索性能 P50 < 100 毫秒, P99 < 500 毫秒
  • 数据产生到可检索整体时延: AVG ≈ 1 分钟,P99 < 2 分钟
  • 存储资源:5 PB (2 副本 TTL 15 天)

实践应用案例

P99 慢请求根因追查

基于 Metrics, Trace, 底层调用分析和容器资源监控进行毛刺慢请求根因的快速定位。

全链路实时监控

支持从任一微服务节点发起拓扑查询,实时观测各节点的流量/延迟/错误率/资源使用率/告警/变更等,快速从全链路视角获取整体状态信息,用于日常巡检、故障排查或压测观测等场景。

活动大促全链路容量预估

各业务线会经常搞些活动来促进用户增长或留存,在准备这些活动时,容量估算是一个必备阶段,过程一般如下:

字节链路追踪系统可以根据入口在历史时段上的 QPS,各节点调用比例,资源使用率等指标自动完成全链路各环节 QPS 增量与资源增量需求的一键估算。

故障来源与影响面分析

当发生异常时,可以从在线存储中快速批量检索到异常 Trace 进行聚合计算,分析错误根源来自哪里,传播到了哪里,影响到了哪些服务,并和昨日同时段错误率进行对比,帮助工程师在遇到故障时快速定位根因。错误传播链计算也支持通过离线订阅的方式,对全时段所有异常 Trace 进行长期计算,以助力于 SLO 长期优化。

小结

Trace 是软件系统监控数据的链接纽带,建立 Metrics/Trace/Log 的可靠关联关系,是构建强大的可观测性能力的基础,基于优质的监控数据,可以回答监控排障,SLO 调优,架构梳理,流量估算,智能化故障归因等众多复杂问题。

字节跳动在发展过程中,逐渐形成了十分复杂的超大规模微服务体系,我们面临着很多挑战,包括线上流量巨大,微服务数量巨大,迭代变化快,研发团队庞大,分工复杂等,但同时也有着难得的机遇,即全公司层面的微服务基础设施十分统一。

面对这样的现状,字节链路追踪系统围绕着一些目标展开建设,这些目标有一些是项目建设之初就明确的,也有一些是在实践过程中反思总结的,主要包括统一数据模型与语义,开放自定义,中心化配置管控,一站式观测平台;业务集成开销最小化,平衡存储效率与检索需求,多机房容灾完备性和最小化架构与依赖复杂度。

接下来分享了字节链路追踪系统的整体实现。数据采集侧的建设主要关注数据模型,语义统一,采样策略以及熔断保护等,并实现中心化配置管控。服务端的建设主要关注平衡成本和延迟,兼顾在线检索和分析计算,保障多机房的容灾完备性。

最后分享了字节链路追踪系统的一些实践应用,例如 P99 慢请求根因追查,全链路实时监控,活动大促全链路容量预估和错误传播分析等。

— 本文结束 —


● 漫谈设计模式在 Spring 框架中的良好实践

● 颠覆微服务认知:深入思考微服务的七个主流观点

● 人人都是 API 设计者

● 一文讲透微服务下如何保证事务的一致性

● 要黑盒测试微服务内部服务间调用,我该如何实现?



关注我,回复 「加群」 加入各种主题讨论群。



对「服务端思维」有期待,请在文末点个在看

喜欢这篇文章,欢迎转发、分享朋友圈


在看点这里
浏览 19
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报