撸论文系列——Dapper

Dapper最初是为了追踪在线服务系统的请求处理过程。比如在大型分布式系统中,用户的一个请求会经过多个子系统的处理,而这些处理是发生在不同服务器甚至不同数据中心的,当请求处理发生异常时,需要快速定位到哪个环节出了问题,这是很关键的。而Dapper就是为了解决这些问题应运而生的。

Dapper有如下三个具体的设计目标:

额外开销要低

对应用保持透明性

可扩展

Dapper在设计之初,参考了一些其他分布式系统的理念,尤其是Magpie和X-Trace。

举一个搜索相关的例子。一个前端服务可能对上百台查询服务器发起一个web查询,每一个查询都有自己的Index。这个查询可能会被发送到多个子系统,分别用于处理广告、进行拼写检查或查找图片视频等。根据每个子系统的查询结果进行筛选聚合最终得到结果汇总到页面上,这样的搜索称为“全局搜索”。那么出现了调用超时或者慢的问题时排查起来就不能确定到底是哪个子系统出现问题了,如果没有监控,工程师将束手难测,由于服务会被多个团队开发和维护,一个工程师尽管是业务架构师也难以保证多点下的服务健康问题。

而且跟踪系统带来的一点的损耗就能被察觉到,所以首先不应对现有系统产生一些性能上的影响。其次,我们应当保证应用开发工程师能够专心进行应用逻辑的开发,而不去太多关心监控和追踪系统,他们只要会使用就够了,甚至不知道是什么都可以。最后是实时性要强,分析的速度要快,理想情况是数据存入跟踪仓库后一分钟内就能统计出来,这样可以对异常情况作出快速响应。

一、Dapper的分布式跟踪

分布式服务的跟踪系统需要记录在一次特定的请求后系统中完成的所有工作的信息。图中展现的是这样一个示例:前端A,两个中间层B和C,以及两个后端D和E。客户端发起一个请求时,首先到达前端,然后发送两个RPC到B和C。B会马上做出响应,而C需要和后端D和E交互再返回给A,由A来响应最初请求。简单的跟踪系统实现是为服务器每一次发送和接收动作收集标识符和时间戳。然而标识符方案有个明显的问题,就是对代码的植入。在生产环境中,因为所有的应用程序都使用了相同的线程模型,控制流和RPC系统。如果可以把代码植入限制在一个很小的通用组件库中,从而实现了监控系统的应用对开发人员是有效的透明。

1.1 跟踪树和span

在dapper的跟踪树结构中,树节点是整个架构的基本单元,每一个节点又是对span的引用。

上图说明了span在一个大型跟踪过程中是什么样子的。在一个典型的dapper跟踪中,为每个RPC对应到一个单一的span上,而且每一个额外的组件层都对应一个跟踪树形结构的层级。

上图是一幅更详细的典型Dapper跟踪span的记录点的视图。如果应用程序开发者选择在跟踪中增加自己的注释(业务数据),这些信息也会和其他span信息一样记录下来。事实上,每个RPC span都记录了一个上游机器和下游服务的详细信息,对应于发送-响应模式,请求的两个节点肯定会有时间戳上的偏差,在这里表现为时间的上限和下限。

1.2 植入点

Dapper可以对应用开发者近乎零成本对分布式系统控制链路进行跟踪,完全依赖于少量通用组件的改造。

当一个线程在处理跟踪控制路径的过程中,dapper把这次跟踪的上下文信息在ThreadLocal中进行存储。

当计算过程式延迟调用或者异步时,开发者通过线程池或其他执行器,使用通用的控制流来回调。Dapper确保这样的回调可以存储跟踪的上下文,当回调函数被调用时,这次跟踪的上下文会有适当的线程关联上。这种方式下,dapper可以使用traceID和spanID来辅助构建异步调用的路径。

基于RPC的通信系统时一个重要的植入点。

1.3 注解

上述植入点足够推导出复杂的分布式系统的跟踪细节,dapper还允许应用程序开发人员在dapper跟踪的过程中添加额外的信息,以监控更高级别的系统行为,或帮助调试问题。

1.4 采样率

某种类型的web服务队植入带来的性能损耗非常敏感。因此除了dapper的收集工作对基本组件的性能损耗限制的尽可能小之外,还要有进一步控制损耗的办法,那就是遇到大量请求时只记录其中的一小部分。

