专栏名称: 朱小厮的博客
著有畅销书:《深入理解Kafka》和《RabbitMQ实战指南》。公众号主要用来分享Java技术栈、Golang技术栈、消息中间件(如Kafka、RabbitMQ)、存储、大数据以及通用型技术架构等相关的技术。
目录
相关文章推荐
四平发布  ·  注意!吉林多个景区结束雪季运营! ·  昨天  
四平发布  ·  注意!吉林多个景区结束雪季运营! ·  昨天  
爱青岛  ·  接下来的青岛是这样的…… ·  昨天  
爱青岛  ·  接下来的青岛是这样的…… ·  昨天  
51好读  ›  专栏  ›  朱小厮的博客

轻松掌控全链路监控:方案概述与对比 | 真的很干!

朱小厮的博客  · 公众号  ·  · 2019-09-11 17:31

正文

点击上方“ 朱小厮的博客 ”,选择“ 设为星标

回复” 资料 “获取新整理的1TB资料


来源:http://uee.me/ba8Mw



0


问题背景


随着微服务架构的流行,服务按照不同的维度进行拆分,一次请求往往需要涉及到多个服务。互联网应用构建在不同的软件模块集上,这些软件模块,有可能是由不同的团队开发、可能使用不同的编程语言来实现、有可能布在了几千台服务器,横跨多个不同的数据中心。因此,就需要一些可以帮助理解系统行为、用于分析性能问题的工具,以便发生故障的时候,能够快速定位和解决问题。


全链路监控组件就在这样的问题背景下产生了。最出名的是谷歌公开的论文提到的 Google Dapper。想要在这个上下文中理解分布式系统的行为,就需要监控那些横跨了不同的应用、不同的服务器之间的关联动作。


所以,在复杂的微服务架构系统中,几乎每一个前端请求都会形成一个复杂的分布式服务调用链路。一个请求完整调用链可能如下图所示:


那么在业务规模不断增大、服务不断增多以及频繁变更的情况下,面对复杂的调用链路就带来一系列问题:

  1. 如何快速发现问题?

  2. 如何判断故障影响范围?

  3. 如何梳理服务依赖以及依赖的合理性?

  4. 如何分析链路性能问题以及实时容量规划?

同时我们会关注在请求处理期间各个调用的各项性能指标,比如:吞吐量(TPS)、响应时间及错误记录等。

  1. 吞吐量,根据拓扑可计算相应组件、平台、物理设备的实时吞吐量。

  2. 响应时间,包括整体调用的响应时间和各个服务的响应时间等。

  3. 错误记录,根据服务返回统计单位时间异常次数。

全链路性能监控 从整体维度到局部维度展示各项指标,将跨应用的所有调用链性能信息集中展现,可方便度量整体和局部性能,并且方便找到故障产生的源头,生产上可极大缩短故障排除时间。


有了全链路监控工具,我们能够达到:

  1. 请求链路追踪,故障快速定位:可以通过调用链结合业务日志快速定位错误信息。

  2. 可视化:各个阶段耗时,进行性能分析。

  3. 依赖优化:各个调用环节的可用性、梳理服务依赖关系以及优化。

  4. 数据分析,优化链路:可以得到用户的行为路径,汇总分析应用在很多业务场景。



1


目标要求


如上所述,那么我们选择全链路监控组件有哪些目标要求呢?Google Dapper中也提到了,总结如下:


1. 探针的性能消耗

APM组件服务的影响应该做到足够小。服务调用埋点本身会带来性能损耗,这就需要调用跟踪的低损耗,实际中还会通过配置采样率的方式,选择一部分请求去分析请求路径。在一些高度优化过的服务,即使一点点损耗也会很容易察觉到,而且有可能迫使在线服务的部署团队不得不将跟踪系统关停。


2. 代码的侵入性

即也作为业务组件,应当尽可能少入侵或者无入侵其他业务系统,对于使用方透明,减少开发人员的负担。


