YoloKokura

原论文:Dapper, a Large-Scale Distributed Systems Tracing Infrastructure

所谓链路追踪,就是对分布式系统在各个组件、各台主机上的性能进行持续监控,让工程师在意识到问题时,能够更方便地把问题定位到某个服务或者某条链路上。要实现这一点,需要达到这些目标:

  1. 部署范围广:一个只能部署在少部分服务组件上的链路追踪系统是没有意义的,要精准定位问题,就要实现对分布式系统所有组件的监控,这要求设计出的监控工具对服务组件或硬件设备没有太多要求;
  2. 持续监控:你永远不可能预测到究竟什么时间哪项服务可能出现故障,而且在分布式系统中,很多问题是很难重现的,这就要求我们能够保证保留现场数据进行分析;
  3. 资源占用少:既然这套系统得部署在每个服务上,那么就必须要求它对原服务的性能影响微乎其微;
  4. 侵入性低:这样的系统最好不要求开发者对原有服务进行太大改造,如果它和服务代码过于耦合,就很可能出现故障。在一个快速迭代的项目里,开发者很难在完成功能模块的同时兼顾一个外来的监控系统;
  5. 可扩容:要能够适应未来系统的扩展。

Dapper的分布式链路追踪

上图展示了一种典型的分布式系统信息流,为了回复用户请求,系统中的五个子模块都需要参与互动,涉及到复杂的组件间通信。一种思路是利用统计学思路,基于大量数据来推测子模块的关联情况;另一种思路是对每一次用户请求和与之相关的子模块通信做全局唯一标记,这样即使只有一次请求,也可以对整个系统进行分析,其缺点是需要对分布式系统进行干涉。Dapper的开发者发现,在他们的生产环境中,所有应用都采用了一致的RPC库、线程模型和控制流,因此要做的改动仅限于一小部分公共库。

Trace树、Spans、Annotations

结合之前的例子不难发现,一条信息链路可以被视为RPC(或其它通信方式)嵌套组成的树形结构。 从前端A分出B和C两个分支,C又会嵌套式地从D和E获取信息。 在Dapper中,这棵树的结点被称为span,记录了该模块处理一条请求的始末时间等信息。

上面两张图分别展示了trace的树形结构和一个span所记录的详细信息。

除此以外,Dapper支持开发者通过特定API给trace注入额外信息,这种信息不光支持字符串,还支持kv信息输入。

Dapper是如何收集trace信息的

Dapper的信息收集分三步走:

  1. 被改造过的公共库会将原始trace信息写入本机日志文件;
  2. Dapper守护进程和生产机器会定期拉取这些日志文件;
  3. 最终trace信息会由Dapper collector写入Bigtable中。

在Dapper的设计里,一个trace所包含的span是不确定的,而Bigtable(如前面的博客介绍的那样),正好支持稀疏的表格结构。作者称,从数据生产到落盘的平均耗时能控制在15秒以内,但仍有四分之一的情况下该时长会增长到数个小时。(考虑到这个过程中有多次磁盘写入、网络延迟,以及Bigtable本身的耗时)

开销控制

Dapper运行时库的主要开销来自于span的创建、销毁,annotation以及日志落盘。根span的创建比子span耗时会更长,因为此时还需要分配全局唯一ID。 磁盘写入的耗时最大,但由于多个日志写入操作会被聚合起来,且写操作相对于应用来说是异步进行的,这部分开销被大大降低了。然而,对于高吞吐的应用来说,这种性能影响仍然是不能忽略的,特别是在每条请求都被追踪的情况下。

Dapper的开销还与对请求的采样率有关。在早期,Dapper在每1024次进程进行一次采样。在吞吐量高的网络应用中,这种较低的采样率仍然能够确保重要的事件被捕获到,但这种采样模式显然对吞吐量低的情况不友好。因此,在后续迭代中,Dapper将改为在每单位时间进行采样,且能够配置采样率参数。

在尽量低的采样率的基础上,Dapper还对最终落盘的trace数据进行二轮采样,以控制收集的数据规模。具体而言,同一个trace的span共享一个trace ID,经这个ID计算的哈希值若低于某个阈值,则将之写入Bigtable,反之则抛弃,这样一来所有同属于一个trace的span都会被保留,且只需要调整一个阈值参数,就可以控制整个Dapper对Bigtable的写入速率。

Tags: