为什么要做调用链监控

随着互联网技术的发展,微服务架构已经成为每个互联网公司的标配。伴随着服务粒度的细化,服务应用数量极速上涨。与此同时,服务与服务之间的调用也变得错综复杂。伴随着调用链路的复杂化,我们也会碰到各种难题,例如:

加班加点的做了功能,上线发布后,如何确认服务一切正常?

客户端收到了错误的提示,但是到底是哪个服务抛出的这个错误?

程序性能有问题,但是具体是哪个环节成了性能的瓶颈?

接口响应很慢,到底是网络问题还是代码问题?

服务调用链路长,每个环节都可能是一个出问题的风险点?

做技术优化,如何丈量我们的服务质量呢?

如果面对这些问题,你一脸茫然。那么我想说,是时候将调用链监控引入你的项目了。

调用链监控原理

说起调用链监控,不得不提Google在2010年发表的Dapper论文。它可以算是调用链的鼻祖了,在该论文发布后,大量的调用链监控产品依据该论文阐述的原理进行了实现。让我们来一起看看调用链监控中的几个重要概念:

Trace一次分布式调用的链路

Span一次本地或者远程方法的调用

Annotation附加在Span上的日志信息

Sampling采样率(客户端按照比例将埋点信息提交给服务端)

上面四个术语为调用链中的核心概念,这么说还是有点抽象,我们用图来具体展示一下:

如上我们图示了一次分布式调用的全过程,图中有三个分布式服务ServiceA、ServiceB、ServiceB,每个方法的调用链信息中涉及到如下三个标记:

tid:tid即为traceid,代表该次调用的唯一ID,在一次分布式调用中,所有方法的tid都相同。如上图中的Tid:1。

sid:sid即为spanid,代表的是一个本地/远程方法调用的唯一ID。如上图中每个绿色框代表的是一次方法调用,每次调用都有自己的sid。

pid:pid其实也是一个spanid,但是它代表的是当前方法的父级方法的spanid。如上图中第一方法调用由客户端发起,是没有pid的。

在上图中所示的调用链中总共包含了7个方法(本地/远程)调用,依次如下:

用客户端发起调用请求后,首先请求进入到A服务。此时会产生调用链信息tid:1,sid:1。

接着发生了一次远程调用Tid:1,pid:1,sid:2,pid为1代表父级方法的spanid为1即为sid=1的方法,同理本次redis远程调用的spanid为2。

Redis远程调用结束后发生了对ServiceB的远程调用Tid:1,pid:1,sid:3,与方法2类似,不同的是本次方法调用的spanid为3。

在ServiceB中,首先是一个本地方法调用Tid:1,pid:3,sid:4,从pid=3可以得出它的父级方法正是方法3。

接着发生了一次对Mysql的远程调用Tid:1,pid:4,sid:5,pid=4代表父级方法为方法4,spanid为5。

Mysql远程调用结束后,ServiceB对ServiceC进行了一次远程调用Tid:1,pid:4,sid:6,同样通过pid和tid我们可以将本次方法调用与整个调用链关联起来。

最后是ServiceC的一个本地方法调用Tid:1,pid:6,sid:7,至此整个调用链到达最远端,这是本次分布式调用链最深处的一个方法。

如上我们对GoogleDapper论文中几个核心概念进行了讲解,相信你已经大致了解了调用链的实现原理。如果觉得还不够过瘾的同学可以通过链接继续阅读,如果你的英语不是很好,也可参考GitHub上的这份中文翻译版本Dapper,大规模分布式系统的跟踪系统。说到这里,我想问问大家,市场上的调用链产品都是基于GoogleDapper来实现的吗?其实不是的,接下来我们一起来看看调用链的发展历史。

调用链监控的前世今生

2010年GoogleDapper问世,其实早在2002年eBay已经有了自己的调用链监控产品,它叫CAL(CentralApplicationLogging)。当时eBay中国研发中心的一位资深工程师作为CAL的核心维护人员,对CAL的方方面面都非常熟悉。

后来他去了美团点评,在2011年的时候,他带领团队研发出了CAT(CentralApplicationTracing),CAT继承了CAL的优点,也增加了很多自己的特色功能,并且它已经在GitHub开源,也在美团点评经受了大流量,高并发应用的检验,是目前业界应用比较广泛和成熟的生产级别调用链监控产品。

随后由于GoogleDapper论文的发布,也伴随着互联网产品的迅速发展,各个大厂依据Dapper纷纷实现了自己的调用链监控产品。在2012年就诞生了3款产品,携程的CTrace,韩国公司Naver的PinPoint,Twitter的Zipkin。