对于应用的程序员来说,是不需要知道有跟踪系统这回事的。如果一个跟踪系统想生效,就必须需要依赖应用的开发者主动配合,那么这个跟踪系统也太脆弱了,往往由于跟踪系统在应用中植入代码的bug或疏忽导致应用出问题,这样才是无法满足对跟踪系统“无所不在的部署”这个需求。


3. 可扩展性

一个优秀的调用跟踪系统必须支持分布式部署,具备良好的可扩展性。能够支持的组件越多当然越好。或者提供便捷的插件开发API,对于一些没有监控到的组件,应用开发者也可以自行扩展。


4. 数据的分析

数据的分析要快 ,分析的维度尽可能多。跟踪系统能提供足够快的信息反馈,就可以对生产环境下的异常状况做出快速反应。分析的全面,能够避免二次开发。



2


功能模块


一般的全链路监控系统,大致可分为四大功能模块:


1.埋点与生成日志

埋点即系统在当前节点的上下文信息,可以分为 客户端埋点、服务端埋点,以及客户端和服务端双向型埋点。埋点日志通常要包含以下内容traceId、spanId、调用的开始时间,协议类型、调用方ip和端口,请求的服务名、调用耗时,调用结果,异常信息等,同时预留可扩展字段,为下一步扩展做准备;

  • 不能造成性能负担:一个价值未被验证,却会影响性能的东西,是很难在公司推广的!

  • 因为要写log,业务QPS越高,性能影响越重。通过采样和异步log解决。

2.收集和存储日志

主要支持分布式日志采集的方案,同时增加MQ作为缓冲;

  • 每个机器上有一个 deamon 做日志收集,业务进程把自己的Trace发到daemon,daemon把收集Trace往上一级发送;

  • 多级的collector,类似pub/sub架构,可以负载均衡;

  • 对聚合的数据进行 实时分析和离线存储;

  • 离线分析 需要将同一条调用链的日志汇总在一起;

3.分析和统计调用链路数据,以及时效性

调用链跟踪分析:把同一TraceID的Span收集起来,按时间排序就是timeline。把ParentID串起来就是调用栈。

抛异常或者超时,在日志里打印TraceID。利用TraceID查询调用链情况,定位问题。

依赖度量:

  • 强依赖:调用失败会直接中断主流程

  • 高度依赖:一次链路中调用某个依赖的几率高

  • 频繁依赖:一次链路调用同一个依赖的次数多

离线分析:按TraceID汇总,通过Span的ID和ParentID还原调用关系,分析链路形态。

实时分析:对单条日志直接分析,不做汇总,重组。得到当前QPS,延迟。

4.展现以及决策支持


3


Google Dapper


3.1 Span

基本工作单元,一次链路调用(可以是 RPC,DB 等没有特定的限制)创建一个 span,通过一个64位 ID 标识它,uuid 较为方便,span 中还有其他的数据,例如描述信息,时间戳,key-value对的(Annotation)tag信息,parent_id 等,其中 parent-id 可以表示 span 调用链路来源。


上图说明了span在一次大的跟踪过程中是什么样的。Dapper记录了span名称,以及每个span的ID和父ID,以重建在一次追踪过程中不同span之间的关系。如果一个span没有父ID被称为root span。所有span都挂在一个特定的跟踪上,也共用一个跟踪id。


Span数据结构


