概述

Google认为系统出现故障是一种常态,基于这种设计理念,Google的工程师们结合Google的实际开发出了Dapper。

这是目前所知的第一种公开其实现的大规模分布式系统的监控基础架构。

基本设计目标

Google使用最多的服务就是它的搜索引擎,以此为例,有资料表明,用户的平均每一次前台搜索会导致Google的后台发生1011次的处理。用户将一个关键字通过Google的输入框传到Google的后台,系统再将具体的查询任务分配到很多子系统中,这些子系统有些是用来处理涉及关键字的广告,有些是用来处理图像、视频等搜索的,最后所有这些子系统的搜索结果被汇总在一起返回给用户。在我们看来很简单的一次搜索实际上涉及了众多Google后台子系统,这些子系统的运行状态都需要进行监控,而且随着时间的推移Google的服务越来越多,新的子系统也在不断被加入。

因此在设计时需要考虑到的第一个问题就是设计出的监控系统应当能够对尽可能多的Google服务进行监控,即广泛可部署性(Ubiquitous Deployment)。另一方面,Google的服务是全天候的,如果不能对Google 的后台同样进行全天候的监控很可能会错过某些无法再现的关键性故障,因此需要进行不间断的监控。

这两个基本要求导致了以下三个基本设计目标。

  • 低开销:这个是广泛可部署性的必然要求。监控系统的开销越低,对于原系统的影响就越小,系统的开发人员也就越愿意接受这个监控系统。
  • 对应用层透明:监控系统对程序员应当是不可见的。如果监控系统的使用需要程序开发人员对其底层的一些细节进行调整才能正常工作的话,这个监控系统肯定不是一个完善的监控系统。
  • 可扩展性:Google的服务增长速度是惊人的,设计出的系统至少在未来几年里要能够满足Google服务和集群的需求。

Dapper监控系统简介

基本概念

对系统行为进行监控的过程非常的复杂,特别是在分布式系统中。为了理解这种复杂性,首先来看下图所示的一个过程。

图片加载失败

  1. 用户发出一个请求X,它期待得到系统对它做出的应答X。
  2. 但是接收到该请求的前端A发现该请求的处理需要涉及服务器B和服务器C,因此A又向B和C发出两个RPC(远程过程调用)。
  3. B收到后立刻做出响应,但是C在接到后发现它还需要调用服务器D和E才能完成请求X。
  4. 因此C对D和E分别发出了RPC,D和E接到后分别做出了应答,收到D和E的应答之后C才向A做出响应。
  5. 在接收到B和C的应答之后A才对用户请求X做出一个应答X。

在监控系统中记录下所有这些消息不难,如何将这些消息记录同特定的请求(本例中的X)关联起来才是分布式监控系统设计中需要解决的关键性问题之一。

一般来说,有两种方案可供选择:

  • 黑盒(Black Box)方案:黑盒方案比较轻便,但是在消息关系判断的过程中,黑盒方案主要是利用一些统计学的知识来进行推断,有时不是很准确。
  • 基于注释的监控(Annotation-based Monitoring)方案:基于注释的方案利用应用程序或中间件给每条记录赋予一个全局性的标示符,借此将相关消息串联起来。

考虑到实际的需求,Google的工程师最终选择了基于注释的方案,为了尽可能消除监控系统的应用程序对被监控系统的性能产生的不良影响,Google的工程师设计并实现了一套轻量级的核心功能库,这将在后面进行介绍。

Dapper监控系统中有三个基本概念:

  • 图片加载失败
  • 监控树(Trace Tree):监控树实际上就是一个同特定事件相关的所有消息,只不过这些消息是按照一定的规律以树的形式组织起来。
  • 区间(Span):树中的每一个节点称为一个区间,区间实际上就是一条记录,所有这些记录联系在一起就构成了对某个事件的完整监控。每个区间包括以下内容:区间名(Span Name)、区间id(Span id)、父id(Parent id)和监控id(Trace id)。区间id是为了在一棵监控树中区分不同的区间。父id是区间中非常重要的一个内容,正是通过父id才能够对树中不同区间的关系进行重建,没有父id的区间称为根区间(Root Span)。区间的长度实际上包括了区间的开始及结束时间信息。一棵监控树中所有区间的监控id是相同的,这个监控id是随机分配的,且在整个Dapper监控系统中是唯一的。正如区间id是用来在某个监控树中区分不同的区间一样,监控id是用来在整个Dapper监控系统中区分不同的监控。
  • 注释(Annotation)。释主要用来辅助推断区间关系,也可以包含一些自定义的内容。

监控信息的汇总

Dapper对几乎所有的Google后台服务器进行监控。海量的消息记录必须通过一定的方式汇集在一起才能产生有效的监控信息。在实际中,Dapper监控信息的汇总需要经过三个步骤,

  1. 将区间的数据被写入到本地的日志文件。
  2. 利用Dapper守护进程(Dapper daemon)和Dapper收集器(Dapper Collectors)将所有机器上的本地日志文件汇集在一起。
  3. 将汇集后的数据写入到Bigtable存储库中。

图片加载失败

关键性技术

前面提到了Dapper的三个基本设计目标,在这三个目标中,实现难度最大的是对应用层透明。为了达到既定设计目标,Google不断进行创新,最终采用了一些关键性技术解决了存在的问题。这些关键性技术概括起来主要包括以下两个方面。

轻量级的核心功能库

这主要是为了实现对应用层透明,设计人员通过将Dapper的核心监控实现限制在一个由通用线程(Ubiquitous Threading)、控制流(Control Flow)和RPC代码库(RPC Library Code)组成的小规模库基础上实现了这个目标。将复杂的功能实现限制在一个轻量级的核心功能库中保证了Dapper的监控过程基本对应用层透明。

二次抽样技术

监控开销的大小直接决定Dapper的成败,因为Google每天需要处理海量的请求,所以设计人员设计了一种非常巧妙的二次抽样方案。

二次抽样顾名思义包括两次抽样过程。如果对所有的请求都进行监控的话所产生的监控数据将会十分的庞大,也不利于数据分析,因此Dapper对这些请求进行了抽样,只有被抽中的请求才会被监控。实践过程中发现对于一些流量较低的服务,低抽样率很可能会导致一些关键性事件被忽略,因此Dapper的设计团队正在设计一种具有适应性抽样率的方案。

但是监控数据量依旧惊人,对此,Dapper团队设计了第二次的抽样。这次抽样发生在数据写入Bigtable之前,具体方法是将监控id散列成一个标量z,其中0≤z≤1。如果某个区间的z小于事先定义好的汇总抽样系数(Collection Sampling Coeficient),则保留这个区间并将它写入Bigtable。否则丢弃不用。也就是说在采样决策中利用z值来决定某个监控树是整棵予以保留还是整棵弃用。这种方法非常的巧妙,因为在必要时只需改动z值就可以改变整个系统的写入数据量。利用二次抽样技术成功地解决了低开销及广泛可部署性的问题。


上面的两种技术手段解决了主要设计问题,这使得Dapper在Google内部得到了广泛的应用。Dapper守护进程已成为Google镜像的一部分,因此Google所有的服务器上都有运行Dapper。

参考文章