APM(应用性能管理)与 Dapper 原理介绍

文章目录 APM(应用性能管理)与 Dapper 原理介绍什么是 APMAPM 介绍 APM 三大特征 APM 的发展历程 DevOpsAPM 的核心思想为什么要使用 APM 好的 APM 应满足的条件 Dapper 的介绍以及基本原......


APM (Application Performance Management) 即应用性能管理(应用性能监控)

APM 主要是针对企业 关键业务的 IT 应用性能和用户体验的监测、优化,提高企业 IT 应用的可靠性和质量。 旨在确保最终用户获得高质量的体验,降低 IT 总拥有成本(TCO)

TCO (Total Cost of Ownership ),即总拥有成本,包括产品采购到后期使用、维护的成本。 这是一种公司经常采用的技术评价标准。

APM 介绍

目前市面的系统基本都是参考 Google 的 Dapper(大规模分布式系统的跟踪系统)来做的。 跟踪业务请求的处理过程,完成对应用系统在前后端处理、服务端调用的性能消耗跟踪,提供可视化的界面来展示对跟踪数据的分析。 通过汇聚业务系统各处理环节的实时数据,分析业务系统各事务处理的交易路径和处理时间,实现对应用的全链路性能监测。

APM 工具与传统的性能监控工具的区别在于,不仅仅提供一些零散的资源监控点和指标,其主要关注在系统内部执行、系统间调用的性能瓶颈分析,这样更有利于定位到问题的具体原因。 APM 致力于检测和诊断应用性能问题,从而能提供应用预期的服务水平。

APM 三大特征

  1. 多级应用性能监控:覆盖通讯协议 1-7 层,通过事务处理过程监控、模拟等手段实现端到端应用监测。
  2. 应用性能故障快速定位:对应用系统各个组件进行监测,迅速定位系统故障,并进行修复或提出修复建议。
  3. 应用性能全面优化:精确分析各组件占用系统资源的情况,并根据应用系统性能要求给出专家建议。

APM 的发展历程

目前 APM 的发展主要经历了前面的三个阶段: 第一阶段:以网络监控基础设施为主,主要监控主机 的 CPU 使用率、I/O、内存资源、网速等,主要以各类网络管理系统(NMS)和各种系统监控工具为代表。

第二阶段:以监控各种基础组件为主,随着互联网的快速发展,为了降低应用开发难度,各种基础组件(如数据库、中间件等)开始大量涌现,所以这个时期应用性能管理主要是监控和管理各种基础组件的性能。

第三阶段:以监控应用本身的性能为主, IT 运维管理的复杂度开始出现爆炸性的增长,应用性能管理的重点也开始聚焦于应用本身的性能与管理上。

第四节阶段属于正在发展的阶段: 云计算方兴未艾,而 DevOps 以及微服务的兴起对传统 APM 产生了很大的冲击,传统厂商也在做一些革新,也做一些微服务方面的尝试和云计算方面的尝试。 随着 Machine Learning、AI 的技术的兴起,对定位故障、定位问题,也会起到一些帮助,基于大数据的分析的手段也会有一些帮助,目前市场上正在初步尝试阶段。

2016 年 Gartner 对 APM 的定义分为三个维度

  1. DEM-Digital experience monitoring:数字体验监控,浏览器及移动设备用户体验监控及利用主动拨测的实现的业务可用性及性能监控。
  2. ADTD-Application discovery, tracing and diagnostics:应用自动发现、追踪和故障诊断,自动发现应用之间的逻辑关系,自动建模、应用组件的深入监控及性能关联分析。
  3. AA-Application analytics:应用分析,通过机器学习,进行针对 JAVA 及. NET 等应用的根源分析。

DevOps

DevOps(Development 和 Operations 的组合词)是一种重视 “软件开发人员(Dev)” 和“IT 运维技术人员(Ops)”之间沟通合作的文化、运动或惯例。 DevOps 可以简洁的理解为 “开发团队与运营团队之间更具协作性、更高效的关系”。 是两个相关趋势碰撞中出现的新术语 更多信息见:https://zh.wikipedia.org/wiki/DevOps

