分布式链路追踪

分布式系统中的请求跟踪

  • 指标和日志信息,是全局化、扁平化的,它们提供的是观察系统的“广角镜头”,但对于一次复杂请求的完整过程,我们还需要一个“长焦镜头”
  • 复杂的分布式系统中,客户端的一次请求操作,可能需要经过系统中多个服务、多个中间件、多台机器的相互协作才能完成
    • 这一系列调用请求中,可能存在着多次串行及并行调用
    • 于是,如何确定客户端的一次操作背后调用了哪些服务、经过了哪些节点、每个模块的调用先后顺序是怎样的以及每个模块的性能问题如何俱变成了难题
    • 还有,一旦某个请求出现了调用失败,用户可获的信息仅有异常本身,异常发生的位置及相关细节就不得不通过分析本次请求相关的每个服务日志,过程复杂且低效

分布式跟踪系统

  • DT是指在分布式系统中跟踪一个用户请求的过程,它包括数据采集、数据传输、数据存储、数据分析和数据可视化等,而捕获此类跟踪构建用户交互背后的整个调用链的视图是调试和监控微服务的关键工具;
    • 分布式调用链其实就是将一次分布式请求还原成调用链路,显式的在后端查看一次分布式请求的调用情况,比如各个节点上的耗时、请求具体由哪些服务节点完成以及每个服务节点的请求状态等;
  • DT系统有助于定位如下问题
    • 请求通过哪些服务完成;

    • 系统瓶颈在何处,每一跳通过多长时间得以完成;

    • 哪些服务之间进行了什么样的通信;

    • 针对给定的请求的每项服务发生了什么;

分布式跟踪的实现方式

基于模式

  • 基于模式(Schema-based):通过开发人员编写时间连接模式以建立变量之间的因果关系,并通过自定义编写的日志消息予以公开;
    • 可视作手动跟踪机制;

    • 与采样机制不兼容,因为在收集所有日志之前,他们会延迟确定因果关系;

  • 黑盒接口(Blackbox Interface):不侵入应用程序,它基于关联预先存在的变量和时间通过统计或回归分析来推断因果关系;
    • 显然,它无法正确地解释异步(缓存或事件驱动的系统)行为、并发、聚合以及专有的代码模式;

  • 元数据传播(Metadata Propagation):这是谷歌关于Dapper的研究论文中所采用的方法

    • 许多实现被设计用于白盒系统,它通过修改组件以传播描述因果活动相关的元数据(例如ID等)

      • 所有基于元数据传播的实现都可以识别单个函数或跟踪点之间的因果关系,并以日志记录的形式记录相关时间内的事件

    • 从本质上讲,手动对组件在特定的跟踪点添加检测机制以跟踪函数、组件和系统之间的因果关系;另外,也可使用通用的RPC库,自动为每个调用添加元数据;
    • 跟踪的元数据包括Trace ID(代表单个跟踪或工作流)和特定跟踪中每个点的Span ID(例如,从客户端发送的请求、服务器接收的请求、服务器响应等)以及Span的开始和结束时间;
    • 有最好时效性的分布式跟踪机制;

端到端的跟踪系统工作逻辑示例

  • 图中的Trace显示了分布式系统的组件内部和组件之间的因果相关的活动工作流程以及其依赖的其它分布式服务;
  • Trace可能包含有关工作流结构的信息(例如执行的操作的因果顺序、并发数量以及分支和连接的位置),以及其性能和资源使用情况;
    • 但是,对于不同的用例,可能只需收集此信息的一部分;

端到端跟踪系统架构

  • 基于于元数据传播的分布式跟踪系统的基础组件

分布式链路追踪起源

  • Dapper是google公司在2008年就开始内部使用经过生产环境验证的链路追踪系统。
  • 2010年Google发布的Dapper论文,《Dapper,a Large_scale Distributed Systems Tracing Infrastructure(2010)》
    • https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/papers/dapper-2010-1.pdf

Google Dapper分布式追踪简介