type Span struct {    TraceID    int64 // 用于标示一次完整的请求id    Name       string    ID         int64 // 当前这次调用span_id    ParentID   int64 // 上层服务的调用span_id  最上层服务parent_id为null    Annotation []Annotation // 用于标记的时间戳    Debug      bool}


3.2 Trace


类似于 树结构的Span集合 ,表示一次完整的跟踪,从请求到服务器开始,服务器返回response结束,跟踪每次 rpc 调用的耗时,存在唯一标识trace_id。比如:你运行的分布式大数据存储一次 Trace 就由你的一次请求组成。


每种颜色的note标注了一个span,一条链路通过TraceId唯一标识,Span标识发起的请求信息。 树节点是整个架构的基本单元,而每一个节点又是对span的引用 。节点之间的连线表示的span和它的父span直接的关系。虽然span在日志文件中只是简单的代表span的开始和结束时间,他们在整个树形结构中却是相对独立的。


3.3 Annotation


注解,用来记录请求特定事件相关信息(例如时间),一个span中会有多个annotation注解描述 。通常包含四个注解信息:

(1) cs :Client Start,表示客户端发起请求
(2) sr :Server Receive,表示服务端收到请求
(3) ss :Server Send,表示服务端完成处理,并将结果发送给客户端
(4) cr :Client Received,表示客户端获取到服务端返回信息

Annotation数据结构

type Annotation struct {    Timestamp int64    Value     string    Host      Endpoint    Duration  int32}



3.4 调用示例


1. 请求调用示例

  1. 当用户发起一个请求时,首先到达前端A服务,然后分别对B服务和C服务进行RPC调用;

  2. B服务处理完给A做出响应,但是C服务还需要和后端的D服务和E服务交互之后再返还给A服务,最后由A服务来响应用户的请求;


2. 调用过程追踪

整个调用过程追踪:

  1. 请求到来生成一个全局TraceID,通过TraceID可以串联起整个调用链,一个TraceID代表一次请求。

  2. 除了TraceID外,还需要SpanID用于记录调用父子关系。每个服务会记录下parent id和span id,通过他们可以组织一次完整调用链的父子关系。

  3. 一个没有parent id的span成为root span,可以看成调用链入口。

  4. 所有这些ID可用全局唯一的64位整数表示;

  5. 整个调用过程中每个请求都要透传TraceID和SpanID。

  6. 每个服务将该次请求附带的TraceID和附带的SpanID作为parent id记录下,并且将自己生成的SpanID也记录下。

  7. 要查看某次完整的调用则 只要根据TraceID查出所有调用记录,然后通过parent id和span id组织起整个调用父子关系。


    3. 调用链核心工作

    1. 调用链数据生成 ,对整个调用过程的所有应用进行埋点并输出日志。

    2. 调用链数据采集 ,对各个应用中的日志数据进行采集。

    3. 调用链数据存储及查询 ,对采集到的数据进行存储,由于日志数据量一般都很大,不仅要能对其存储,还需要能提供快速查询。

    4. 指标运算、存储及查询 ,对采集到的日志数据进行各种指标运算,将运算结果保存起来。

    5. 告警功能 ,提供各种阀值警告功能。


    4. 整体部署架构



    1. 整体部署架构

    2. 通过AGENT生成调用链日志。

    3. 通过logstash采集日志到kafka。

    4. kafka负责提供数据给下游消费。

    5. storm计算汇聚指标结果并落到es。

    6. storm抽取trace数据并落到es,这是为了提供比较复杂的查询 。比如通过时间维度查询调用链,可以很快查询出所有符合的traceID, 根据这些traceID再去 Hbase 查数据就快了

    7. logstash将kafka原始数据拉取到hbase中。 hbase的rowkey为traceID,根据traceID查询是很快的


5. AGENT无侵入部署

通过AGENT代理无侵入式部署,将性能测量与业务逻辑完全分离,可以测量任意类的任意方法的执行时间,这种方式大大提高了采集效率,并且减少运维成本。 根据服务跨度主要分为两大类AGENT


    a. 服务内AGENT ,这种方式是通过 Java 的agent机制,对服务内部的方法调用层次信息进行数据收集,如方法调用耗时、入参、出参等信息。

    b. 跨服务AGENT ,这种情况需要对主流RPC框架以插件形式提供无缝支持。并通过提供标准数据规范以适应自定义RPC框架:

    (1)Dubbo支持;
    (2)Rest支持;
    (3)自定义RPC支持;

    6. 调用链监控好处
    1. 准确掌握生产一线应用部署情况

    2. 从调用链全流程性能角度, 识别对关键调用链,并进行优化

    3. 提供可追溯的性能数据 ,量化 IT 运维部门业务价值;

    4. 快速定位代码性能问题 ,协助开发人员持续性的优化代码;

    5. 协助开发人员进行白盒测试 ,缩短系统上线稳定期;



4


方案比较


市面上的全链路监控理论模型大多都是借鉴 Google Dapper 论文,本文重点关注以下三种APM组件:

  1. Zipkin: 由Twitter公司开源,开放源代码分布式的跟踪系统,用于收集服务的定时数据,以解决微服务架构中的延迟问题,包括: 数据的收集、存储、查找和展现。

  2. Pinpoint: 一款对Java编写的大规模分布式系统的APM工具,由韩国人开源的分布式跟踪组件。

  3. Skywalking: 国产的优秀APM组件,是一个对JAVA分布式应用程序集群的业务运行情况进行追踪、告警和分析的系统。

以上三种全链路监控方案需要对比的项提炼出来:


  1. 探针的性能

    主要是agent对服务的吞吐量、CPU和内存的影响。 微服务的规模和动态性使得数据收集的成本大幅度提高。

  2. collector的可扩展性

    能够水平扩展以便支持大规模服务器集群。

  3. 全面的调用链路数据分析

    提供代码级别的可见性以便轻松定位失败点和瓶颈。

  4. 对于开发透明,容易开关

    添加新功能而无需修改代码,容易启用或者禁用。

  5. 完整的调用链应用拓扑

    自动检测应用拓扑,帮助你搞清楚应用的架构


4.1 探针的性能


比较关注探针的性能,毕竟APM定位还是工具,如果启用了链路监控组建后,直接导致吞吐量降低过半,那也是不能接受的。 对skywalking、zipkin、pinpoint进行了压测,并与基线(未使用探针)的情况进行了对比。


选用了一个常见的基于Spring的应用程序,他包含Spring Boot, Spring MVC,redis客户端,mysql。 监控这个应用程序,每个trace,探针会抓取5个span(1 Tomcat, 1 SpringMVC, 2 Jedis, 1 Mysql)。 这边基本和 skywalkingtest 的测试应用差不多。


模拟了三种并发用户: 500,750,1000。 使用jmeter测试,每个线程发送30个请求,设置思考时间为10ms。 使用的采样率为1,即100%,这边与生产可能有差别。 pinpoint默认的采样率为20,即50%,通过设置agent的配置文件改为100%。 zipkin默认也是1。 组合起来,一共有12种。 下面看下汇总表:



从上表可以看出,在三种链路监控组件中,skywalking的探针对吞吐量的影响最小,zipkin的吞吐量居中。pinpoint的探针对吞吐量的影响较为明显,在500并发用户时,测试服务的吞吐量从1385降低到774,影响很大。 然后再看下CPU和memory的影响,在内部服务器进行的压测,对CPU和memory的影响都差不多在10%之内。


4.2 collector的可扩展性


collector的可扩展性,使得能够水平扩展以便支持大规模服务器集群。

  1. zipkin

    开发zipkin-Server(其实就是提供的开箱即用包),zipkin-agent与zipkin-Server通过http或者mq进行通信,http通信会对正常的访问造成影响,所以还是推荐基于mq异步方式通信,zipkin-Server通过订阅具体的topic进行消费。 这个当然是可以扩展的,多个zipkin-Server实例进行异步消费mq中的监控信息。



  2. skywalking

    skywalking的collector支持两种部署方式: 单机和集群模式。collector与agent之间的通信使用了gRPC。

  3. pinpoint

    同样,pinpoint也是支持集群和单机部署的。 pinpoint agent通过thrift通信框架,发送链路信息到collector。

4.3 全面的调用链路数据分析

全面的调用链路数据分析,提供代码级别的可见性以便轻松定位失败点和瓶颈。

  1. zipkin


    zipkin的链路监控粒度相对没有那么细 ,从上图可以看到调用链中具体到接口级别,再进一步的调用信息并未涉及。

  2. skywalking


    skywalking 还支持20+的中间件、框架、类库 ,比如: 主流的dubbo、Okhttp,还有DB和消息中间件。上图skywalking链路调用分析截取的比较简单,网关调用user服务,由于支持众多的中间件,所以skywalking链路调用分析比zipkin完备些

  3. pinpoint


    pinpoint应该是这三种APM组件中, 数据分析最为完备的组件 提供代码级别的可见性以便轻松定位失败点和瓶颈,上图可以看到对于执行的sql语句,都进行了记录。 还可以配置报警规则等,设置每个应用对应的负责人,根据配置的规则报警,支持的中间件和框架也比较完备。

4.4 对于开发透明,容易开关

对于开发透明,容易开关,添加新功能而无需修改代码,容易启用或者禁用。 我们期望功能可以不修改代码就工作并希望得到代码级别的可见性。

对于这一点,Zipkin 使用修改过的类库和它自己的容器(Finagle)来提供分布式事务跟踪的功能。 但是,它要求在需要时修改代码。 skywalking和pinpoint都是基于字节码增强的方式,开发人员不需要修改代码,并且可以收集到更多精确的数据因为有字节码中的更多信息。


4.5 完整的调用链应用拓扑

自动检测应用拓扑,帮助你搞清楚应用的架构。



面三幅图,分别展示了APM组件各自的调用拓扑,都能实现完整的调用链应用拓扑。 相对来说,pinpoint界面显示的更加丰富,具体到调用的DB名,zipkin的拓扑局限于服务于服务之间。


4.6 Pinpoint与Zipkin细化比较

4.6.1 Pinpoint与Zipkin差异性

  1. Pinpoint 是一个完整的性能监控解决方案: 有从探针、收集器、存储到 Web 界面等全套体系; 而 Zipkin 只侧重收集器和存储服务,虽然也有用户界面,但其功能与 Pinpoint 不可同日而语。 反而 Zipkin 提供有 Query 接口,更强大的用户界面和系统集成能力,可以基于该接口二次开发实现。

  2. Zipkin 官方提供有基于 Finagle 框架(Scala 语言)的接口,而其他框架的接口由社区贡献,目前可以支持 Java、Scala、Node、Go、Python、Ruby 和 C# 等主流开发语言和框架; 但是 Pinpoint 目前只有官方提供的 Java Agent 探针,其他的都在请求社区支援中(请参见 #1759 和 #1760)。

  3. Pinpoint 提供有 Java Agent 探针,通过字节码注入的方式实现调用拦截和数据收集,可以做到真正的代码无侵入,只需要在启动服务器的时候添加一些参数,就可以完成探针的部署; 而 Zipkin 的 Java 接口实现 Brave,只提供了基本的操作 API,如果需要与框架或者项目集成的话,就需要手动添加配置文件或增加代码。

  4. Pinpoint 的后端存储基于 HBase,而 Zipkin 基于 Cassandra。


4.6.2 Pinpoint与Zipkin相似性

Pinpoint 与 Zipkin 都是基于 Google Dapper 的那篇论文,因此理论基础大致相同。 两者都是将服务调用拆分成若干有级联关系的 Span,通过 SpanId 和 ParentSpanId 来进行调用关系的级联;最后再将整个调用链流经的所有的 Span 汇聚成一个 Trace,报告给服务端的 collector 进行收集和存储。

即便在这一点上,Pinpoint 所采用的概念也不完全与那篇论文一致。 比如他采用 TransactionId 来取代 TraceId,而真正的 TraceId 是一个结构,里面包含了 TransactionId, SpanId 和 ParentSpanId。 而且 Pinpoint 在 Span 下面又增加了一个 SpanEvent 结构,用来记录一个 Span 内部的调用细节(比如具体的方法调用等等),因此 Pinpoint 默认会比 Zipkin 记录更多的跟踪数据。







请到「今天看啥」查看全文