在应用服务各节点相互调用的时候,从中记录并传递一个应用级别的标记,这个标记可以用来关联各个服务节点之间的关系。 比如两个应用服务节点之间使用 HTTP 作为传输协议的话,那么这些标记就会被加入到 HTTP 头中。 可见如何传递这些标记是与应用服务节点之间使用的通讯协议有关的,常用的协议就相对容易加入这些内容,一些按需定制的可能就相对困难些,这一点也直接决定了实现分布式追踪系统的难度。

随着公司业务的与日俱增,各个系统也越来越复杂,服务间的调用,服务的依赖,以及分析服务的性能问题也越棘手,因此引入服务追踪系统尤为重要。 现有的 APM,基本都是参考 Google 的 Dapper 的体系来做的。通过跟踪请求的处理过程,来对应用系统在前后端处理、服务端调用的性能消耗进行跟踪(每个请求的完整调用链路,收集调用链路上每个服务的性能数据),方便工程师能够快速定位问题。

好的 APM 应满足的条件

总的来说,一个优秀的 APM 系统应该满足以下五个条件

  1. 低消耗,高效率:被跟踪的系统为跟踪所付出的系统资源代价要尽量小,现在主流的 APM 对于系统资源的消耗在 2.5%-5% 左右,但是这个数值应该越小越好,因为在大规模的分布式系统下,一个单节点的资源是无法把控的,可能是超强配置,也可能是老爷机,只跑几个小服务,但是本身性能已经十分吃紧了,如果这时候跟踪应用再一跑,很可能这个节点就挂掉了,得不偿失。
  2. 低侵入性,足够透明:作为跟踪系统,侵入性是不可能不存在的,关键这种侵入性要在哪个层面,如何在越底层的层面上侵入,对于开发者的感知和需要配合跟踪系统的工作就越少,如果在代码层面就需要进行侵入,那对于本身业务就比较复杂的应用来说,代码就更加冗余复杂了,也不利于开发者快节奏的开发。
  3. 灵活的延展性:不能随着微服务和集群规模的扩大而使分布式跟踪系统瘫痪,要能够充分考虑到未来分布式服务的规模,跟踪系统至少要在未来几年内完全吃得消。
  4. 跟踪数据可视化和迅速反馈:要有可视化的监控界面,从跟踪数据收集、处理到结果的展现尽量做到快速,就可以对系统的异常状况作出快速的反应
  5. 持续的监控: 要求分布式跟踪系统必须是 7x24 小时工作的,否则将难以定位到系统偶尔抖动的行为

Dapper–Google 生产环境下的分布式跟踪系统。 Dapper 的英文论文:http://static.googleusercontent.com/media/research.google.com/zh-CN//pubs/archive/36356.pdf 中文论文:https://bigbully.github.io/Dapper-translation/ (建议一定要看下)

分布式调用跟踪系统实际上是随着微服务才火起来的一个概念,Google 早在很多年前(2000 年初便已经使用自研容器)已经微服务化了,所以他的分布式跟踪理论目前是最成熟的。 分布式跟踪系统出现的原因简单的说是因为在分布式系统中一次请求中会包含很多的 RPC,迫切需要一些可以帮助理解系统行为和分析性能问题的工具,需要断定具体哪个服务拖了后腿。

根据论文来看,先看一个例子:

A~E 分别表示五个服务,用户发起一次请求到 A,然后 A 分别发送 RPC 请求到 B 和 C,B 处理请求后返回,C 还要发起两个 RPC 请求到 D 和 E,和 D 和 E 交互之后再返还给 A,由 A 来响应最初的请求。 对于这样一个请求,简单实用的分布式跟踪的实现,就是为服务器上每一次你发送和接收动作来收集跟踪标识符 (message identifiers) 和时间戳(timestamped events)。也就是将这些记录与特定的请求进行关联到一起。

如何将每个服务的日志与每一条记录与特定的请求关联到一起

目前学术界与工业界有如下两种方案,将一些记录与某个特定的请求关联到一起

