新手问题 jaeger的技术演进之路

cdh0805010118 · 2018年07月14日 · 426 次阅读

jaeger uber 的分布式跟踪系统,它是 CNCF 基金会的第 15 个项目。在 16 年前,uber 内部使用的分布式跟踪系统,是采用的 twitter 公司的解决方案——Zipkin。 后来,uber 内部想把 Zipkin 的拉取改为推送架构,就逐渐的形成了自己的分布式跟踪系统,最终演变为产品:jaeger。

Merckx

uber 早期的追踪系统叫做 Merckx,它应该是使用了 Zipkin 解决方案,Merckx 采用了拉取架构,可以从 kafka 队列中拉取数据流。但是它最大的不足之处表现在两个方面:

  1. 它的设计主要面向 Uber 使用整体式 API 的年代。缺乏分布式上下文传播 context 的概念,虽然可以记录 SQL 查询、Redis 调用,甚至对其他服务的调用,但是无法进一步深入。(具体不太清楚)
  2. 另一个有趣的局限是数据存储在全局线程的本地存储中,随着 Tornado web 框架的引入,这种方式变得不可行。

TChannel

随后,随着微服务的需要求到来,RPC 框架变得越来越重要,2015 年初内部开始开发 RPC 框架——TChannel。其中设计目标之一是将类似于 Dapper 分布式追踪能力融入到协议中,而 OpenTracing 标准产生于 2016 年 11 月份左右,所以 TChannel 一开始并不遵循 OpenTracing 标准。至于后面的发展,后面再看。::TODO

虽然 TChannel 是与 Zipkin 解决方案完全无关,但是还是借鉴了后者的一些追踪设计。从内部来看,TChannel 的 Span 在格式上与 Zipkin 几乎完全相同,也使用了 Zipkin 所定义的注释,例如:"cs"(Client Send) 和"cr"(Client Receive)。

TChannel 使用追踪报告程序(Reporter)接口将收集到的进程外追踪 Span 发送至追踪系统的后端。该技术自带的库默认包含一个使用 TChannel 本身和 Hyperbahn 实现的报告程序以及发现和路由层,借此将 Thrift 格式的 Span 发送至收集器集群。

TChannel 客户端库接近我们所需要的分布式追踪系统,该客户端库提供了下列模块:

  1. 追踪上下文传播以及带内请求(IPC/RPC)
  2. 通过编排 API 记录追踪 Span;(也就是,把 trace 跟踪进行组件封装)
  3. 追踪上下文的进程内传播;(这个一般都很少使用)
  4. 将进程外追踪数据报告至追踪后端所需的格式和机制(也就是,Span 相关数据转换为 Collector 和 Stoage 能够接收和存储的数据格式,这个工作既可以交给 Collector 来做,也可以 Agent 主动做好再推送)

该系统唯独缺少了追踪后端本身,即 Collector 和 Storage。追踪上下文的传输格式和报表程序使用的默认 Thrift 格式在设计上都可以非常简单直接地将 TChannel 和 Zipkin 后端集成。然而当时只能通过 Scribe 将 Span 发送至 Zipkin,而 Zipkin 只支持 Cassandra 格式的数据存储。因为当时 Uber 对这个存储没有什么技术经验,所以,他们自己开发了一套后端原型系统,并结合 Zipkin UI 的一些自定义组件构建了一个完成的分布式跟踪系统。

也即,uber 开发的后端原型系统 Collector 和 Storage 分别是 tcollector(node.js) 和 Riak 存储 (Spans),Solr 索引库 (Indexing), 然后通过 Zipkin UI 来进行查询。

但是随着业务迅猛发展, 后端原型系统架构所使用的 Riak/Solr 存储系统无法妥善缩放以适应 Uber 的流量,同时很多查询功能依然无法与 Zipkin UI 实现足够好的交互操作。同时 Uber 内部系统语言上的异构,以及还有很多核心业务使用的自己 RPC 框架,这些异构的技术环境使得分布式追踪系统的构建变得困难。

Jaeger

针对 Merckx 产品和 Uber 内部技术的异构,使得需要更专职的团队做分布式跟踪系统——Jaeger。Jaeger 的目标:将现有的 Merckx 原型系统转换为可以全局运用的生产系统,让分布式追踪功能可以适用并适应 Uber 的微服务

