一、问题背景
随着微服务架构的流行,服务按照不同的维度进行拆分,一次请求往往需要涉及到多个服务。互联网应用构建在不同的软件模块集上,这些软件模块,有可能是由不同的团队开发、可能使用不同的编程语言来实现、有可能布在了几千台服务器,横跨多个不同的数据中心。因此,就需要一些可以帮助理解系统行为、用于分析性能问题的工具,以便发生故障的时候,能够快速定位和解决问题。
全链路监控组件就在这样的问题背景下产生了。最出名的是谷歌公开的论文提到的Google Dapper。想要在这个上下文中理解分布式系统的行为,就需要监控那些横跨了不同的应用、不同的服务器之间的关联动作。
所以,在复杂的微服务架构系统中,几乎每一个前端请求都会形成一个复杂的分布式服务调用链路。一个请求完整调用链可能如下图所示:
那么在业务规模不断增大、服务不断增多以及频繁变更的情况下,面对复杂的调用链路就带来一系列问题:
- 如何快速发现问题?
- 如何判断故障影响范围?
- 如何梳理服务依赖以及依赖的合理性?
- 如何分析链路性能问题以及实时容量规划?
同时我们会关注在请求处理期间各个调用的各项性能指标,比如:吞吐量(TPS)、响应时间及错误记录等。
- 吞吐量,根据拓扑可计算相应组件、平台、物理设备的实时吞吐量。
- 响应时间,包括整体调用的响应时间和各个服务的响应时间等。
- 错误记录,根据服务返回统计单位时间异常次数。
二、全链路性能监控
全链路性能监控从整体维度到局部维度展示各项指标,将跨应用的所有调用链性能信息集中展现,可方便度量整体和局部性能,并且方便找到故障产生的源头,生产上可极大缩短故障排除时间。
有了全链路监控工具,我们能够达到:
- 请求链路追踪,故障快速定位:可以通过调用链结合业务日志快速定位错误信息。
- 可视化:各个阶段耗时,进行性能分析。
- 依赖优化:各个调用环节的可用性、梳理服务依赖关系以及优化。
- 数据分析,优化链路:可以得到用户的行为路径,汇总分析应用在很多业务场景。
目标要求
如上所述,那么我们选择全链路监控组件有哪些目标要求呢?
Google Dapper中也提到了,总结如下:
1、探针的性能消耗
APM组件服务的影响应该做到足够小。服务调用埋点本身会带来性能损耗,这就需要调用跟踪的低损耗,实际中还会通过配置采样率的方式,选择一部分请求去分析请求路径。在一些高度优化过的服务,即使一点点损耗也会很容易察觉到,而且有可能迫使在线服务的部署团队不得不将跟踪系统关停。
2、代码的侵入性
即也作为业务组件,应当尽可能少入侵或者无入侵其他业务系统,对于使用方透明,减少开发人员的负担。
对于应用的程序员来说,是不需要知道有跟踪系统这回事的。如果一个跟踪系统想生效,就必须需要依赖应用的开发者主动配合,那么这个跟踪系统也太脆弱了,往往由于跟踪系统在应用中植入代码的bug或疏忽导致应用出问题,这样才是无法满足对跟踪系统“无所不在的部署”这个需求。
3、可扩展性
一个优秀的调用跟踪系统必须支持分布式部署,具备良好的可扩展性。能够支持的组件越多当然越好。或者提供便捷的插件开发API,对于一些没有监控到的组件,应用开发者也可以自行扩展。
4、数据的分析
数据的分析要快,分析的维度尽可能多。跟踪系统能提供足够快的信息反馈,就可以对生产环境下的异常状况做出快速反应。分析的全面,能够避免二次开发。
功能模块
一般的全链路监控系统,大致可分为四大功能模块:
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、展现以及决策支持
三、Google Dapper
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数据结构:
Trace
类似于树结构的Span集合,表示一次完整的跟踪,从请求到服务器开始,服务器返回response结束,跟踪每次RPC调用的耗时,存在唯一标识trace_id。比如:你运行的分布式大数据存储一次Trace就由你的一次请求组成。
说明:
每种颜色的note标注了一个Span,一条链路通过TraceId唯一标识,Span标识发起的请求信息。树节点是整个架构的基本单元,而每一个节点又是对Span的引用。节点之间的连线表示的Span和它的父Span直接的关系。虽然Span在日志文件中只是简单的代表Span的开始和结束时间,他们在整个树形结构中却是相对独立的。
Annotation
注解,用来记录请求特定事件相关信息(例如时间),一个Span中会有多个annotation注解描述。通常包含四个注解信息:
cs:Client Start,表示客户端发起请求
sr:Server Receive,表示服务端收到请求
ss:Server Send,表示服务端完成处理,并将结果发送给客户端
cr:Client Received,表示客户端获取到服务端返回信息
Annotation数据结构:
调用示例
请求调用示例:
当用户发起一个请求时,首先到达前端A服务,然后分别对B服务和C服务进行RPC调用;
B服务处理完给A做出响应,但是C服务还需要和后端的D服务和E服务交互之后再返还给A服务,最后由A服务来响应用户的请求。
调用过程追踪:
- 请求到来生成一个全局TraceID,通过TraceID可以串联起整个调用链,一个TraceID代表一次请求。
- 除了TraceID外,还需要SpanID用于记录调用父子关系。每个服务会记录下parent id和span id,通过他们可以组织一次完整调用链的父子关系。
- 一个没有parent id的span成为root span,可以看成调用链入口。
- 所有这些ID可用全局唯一的64位整数表示;
- 整个调用过程中每个请求都要透传TraceID和SpanID。
- 每个服务将该次请求附带的TraceID和附带的SpanID作为parent id记录下,并且将自己生成的SpanID也记录下。
- 要查看某次完整的调用则只要根据TraceID查出所有调用记录,然后通过parent id和span id组织起整个调用父子关系。
调用链核心工作:
- 调用链数据生成,对整个调用过程的所有应用进行埋点并输出日志。
- 调用链数据采集,对各个应用中的日志数据进行采集。
- 调用链数据存储及查询,对采集到的数据进行存储,由于日志数据量一般都很大,不仅要能对其存储,还需要能提供快速查询。
- 指标运算、存储及查询,对采集到的日志数据进行各种指标运算,将运算结果保存起来。
- 告警功能,提供各种阀值警告功能。
整体部署架构
说明:
- 通过Agent生成调用链日志。
- 通过Logstash采集日志到Kafka。
- Kafka负责提供数据给下游消费。
- storm计算汇聚指标结果并落到ES。
storm抽取trace数据并落到ES,这是为了提供比较复杂的查询。比如通过时间维度查询调用链,可以很快查询出所有符合的traceID,根据这些traceID再去Hbase查数据就快了。 - Logstash将Kafka原始数据拉取到HBase中。HBase的rowkey为traceID,根据traceID查询是很快的。
Agent无侵入部署
通过Agent代理无侵入式部署,将性能测量与业务逻辑完全分离,可以测量任意类的任意方法的执行时间,这种方式大大提高了采集效率,并且减少运维成本。根据服务跨度主要分为两大类AGENT:
服务内Agent,这种方式是通过Java的Agent机制,对服务内部的方法调用层次信息进行数据收集,如方法调用耗时、入参、出参等信息。
跨服务Agent,这种情况需要对主流RPC框架以插件形式提供无缝支持。并通过提供标准数据规范以适应自定义RPC框架:
Dubbo支持;
Rest支持;
自定义RPC支持;
调用链监控好处
- 准确掌握生产一线应用部署情况;
- 从调用链全流程性能角度,识别对关键调用链,并进行优化;
- 提供可追溯的性能数据,量化IT运维部门业务价值;
- 快速定位代码性能问题,协助开发人员持续性的优化代码;
- 协助开发人员进行白盒测试,缩短系统上线稳定期。
四、方案比较
市面上的全链路监控理论模型大多都是借鉴Google Dapper论文,本文重点关注以下三种APM组件:
- Zipkin:由Twitter公司开源,开放源代码分布式的跟踪系统,用于收集服务的定时数据,以解决微服务架构中的延迟问题,包括:数据的收集、存储、查找和展现。
- Pinpoint:一款对Java编写的大规模分布式系统的APM工具,由韩国人开源的分布式跟踪组件。
- Skywalking:国产的优秀APM组件,是一个对JAVA分布式应用程序集群的业务运行情况进行追踪、告警和分析的系统。
以上三种全链路监控方案需要对比的项提炼出来:
- 探针的性能,主要是Agent对服务的吞吐量、CPU和内存的影响。微服务的规模和动态性使得数据收集的成本大幅度提高。
- Collector的可扩展性,能够水平扩展以便支持大规模服务器集群。
- 全面的调用链路数据分析,提供代码级别的可见性以便轻松定位失败点和瓶颈。
- 对于开发透明,容易开关,添加新功能而无需修改代码,容易启用或者禁用。
- 完整的调用链应用拓扑,自动检测应用拓扑,帮助你搞清楚应用的架构
五、Tracing和Monitor区别
Monitor可分为系统监控和应用监控。系统监控比如CPU,内存,网络,磁盘等等整体的系统负载的数据,细化可具体到各进程的相关数据。这一类信息是直接可以从系统中得到的。应用监控需要应用提供支持,暴露了相应的数据。比如应用内部请求的QPS,请求处理的延时,请求处理的error数,消息队列的队列长度,崩溃情况,进程垃圾回收信息等等。Monitor主要目标是发现异常,及时报警。
Tracing的基础和核心都是调用链。相关的Metric大多都是围绕调用链分析得到的。Tracing主要目标是系统分析。提前找到问题比出现问题后再去解决更好。
Tracing和应用级的Monitor技术栈上有很多共同点。都有数据的采集,分析,存储和展式。只是具体收集的数据维度不同,分析过程不一样。
一般调用链追踪用Skywalking/OpenTracing/Zipkin,Metrics监控使用Promethus和Grafana,日志收集使用ELK技术栈。
总结
1、说明了对于微服务架构项目,全链路监控系统的重要性
2、全链路监控系统的4大功能模块
3、根据Google Dapper论文,说明了全链路监控系统的原理:请求到来生成一个全局TraceID,通过TraceID可以串联起整个调用链,一个TraceID代表一次请求。一次链路调用(可以是RPC,DB等没有特定的限制)创建一个SpanID,Span中还有其他的数据,例如描述信息,时间戳,key-value对的(Annotation)tag信息,parent_id等,其中parent-id可以表示Span调用链路来源。
4、调用链监控好处
5、主流的全链路监控方案:Zipkin、Skywalking和Pinpoint
6、Tracing和Monitor区别