dapper设计要求

  • 无处不在的部署:
    • 任何服务都应该被监控到,任何服务出问题都要做到有据可查。
  • 持续的监控
    • 做到7*24小时全天候监控,任何时候出了问题都要基于监控数据追踪问题根源。

dapper设计目标

  • 低消耗
    • dapper跟踪系统对服务的影响应该做到最小,在一些高并发的场合,即时很小的影响也可能会导致服务出现延迟、负载变高或不可用,从而导致业务团队可能会停止dapper系统。
  • 对应用透明
    • 应用程序对dapper系统无感知甚至不知道dapper系统的存在,假如一个跟踪系统必须依赖于应用的开发者配合才能实现跟踪,也及时需要在应用中植入跟踪代码,那么可能会因为代码产生bug或导致应用出问题。
  • 可伸缩性
    • 针对未来众多的服务和大规模业务集群,dapper系统应该能满足未来在性能的压力和功能上的需求。

dapper请求链路

dapper跟踪模型

  • 但是,对于不同的用例,可能只需收集此信息的一部分;Dapper跟踪模型使用的树形结构,Span以及Annotation; 树节点是整个架构的基本单元,而每一个节点又是对span的引用
    • 节点之间的连线表示的span和它的父span直接的关系
    • Dapper记录了span名称,以及每个span的ID和父ID,以重建在一次追踪过程中不同span之间的关系
    • 所有span都挂在一个特定的跟踪上,也共用一个跟踪id

dapper数据采集

黑盒法

黑盒发无须任何侵入性代码,它的优势在于无须修改代码,缺点在于记录不是很准确,且需要大量数据才能够推导出服务间的关系。

标记法

标记法需要为每个请求打标记,并通过一个全局标识符将请求途径的所有服务信息串联,复盘整个链路。标记法记录准确,但它的缺点也很明显,需要将标记代码注入到每个服务中。

Trace和Span

  • Trace是某事务或请求的完整流程,其间的每次调用表现为一个Span,附带的意义为一个持续的时长
    • 例如,Service A调用Service B时,网络报文来回传输的这段时长可用一个Span表示
    • 而Service B自身处理请求的时长也可用一个Span表示,这两个Span隶属于同一个Trace
  • Trace由三个主要元素构成
    • Span:基本工作单元,是指某事务或请求中的一次调用,例如一个REST调用或数据库操作等;
      • 它通过一个64位ID唯一标识,具有摘要、时间戳事件、关键值注释 (Tags)、Span ID、以及进度ID(通常是 IP 地址)和其它可选的元数据等属性;
      • 各Span知道他们的父级Span以及所属的Trace; 
    • Trace 树:一系列相关联的Span组成树状结构;
    • Annotation(标注):用来及时记录一个事件的存在,一些核心 Annotation 用来定义一个请求的开始和结束;

Span Context及其传递

  • Dapper的各种克隆实现中,其Annotation通常被称作Span Context;

    • Span Context:Span的上下文信息,包括Trace ID、Span ID以及其它需要传递到下游服务的内容;

      • 具体的实现需要将Span Context通过某种序列化机制(Wire Protocol)在进程(或服务)边界上进行传递,以将不同进程中的Span关联到同一个Trace上,这也称为Context Propagation
      • 这些Wire Protocol可以基于文本(例如HTTP header),也可以是二进制协议;

    •  如下图中,Service A在请求中注入跟踪相关的Header,而Service B读取这些Header,即为Span Context的传播

dapper跟踪树和span

Span代表系统中具有开始时间和执行时长的请求跨度,Span之间通过嵌套或者顺序排列建立逻辑因果关系。在dapper跟踪树结构中,树节点是整个架构的基本单元,是请求从前端到后端不同应用之间层级机构,而每一个节点又是对span的引用,节点之间的连接线表示的span和它的父span直接的关系。

