Google Dapper-大规模分布式系统的基础跟踪设施

2023-05-16

[说明:本文是阅读Google论文“Dapper, a Large-Scale Distributed Systems Tracing Infrastructure”之后的一个简要总结,完整译文可参考此处。 另论文“Uncertainty in Aggregate Estimates from Sampled Distributed Traces”中有关于采样的更详细分析。此外,Twitter开源的Zipkin就是参考Google Dapper而开发。]

Dapper最初是为了追踪在线服务系统的请求处理过程。比如在搜索系统中,用户的一个请求在系统中会经过多个子系统的处理,而且这些处理是发生在不同机器甚至是不同集群上的,当请求处理发生异常时,需要快速发现问题,并准确定位到是哪个环节出了问题,这是非常重要的,Dapper就是为了解决这样的问题。

对系统行为进行跟踪必须是持续进行的,因为异常的发生是无法预料的,而且可能是难以重现的。同时跟踪需要是无所不在,遍布各处的,否则可能会遗漏某些重要的点。基于此Dapper有如下三个最重要的设计目标:低的额外开销,对应用的透明性,可扩展。同时产生的跟踪数据需要可以被快速分析,这样可以帮助用户实时获取在线服务状态。

实现方法

低的额外开销:不是对所有的请求进行跟踪而是要采样,收集跟踪数据时进行二次采样

对应用的透明性:修改多线程,控制流,RPC等基础库代码,插入负责跟踪的代码。在Google,应用使用的是相同的多线程,控制流,RPC等基础库代码,所以仅通过修改它们就可以实现跟踪功能。当线程进行一个被跟踪的处理时,Dapper会将一个trace context关联到线程本地化存储中。trace context中包含了span相关属性,比如trace和span id。对于需要进行异步处理的情况,Google开发者通常都会采用一个通用的控制流库来实现回调,并将它们调度到一个线程池或是执行器中调用。Dapper保证所有回调都会保存它们创建者的trace context,同时在该回调被调用时该trace context也会被关联到对应线程。这样,Dapper就可以实现这种异步处理过程的跟踪。对于被跟踪的RPC调用,span和trace id也会跟着被从客户端传到服务端。从功能上看这部分代码主要包括span的创建,采样,本地磁盘日志写入,但是因为它会被很多应用所依赖,维护和bug fix难度高,需要非常高的稳定性和健壮性。同时还要轻量,实际上这部分代码的C++实现也总共不到1000行。

Dapper支持用户直接获取Tracer对象,并输出自己的自定义信息,用户可以输出自己任意想输出的内容,为防止用户过度输出,提供用户可配置参数来控制其上限。

跟踪时需要对请求进行标记,会产生一个唯一ID(在Dapper中是一个64位整数)用来标识该请求。对于Dapper来说,一个trace(跟踪过程)实际上是一颗树,树中的节点被称为一个span,根节点被称为root span。如下图所述:

需要注意的是一个span可能包含来自多个主机的信息;实际上每个RPC span包含了来自客户端和服务端的信息。但是客户端和服务端的时钟是有偏差的,文中并未指明如何解决这个问题,只是说可以利用如下事实"RPC的发起客户端是在服务端接收到前进行地,针对该RPC的响应则是由服务端在客户端收到前发出的",确定时间戳的一个上下界。

Dapper的整个数据收集过程如下图所示:首先将span数据写入本地日志文件,然后将数据收集并写入Bigtable,每个trace记录将会被作为表中的一行,Bigtable的稀疏表结构非常适合存储trace记录,因为每条记录可能有任意个的span。整个收集过程是out-of-band的,与请求处理是完全不相干的两个独立过程,这样就不会影响到请求的处理。如果改成in-band的,即将trace数据与RPC响应报文一块发送回来,会影响到应用的网络状况,同时RPC调用也有可能不是完美嵌套的,某些RPC调用可能会在它本身依赖的那些返回前就提前返回了。

Dapper提供API允许用户直接访问这些跟踪数据。Google内部开发人员可以基于这些API开发通用的或者面向具体应用的分析工具。这一点,对于提高Dapper的作用和影响力起到了出人意料的效果。

跟踪开销

如果跟踪带来的额外开销太高,用户通常会选择关掉它,因此低开销非常重要。采样可以降低开销,但是简单的采样可能导致采样结果无代表性,Dapper通过采用自适应的采样机制来满足性能和代表性两方面的需求。

