字节跳动在发展过程中,逐渐形成了十分复杂的超大规模微服务体系,对后端整体的可观测性解决方案提出了极高的要求。为了解决这个问题,基础架构智能运维团队自研链路追踪系统,将海量 Metrics/Trace/Log 数据进行整合与统一,并在此基础上实现了新一代的一站式全链路观测诊断平台,帮助业务解决监控排障、链路梳理、性能分析等问题。本文将会介绍字节跳动链路追踪系统的整体功能和技术架构,以及实践过程中我们的思考与总结。
可观测性的三大基础数据是 Metrics / Log / Trace。说到这三大件,可能大家会想到当需要监控变化趋势和配置告警时就去用 Metrics;当需要细查问题时去查 log;对于微服务数量较多的系统,还得有 Trace,Trace 也可以看做一种标准结构化的 log,记录了很多固定字段,例如上下游调用关系和耗时,查看上下游调用关系或者请求耗时在链路各节点上的分布可以查看 Trace。
但是如果带着孤立的观点去用这些数据的话,数据的价值会大大降低。举例一个常见的场景,通过 Metrics 得知某服务 SLA 降低,错误率上升,怎么去排查根因呢?先去找错误日志吧,可是我看到的错误日志是不是真的和这个错误率上升有关系呢?得翻翻代码看看这些错误日志都是哪里打出来的,代表什么意思。再去找找有没有错误 Trace?找出来的 Trace 也不太确定是不是和这个错误率上升有关系,还是得看代码确认下。终于通过一行行的代码和数据比对,确认到这个错误是下一层服务返回给我的,把那个服务的负责人拉进来一起排查吧,然后这个群越拉越大,更多的人被拖进来一层一层地查下去,最终定位到是某个底层服务上线了一个变更导致 Panic,错误层层向上传播导致服务 SLA 降低。
这个过程很不美好,需要工程师理解每一项数据的底层逻辑,才能充分利用它们去解决具体问题。而在复杂的大规模微服务系统中,没有单个工程师能够做到熟悉每一个微服务的底层逻辑,因此复杂微服务系统的排障和观测往往是一项有挑战的困难工作。
如果所有微服务的监控数据都是遵循统一模型和语义规范并且天生高度关联的呢?
在软件系统中,每秒钟有无数的 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。高度的一致性对于基础架构团队建设公司级别的统一链路追踪系统提供了有利的基础。
面对这样的现状,字节链路追踪系统围绕着一些目标展开建设。我们的功能性目标主要包括这几个方面:
在功能性目标的背后,我们追求的技术目标主要围绕这几个方面:
统一的数据模型是 Trace 的基础,字节链路追踪系统的数据模型设计借鉴了 opentracing 和 CAT 等优秀的开源解决方案,结合字节内部实际生态和使用习惯,使用如下数据模型:
下图展示了使用字节链路追踪系统 SDK 埋 Trace 的代码示例。注意其中 Context 贯穿整个请求生命周期,在进程内和跨进程间持续传递,将数据串联起来。
继续这个示例,我们结合下图阐述一下如何基于这套模型将 Metric / Trace / Log 进行可靠关联的。
Metric 关联 Trace:
Trace 关联 Log:
仅有统一的抽象数据模型还不够。如果每个服务都五花八门的随意打 tag 没有统一标准,那么即使有统一抽象模型也很难建设高质量的观测平台。必须对 HTTP Server, RPC Server, RPC Client, MySQL Client, Redis Client, MQ Consumer, MQ Producer 等各类主流场景都进行统一的语义规范,确保不同语言不同框架在相同场景下上报的数据遵循统一语义规范,才能够真正获取高质量的可观测性数据。
语义规范没有唯一标准,下面给出字节内部目前使用的部分语义规范作为参考示例。
通用基础字段
字段名称 | 描述 |
---|---|
ServiceName | 服务名称 |
Framework | 服务所使用的框架组件 |
DC | 服务所在机房 |
Cluster | TCE 上服务的逻辑集群 |
DeployStage | 部署阶段(小流量,单机房,全流量) |
IPV4 | IPV4 地址 |
IPV6 | IPV6 地址 |
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) |
Latency | RPC 调用耗时(单位微秒) |
由于字节整体线上流量非常大,微服务数目众多,不同微服务的性能敏感度、成本敏感度和数据需求各有不同,例如有些服务涉及敏感数据,必须有非常完整的追踪数据;有些服务性能高度敏感,需要优先控制采样数最小化 Overhead;测试泳道、小流量灰度或者线上问题追查等场景会需要不同的采样策略;常规流量和发生异常的流量也需要不同的采样策略。因此灵活的采样策略以及调控手段非常必要。字节链路追踪系统主要提供了如下几种采样模式:
我们结合一个示例来更好的理解什么是 PostTrace。左图是一个请求,按照阿拉伯数字标识的顺序在微服务间发生了调用,本来这条 trace 没有采样,但是在阶段 5 时发生了异常,触发了 posttrace,这个 posttrace 信息可以从 5 回传到 4,并传播给后续发生的 6 和 7,最后再回传到 1,最终可以采集到 1,4,5,6,7 这几个环节的数据,但是之前已经结束了的 2、3 环节则采集不到。右图是我们线上的一个实际的 posttrace 展示效果,错误层层向上传播最终采集到的链路的样子。PostTrace 对于错误链传播分析、强弱依赖分析等场景有很好的应用。
这些采样策略可以同时组合使用。需注意,采样不影响 Metrics 和 Log。Metrics 是全量数据的聚合计算结果,不受采样影响。业务日志也是全量采集,不受采样影响。
为了提高效率,方便不同团队高效工作,字节链路追踪系统提供了丰富的中心化配置管控能力,核心能力包括以下几个方面:
字节链路追踪系统从数据接入侧、消费存储到查询整体模块架构如上图所示。这里说一些技术细节:
前面讲目标时提到,链路追踪系统作为一个可观测性基础设施,需要优先考虑当发生断网或拥塞、机房宕机等灾难场景,业务急需观测线上状况时,保持可用。
字节链路追踪系统采用单元化部署机制,写入数据流上各机房间无通信,顶层查询模块部署在汇聚机房(同时在主机房部署备用查询节点),查询时向各机房发起检索并合并结果。
除了基础的实时检索能力以外,场景化的数据聚合分析计算也是链路追踪系统的一个重要需求,目前字节链路追踪系统支持的分析计算场景主要包括:
不同的需求场景可以选择不同的计算模式,目前字节链路追踪系统采用的计算模式主要有三种:
大部分场景的的 Trace 分析计算实质都是批量 Trace 的 MapReduce 计算,基础逻辑算子在不同的计算模式中可以复用。例如错误传播链分析计算,既可以在故障时刻进行即兴抽样计算快速得到分析结果,也可以通过离线批计算的方式进行长期订阅用于 SLO 持续优化。
基于 Metrics, Trace, 底层调用分析和容器资源监控进行毛刺慢请求根因的快速定位。
支持从任一微服务节点发起拓扑查询,实时观测各节点的流量/延迟/错误率/资源使用率/告警/变更等,快速从全链路视角获取整体状态信息,用于日常巡检、故障排查或压测观测等场景。
各业务线会经常搞些活动来促进用户增长或留存,在准备这些活动时,容量估算是一个必备阶段,过程一般如下:
字节链路追踪系统可以根据入口在历史时段上的 QPS,各节点调用比例,资源使用率等指标自动完成全链路各环节 QPS 增量与资源增量需求的一键估算。
当发生异常时,可以从在线存储中快速批量检索到异常 Trace 进行聚合计算,分析错误根源来自哪里,传播到了哪里,影响到了哪些服务,并和昨日同时段错误率进行对比,帮助工程师在遇到故障时快速定位根因。错误传播链计算也支持通过离线订阅的方式,对全时段所有异常 Trace 进行长期计算,以助力于 SLO 长期优化。
Trace 是软件系统监控数据的链接纽带,建立 Metrics/Trace/Log 的可靠关联关系,是构建强大的可观测性能力的基础,基于优质的监控数据,可以回答监控排障,SLO 调优,架构梳理,流量估算,智能化故障归因等众多复杂问题。
字节跳动在发展过程中,逐渐形成了十分复杂的超大规模微服务体系,我们面临着很多挑战,包括线上流量巨大,微服务数量巨大,迭代变化快,研发团队庞大,分工复杂等,但同时也有着难得的机遇,即全公司层面的微服务基础设施十分统一。
面对这样的现状,字节链路追踪系统围绕着一些目标展开建设,这些目标有一些是项目建设之初就明确的,也有一些是在实践过程中反思总结的,主要包括统一数据模型与语义,开放自定义,中心化配置管控,一站式观测平台;业务集成开销最小化,平衡存储效率与检索需求,多机房容灾完备性和最小化架构与依赖复杂度。
接下来分享了字节链路追踪系统的整体实现。数据采集侧的建设主要关注数据模型,语义统一,采样策略以及熔断保护等,并实现中心化配置管控。服务端的建设主要关注平衡成本和延迟,兼顾在线检索和分析计算,保障多机房的容灾完备性。
最后分享了字节链路追踪系统的一些实践应用,例如 P99 慢请求根因追查,全链路实时监控,活动大促全链路容量预估和错误传播分析等。
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8