1. 黑盒方案(black box)

黑盒方案假定需要跟踪的除了上述信息之外没有额外的信息,这样就可以使用统计回归技术来推断两者之间的关系。

日志还是一样的记录,只是通过机器学习的方法来关联记录与特定的请求。 以一条特定请求 RequestX 为变量,通过黑盒(也就是机器学习的模型,比如回归分析)从 A 的日志中找出一条记录与之对应,同理可以找出 B、C、D、E 等等的相关记录。

黑盒方案的优势就是不需要改变现有日志记录方法,但是缺点非常明显,由于依赖统计推理,黑盒方案的精度往往不高(可以经过大量的数据学习和训练,进行提高精度),实际使用中效果不好。

(比如 Project5,WAP5 和 Sherlock)

2. 基于标注的方案 (annotation-based)

基于标注的方案依赖于应用程序或中间件明确地标记一个所有服务全局 ID,借此将一串请求关联起来。 比如对 RequestX 来说,赋予一个标志符 1000,后续相关各个服务都会将标识符 1000 与记录一起打在日志里。 这种方法的优势就是比较精确,目前 google、twitter、淘宝等都采用这种方式。

具体的做法就是根据请求中的 TraceID 来获取 Trace 这个实例,各种编程语言有各自的方式。获取到 Trace 实例后就可以调用 Recorder(记录器)来记录 Span 了,记录值先直接以日志的形式存在本地,然后跟踪系统会启动一个 Collector(收集器) Daemon(守护线程)来收集日志,然后整理日志写入数据库。 解析的日志结果建议放在 BigTable(Cassandra、HDFS 等) 这类稀疏表的数据库里。因为每个 Trace 携带的 Span 可能不一样,最终的记录是每一行代表一个 Trace,这一行的每一列代表一个 Span。

但是基于标注的方案最主要的缺点也很明显,需要代码植入。(所以如何减少代码侵入是最大的问题)

对于减少代码的侵入性,建议将核心跟踪代码做的很轻巧,然后把它植入公共组件中,比如线程调用、控制流以及 RPC 库。

跟踪树和 span

分布式跟踪系统要做的就是记录每次发送和接受动作的标识符和时间戳,将一次请求涉及到的所有服务串联起来,只有这样才能搞清楚一次请求的完整调用链。

在 Dapper 中使用 Trace 表示对一次请求完整调用链的跟踪,将两个服务例如上面的服务 A 和服务 B 的请求 / 响应过程叫做一次 span。 可以看出每一次跟踪 Trace 都是一个树型结构,span 可以体现出服务之间的具体依赖关系。

每个跟踪树 Trace 都要定义一个全局唯一的 TraceID,推荐用 64 位的整数表示,在这个跟踪中的所有 Span 都将获取到这个 TraceID。 每个 Span 都有一个 ParentSpanID 和它自己的 SpanID。上图那个例子中 A 服务的 ParentSpanID 为空,SpanID 为 1;然后 B 服务的 ParentSpanID 为 1,SpanID 为 2;C 服务的 ParentSpanID 也为 1,SpanID 为 3,以此类推。

在 Dapper 跟踪树结构中,树节点是整个架构的基本单元,而每一个节点又是对 span 的引用。节点之间的连线表示的 span 和它的父 span 直接的关系。虽然 span 在日志文件中只是简单的代表 span 的开始和结束时间,他们在整个树形结构中却是相对独立的.

图 2:5 个 span 在 Dapper 跟踪树种短暂的关联关系

在上图中说明了 span 在一个大的跟踪过程中是什么样的。Dapper 记录了 span 名称,以及每个 span 的 ID 和父 ID,以重建在一次追踪过程中不同 span 之间的关系。如果一个 span 没有父 ID 被称为 root span。所有 span 都挂在一个特定的跟踪上,也共用一个跟踪 id(traceID 在图中未示出)。所有这些 ID 用全局唯一的 64 位整数标示。在一个典型的 Dapper 跟踪中,我们希望为每一个 RPC 对应到一个单一的 span 上,而且每一个额外的组件层都对应一个跟踪树型结构的层级。

