目前很多的大中小公司都在探索和实践着微服务这种软件开发架构,将复杂庞大的重量型项目通过明确定义的 API 拆分成多个小型可复用的独立服务单元或者说是服务组件,这样让应用程序更易于扩展和更快地开发,从而加速创新并缩短新功能的上市时间。但是复用和拆分依旧简化不了系统的复杂度,虽然微服务架构拆分了业务单元,但是各种组件之间的调用也是错中复杂,因此带来一系列的问题:
- 对于服务的维护者来说,观察某一个请求(事件)的整个生命周期也是很困难;
- 难以进行用户体验优化
- 后台真实错误原因分析
分布式系统内各组件的调用情况等为了解决这个问题,曾在90年代末出现的Tracing技术流行了起来,在早期,出现了一大批优秀的 Tracing 软件:
- Dapper(Google) : 各 tracer 的基础
- Zipkin
- 鹰眼(taobao)
- 谛听(盘古,阿里云云产品使用的Trace系统)
- ...
尽管分布式追踪系统发展很快,种类繁多,但核心步骤一般有三个:代码埋点
, 数据存储
、 查询展示
。但在数据采集过程中,由于需要侵入用户代码,并且不同系统的 API 并不兼容,这就导致了如果您希望切换追踪系统,往往会带来较大改动,出现了痛点,就会有人解决,为了解决不同的分布式追踪系统 API 不兼容的问题,诞生了 OpenTracing 规范,Opentracing也正是为了解决这样的痛点。
- OpenTracing 进入 CNCF,正在为全球的分布式追踪,提供统一的概念和数据标准。
- OpenTracing 通过提供平台无关、厂商无关的 API,使得开发人员能够方便的添加(或更换)追踪系统的实现。
也就是说,遵从Opentracing的规范,就相当于在应用程序/类库和追踪或日志分析程序之间定义了一个轻量级的标准化层,解耦了代码和Tracing API。
那么tracing究竟是什么?
在广义上,一个trace代表了一个事务或者流程在(分布式)系统中的执行过程。在OpenTracing标准中,trace是多个span组成的一个有向无环图(DAG),每一个span代表trace中被命名并计时的连续性的执行片段。
分布式追踪中的每个组件都包含自己的一个或者多个span。例如,在一个常规的RPC调用过程中,OpenTracing推荐在RPC的客户端和服务端,至少各有一个span,用于记录RPC调用的客户端和服务端信息
一个父级的span会显示的并行或者串行启动多个子span。在OpenTracing标准中,甚至允许一个子span有个多父span(例如:并行写入的缓存,可能通过一次刷新操作写入动作)。
在一个分布式系统中,追踪一个事务或者调用流一般如上图所示。虽然这种图对于看清各组件的组合关系是很有用的,但是,它不能很好显示组件的调用时间,是串行调用还是并行调用,如果展现更复杂的调用关系,会更加复杂,甚至无法画出这样的图。另外,这种图也无法显示调用间的时间间隔以及是否通过定时调用来启动调用。一种更有效的展现一个典型的trace过程,如下图所示:
这种展现方式增加显示了执行时间的上下文,相关服务间的层次关系,进程或者任务的串行或并行调用关系。这样的视图有助于发现系统调用的关键路径。通过关注关键路径的执行过程,项目团队可能专注于优化路径中的关键位置,最大幅度的提升系统性能。例如:可以通过追踪一个资源定位的调用情况,明确底层的调用情况,发现哪些操作有阻塞的情况。
tracing,monitoring和logging
可是说起tracing,脑子就会有一些疑问,tracing,monitoring和logging之间究竟有什么区别呢?先来看一张图
对于监控来说,比如我们常用的Prometheus是通过pull的方式有频率的定量的向目标收集指标,然后将数据进行聚合计算,形成报告,对有问题的异常数据进行报警,所以Monitoring的需求并没有包含非常高的并发量和通讯量。反过来说:高并发、大数据量的需求并不适用于Monitoring这个点。
在了解Opentracing之后,你会发现Optracing规范是有标准固定的格式的,这也是和logging最大的不同,tracing搜集一个事件过程中所有指定搜集的项,比如某个接口的请求时间,当收到某个SQL后,这个SQL的实行时间等,会将这些时间汇总到一个报告中,形成一个tracing,因为Tracing是针对某一个事件(一般来说就是一个API),而这个API可能会和很多组件进行沟通,后续的所有的组件沟通无论是内部还是外部的IO,都算作这个API调用的Tracing的一部分。可以想见在一个业务繁忙的系统中,API调用的数量已经是天文数字,而其衍生出来的Tracing记录更是不得了的量。其特点就是高频、巨量,一个API会衍生出大量的子调用。
一般来说我们在大型系统中提到Logging说的都不是简单的日志,而是日志聚合系统。从本质上来说,Monitoring和Tracing都是Logging,Logging是这三者中覆盖面最大的超集,而前两者则是其一部分的子集。Logging最麻烦的是,开发者也不会完全知道最后记录进入到日志系统里的一共会有哪些东西,只有在使用(检索)的时候才可能需要汇总查询总量中的一部分。
每个组件都有它自己存在的必要性:
- Monitoring系统(Prometheus)从根本的需求和基本设计上就不可能支持Tracing和Logging:低频 vs 高频、低量 vs 高量,其从设计到实现就只为了监控服务
- Tracing系统(Jaeger)对提供的数据有格式要求,且处理方式和一般的Logging也不同,有更限定的应用范围
- Logging系统(ELK)可以处理前两者的需求,但前两者的领域有更专业的工具就不推荐直接使用普通的日志聚合系统了;Logging系统一般用来处理大型系统的日志聚合以及检索查询
OpenTracing 实现之Jaeger 和 Zipkin
Jaeger目前是CNCF中的一个孵化项目,是 Uber 推出的一款开源分布式追踪系统,兼容 OpenTracing API,所以我们这里主要说一说Opentracing的后起之秀jaeger。先来看看Jaeger的架构图:
- Jaeger Client - 为不同语言实现了符合 OpenTracing 标准的 SDK。应用程序通过 API 写入数据,client library 把 trace 信息按照应用程序指定的采样策略传递给 jaeger-agent。
- Agent - 它是一个监听在 UDP 端口上接收 span 数据的网络守护进程,它会将数据批量发送给 collector。它被设计成一个基础组件,部署到所有的宿主机上。Agent 将 client library 和 collector 解耦,为 client library 屏蔽了路由和发现 collector 的细节。
- Collector - 接收 jaeger-agent 发送来的数据,然后将数据写入后端存储。Collector 被设计成无状态的组件,因此您可以同时运行任意数量的 jaeger-collector。
- Data Store - 后端存储被设计成一个可插拔的组件,支持将数据写入 cassandra、elastic search。
- Query - 接收查询请求,然后从后端存储系统中检索 trace 并通过 UI 进行展示。Query 是无状态的,您可以启动多个实例,把它们部署在 nginx 这样的负载均衡器后面。
当处理的数据量很大的时候,jaeger-collector就会面临着一些性能的瓶颈,无法及时存储 jaeger-agent 发送来的数据,因此官方也支持使用kafka做为缓冲区,下面是架构图:
在熟悉了jaeger之后,为了更快的熟悉并掌握jaeger的使用,我们在kubernetes上安装一下jaeger,并且在Edge Router Traefik2.0中使用jaeger配置一下tracing. jaeger的官方支持四种后端存储:
- Cassandra 3.4
- Elasticsearch 5.x, 6.x, 7.x
- Kafka
- memory storage
因为我们k8s集群里面已经运行着日志集群了,所以我们直接时候用Elasticsearch作为jaeger的后端存储.
代码语言:javascript复制☸️ devcluster