在图中说明了span在一个大的跟踪过程中是什么样的,dapper记录了span名称,以及每个span的ID和父ID,以重建在一次追踪过程中不同span之间的关系,如果一个span没有父ID被称为root span,所有span都挂在一个特定的跟踪上,也共用一个跟踪id,所有这些ID用全局唯一的64位整数标示,在一个典型的dapper追踪中,我们希望为每一个RPC对应到一个单一的span上,而且每一个额外的组件层都对应一个跟踪树型结构的层级。

任何一个span可以包含来自不同的主机信息这些要记录下来,事实上每一个RPC span可以包含客户端和服务器两个过程的注释,使得连接两个主机的span会成为途中所说的span由于客户端和服务器上的时间戳来自不同的主机,还必须考虑到时间偏差,在分析工具就利用了时间偏差,即RPC客户端发送一个请求之后,服务端才能接收到,对于响应也是一样的(服务器先响应,然后客户端才能接收到这个响应),这样一来,服务器端的RPC就有一个时间戳的一个开始和结束,然后就可以计算出时间损耗。

Span的四个状态:

  • Client Send(CS):客户端发送请求的时间,也是Span的起始;
  • Server Received(SR):服务端的接收时间;SR减去CS时间戳便可得到网络延迟;
  • Server Send(SS):服务端发送时间;SS减去SR时间戳即为服务端需要的处理请求时长;
  • Client Received(CR):客户端接收时间,同时意味着Span的结束;CR减去CS时间戳即为客户端从服务端获取回复的全部时长;

dapper跟踪树和span示意图

生成Span启用跟踪

  •  Google Dapper通过基于标注 (Annotation-based) 的监控方案生成Span;
    • 基于标注的方式是指在代码中定义Span,并根据请求中的Trace ID来获取Trace实例本身,因此,对代码有一定的侵入性
      • 出于减少代码侵入性的目的,应该将核心跟踪代码做的很轻巧,然后把它植入公共组件中,比如线程调用、控制流以及RPC库
    • 获取到Trace实例后即能调用Recorder来记录Span

      • 记录值先直接以日志的形式存在本地,然后由跟踪系统启动一个Collector Daemon来收集日志、整理日志并写入数据库

      • Dapper建议把解析的日志结果放在BigTable 一类的存储系统中;

  • 目前,启用分布式跟踪有两个可用的基础选项

    • 具有上下文传递功能的服务网络或流量探测系统:代理自身具有检测(instrumetation)功能并能发送跟踪数据;
      • Envoy自身已经集成分布式跟踪功能,因此支持不侵入应用代码而跟踪;

    • 具有上下文传递功能的代码检测:代码调用的某底层库已经具有检测功能或由程序员手动添加自定义检测机制;

dapper植入点

dapper可以实现对于应用开发者近乎零侵入的成本对分布式请求链路进行跟踪,主要通过组件库实现:

当一个线程在处理跟踪请求链路的过程中,dapper把这次跟踪的上下文在ThreadLocal中进行存储,追踪上下文是一个小而且容易复制的空间,其中记录了scan的属性信息,比如跟踪ID和span ID。

当用户的请求处理过程是延迟调用的或是异步的,大多数google开发者通过线程池或其它执行器,使用一个通用的控制流库来回调,dapper确保所有这样的回调可以存储这次跟踪的上下文,而当回调函数被触发时,这次跟踪的上下文会与适当的线程关联上。在这种方式下,dapper可以使用trace ID和span ID来辅助构建异步调用的路径。

几乎所有的google的进程间通信是建立在一个用C++和java开发的RPC框架上。我们把跟踪植入该框架来定义RPC中所有的span,span的ID和跟踪的ID会从客户端发送到服务端,像那样的基于RPC的系统被广泛使用在google中,这是一个重要的植入点,当那些非RPC通信框架发展成熟并找到了自己的用户群之后,我们会计划对RPC通信框架进行植入。

dapper Annotation

dapper还允许应用程序开发人员在dapper跟踪的过程中添加额外的信息,以监控更高级别的系统行为,或帮助调式问题,dapper允许用户通过一个简单的API定义带时间戳的Annotation,这些Annotation可以添加任意内容,为了保护dapper的用户意外的过分热衷于日志的记录,每个跟踪span有一个可配置的总Annotation量的上限,但是应用程序级的Annotation是不能替代用于表示span结构的信息和记录这RPC相关的信息。