图 3:在图 2 中所示的一个单独的 span 的细节图

span 除了记录 ParentSpanID 和自己的 SpanID 外,还会记录自己请求其他服务的时间和响应时间。由于客户端和服务器上的时间戳来自不同的主机,必须考虑到时间偏差,为了解决这个问题需要约定一个前提,即 RPC 客户端必须发出请求后,服务端才能收到,即如果服务端的时间戳比客户端发出请求的时间戳还靠前,那么就按请求时间来算,响应时间也是如此。(RPC 客户端发送一个请求之后,服务器端才能接收到,对于响应也是一样的(服务器先响应,然后客户端才能接收到这个响应)),这样一来,服务器端的 RPC 就有一个时间戳的一个上限和下限。

从上图可以首先能看出来这个 span 是一次”Hello.Call” 的 RPC,SpanID 是 5,ParentSpanID 是 3,TraceID 是 100。 我们重点看一下 Client Send, Server Recv, Server Send, Client Recv 即 CS, SR, SS, CR。

  • CS:客户端发送时间
  • SR:服务端接收时间
  • SS: 服务端发送时间
  • CR: 客户端接收时间

通过收集这四个时间戳,就可以在一次请求完成后计算出整个 Trace 的执行耗时和网络耗时,以及 Trace 中每个 Span 过程的执行耗时和网络耗时。

  • 服务调用耗时 = CR - CS
  • 服务处理耗时 = SS - SR
  • 网络耗时 = 服务调用耗时 - 服务处理耗时

span 的开始时间和结束时间,以及任何 RPC 的时间信息都通过 Dapper 在 RPC 组件库的植入记录下来。如果应用程序开发者选择在跟踪中增加他们自己的注释(如图中 “foo” 的注释)(业务数据),这些信息也会和其他 span 信息一样记录下来。

如何实现应用级透明?

在 google 的环境中,所有的应用程序使用相同的线程模型、控制流和 RPC 系统,既然不能让工程师写代码记录日志,那么就只能让这些线程模型、控制流和 RPC 系统来自动帮助工程师记录日志了。

举个例子,几乎所有的 google 进程间通信是建立在一个用 C++ 和 JAVA 开发的 RPC 框架上,dapper 把跟踪植入这个框架,span 的 ID 和跟踪的 ID 会从客户端发送到服务端,这样工程师也就不需要关心应用实现层次。

Dapper 跟踪收集的流程

分为 3 个阶段:

  1. 各个服务将 span 数据写到本机日志上;
  2. dapper 守护进程进行拉取,将数据读到 dapper 收集器里;
  3. dapper 收集器将结果写到 bigtable 中,一次跟踪被记录为一行。

跟踪损耗

跟踪系统的成本由两部分组成:

  1. 正在被监控的系统在生成追踪和收集追踪数据的消耗导致系统性能下降
  2. 需要使用一部分资源来存储和分析跟踪数据。虽然你可以说一个有价值的组件植入跟踪带来一部分性能损耗是值得的,我们相信如果基本损耗能达到可以忽略的程度,那么对跟踪系统最初的推广会有极大的帮助。

接下来展现一下三个方面:Dapper 组件操作的消耗,跟踪收集的消耗,以及 Dapper 对生产环境负载的影响。我们还介绍了 Dapper 可调节的采样率机制如何帮我们处理低损耗和跟踪代表性之间的平衡和取舍。

生成跟踪的损耗

生成跟踪的开销是 Dapper 性能影响中最关键的部分,因为收集和分析可以更容易在紧急情况下被关闭。Dapper 运行库中最重要的跟踪生成消耗在于创建和销毁 span 和 annotation,并记录到本地磁盘供后续的收集。根 span 的创建和销毁需要损耗平均 204 纳秒的时间,而同样的操作在其他 span 上需要消耗 176 纳秒。时间上的差别主要在于需要在跟 span 上给这次跟踪分配一个全局唯一的 ID。