随后在2014年,阿里研发了Eagleye,京东研发了Hydra。接着诞生了调用链监控的标准规范OpenTracing,面对各大厂的调用链监控产品,他们使用不兼容的API来实现各自的应用需求。尽管这些分布式追踪系统有着相似的API语法,但各种语言的开发人员依然很难将他们各自的系统(使用不同的语言和技术)和特定的分布式追踪系统进行整合,OpenTracing希望可以解决这个问题,因此颁布了这套标准。于此也诞生了Uber的Jaeger,国人吴晟做的SkyWalking(现在已经捐赠给了Apache)均实现了OpenTracing标准,对OpenTracing感兴趣的可以去官网了解更多内容。

如下图示发展史:

如上,我们介绍了主流的调用链产品诞生历史,那么如此多的产品,我们究竟该选用哪款呢?我们将他们的功能特点总结了一个表格,如下:

功能CATZipkinPinpoint调用链可视化有有有聚合报表强少中ServerMap简单依赖图简单好埋点方式侵入侵入非侵入字节码增强Heartbeat支持有无有Metric支持简单无无告警支持有无有多语言支持Java/.Net丰富只有Java界面中文支持好有无社区支持内置文档,作者在国内文档较丰富,中文社区一般文档一般,暂无中文社区国内案例携程、点评、陆金所、拍拍贷京东、阿里定制不开源唯品会改造定制源头祖先eBayCALGoogleDapperGoogleDapper类似产品暂无UberJaeger,SpringCloudSleuthApacheSkywalking

如上我们将各大调用链产品分为了典型的三类:

CAT类:鼻祖CAL,侵入式埋点,国内公司使用较广。

Zipkin类:鼻祖GoogleDapper,侵入式埋点,国内使用不广。

PinPiont类:鼻祖GoogleDapper,非侵入式卖点,采用字节码增强技术。

综合使用广泛度,文档支持等因素,上手我们推荐使用点评的CAT产品,接下来就让我们一起揭开CAT的神秘面纱吧。

开源调用链监控产品CAT

简介

以上为来自GitHub站点的CAT简介,CAT服务器端虽然基于Java开发,但是它提供了其他众多语言的客户端,并且也提供了各种基础组件的框架埋点案例。从上一节的调用链产品的发展史,我们也知道了CAT核心设计思想源于eBay的CAL产品,在2011年由点评团队研发并且开源。接下来让我们一起看看CAT为我们提供了哪些丰富的监控功能吧。

报表功能

CAT之所以在国内互联网公司也得益于它的丰富报表功能,它总共为我们提供了如下5种报表功能。

Transaction报表

用于统计代码块(例如创建订单方法)的执行时间,调用次数。展示如下图:

Event报表

主要用于统计代码段的运行次数,但是相比Transaction少了响应时间统计,侧重点在于调用次数,调用的成功率。

Problem报表

主要用于展示根据Transaction和Event数据分析出来可能存在问题的请求,例如耗时较长的SQL。

Heartbeat报表

主要用于展示JVM的使用信息,例如GC信息,占用内存信息等。

Business报表

告警功能

CAT生成了丰富的报表功能,顺理成章的对这些报表数据进行一定的规则限制,超出规则范围即可向开发人员发出告警信息。例如Transaction报警,当URL类型的Transaction在1分钟之内执行次数大于1的时候,即发出报警,可以做如下配置:

告警功能总体来说都是一些设置,操作比较简单,CAT站点有详细的介绍,就不展开了,感兴趣的小伙伴可以去了解一下。

架构设计

CAT架构主要分为2块:服务器端、客户端,接下来我们分别对这2个模块的架构设计进行分析。

服务器端

CAT服务器需要承接客户端发送过来的应用埋点信息,成千上万的应用节点需要与服务器端连接进行信息传输,因此服务器端需要具要具备高性能的特点。服务器端采用Netty非阻塞IO来实现与客户端的交互,接收到消息以后存储在内存的队列中,然后有一个线程专门负责将接收的消息按照类型(Transaction、Event等)分发到不同的队列中。

这些队列会分别有一批Analyzer线程(这样的话,当某个类型的数据量大过大的时候我们可以调整该类型的Analyzer线程数量即可)在监听,监听到有消息后会对消息进行解析,解析后生成的报表数据然后存储在MySQL中。当然啦,由于客户端埋点数据量大,这些数据会以异步方式转存在HDFS中。

客户端