除了简单的文本Annotation,Dapper也支持的key-value映射的Annotation,提供给开发人员更强的跟踪能力,如持续的计数器,二进制消息记录和在一个进程上跑着的任意的用户数据。键值对的Annotation方式用来在分布式追踪的上下文中定义某个特定应用程序的相关类型。

dapper采样率

低损耗的是Dapper的一个关键的设计目标,因为如果这个工具价值未被证实但又对性能有影响的话,你可以理解服务运营人员为什么不愿意部署它。况且,我们想让开发人员使用Annotation的API,而不用担心额外的开销。我们还发现,某些类型的Web服务对植入带来的性能损耗确实非常敏感。因此,除了把Dapper的收集工作对基本组件的性能损耗限制的尽可能小之外,我们还有进一步控制损耗的办法,那就是遇到大量请求时只记录其中的一小部分。

dapper跟踪的收集实现步骤

dapper的跟踪记录和收集管道的过程分为三个阶段:

  1. span数据写入到本地日志文件中。
  2. 然后dapper的守护进程和收集组件把这些数据从生产环境的主机中进行读取。
  3. 最终写到dapper的数据仓库中。

一个跟踪被设计成Bigtable中的一行,每一列相当于一个span。bigtable的支持系数表格布局正适合这种情况,因为每一次跟踪可以有任意多个span。

dapper跟踪的代价

在生产环境的跟踪数据处理中,dapper的守护进程从来没有超过0.3%的单核cpu使用率,而且只有很少量的内存使用,另外还限制了dapper守护进程为内核scheduler最低的优先级,以防在一台高负载的服务器上发生cpu竞争。

dapper也是一个带宽资源的轻量级的消费者,每个span在我们的仓库中传输只占用了平均426的byte,作为网络行为中的极小部分,dapper的数据收集在google的生产环境中的只占用了0.01%的网络资源。

创建root span:205纳秒,创建一般的span:176纳秒

创建一个Annotation:40纳秒

写到本地磁盘 dapper本地进程:<0.3% cpu, <0.0.1%网络

dapper应用场景

性能分析:开发人员针对请求延迟的目标进行跟踪,并对容易优化的地方进行定位。

正确性分析:发现一些只读请求应该是访问从库但是却访问了主库等类似业务场景。

理解系统:全局优化系统,理解每个查询的整体代价。

测试新版本:发现新版本的bug和性能问题。

解决依赖关系:找到服务之间的依赖关系。

分布式跟踪框架

OpenTracing

  • 中立的(厂商无关、平台无关)分布式追踪的 API 规范,提供统一接口,方便开发者在自己的服务中集成一种或多种分布式追踪的实现;
  • OpenTracing自身是一个Library,它定义了一套通用的数据上报接口,各个分布式追踪系统都可实现这套接口;应用程序只需要对接 OpenTracing,而无需关心后端采用的到底什么分布式追踪系统;
  • 于 2016年10月加入CNCF基金会

  • 缺点是对于如何跟踪关系还缺乏标准

OpenCensus

  • 由Google开源的一个用来收集和追踪应用指标的第三方库

  • 提供了一套统一的测量工具:跨服务捕获跟踪span、应用级别指标以及来自其他应用的元数据(例如日志)

  • 它将自己定义为更多的可观察性框架,而不仅仅是跟踪;

OpenTelemetry

  • OpenCensus同OpenTracing用例和用途方面有很多重叠,但它们在API理念和方法方面有所不同,甚至是不相兼容
  • OpenTelemetry是由OC和OT合并生成的一个新项目;

自托管分布式跟踪系统

Zipkin

  • 由Twitter基于Dapper论文实现的开源分布式追踪系统,通过收集分布式服务执行时间的信息来达到追踪服务调用链路、以及分析服务执行延迟等目的;
  • 架构组件

    • collector:信息收集器守护进程
    • storage:存储组件
    • API:search API查询进程
    • web UI:用户界面