如果一个 span 没有被采样的话,那么这个额外的 span 下创建 annotation 的成本几乎可以忽略不计,他由在 Dapper 运行期对 ThreadLocal 查找操作构成,这平均只消耗 9 纳秒。如果这个 span 被计入采样的话,会用一个用字符串进行标注–在图 4 中有展现–平均需要消耗 40 纳秒。这些数据都是在 2.2GHz 的 x86 服务器上采集的。

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

跟踪收集的消耗

谷歌的统计数据: 最坏情况下,Dapper 收集日志的守护进程在高于实际情况的负载基准下进行测试时的 cpu 使用率:没有超过 0.3% 的单核 cpu 使用率。 限制了 Dapper 守护进程为内核 scheduler 最低的优先级,以防在一台高负载的服务器上发生 cpu 竞争。 Dapper 也是一个带宽资源的轻量级的消费者,每一个 span 在我们的仓库中传输只占用了平均 426 的 byte。作为网络行为中的极小部分,Dapper 的数据收集在 Google 的生产环境中的只占用了 0.01% 的网络资源。

图 3:Dapper 守护进程在负载测试时的 CPU 资源使用率

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

每个请求都会利用到大量的服务器的高吞吐量的线上服务,这是对有效跟踪最主要的需求之一;这种情况需要生成大量的跟踪数据,并且他们对性能的影响是最敏感的。在表 2 中我们用集群下的网络搜索服务作为例子,我们通过调整采样率,来衡量 Dapper 在延迟和吞吐量方面对性能的影响。

图 4:网络搜索集群中,对不同采样率对网络延迟和吞吐的影响。延迟和吞吐的实验误差分别是 2.5% 和 0.15%。

我们看到,虽然对吞吐量的影响不是很明显,但为了避免明显的延迟,跟踪的采样还是必要的。然而,延迟和吞吐量的带来的损失在把采样率调整到小于 1/16 之后就全部在实验误差范围内。在实践中,我们发现即便采样率调整到 1/1024 仍然是有足够量的跟踪数据的用来跟踪大量的服务。保持 Dapper 的性能损耗基线在一个非常低的水平是很重要的,因为它为那些应用提供了一个宽松的环境使用完整的 Annotation API 而无惧性能损失。使用较低的采样率还有额外的好处,可以让持久化到硬盘中的跟踪数据在垃圾回收机制处理之前保留更长的时间,这样为 Dapper 的收集组件给了更多的灵活性。

采样

分布式跟踪系统的实现要求是性能低损耗的,尤其在生产环境中分布式跟踪系统不能影响到核心业务的性能。 Google 也不可能每次请求都跟踪的,所以要进行采样,每个应用和服务可以自己设置采样率。采样率应该是每个应用自己的配置里配置的,这样每个应用可以动态调整,特别是刚应用刚上线使可以适当调高采样率。

一般在系统峰值流量很大的情况下,只需要采样其中很小一部分请求,例如 1/1000 的采样率,即分布式跟踪系统只会在 1000 次请求中采样其中的某一次。

可变采样

任何给定进程的 Dapper 的消耗和每个进程单位时间的跟踪的采样率成正比。Dapper 的第一个生产版本在 Google 内部的所有进程上使用统一的采样率,为 1/1024。这个简单的方案是对我们的高吞吐量的线上服务来说是非常有用,因为那些感兴趣的事件 (在大吞吐量的情况下) 仍然很有可能经常出现,并且通常足以被捕捉到。

然而,在较低的采样率和较低的传输负载下可能会导致错过重要事件,而想用较高的采样率就需要能接受的性能损耗。对于这样的系统的解决方案就是覆盖默认的采样率,这需要手动干预的,这种情况是我们试图避免在 dapper 中出现的。

我们在部署可变采样的过程中,参数化配置采样率时,不是使用一个统一的采样方案,而是使用一个采样期望率来标识单位时间内采样的追踪。这样一来,低流量低负载自动提高采样率,而在高流量高负载的情况下会降低采样率,使损耗一直保持在控制之下。实际使用的采样率会随着跟踪本身记录下来,这有利于从 Dapper 的跟踪数据中准确的分析。