trace的生成开销对Dapper来说是最关键的,因为数据的收集和分析可以临时关掉,只要数据一直生成就可以后面再进行收集分析。在trace生成中,最大头的地方在于span和annotation的创建和销毁上。根span的创建和销毁平均需要204ns,普通span只需要176ns,区别在于根span需要产生一个全局唯一的trace id。如果span没有被采样到,那么对它添加annotation的开销基本可忽略,大概需要9ns。但是如果是被采样到的话,那么平均开销是40ns。这些测试是在一个2.2GHZ的x86服务器上进行的。本地磁盘写入是Dapper运行库中最昂贵的操作,但是它们可以异步化,批量化,因此它们基本上只会影响到那些高吞吐率的应用。

将跟踪数据通过Dapper后台进程读出也会产生一些开销。但是根据我们的观察,Dapper daemon的CPU开销始终在0.3%之下,内存占用也很少,此外也会带来轻量的网络开销,但是每个span平均只有426字节大小,网络开销只占了整个产品系统流量的0.01%不到。

对于那些每个请求都可能产生大量跟踪数据的应用来说,我们还会通过采样来降低开销。我们通过保证跟踪开销可以始终保持在很低的水平上,使得用户可以放心大胆的使用它。最初我们的采样策略很简单,是在每1024个请求中选择一个进行跟踪,这种模式对于那种请求量很高的服务来说,可以保证跟踪到有价值的信息,但是对于那些负载不高的服务来说,可能会导致采样频率过低,从而遗漏重要信息。因此我们决定以时间为采样单位,保证单位时间内可以进行固定次数的采样,这样采样频率和开销都更好控制了。

应用

用户可以通过DAPI(Dapper“Depot API”)直接访问跟踪数据。DAPI提供如下几种访问方式:指定trace id进行访问;大规模批量访问,用户可以通过MapReduce job并行访问,用户只需要实现一个以Dapper trace为参数的虚函数,在该函数内完成自己的处理即可,框架负责为用户指定时间段内的所有trace调用该函数;通过索引进行访问,Dapper会为跟踪数据建立索引,用户可通过索引进行查询,因为trace id是随机生成的,因此用户通常需要通过服务名或机器名进行检索(实际上Dapper是按照(服务名,机器名,时间戳)进行索引的)。

大部分用户都是通过一个交互式web接口来使用Dapper的,典型流程如下图所示:

1.用户输入他感兴趣的服务和时间窗口,选择相应跟踪模式(这里是span名称),以及他最关心的某个度量参数(这里是服务延迟)

2.页面上会展示一个指定服务的所有分布式执行过程的性能摘要,用户可能会对这些执行过程根据需要进行排序,然后选择一个详细看

3.一旦用户选定了某个执行过程后,将会有一个关于该执行过程的图形化描述展现出来,用户可以点击选择自己关心的那个过程

4.系统根据用户在1中选择的度量参数,以及3中选择的具体过程,显示一个直方图。在这里显示的是有关getdocs的延迟分布的直方图,用户可以点击右侧的example,选择具体的一个执行过程进行查看

5.显示关于该执行过程的具体信息,上方是一个时间轴,下方用户可以进行展开或折叠,查看该执行过程各个组成部分的开销,其中绿色代表处理时间,蓝色代表花在网络上的时间。

经验教训

在开发过程中使用Dapper,可以帮助用户提高性能(分析请求延迟,发现关键路径上不必要的串行化),进行正确性检查(用户请求是否正确发送给了服务提供者),理解系统(请求处理可能依赖很多其他系统,Dapper帮助用户了解总体延迟,重新设计最小化依赖),测试(新代码release前要通过一个Dapper跟踪测试,验证系统行为和性能)。比如,通过使用Dapper,Ads Review团队将延迟降低了两个数量级。

同时我们还将跟踪系统与异常监控系统进行集成,如果异常发生在一个采样到的Dapper tracer上下文中,那么相应的trace和span id还会被作为异常报告的元数据,前端的异常监控服务还会提供相应链接指向跟踪系统。这样可以帮助用户了解异常发生时的情况。

解决长尾延迟,帮助用户分析复杂系统环境下的延迟问题。网络性能的瞬时下降不会影响系统的吞吐率,但是对延迟有很大影响。很多开销昂贵的查询模式是由于未预料到的服务间的交互造成的,Dapper的出现使得这种问题的发现变得非常容易。