首先数据写入本地日志文件中,然后dapper的守护进程和收集组件把这些数据从生产环境的主机中拉出来,最终写到dapper的bigtable仓库中。一次跟踪被设计成Bigtable中的一行,每一列相当于一个span。bigtable的支持稀疏表格布局正适合这种情况。

dapper还提供了一个API来简化访问我们仓库中的跟踪数据。开发人员用这个API,以构建通用和特定应用程序的分析工具。

2 处理跟踪损耗

主要有三方面:dapper组件操作的消耗,跟踪收集的消耗,以及dapper对生产环境负载的影响。

2.1 生成跟踪的消耗

生成跟踪的开销是dapper性能影响中最关键的部分,因为收集和分析可以更容易在紧急情况下被关闭。其中最关键的消耗在于创建和销毁span和annotation,并记录在本地磁盘供后续的收集。时间上的差别主要在于需要在跟span上给这次跟踪分配一个全局唯一的ID。

在dapper运行期写入到本地磁盘是最昂贵的操作,但是他们的可见损耗大大减少,因为写入日志文件和操作相对于被跟踪的应用系统来说都是异步的。不过,日志写入的操作如果在大流量的情况,尤其是每一个请求都被跟踪的情况下就会变得可以察觉到。

2.2 跟踪收集的损耗

读出跟踪数据也会对正在被监控的负载产生干扰。在生产环境下,跟踪数据处理中,这个守护进程从来没有超过0.3%的单核CPU使用率,而且只有很少量的内存使用。而且dapper在网络资源的带宽占用和速率的影响上也小的多。

2.3 在生产环境下对负载的影响

每个请求都会利用到大量的服务器的高吞吐量的线上服务,这是对有效跟踪最主要的需求之一;这种情况需要生成大量的跟踪数据,并且对性能的影响是非常敏感的。保持dapper的性能损耗基线在一个非常低的水平是很重要的,因为它为那些应用提供了一个宽松的环境使用完整的Annotation API而不用担心性能损失。使用较低的采样率还有额外的好处,可以让持久化到硬盘中的跟踪数据在垃圾回收机制处理之前保留更长的事件,这样为dapper的收集组件给了更多的灵活性。

三、经验

Dapper在Google中被广泛应用,一部分直接通过dapper的用户界面,另一部分间接地通过对dapper API的二次开发或者建立在基于api的应用上。

3.1 在开发中使用dapper

google adworlds系统时围绕一个大型的关键词定位准则和相关文字广告的数据库搭建的。dapper帮助他们改进了服务:开发人员针对请求延迟的目标进行跟踪,并对容易优化的地方进行定位。系统具有只读副本策略和读写的主策略。dapper被用来在很多情况中确定,那种查询时无需通过主策略访问而可以采用副本策略访问(因为数据访问廉价)。dapper的跟踪用于评估总的查询成本,促进重新对业务的设计,用以在他们的系统以来上减少负载。另外,google还有一些系统在从通用调试日志中收集和集中信息,把那些系统的海量数据和dapper仓库整合也是有一定价值的。

3.2 推断服务依赖

由于任务之间的依赖是动态改变的,所以不可能仅从配置信息上推到出所有这些服务之间的依赖关系。google的可称为“Service Dependencies”的项目时通过使用跟踪Annotation和DAPI MR接口来实现自动化确定服务依赖归属的。

3.3 分层和共享存储系统

在google的许多存储系统时由多重独立复杂层级的分布式基础设备组成的。例如,google的APP Engine就是搭建在一个可扩展的实体存储系统上的。该实体存储系统在基于Bigtable上公开某些RDBMS功能。Bigtalbe的同时使用Chubby及GFS。再者,像Bigtable这样的系统简化了部署,并更好的利用了计算资源。

在GFS层面,一个用户或多个用户产生的信息的场景是很难界定的。而通过dapper这样的工具,可以很清楚对共享服务的竞争进行调试和发现。

总结:

dapper几乎部署在所有的google系统上,并可以在不需要应用级修改的情况下进行跟踪,而且没有明显的性能影响。结合对开发人员提供简单API和对应用系统完全透明来增强跟踪的这个决定是非常有价值的。

最后,通过开放dapper跟踪仓库给内部开发者,可以促使更多的基于跟踪仓库的分析工具的产生,而仅仅由dapper团队去探究结果远达不到这么大的规模,这个决定促使了设计和实施的展开。

参考资料:《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181104G0JZ9M00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券