应对积极采样 (Coping with aggressive sampling)

新的 Dapper 用户往往觉得低采样率–在高吞吐量的服务下经常低至 0.01%–将会不利于他们的分析。我们在 Google 的经验使我们相信,对于高吞吐量服务,积极采样 (aggressive sampling) 并不妨碍最重要的分析。如果一个显着的操作在系统中出现一次,他就会出现上千次。低吞吐量的服务–也许是每秒请求几十次,而不是几十万–可以负担得起跟踪每一个请求,这是促使我们下决心使用自适应采样率的原因。

在收集过程中额外的采样

上述采样机制被设计为尽量减少与 Dapper 运行库协作的应用程序中明显的性能损耗。Dapper 的团队还需要控制写入中央资料库的数据的总规模,因此为达到这个目的,我们结合了二级采样。

目前我们的生产集群每天产生超过 1TB 的采样跟踪数据。Dapper 的用户希望生产环境下的进程的跟踪数据从被记录之后能保存至少两周的时间。逐渐增长的追踪数据的密度必须和 Dapper 中央仓库所消耗的服务器及硬盘存储进行权衡。对请求的高采样率还使得 Dapper 收集器接近写入吞吐量的上限。

为了维持物质资源的需求和渐增的 Bigtable 的吞吐之间的灵活性,我们在收集系统自身上增加了额外的采样率的支持。我们充分利用所有 span 都来自一个特定的跟踪并分享同一个跟踪 ID 这个事实,虽然这些 span 有可能横跨了数千个主机。对于在收集系统中的每一个 span,我们用 hash 算法把跟踪 ID 转成一个标量 Z,这里 0<=Z<=1。如果 Z 比我们收集系统中的系数低的话,我们就保留这个 span 信息,并写入到 Bigtable 中。反之,我们就抛弃他。通过在采样决策中的跟踪 ID,我们要么保存、要么抛弃整个跟踪,而不是单独处理跟踪内的 span。我们发现,有了这个额外的配置参数使管理我们的收集管道变得简单多了,因为我们可以很容易地在配置文件中调整我们的全局写入率这个参数。

如果整个跟踪过程和收集系统只使用一个采样率参数确实会简单一些,但是这就不能应对快速调整在所有部署的节点上的运行期采样率配置的这个要求。我们选择了运行期采样率,这样就可以优雅的去掉我们无法写入到仓库中的多余数据,我们还可以通过调节收集系统中的二级采样率系数来调整这个运行期采样率。Dapper 的管道维护变得更容易,因为我们就可以通过修改我们的二级采样率的配置,直接增加或减少我们的全局覆盖率和写入速度。

最重要的 Dapper 的不足

  1. 合并的影响:我们的模型隐含的前提是不同的子系统在处理的都是来自同一个被跟踪的请求。在某些情况下,缓冲一部分请求,然后一次性操作一个请求集会更加有效。(比如,磁盘上的一次合并写入操作)。在这种情况下,一个被跟踪的请求可以看似是一个大型工作单元。此外,当有多个追踪请求被收集在一起,他们当中只有一个会用来生成那个唯一的跟踪 ID,用来给其他 span 使用,所以就无法跟踪下去了。我们正在考虑的解决方案,希望在可以识别这种情况的前提下,用尽可能少的记录来解决这个问题。
  2. 跟踪批处理负载:Dapper 的设计,主要是针对在线服务系统,最初的目标是了解一个用户请求产生的系统行为。然而,离线的密集型负载,例如符合 MapReduce 模型的情况,也可以受益于性能挖潜。在这种情况下,我们需要把跟踪 ID 与一些其他的有意义的工作单元做关联,诸如输入数据中的键值(或键值的范围),或是一个 MapReduce shard。
  3. 寻找根源:Dapper 可以有效地确定系统中的哪一部分致使系统整个速度变慢,但并不总是能够找出问题的根源。例如,一个请求很慢有可能不是因为它自己的行为,而是由于队列中其他排在它前面的 (queued ahead of) 请求还没处理完。程序可以使用应用级的 annotation 把队列的大小或过载情况写入跟踪系统。此外,如果这种情况屡见不鲜,那么在 ProfileMe 中提到的成对的采样技术可以解决这个问题。它由两个时间重叠的采样率组成,并观察它们在整个系统中的相对延迟。
  4. 记录内核级的信息:一些内核可见的事件的详细信息有时对确定问题根源是很有用的。我们有一些工具,能够跟踪或以其他方式描述内核的执行,但是,想用通用的或是不那么突兀的方式,是很难把这些信息到捆绑到用户级别的跟踪上下文中。我们正在研究一种妥协的解决方案,我们在用户层面上把一些内核级的活动参数做快照,然后绑定他们到一个活动的 span 上。