新的团队在 Cassandra 集群方面已经具备运维经验,该数据库直接为 Zipkin 后端提供支持,因此团队决定弃用 Riak/Solr 存储系统。同时,为了接受 TChannel 流量并将数据兼容 Zipkin 的二进制格式存储在 Cassandra 中,使用 Golang 重新实现了 collector。这样对于 Zipkin 的 dashboard 就无需改动,完全兼容了。

同时一个很大的改进点,他们还为每个收集器构建了一套可动态配置的倍增系统 (Multiplication factor),借此将入站流量倍增 N 次,这主要是为了分布式跟踪系统的压测,看看 Jaeger 的延展性。

这里我们可以看到,Jaeger 的早期架构依然依赖于 Zipkin UI 和 Zipkin 存储格式。

还面临的一个业务需求,uber 内部还有很多核心业务没有使用 TChannel RPC 框架,为了给公司内部提供透明无侵入的 trace 服务,组件或者公共服务的编排变得非常重要,各种语言的客户端库提供,为了用不同语言提供一致的编排 API,所有客户端库从一开始就采用了OpenTracing API

Jaeger 还提供了一个采样策略,防止流量过大,trace 对业务造成抖动比较大。策略包括:1. 全量采样;2. 基于概率的采样;3.限速采样

Jaeger 将有关最恰当的采样策略决策交给追踪后端系统 Collector 服务,服务的开发者不再需要猜测最合适的采样速率。而后端可以根据流量变化动态地调整采样速率。

ps: 其实这里也有另一个问题:如果交给后端collector服务进行采样决策,那么agent肯定是全量trace推送给collector,那么agent所在的业务服务压力也大,同时TChannel网络的压力也很大

对于上面我提到的一个问题,Jaeger是这样回答的:

    后端collector服务可以按照流量模式的变化动态地调整采样速率,并反馈到各个服务的agent,形成反馈环路, 在线路中叫做:Control Flow

上面的回答,非常吸引人;因为它既不需要业务方考虑流量的增长趋势来选择合适的采样速率,同样,Jaeger的采样率是基于全局流量控制的,所以它具有动态的调整和反馈。这样的策略让人非常舒适

另一个需要解决的问题是,TChannel 框架的服务发现和服务注册,需要依赖 Hyperbahn。但是对于希望在自己的服务中运用追踪能力的工程师,这种依赖造成了不必要的摩擦。(ps: 这句话含义不是很明白?是有自己的服务发现和服务注册吗?比如:etcd,zookeeper, consul 等)

为了解决上面这个问题,我们事先了一种 jaeger-agent 边车 (Sidecar) 进程, 并将其作为基础架构组件,与负责收集度量值的代理一起部署到所有宿主机上,所有与路由与发现有关的依赖项都封装在这个 jaeger-agent 中。

此外 uber 还重新设计了客户端库,可将追踪 Span 报告给本地 UDP 端口,并能轮询本地会换接口上的代理获取采样策略。新的客户端只需要最基本的网络库,架构上的这种变化向着我们先追踪后采样的愿景迈出了一大步,我们可以在代理的内存中对追踪记录进行缓存,这点类似于 Appdash 的 ChunkedCollector 方法,在 agent 以时间和大小两个维度进行缓存。

目前的 Jaeger 架构:后端组件使用 Golang 实现,客户端库使用了四种支持 OpenTracing 标准的语言,一个机遇 React 的 Web 前端,以及一个机遇 Apache Spark 的后处理和聚合数据管道。

Jaeger UI

Zipkin UI 是 Uber 在 Jaeger 中使用的最后一个第三方软件。由于要将 Span 以 Zipkin Thrift 格式存储在 Cassandra 中并与 UI 兼容,这对后端和数据模型都有了很大的限制,而且数据转换也是个频繁的操作。尤其是 Zipkin 模型不支持 OpenTracing 标准;不支持客户端库两个非常重要的功能:1. 键值对日志;2. 更为通用的有向无环图而非 span 树所代表的跟踪。所以 uber 下决心彻底革新后端所用的数据模型,并编写新的 UI。则表示 Collector 和 Storage 的数据存储模型抛弃了 Zipkin,使用了 OpenTracing 标准。其他优化点这里不展开了。

参考资料

sidecar

优步分布式追踪技术再度精进

更多原创文章干货分享,请关注公众号
  • 加微信实战群请加微信(注明:实战群):gocnio
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册