前言
传统的单体服务通过日志和性能监控为我们的系统提供了良好的观测手段,随着服务之间的交互越来越多,越来越复杂,这种“各自为政”的策略将使我们看不到整体的关联性。为了提高系统的可见性观察,分布式链路追踪被提了出来,并迅速发展。
背景
分布式体系的构建是以“拆”为核心,其目标是职责分明、高度自治。不同的模块甚至会由不同的团队负责,用不同的语言编写。当我们想要组合这些服务,对外提供统一功能时,我们还需要考虑它的一个可观察性。比如,请求里的服务依赖有哪些,各个节点的耗时是怎么样的,瓶颈在哪里等。
像这种涉及上下文请求、端到端的流向监控便是分布式链路追踪了。当我们的系统出现瓶颈或者故障时,就能根据收集到的信息快速定位问题、解决问题。这也是它的价值所在。
不过,在面对一个复杂的系统时,分布式链路追踪考虑的点就有很多了,主要有以下几点:
- 透明性:各个模块可能是由不同语言编写,我们需要考虑接入成本,最好是无需改动什么,便可以完成接入。
- 可靠性:上下文的数据收集是 24 小时持续进行的,分布式链路追踪需要考虑稳定性及规模拓展。
- 独立性:监控是辅助行为,即使链路追踪繁忙或失败,也不当影响业务的运行。
当然,最核心的设计还是在于如何将各个节点的统计信息串联起来,并进行分析展示。
解决方案
从大的层面来讲,分布式追踪其实跟日志收集优点类似。比如需要在每个节点记录性能数据,然后由专门的收集组件将数据发送到核心组件。核心组件将数据进行存储并进行一个关联关系的添加,毕竟一条完整的链路数据是来源于各个服务的。当数据分析完后,我们就可以在 Dashboard 里搜索结果了。
像现在主流的分布式链路追踪产品:Jaeger 就是这么设计的。不过,Jaeger 也是受 Google 的 Dapper 启发设计的。Dapper 是最早的跟分布式链路有关的实施产品,并有一篇论文: Dapper, a Large-Scale Distributed Systems Tracing Infrastructure
,算是分布式链路追踪系统的鼻祖了,有兴趣的小伙伴们可以自行搜索查看。
当分布式链路的产品越来越多时,统一标准便成了很多人的心声,毕竟兼容处理也是很麻烦的。后面 CNCF(云原生计算基金会)推出了 OpenTracing 项目。OpenTracing 是与平台厂商无关的链路解决方案。它并不提供具体的实现代码,仅仅只是制定规范,让接入它的人能有个一致的协议。
当前根据这个标准实现的产品比较多,像刚刚提及的 Jaeger,还有 Apache 的 Skywalking 等。今天我们来详细看下 OpenTracing 的总体设计,以及它的实现产品:Jaeger。或许以后我们也可以根据 OpenTracing 标准来实现一款属于自己的分布式链路追踪产品。
概念
Trace & Span
在广义上来讲,我们将某一次请求的完整链路抽象成了 Trace 概念。一个 Trace 就代表了一次流程的执行过程。它实际上就是一个有向无环图:
为了能清晰的描述链路里的上下文请求,我们将这些关键路径抽象为了一个个的 Span,每个 Span 是一个基本单元,具有操作名称、操作的开始、持续时间。通过各个 Span 的嵌套和排序,我们就可以建立起因果关系模型了。
这种模型能让我们更好的理解服务的层次关系、执行的上下文时间等,有助于我们快速的发现系统的调用情况。
其中,Span 除了上面的基本属性,还拥有其他关联的特性字段:
- Span Tag,Span 标签集合。
- Span Log,属于一组 Span 的日志集合
- SpanContext:Span 上下文对象,用来描述与其他 Span 的关系,有 ChildOf(父子) 和 FollowsFrom(跟随)。SpanContext 的信息添加和获取分别是通过 Injected 和 Extracted 某个 Carrier (载体)来实现的。
功能模块
当数据模型出来后,我们就可以定义接口了。当然,由于 OpenTracing 并不负责具体的实现,所以这里的接口更多是一种功能模块的描述,然后通过类似伪代码的形式来公开。此处并不具体描述这些接口的传参、返回格式,主要来看看有哪些功能模块。
首先是跨进程的边界信息传递。在这里,我们会涉及到 Span 的创建,SpanContext 的 carrier(载体)注入(Inject),以及从 carrier(载体)的提取(Extract)。关于边界的信息传递,我们可以通过埋点来统一处理,比如 Request 和 Response 的拦截器机制。
接着是 Span 生命周期的管理,上面创建出 Span 后,我们就需要有一个明确的完成时间来结束 Span。当然,如果 Span 需要有什么特性标注,那么我们也可以在这个功能模块里实现,比如 Span 的 Tag 设置。
最后是 SpanContext,由于 SpanContext 关联了上下文,所以它比较关键,在 OpenTracing 里它更多的是一个概念,具体需要哪些功能可以由开发者自己实现一套。只要包含了上面的 Inject(注入)和 Extract(提取)即可。
Jaeger
上面的标准为我们定义了功能模块及模型接口。那么我们围绕这些结构和 API 也就能实现具体的产品了。Uber 出品的 Jaeger 就是其中的一个。它主要实现了 OpenTracing 并提供以下功能:
- 分布式上下文传播
- 分布式事务监控
- 结构化的根本原因分析
- 服务依赖关系分析
- 性能分析、延迟分析
在进行具体实现的时候,Jaeger 为了能提供更好的拓展性,进行了组件的拆分,每个组件都支持单独部署,这也符合了微服务的设计理念。主要的组件如下:
- jaeger-client:Jaeger 客户端,根据 OpenTracing 的标准实现了对应的 API。例如当发生 RPC 等跨服务调用时,此时会触发 client 的 span 创建,并确定好 SpanContext 关系。
- jaeger-agent:本地存储 client 的 Span 信息,随后会将数据批量的上传到 Jaeger Collector。
- jaeger-collector:存储 agent 传来的数据,会为其建立索引并转换。
- jaeger-query:jaeger 的 dashboard 数据展示。
它的总体架构图如下:
最终,我们将看到如下的查询 UI:
可以看到,服务依赖、耗时分析等 Jaeger 都帮我们通过 web UI 呈现出来了!
总结
本文主要介绍了分布式链路追踪的标准规范:OpenTracing 以及它的实现:Jaeger。事实证明,在越复杂的系统里,我们越要尽可能的利用这些出色的组件,对其监控起来。只有提高系统的可见性,我们才不至于在问题出现的时候手忙脚乱。
参考
感兴趣的朋友可以搜一搜公众号「 阅新技术 」,关注更多的推送文章。
可以的话,就顺便点个赞、留个言、分享下,感谢各位支持!
阅新技术,阅读更多的新知识。