MapReduce 模型

MapReduce 是一种编程模型,用于大规模数据集(大于 1TB)的并行运算。概念 "Map(映射)“和"Reduce(归约)”,是它们的主要思想,都是从函数式编程语言里借来的,还有从矢量编程语言里借来的特性。它极大地方便了编程人员在不会分布式并行编程的情况下,将自己的程序运行在分布式系统上。 当前的软件实现是指定一个 Map(映射)函数,用来把一组键值对映射成一组新的键值对,指定并发的 Reduce(归约)函数,用来保证所有映射的键值对中的每一个共享相同的键组。

ProfileMe 提到的成对采样技术

英文论文地址:https://www.cs.tufts.edu/comp/150PAT/tools/dcpi/micro30.pdf

Pinpoint

Pinpoint 是一个 APM(应用程序性能管理)工具,适用于用 Java / PHP 编写的大型分布式系统。 受 Dapper 的启发,Pinpoint 提供了一种解决方案,通过跟踪分布式应用程序之间的事务,帮助分析系统的整体结构以及它们中的组件如何相互连接。 对 Java 领域的性能分析有兴趣的都应该看看这个开源项目,这个是一个韩国团队实现并开源出来的,通过 JavaAgent 的机制来做字节码代码植入 (另外还有 ASM 字节码技术),实现加入 traceid 和抓取性能数据的目的。

github 地址:https://github.com/naver/pinpoint

NewRelic、Oneapm 之类的工具在 Java 平台上的性能分析也是类似的机制。 NewRelic:国外收费的 APM Oneapm:国内收费的 APM

Pinpoint 架构示意图

Pinpoint 通过在 Host App 启动时加入 PinpointAgent 来采集数据,然后把采集到的跟踪数据和性能数据实时发送到 Pinpoint Collector,Collector 收集之后存放到 HBase 数据库中,由 HBase 做 MapReduce 运算,分析出分布式系统的机器访问关系拓扑图、每个节点的线程状态、请求 / 响应数据、调用栈信息、应用程序的性能数据,通过 Pinpoint WebUI 可以进行实时的展示。

侵入性

PinPoint 采用的是 Java Agent 向节点应用指定的函数前注入 before 和 after 逻辑,向服务器发送消息,因此基本不用修改代码,只需简单修改一下配置;

APM 探针的基本原理

SkyWalking

Skywalking 是由国内一位叫吴晟的工程师开源,已加入 Apache 孵化器,是一个 APM 系统,为微服务架构和云原生架构系统设计。它通过探针自动收集所需的指标,并进行分布式追踪。通过这些调用链路以及指标,Skywalking APM 会感知应用间关系和服务间关系,并进行相应的指标统计。Skywalking 支持链路追踪和监控应用组件基本涵盖主流框架和容器,如国产 PRC Dubbo 和 motan 等,国际化的 spring boot,spring cloud。

github 地址:https://github.com/apache/incubator-skywalking

Skywalking 总体架构主要分为三部分:

  1. SkywalkingAgent:探针,原理同 Pinpoint 一样,使用 JavaAgent 做字节代码植入,用来收集和发送数据到 Skywalking Collector;
  2. SkywalkingCollector:链路数据收集器,数据可以落地到 ElasticSearch 或者 H2;
  3. SkywalkingUI:Web 可视化平台,用来展示落地的数据;

Zipkin