帮助用户进行服务间依赖的推理。Google维护了非常多的集群,每个集群上承载了各种各样的任务,而任务间可能存在依赖关系。但是各个任务需要精确知道它所依赖的服务信息,以帮助发现瓶颈或进行服务的移动。服务间的依赖是复杂的而且是动态的,单纯依赖配置文件很难判断。但是通过使用Dapper的trace信息和DAPI MapReduce接口可以自动化的确定服务间依赖关系。

帮助网络管理员对跨集群的网络活动进行应用层的分析。帮助发现某些昂贵的网络请求开销的产生原因。

很多存储系统都是共享的。比如GFS,会有很多用户,有的可能是直接访问GFS,有的可能是比如通过Bigtable产生对GFS的访问,如果没有Dapper,对这种共享式系统将会很难调试,通过Dapper提供的数据,共享服务的owner可以方便的对用户根据各项指标(比如网络负载,请求耗时)进行排序。

救火。但是不是所有的救火,都可以使用Dapper来完成。比如,那些正在救火的Dapper用户需要访问最新的数据,但是他可能根本没有时间写一个新的DAPI代码或者等待周期性报告的产生。对于那些正在经历高延迟的服务,Dapper的用户接口并不适于用来快速定位延迟瓶颈。但是可以直接与Dapper daemon进行交互,通过它可以很容易地收集最新数据。在发生灾难性的故障时,通常没有必要去看统计结果,单个例子就可以说明问题。但是对于那些共享存储服务来说,信息聚合会很重要。对于共享服务来说,Dapper的聚合信息可以用来做事后分析。但是如果这些信息的聚合无法在问题爆发后的10分钟之内完成,那么它的作用将会大大削弱,因此对于共享服务来说,Dapper在救火时的效果没有想象中的那么好。

通过开放跟踪数据给用户,激发了用户创造力,产生了很多意料之外的应用。那些没有跟踪功能的应用只需要用新的库重新编译下它们的程序,就获得了跟踪功能,迁移非常方便。

但是还存在如下一些需要改进的地方:

合并产生的影响。我们通常假设各种子系统会一次处理一个请求。但是某些情况下,请求会被缓存,然后一次性地在一组请求上执行某个操作(比如磁盘写入)。在这种情况下,被追踪的请求拿到的实际上并不是它本身的处理过程。

对批量处理进行跟踪。虽然Dapper是为在线服务系统而设计,最初是为了理解用户发送请求给Google后产生的一系列系统行为。但是离线的数据处理实际上也有这样的需求。在这种情况下,我们可以将trace id与某些有意义的工作单元进行关联,比如输入数据里的一个key(或者是一个key range)。

寻找根本原因。Dapper可以迅速找到系统短板,但是在寻找根本原因时并不那么高效。比如某个请求变慢可能并不是因为它自己的原因,而是因为在它之前已经有很多请求在排队。用户可以在应用层将某些参数,比如队列大小交给跟踪系统。