Jaeger

  • 由Uber实现的一款开源分布式追踪系统,兼容OpenTracing API

APM

APM系统(Application Performance Management,即应用性能管理)

APM概述

早期APM工具功能比较单一,主要以监控CPU使用率、I/O、内存资源、网速等网络基础设施为主,后来随着中间件技术的不断发展,APM也开始监控缓存、数据库、MQ等各种基础组件的性能,微服务兴起之后,系统功能被模块化,再加上k8s与容器化的兴起即应用数量的爆炸式增长,各模块和服务之间的调用链路、相应时间。负载等越来越不好通过传统的工具进行监控和统计,此时APM系统应运而生。

APM项目

  1. CAT:由国内美团点评开源的,基于java语言开发,目前提供java、C/C++、nodejs、python、go等语言的客户端,监控数据会全量统计,国内很多公司在用,例如美团点评、携程、拼多多等。CAT需要开发人员手动在应用程序中埋点,对代码侵入性比较强。
  2. Zipkin:由Twitter公司开发并开源,基于java语言实现,侵入性性对于CAT要低一点,需要对web.xml等相关配置文件进行修改,但依然对系统有一定的侵入性,Zipkin可以轻松与spring cloud进行集成,也是spring cloud推荐的APM系统。
  3. jaeger:由Uber退出的一款开源的分布式追踪系统,只要使用go语言开发,对于业务代码侵入性较少。
  4. Pinpoint:韩国团队开源的APM产品,运用了字节码增强技术,只需要在启动时添加启动参数即可实现APM功能,对代码无侵入,目前支持java和php语言,底层采用HBase来存储数据,探针收集的数据粒度非常细,但性能损耗较大,因其出现的时间较长,完成度也很高,文档也较为丰富,应用的公司较多。
  5. SkyWalking:由国内开源爱好者吴晟开源并提交到Apache孵化器的开源项目,2017年12月skywalking称为apache国内首个个人孵化项目,2019年4月17日skywalking从apache基金会的孵化器毕业成为顶级项目,目前skywalking支持java、.net、nodejs、go、python等探针,数据存储支持mysql、elasticsearch等,skywalking与pinpoint相同,对业务代码无侵入,不过探针采集数据粒度相较于pinpoint来说略粗,但性能表现优秀,目前skywalking增长势头强劲,社区活跃,中文文档齐全,没有语言障碍,支持多语言探针,这些都是skywalking的优势所在,还有就是skywalking支持很多框架,包括很多国产框架,例如,Dubbo、gRPC、SOFARPC等等,同时也有很多开发者正在不断向社区提供更多插件以支持更多组件无缝接入skywalking。
  6. 开源的:piwik
  7. 商业的:百度统计/growingio等

OpenTracing规范

由于APM系统较多,各个分布式链路追踪产品的API并不兼容,如果用在各个产品之间进行切换,成本非常高,因此社区成立了OpenTracing组织,OpenTracing通过制定统一的API标准和数据结构模型,从而帮助开发人员和用户能够方便地使用或更换追踪系统。

OpenTracing数据模型

Trace

一个Trace代表一个事务、请求或是流程在分布式系统中的执行过程。OpenTracing中的一条Trace被认为是一个由多个span组成的有向无环图,一个span代表系统中具有开始时间和执行时长的逻辑单元,span一般会有一个名称,一条trace中span是首尾连接的(从请求开始到响应结束)。

Span

span代表系统中具有开始时间和执行时长的请求跨度,span之间通过嵌套或者顺序排序建立逻辑因果关系。