CAT客户端负责将客户端的埋点信息传输到服务器端,在一个调用链中从创建消息树为开始,到消息树构建完成为结束,中间涉及到的所有埋点信息都将被存储在线程本地(使用ThreadLocal来实现)。消息树构建完成后会存储在内存队列中,然后由异步线程(这样可以将CAT客户端对应用本身的影响降到最低)的方式将数据传输到服务器端。

实战应用

部署服务器端

环境要求:

以及之上(2.6内核才可以支持epoll),线上服务端部署请使用Linux环境、Mac以及Windows环境可以作为开发环境,美团点评内部。

Java6、7、8,服务端推荐使用JDK7的版本,客户端JDK6、7、8都支持。

Maven3及以上。

、5.7,更高版本MySQL都不建议使用,不清楚兼容性。

J2EE容器建议使用Tomcat,建议使用推荐版本7.x或8.0.x。

下载源代码(通过GitHub站点下载CAT源码)。

配置CAT的报表数据源信息:在本机上创建文件/data/appdatas/cat/然后将文件中的数据库配置改为你自己的数据库。

?xmlversion="1.0"encoding="utf-8"?data-sourcesdata-sourceid="cat"maximum-pool-size3/maximum-pool-sizeconnection-timeout1s/connection-timeoutidle-timeout10m/idle-timeoutstatement-cache-size1000//driverurl![CDATA[jdbc:mysql://127.0.0.1:3306/cat]]/urluser*******/userpassword*******/passwordconnectionProperties![CDATA[useUnicode=truecharacterEncoding=UTF-8autoReconnect=truesocketTimeout=120000]]/connectionProperties/properties/data-source/data-sources

CAT服务端启动以后会连接该数据库,当然了,我们也要提前创建好CAT所依赖的数据库信息,DDL我们可以在CAT的源代码中找到。

打WAR包(当然CAT官方也有提供WAR包,可直接下载使用)。

将WAR放置到Tomcat的部署目录webapps后,直接启动Tomcat,然后访问http://localhost:8080/cat。

这个时候如果正常的话你会看到CAT的管理端页面,如下:

埋点实战

启动客户端之前,我们需要为客户端准备好配置文件/data/appdatas/cat/,设置好CAT服务器端:

?xmlversion="1.0"encoding="utf-8"?configmode="client"serversserverip="本机内网ip"port="2280"http-port="8080"//servers/config

我们共创建了3个应用来模拟分布式调用链:SERVICE-A、SERVICE-B、SERVICE-C。

添加客户端依赖

/groupIdartifactIdcat-client//version/depency

标识客户端身份

创建文件resources/META-INF/,然后添加如下内容:

=应用名称

每个应用都需要有唯一的应用名称。

埋点

我们需要定制2个拦截器,分别拦截进来的请求和出去的请求来设置CAT上下文信息,这样既可将分布式调用链的埋点信息关联起来。代码也比较简单,主要思路就是将埋点的父级信息,通过Header传递给下文:

1.入口拦截器,拦截进来的所有请求,获取CAT埋点的上文信息。参考如下:

/***http协议传输,远程调用链目标端接收context的filter,*通过header接收rootId、parentId、childId并放入CatContextImpl中,调用()进行调用链关联*注:若不涉及调用链,则直接使用中提供的filter即可*使用方法(视项目框架而定):*1、web项目:在中引用此filter*2、Springboot项目,通过注入bean的方式注入此filter*@authorsoar*@date2019-01-10*/@Component@WebFilter(urlPatterns="/*",filterName="catFilter")publicclassCatContextServletFilterimplementsFilter{privateString[]urlPatterns=newString[0];@Overridepublicvoidinit(FilterConfigfilterConfig)throwsServletException{Stringpatterns=("CatHttpModuleUrlPatterns");if(patterns!=null){patterns=();urlPatterns=(",");for(inti=0;;i++){urlPatterns[i]=urlPatterns[i].trim();}}}@OverridepublicvoiddoFilter(ServletRequestservletRequest,ServletResponseservletResponse,FilterChainfilterChain)throwsIOException,ServletException{HttpServletRequestrequest=(HttpServletRequest)servletRequest;Stringurl=().toString();for(StringurlPattern:urlPatterns){if((urlPattern)){url=urlPattern;}}CatContextImplcatContext=newCatContextImpl();(,(_HTTP_HEADER_ROOT_MESSAGE_ID));(,(_HTTP_HEADER_PARENT_MESSAGE_ID));(,(_HTTP_HEADER_CHILD_MESSAGE_ID));(catContext);Transactiont=(_URL,url);try{("",(),,().toString());("",());(servletRequest,servletResponse);();}catch(Exceptionex){(ex);(ex);throwex;}finally{();}}@Overridepublicvoiddestroy(){}}