记录内核级信息。我们有很多工具可以进行内核执行过程的跟踪和profiling,但是直接将内核级信息捆绑到一个应用层的trace context中很难优雅的实现。我们打算采用一种折中的解决方案,通过在应用层获取内核活动参数的快照,然后将它们与一个活动span关联起来。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Google Dapper-大规模分布式系统的基础跟踪设施 的相关文章

  • 文件的原子操作

    文件的原子操作是指一个操作一旦启动 xff0c 则无法能被破坏它的其它操作打断 1 写文件原子操作 无论是两个打开 xff0c 还是dup xff0c 同时操作一个文件都可能引起混乱 xff0c 解决这个问题的方法是 xff0c 可以通过O
  • 目录操作

    创建目录 xff1a int mkdir const char pathname mode t mode xff1b pathname xff0c 路径 xff1b mode xff0c 目录访问权限 xff1b 返回值 xff1a 成功
  • 【UE4学习】6.粒子系统

    文章目录 粒子系统常用参数Simple Sprite Burst EmitterEmitter SettingsEmitter SpawnEmitter UpdateParticle SpawnParticle UpdateAdd Even
  • java中Array/List/Map/Object与Json互相转换详解

    JSON JavaScript Object Notation xff1a 是一种轻量级的数据交换格式 一 JSON建构有两种结构 xff1a 对象和数组 1 对象 xff1a 对象在js中表示为 扩起来的内容 xff0c 数据结构为 ke
  • ZipInputStream解压远程文件报错,java.lang.IllegalArgumentException: MALFORMED[1]

    我遇到的问题是报的这个错java lang IllegalArgumentException MALFORMED 1 at java util zip ZipCoder toString ZipCoder java 65 不是 java l
  • OAuth2.0接百度平台进行授权

    百度开发文档 xff1a https openauth baidu com doc regdevelopers html 1 注册开发者账号并创建一个应用 2 创建应用后 xff0c 获取API Key和Secret Key 3 创建一个S
  • Spring 中最常用的 11 个扩展点

    1 自定义拦截器 spring mvc拦截器根spring拦截器相比 xff0c 它里面能够获取HttpServletRequest和HttpServletResponse等web对象实例 spring mvc拦截器的顶层接口是 xff1a
  • 经典排序算法

    https juejin cn post 7198840786766102589
  • SpringBoot项目启动加载时排除某一个类

    在Application启动类上 xff0c 用这个注解就可以指定某个类不加载进容器 64 ComponentScan
  • 手写一个生产者/消费者模式(三种方式实现)

    这种设计模式需要满足以下三点要求 xff1a xff08 1 xff09 生产者生产数据到缓冲区中 xff0c 消费者从缓冲区中取数据 xff08 2 xff09 如果缓冲区已经满了 xff0c 则生产者线程阻塞 xff1b xff08 3
  • Android中Okhttp,Volley,Retrofit网络框架优缺点及对比

    Okhttp xff1a Square 公司开源的 OkHttp 是一个专注于连接效率的 HTTP 客户端 OkHttp 提供了对 HTTP 2 和 SPDY 的支持 xff0c 并提供了连接池 xff0c GZIP 压缩和 HTTP 响应
  • Google身份验证服务端实现

    import org apache commons codec binary Base32 import org apache commons codec binary Base64 import javax crypto Mac impo
  • 下载jdk8登录账号

    目前在官网下载低于jdk1 8的java jdk的时候需要登陆 xff0c 这边分享一个账号 xff0c 方便下载 2696671285 64 qq com 密码 xff1a Oracle123
  • idea中maven项目 jar包下载不完整解决办法

    有时从git上clone项目 xff0c maven工程 xff0c 有时候pom xml在project标签处报错 xff1a Failed to read artifact descriptor for xxx jar 这种有时候时ja
  • 【RoboMaster】舵机驱动&蓝牙模块教程

    本文是为参加2021赛季北京理工大学机器人队校内赛所写的简单教程 xff0c 意在帮助参赛选手快速了解校内赛所需模块的使用方法 xff0c 以及其与薪火培训知识的联系 舵机驱动 硬件接线 舵机是由直流电机 减速齿轮组 传感器和控制电路组成的
  • vs编译程序加快速度的方法

    在使用VS2013编译C 43 43 程序的时候 xff0c 修改某个文件 xff0c 会使整个工程都重新编译一遍 xff0c 为了使编译速度加快 xff0c 可以修改C 43 43 配置属性 xff1a 第一因时间引起的 xff1a 1
  • Visual Studio中gets报错解决方法

    方法如下 xff1a 1 这是敲出gets后报的错 2 经过查找资料 xff0c 知道vs2015之后就不支持gets了 xff0c 变成了gets s xff0c 并且后面的括号中也不能单独写一个数组名 xff0c 还需加上数组内的个数
  • 利用RCLCPP实现话题的发布与订阅

    目录 1 创建节点2 编写发布与订阅节点2 1 发布节点 xff08 topic publisher 01 cpp xff09 2 2 订阅节点 xff08 topic subscribe 01 cpp xff09 2 3 修改CmakeL
  • ROS2实现虚拟串口通信

    目录 1 下载demo文件1 1安装python3 serial 2 下载虚拟串口模拟器socat3 串口通信测试3 1代码修改3 2开启uart example py3 3开启发送端口dev pts 23 4进入demo文件夹 xff0c
  • ubuntu编译卡死解决

    添加交换空间 一 xff0c 查看当前系统的swap大小 span class token function free span span class token parameter variable m span 二 xff0c 创建一个

随机推荐