这个是 twitter 开源出来的,也是参考 Dapper 的体系来做的。Zipkin 的 Java 应用端是通过一个叫 Brave 的组件来实现对应用内部的性能分析数据采集。Brave(github.com/openzipkin/…

github 地址:https://github.com/openzipkin/zipkin

Zipkin 架构图

Zipkin 主要分为四个部分:

  1. ZipkinCollector:采集数据传输到 Collector 之后,Collector 负责校验数据、存储数据、为数据建立索引;
  2. Storage:Zipkin 的数据可以存储在 Cassandra、ElasticSearch 和 MySQL 中;
  3. Query API:提供数据的查询和检索服务;
  4. Web UI:可视化展示平台,用于展示跟踪数据。

CAT

CAT 是由美团点评开源的项目,基于 Java 开发的实时应用监控平台,包括实时应用监控,业务监控,可以提供十几张报表展示。Cat 的定位是实时监控平台,但与其说是监控平台,更像是个数据仓库,在数据仓库的基础上提供丰富的报表分析功能。不过 CAT 实现跟踪的手段,是要在代码里硬编码写一些 “埋点”,也就是侵入式的。这样做有利有弊,好处是可以在自己需要的地方加埋点,比较有针对性;坏处是必须改动现有系统,很多开发团队不愿意。

github 地址:https://github.com/dianping/cat

CAT 架构图

CAT 分客户端和服务器端,客户端使用 cat 接口向服务器端上报统一格式的日志信息。CAT 的客户端是产生日志的地方(一般来说就是被监控的应用,上图中的 "应用" 节点),相应的服务器端则是接受日志、消费日志的地方(上图中的 server 节点),日志消费后生成会日志报表。

对比

方案依赖实现方式存储JVM 监控trace 查询侵入部署成本
PinpointJava 6,7,8 maven3+ Hbase0.94+java 探针,字节码增强HBase支持需要二次开发最低较高
SkyWalkingJava 6,7,8 maven3.0+ nodejs zookeeper elasticsearchjava 探针,字节码增强elasticsearch , H2 ,mysql,TIDN,Sharding Sphere支持支持
ZipkinJava 6,7,8 Maven3.2+ rabbitMQ拦截请求,发送(HTTP,mq)数据至 zipkin 服务内存 , mysql , Cassandra , Elasticsearch不支持支持高,需要开发
CATJava 6 7 8、Maven 3+ MySQL 5.6 5.7、Linux 2.6+ hadoop 可选代码埋点(拦截器,注解,过滤器等)mysql , hdfs不支持支持高,需要埋点

基于对程序源代码和配置文件的低侵入考虑,推荐的选型顺序依次是 Pinpoint > SkyWalking > Zipkin > CAT

Pinpoint:基本不用修改源码和配置文件,只要在启动命令里指定 javaagent 参数即可,对于运维人员来讲最为方便; SkyWalking:不用修改源码,需要修改配置文件; Zipkin:需要对 Spring、web.xml 之类的配置文件做修改,相对麻烦一些; CAT:因为需要修改源码设置埋点,因此基本不太可能由运维人员单独完成,而必须由开发人员的深度参与了;

相对于传统的监控软件(Zabbix 之流)的区别,APM 跟关注在对于系统内部执行、系统间调用的性能瓶颈分析,这样更有利于定位到问题的具体原因,而不仅仅像传统监控软件一样只提供一些零散的监控点和指标,就算告警了也不知道问题是出在哪里。

总结

主流 APM 工具为了更好地进行推广,主要采用了侵入程度低的方式完成对应用代码的改造。并且为了应对云计算、微服务、容器化的迅速发展与应用带来的 APM 监控的数据的海量增长的趋势,数据落地方式也主要以海量存储数据库为主。

未来在数据分析和性能分析方面,大数据和机器学习将在 APM 领域发挥重要的作用,APM 的功能也将从单一的资源监控和应用监控,向异常检测、性能诊断、未来预测等自动化、智能化等方向发展。

后面有时间会进行整理一篇 JavaAgent 机制以及 ASM 字节码技术的文章。