每个Span都封装了以下状态:

  • 操作名称:例如访问的具体RPC服务,访问的URL地址等。
  • 开始时间戳
  • 完成时间戳
  • span Tag:一组零个或多个 key:value Span 标签。键必须是字符串。这些值可以是字符串、布尔值或数字类型。
  • span Log:一组零个或多个Span Logs,每个日志本身都是一个键:值映射与时间戳配对。键必须是字符串,但值可以是任何类型。并非所有 OpenTracing 实现都必须支持每种值类型。
  • spanContext:Trace的全局上下文信息。
    • 任何依赖于 OpenTracing 实现的状态(例如,跟踪和跨度 ID)都需要跨进程边界引用不同的Span。
    • Baggage Items,它们只是跨越流程边界的键值对。
  • References:span之间的引用关系。

Tags

每个span可以有多个键值对形式的Tags,Tags是没有时间戳的,只是为span添加一些简单解析和补充信息。

APM对比

skywalking

https://skywalking.apache.org/docs/main/latest/readme/

https://github.com/apache/skywalking

skywalking特定

  • 实现从请求跟踪、指标收集和日志记录的完整信息记录。
  • 多语言自动探针,支持java、go、python、php、nodejs、lua、rust等客户端。
  • 内置服务网格可观察性,支持lstio+envoy service mesh收集和分析数据。
  • 模块化架构、架构、集群管理、使用插件集合都可以进行自由选择。
  • 支持告警
  • 优秀的可视化效果。

skywalking组件

  • OAP平台(Observability Analysis Platform,可观测分析平台)或OAPserver,它是一个高度插件化的轻量级分析程序,由兼容各种探针Receiver、流式分析内核和查询内核三部分构成。
  • 探针:基于无侵入式的收集,并通过HTTP或者gRPC方式发送数据到OPA Server。
  • 存储实现(StorageImplementors)skywalking OPA Server支持多种存储实现并且提供了标准接口,可支持不同的存储后端。
  • UI模块:通过标准的GraphQL协议进行统计数据和展示。

skywalking设计

  • 模块化设计
    • 探针负责收集数据
    • 前端负责展示数据
    • 后端负责从后端存储读写数据
    • 后端存储负责持久化数据
  • 轻量化设计
    • skywalking在设计之处就提出了轻量化设计理念,skywalking使用最轻量级的jar包模式,实现强大的数据处理和分析能力、可扩展能力和模块化能力。
  • 面向协议设计
    • 面向协议设计是skywalking从5.x开始严格遵循的首要设计原则,组件之间使用标准的协议进行数据交互。

skywalking探针协议

  • 探针上报协议:协议包括语言探针的注册、Metric数据上报、Tracing数据上报等标准,java、go等探针都需要严格遵守此协议的标准。
  • 探针交互协议:因为分布式追踪环境,探针间需要借助HTTP Header、MQ Header在应用之间进行通信和交互,探针交互协议就定义了交互的数据格式。
  • Service Mesh协议:是skywalking对Service Mesh抽象的专用协议,任何Mesh类的服务都可以通过此协议直接上传指标数据,用于计算服务的指标数据和绘制拓扑图。
  • 第三方协议:对大型的第三方开源项目尤其是Service Mesh核心平台Istio和Envoy提供核心协议适配,支持针对Istio+Envoy Service Mesh进行无缝对接。

skywalking数据查询协议

  • 元数据查询:查询在skywalking注册的服务、服务实例、Endpoint等元数据信息。
  • 拓扑关系查询:查询全局、单个服务、Endpoint的拓扑图及依赖关系。
  • Metrics指标查询:查询指标数据。
  • 聚合指标查询:区间范围均值查询及Top N排名数据查询等。
  • Trace查询:追踪数据的明细查询。
  • 告警查询:基于表达式,判定指标数据是否超出阈值。

skywalking优势

  • 兼容性好:支持传统的部署架构dubbo和spring cloud,也支持云原生中的Istio和Envoy。
  • 易于部署和后期维护:组件化,可以自定义部署,后期横向扩容简单。
  • 高性能:每天数T的数据无压力。
  • 易于二次开发:标准的http和grpc协议,开源的项目,企业可以自主二次开发。

 

posted @ 2022-08-03 18:17  小吉猫  阅读(440)  评论(0编辑  收藏  举报