2.出口拦截器,拦截出去的请求,为下文设置CAT的上文信息。

/***@description:*@create:2019-11-2117:42*/@ComponentpublicclassRestInterceptorimplementsClientHttpRequestInterceptor{@OverridepublicClientHttpResponseintercept(HttpRequestrequest,byte[]body,ClientHttpRequestExecutionexecution)throwsIOException{Transactiont=(_REMOTE_CALL,().toString());try{HttpHeadersheaders=();//保存和传递CAT调用链上下文=newCatContextImpl();(ctx);(_HTTP_HEADER_ROOT_MESSAGE_ID,());(_HTTP_HEADER_PARENT_MESSAGE_ID,());(_HTTP_HEADER_CHILD_MESSAGE_ID,());//保证请求继续被执行ClientHttpResponseresponse=(request,body);();returnresponse;}catch(Exceptione){().logError(e);(e);throwe;}finally{();}}}

埋点设置好后,我们启动应用来测试一把。

调用链视图

这三个Demo应用,我们调用链关系为Service-A-Service-B-Service-C。启动应用后访问http://localhost:8888/a,返回结果为:

HelloYou!ThisisfrommsgfromC.

证明请求成功,我们查看CAT的Transaction视图:

如图我们看到的URL即为入口拦截器拦截请求的埋点,RemoteCall为出口拦截器的埋点。点开LogView即可查看详细信息:

如上图我们可以看出,这条分布式调用链总共涉及到三个应用,并且每个应用上的埋点信息也显示出来了,以树形结构展示深度,比较直观。

生产环境实践

讨论了CAT的基本实战以后,我们来看一看生产环境的CAT应该怎么样的一个部署配置。首先CAT服务器端的需要做大量的报表计算,因此建议部署2台高配置的物理机来完成此功能,其他功能就可以部署虚拟机来完成,简要的部署架构图如下:

物理机配置建议为:CPU32core、Mem64g、3.6TSAS盘、OSCentOS7。

JVM建议配置:-Xms50g–Xmx50g–XX:+UseG1GC。

SpringCloudSleuth

相信经过前面的探讨,你已经大致了解了CAT的基本使用规则。本小节让我们一起来看看SpringCloudSleuth吧。

简介

SpringCloudSleuth相当于是一个调用链监控系统的客户端,它会收集埋点信息,然后将这些信息传输到监控系统后端。它从Dapper、Zipkin和HTrace这三个项目里面借鉴了大量的设计理念,对于开发者来说它相当于是透明的,应用里面所有交互点都会被记录下来,可以通过日志的方式打印出来,也可以发送到远程监控系统去做分析展示,接下来就让我们将它和Zipkin对接起来吧。

启动Zipkin

首先我们需要在Zipkin官网下载jar包,下载完成后使用启动Zipkin服务。如下终端展示即为启动成功:

我们从上面的启动日志里面可以看到Zipkin在监听9411端口,访问http://localhost:9411/查看Zipkin界面:

应用接入

我们需要在应用中引入SpringCloud封装好的组件:spring-cloud-starter-sleuth和spring-cloud-sleuth-zipkin。只需要在应用的父POM中添加如下依赖即可:

////groupIdartifactIdspring-cloud-depencies/artifactIdversion${}/versiontypepom/typescopeimport/scope/depency/depencies//groupIdartifactIdspring-cloud-starter-sleuth/artifactId//groupIdartifactIdspring-cloud-sleuth-zipkin/artifactId/depency/depencies

这样我们就将Sleuth和Zipkin的相关依赖引入了项目。注意我们还需要为每个项目在配置文件中添加应用名称:

=Service-X

这样配置就结束了,接下来我们还是启动CAT实战中的3个服务,同样访问A服务的hello接口。然后我们来查看Zipkin服务端的调用链报表:

如上我们可以看到本次分布式调用的链路信息已经在Zipkin界面展示出来了,点开这条记录我们可以查看到更详细的信息:

如上我们可以很直观的查看服务之间的依赖关系,这将很有利于我们对服务拆分的粒度。你可能已经发现了,Zipkin的画面比起CAT更加好看,同时调用链埋点非常方便,我们没有手动添加埋点,全是Sleuth组件悄悄的帮我们做了埋点。但是Zipkin相比CAT的报表功能弱了一些,而且没有JVM内存信息展示,也没报警之类的功能。综合起来看,CAT是更适合我们作为入门级的一款调用链监控产品,推荐使用。至此,相信你已经大致了解了调用链产品CAT和Zipkin的基本使用,快去实战一下吧。