2

快狗打车CTO沈剑:低成本搞定分布式调用链追踪系统(附PPT)

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=Mzg4NzU1MzIzMA%3D%3D&%3Bmid=2247532439&%3Bidx=1&%3Bsn=ff020a8de7e865661a3093ca2a3b2b14
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

AB7Bn23.gif!mobile

本文根据沈剑老师在〖2020 Gdevops全球敏捷运维峰会〗现场演讲内容整理而成。

Rf22Ynb.jpg!mobile

(点击文末“阅读原文”可获取完整PPT)

讲师介绍

沈剑 到家集团技术VP&技术委员会主席,快狗打车CTO,互联网架构技术专家,“架构师之路”公众号作者。曾任百度高级工程师,58同城技术委员会主席、高级架构师、技术学院优秀讲师。

大家好,我是沈剑,接下来将会跟大家分享一下到家集团/快狗打车的整个分布式调用链追踪系统的落地。

一、有微服务之前,我们架构是怎样的?

很多公司未必是微服务的架构体系,在微服务之前,大家的架构是什么样的呢?可能有一个站点应用,端上可能是APP或者是H5、PC浏览器,然后你会有一个站点应用层,没有微服务的话,它可能直接连的是缓存,直接连的是数据库,保证高可用的话,它可能是一个集群。

在微服务架构之前,这样的一个架构未必会存在追查调用链的痛点,未必会存在追查Bug困难的痛点,未必会存在寻找新的瓶颈的痛点。因为有这一个节点,所有的后端都是这一个节点来连接的,它直接站点应用连缓存,连数据库。

Mv636fn.png!mobile

如果要定位线上Bug,可能加一些日志,在这个模块,在这个站点应用,在里面加一些日志。要定位就加一些日志,然后看你的函数是怎么样调用的,调用在日志里其实能够体现出来,所以追查Bug和查调用链是相对比较容易的,包括性能。

比如要做压测,发现压不上去,一秒钟只能执行几十个。你想要找性能的主要矛盾在哪里?时间花在了哪里?因为你的这一个站点应用也通过加日志的方式的,我就看我的主流程有10个步骤,每个步骤我把时间都打出来,发现其中有一个步骤花的时间是500毫秒,这一个步骤你在递归进去,这一个步骤里面我又调用了哪些?这么递归下去,因为它是单站点应用的一个节点,最多做集群化,但是集群化的每一个节点的本地执行是相同的。通过层层加日志的方式去看每一步的执行时间,其实也相对较容易定位新的瓶颈。

二、微服务架构后,我们的痛点是什么?

如果在微服务之前,可能并不会遇到这样的痛点,但是一旦微服务化之后,一旦微服务化之后,我们的痛点是什么?如果大家也是微服务的体系,有Service内层,不管是Devops的Service还是自源框架的Service,只要有Service内层,我相信一定会遇到与我们类似的痛点。

痛点一:定位线上故障的过程

比如说定位线上故障,极其麻烦。比如我们用户反馈入不了,我作为登录系统站点的负责人,我这个站点有4个集群,我会SSH去到第1个节点,去公有日志,看Exception,看Timeout,发现是Ok的。SSH到第2个节点,SSH到第3个节点都是ok的,SSH到第4个节点,发现有一个节点,调用下游的某一个地方有超时,下游是我的一个服务。而微服务的整个架构,它调用下游的服务,像调用本地函数一样方便,能够提升研发效率。不用每个节点都关注底层的一些缓存,分库分表,存储引擎,不用关注这些复杂性,像调本地函数一样去拿一个远端的数据,我传一个UID过去,你就给了我一个用户的实体了,像调本地函数一样。

我发现我调用PassCloud的微服务,好像超时了,但是PassCloud微服务,又不是我负责的,我得找相关的同事,于是我给基础架构部的一个同学说我调用PassCloud超时了,用户用不了,你赶快查一查。然后PassCloud的微服务,它可能又调了其他的服务,当然它也可能没有调其他服务,没有调其他服务,路径短的话,对架构部的同事他问题解决比较方便,但他也是一步一步的SSH到线上Passport集群的每一台机器,公有日志看Exception,看Timeout。他发现自己直接连数据库,数据库好像不行了,打给DBA说PassCloud调用你的某一个数据库超时了,DBA同学,然后也去看他的数据库集群,发现好像是网络的问题,就打给运维的同学,运维的同学一看,某云厂商挂了,或者是网络出现问题了。

我们会发现定位问题的过程非常的麻烦,可能解决的过程就是把这个节点下掉,网络恢复一下,重启一下就解决了。解决问题的过程可能相对较容易,但是定位问题的过程非常的麻烦。大家如果有用微服务架构的话,公司可能也存在这种现象,它要从站点层到服务一到服务二到数据库,可能还涉及缓存,涉及到网络,时间花在哪里?你可能定位的过程非常的长。

痛点二:定位性能瓶颈的过程

第二个定位性能的瓶颈,其实也是非常麻烦,过程跟上面的有点像。如果是没有做微服务的,可能一个模块加时间打日志就能够知道。但是微服务的话,它其实调用都是跨机器,跨进程的。我站点的 HTTP接口花了三秒钟,这三秒钟花在哪个服务上,这个定位的过程相对比较麻烦。这个服务它又依赖于其他服务,其他的服务又依赖于数据库,依赖于缓存,依赖于其他的服务,要找出我这个 HTTP接口具体的性能主要矛盾在哪里的周期也会非常的长。可能大家也有这样的痛点,就是微服务架构之后,想要整体的性能提升,但去寻找主要矛盾很麻烦。

痛点三:不合理调用导致扩缩容的过程

第三个是不合理的调用,以及扩容缩容的过程比较麻烦。举我们曾经的例子,因为微服务调用方写代码太方便和高效了,他像调用本地函数一样去调用一个远端的服务,有时候他调用甚至都不知道自己调用的一个远端的服务。

比如我们曾经线上出现过的个问题,就是由城市ID拿城市的一个具体信息。比如说北京的承载力是一,我要一传进去后返回北京,这里它可能有一个服务,我们叫做城市品类服务。我们专门有一个服务,但是工程师在写这套代码的时候,他可能觉得服务部做个Map,做个配置就行了,他可能都未必意识到了自己在写下这行代码的时候,调用的时候是调用一个远端的服务,他可能觉得自己直接本地很高效的去实现了这个东西,传一个一进去,传一个北京出来。所以他把这个调用写在了一个For循环的内部,本来执行一次就行,一传过去,北京拿回来,它写在了一个For循环内部,它可能就调用50次,每次都有一拿北京。如果是一个本地的MAP也没什么大问题,但是最后它其实是一个远端的调用,网络上它可能就是几十毫秒级别,但一个For循环100次,它可能几百毫秒甚至秒级别了。

最后如果我们要去找到哪个地方有一个不合理的调用,没有分布式的调用链的追踪体系的话,其实很难,包括在扩容缩容的过程中,整个微服务架构体系我们要扩容缩容哪个节点这个环节。我调了5个微服务,容量好像扛不住了,但是我要扩容我调用了5个的哪一个微服务,以及怎么来扩,这可能都是问题。如果大家用了微服务架构体系,可能也会存在这样的问题。

三、分布式调用链追踪系统的特点

解决方案是分布式调用链追踪系统,它能够解决上述的问题。分布式调用链追踪系统,它是什么?其实在解释它解决问题的过程中,基本上就能够知道它是做什么的。

UVFNVjy.png!mobile

1、全链路

当一个HTTP请求进入到你的站点层,你调了哪一个服务,这个服务是广度调用还是深度调用,以及服务后面调的数据库,也可能调缓存,这整体是一个递归的过程。一个请求花了500毫秒,这500毫秒花在哪里了?这个服务100毫秒,另一个服务80毫秒,服务的递归、调用、深度、广度,它是全链路的一个呈现。

2、跨进程

为什么单节点的好做,因为单节点的上下文所有信息都有,用本地时间就能够描述绝对的时间,是偏序的。但是微服务架构体系,请求是由这台机器上的这个进程去了另外一台机器的另一个进程,另一个数据库,去了机器的缓存节点。它是跨进程的,所以整个分布式的调用链,信息的收集,包括时序和全流量,其实比较难。

3、全流量

微服务不只是描述依赖关系,还要精确到每一个具体的请求。

打日志,它会有多个ID,有快速的ID。 如果是单节点的话,通过一个日志文件里的Request ID就能够全部串起来,知道调用哪一个函数,调用哪个数据库,调用哪个用了多长时间。 但由于微服务它是跨进程、跨机器的,所以它全流量的收集、呈现,其实相对比较困难。

四、分布式调用链追踪系统的难点

1、难点

微服务它的难点也是因为它全流量跨进程,包括全球量化进程、全链路,所以对如何跨进程的去标识它是同一个HTTP请求过来的,跨进程的串联标识,跨进程的时序标识是个问题。上面说的单节点,用本地时间就可以做偏序,但是由于微服务它跨进程,可能另一台机器的本地时间比我这台机器的本地时间要快或者是要慢,如果用本地时间的话,可能就不能够描述全链路的请求的一个持续了。

ZNbmauB.jpg!mobile

包括深度标识。深度标识有时候是第1个模块调第2个模块,第2个模块调第3个模块,它是深度的,与第1个模块先调第2个模块,然后第1个模块再调第2个模块,你会发现它的日志,时序可能是一样的,但是它的调用深度完全不一样。A调B,A再调C,与A调B,B调C,它可能对在日志层面都是 A和C分别被调了一次。

深度广度如何来做? 包括数据的收集和可视化。 我们要精确到任何一个请求,要怎么来收集,怎么样在后端可视化,让大家看到在追踪Bug的过程中所需要掌握的信息。

哪些信息需要要掌握呢? 比如参数信息,调用的接口,数据库执行的是哪一个Circle语句,访问缓存的Key是什么? 这些信息都可能是大家在追抓、追踪Bug的过程中非常重要的一些信息。 能不能在后台统一的展现出来哪一个服务调到哪一个函数,参数是什么,缓存Key是什么,数据库Circle是什么,这些对于我们定位问题都非常有帮助。

最后我还标红了一点,叫做如何低成本的去实现这个事情。首先到家集团、快狗大车,是创业型公司,14、15年才成立的,到现在,快速打车也就100来人,公司不会给我们一个20人的团队,研发半年的时间,去做出来一个系统。没有这样的资源,没有这样的成本,所以对于创业型公司来说,如何低成本去实现分布式的调用链追踪系统,解决上述难题,也是创业公司非常关注的。

2、我们的实践

来看一下我们是怎么弄的。首先下图是我上文所述的微服务架构的一个请求流程。如果是微服务的架构,端上请求通过反向代理,到了站点业务层,站点应用层如果没有微服务架构的话,直接是在站点应用层会调数据库。但是如果有站点应用层的话,则会调服务,服务对上游提供友好的RPC接口,让上游能够像调本地函数一样去调拿原来的数据,而由服务这一层屏蔽了缓存数据库组成分库、分表、存储引擎各种复杂性。

FfyQVnU.png!mobile

所以我们今天这一套是适用于这个架构体系的,然后是服务,我这边虽然是一个Service节点,但其实服务有很多,服务之间也可能有业务服务,基础服务,也可能有互相调用。所以具体到一个请求,这个图表现的是依赖关系,端上、反向代理、站点应用、服务,服务它去帮助数据库缓存。

qiyqAjB.png!mobile

这是个非常具体的HTTP请求,从这边进来,到这边出去。然后在这个请求,这条时间线,它可能调了第一个服务,微服务有RPC Client调RPC Server ,然后它可能又调了缓存,高调的换车。然后它也可能调到第二个服务,可能又调了缓存,甚至它还可能直接用DAO去调了数据库,所以在这个进程的时间线上,你看到的整个执行过程是这样,但是它是一个递归了,你调了这个服务,这个服务它可能又调了其他的服务,调了数据库。

所以我们会发现对于分布式调用链追踪系统,希望它最后呈现出来一个这样的,希望它呈现出总时间,每一块的分时间,调哪个函数,传的参数。这一块调了,会呈现这个时间,调了它的Circle、Key、每一块的执行时间。就整个一递归的依赖关系,每一块的执行时间、核心参数,如果对数据库还有SQL语句,对于缓存则有Key,这些信息我希望通过一个界面能够有的。

有了这样一个分布式调用链追踪系统,对于我们定位线上问题,定位性能主要矛盾有帮助,一看就知道是哪个地方花了50%的执行时间,就可以优化这个地方以及其他不合理的调用。如果调了下游的服务但写在了For循环内部,这个图可能会有这个,即呈现出RPC Client调Server可能有50个。

五、分布式调用链追踪系统的最佳实践

接下来是我们的最佳实践。我们现在要做这个事情,我们要怎么来做。假如我们现在要达到的效果就是一个HTTP请求,所有每一块的执行时间,关键参数都要有。而且这是一个请求的,所以它有跨进程的请求ID的串联,还有时序的串联,深度的串联,怎么样解决?怎么样低成本的统一来解决。我们的最佳实践:

1、统一框架

我们会发现在这个过程中我们用了至少两个框架,站点的框架和服务的框架。如果公司的站点的框架是统一的,服务的框架是统一的。我们可以将这些数据和关键参数收集,收口在这两个集中的地方,比如说我在站点的框架的进来和出来两个地方做两个时间差,相关的关键参数收集上来,所有的HTTP请求的总执行时间和关键参数就收下来了。如果我们公司的服务框架是统一的,我们在RPC Client的出口统一发出时间和接收时间,打两个点,以及相关的参数收集上来,服务端接收到请求的时间和返回请求的时间,以及相关的参数收集上来,所有的微服务,所有微服务提供的接口的所有信息就全部收集上来了,所以只需要架构部在这个站点框架和服务框架做简单的改动,所有的业务部门只要使用我们框架的,所有的数据就收集上来了。

最怕是什么?最怕是每个部门需要自己的接口埋点。这个部门你要看这10个接口,我要看这10个接口,你把我的客户端埋到你的这10个接口里面去,这种基础组件、基础能力、基础平台是推广不下去的,需要所有的业务部门配合修改升级是很难的。

所以如果是统一的框架,只需要架构部在两个框架的少数几个地方做升级修改,收集时间数据、参数数据,就可以完成这个事情。

2、统一组件

除了统一框架,还要统一组件,我们要用到哪些组件,就对应到这个里面,在上图其实你会发现三个组件,我们在里面调了数据库,我们是通过DB的Client去调的。缓存的访问,我们公司统一技术栈必须收口在Redis和memcache。我们memcache有个客户端调用,Redis有个客户端调用,我们整个统一收口的是这三个组件,如果这三个组件是统一的,我们会发现在这三个的客户端调用处稍加升级,就可以收集所有的数据库访问和所有缓存访问的执行时间以及关键参数,数据库收集SQL语句,缓存收集Key的信息,但前提是组件是统一的。

3、统一配置管理(设计服务发现,自动扩容)

统一配置管理,包括上下游的依赖关系。这个集群依赖于多少个集群,配置管理必须要统一的维护,这样依赖关系和调用链关系就比较容易拿到。这是前期需要做一些规划的,不能A部门用它的技术栈,B部门用它的技术栈,这样做全公司的统一调用链就可能非常难。所以我们也会发现早期技术规划的架构师或者是技术负责人,他的一些统一的规划,其实对于后续做一些技术体系的建设是比较有帮助的。

对于配置管理,大家应该也会有类似的痛点,潜在的痛点可能有这三个。这个也是我们曾经有的。

1)阶段一:配置私藏

比如我是某一个服务的负责人,比如Passport,有很多人调用了我,但我不知道谁调用了我。这是我的痛点,不知道谁依赖于我。如果扩容缩容,我有两个节点,我发现扛不住了,要扩容第三个节点,我不知道谁依赖我,就不知道谁要配合我升级或者要配合我修改配置,系统的依赖关系不清楚。

RbMjqiA.jpg!mobile

最早的时候是怎么做的?最早的时候,我这个集群的两个IP以及端口的配置,是配置在我上游的配置文件里的。这个可能大家早期都这么做的。

业务A调了Service D,那么Service D它的集群有两个节点,IP 1和IP 2是写在 A的conf里的,业务A的conf,这个conf里配置了,我依赖于D,D的IP。业务B调了它,B的conf里也配置了,我依赖了D, C也同理。如果是这样的话,第一我是不知道谁依赖我。第二,我在做扩容的时候,我要反向通知我的上游,并且要求他们去修改配置。比如说我要扩容,我要由1、2变成3,或者是说我的1要下调,我还要扩容三个节点变成2、3、4、5。

那么第一,我得找到ABC,因为调用我,不需要经过我的同意,只要拿到我的RPC Client,以及拿到我的IP 1和IP 2,就可以调用我,我是不知道谁调用了我。于是在我们公司,为解决这个问题,原先是建了一个大群,叫做到家集团技术Order,里面有300人,现在我是Service D的负责人,我在外面丢一个消息说我D要升级了,IP要改了,所以谁依赖我,我换一下配置升级一下。

这个群,它每天弹消息。在做研发的时候,什么是最影响我们研发效率的?根据我的经验,就是群消息和邮件。每天有一个在那儿弹,有一个在那儿闪,这个消息我们得看,万一是跟自己相关的。万一我是业务A的人,他D升级,这个消息就跟我相关,但是大概率消息跟自己没关系,因为公司有200个服务,我只依赖了5个,消息大概率是195个人发的,但是又不得不看消息,很影响研发效率。同时还有邮件,后面升级了,升级为发群邮件,但是道理是一样的,技术Order邮件组来邮件,说有一个服务升级扩容了,谁依赖它的,就需要看一下,检查一下。如果有一个人没有检查到,而我把原来的断了,他服务就影响了。包括每个上游的升级步骤和升级节奏也是不同的,我们会发现自己扩容了几个节点,旧节点的压力大,新节点的流量迟迟不能够迁移过来,这是我们曾经的痛点。

2)阶段二:全局配置文件

对于这种配置管理的方式,我们叫配置市场。所有下游的这些配置是市场在上游的自己的配置文件中,而且还做了配置的这种冗余,数据冗余会引发一致性问题。那要怎么升级?如果大家也存在刚刚我们曾经遇到的痛点可以看下。

第一步先将冗余的数据收口,就只能存一份。这一份存在哪里?我们可以存在全局配置文件里,而且这个改造对于原有的架构体系冲击非常的小,只需要运维层面建一些统一的规范,就可以做到。

比如说我是一个公共的服务,很多人依赖了我,那么我就将我的配置会写在一个比如说全局的配置文件中Global.conf,我是D,Global.conf了,我的IP 1和IP 2,所有的业务测,它的修改则是原先是从自己的配置文件里去读,现在则统一到配置文件里去读,这个对原有的架构体系的冲击非常的小,对于业务这边代码的影响也非常的小,所以升级上来是非常快的,也非常有效。这样,以后如果我要扩容,比如说我要变 IP 1和IP 2,变成加2个节点,那么所有的上游在任何一次服务升级或者重启的过程中,就能够自动的读取最新的节点迁移上来,就不需要我反向的去发群邮件或者是发群消息说谁依赖了我,自己改一下配置,我就统一的改在那个地方配置就行了。

但是依然存在我不知道谁依赖了我的问题,如果我不知道谁依赖了我,那么可能一些服务治理,调用方限流的一些工作可能做不了。同时如果有一个业务它迟迟不重启,迟迟不读取新的配置文件,它其实流量也迁移不过来,但是只要配合上两个小组件,它就可以自动的干这个事情。

一个叫做文件监控组件,一个叫做动态连接词组件。文件监控组件其实就是监控配置文件的变化。如果配置文件有变化,不一样了,实施方式也非常简单。怎么来做?每隔一秒钟去检查一下最近更新实践,或者每隔一秒钟去检查一下配置文件的MD5,一旦发生变化则毁掉,非常容易。

实现了文件监控组件之后,动态连接词组件,动态连接词组件是RPC框架中的一个非常基础的组件。你是一个RPC的Server,通过IPC的Client来调动它,RPC的Client它里面有连接词,你IP 1要建连接,IP 2要建连接,所谓的动态连接词就是能够动态地增加连接和减少连接。

你的配置文件回调,你发现IP 1没了,那么你的动态链接只要销毁与原来IP 1节点建立的连接。你的配置文件回调说增加IP 3和IP 4节点,你的动态链接只要动态的新建IP 3和IP四的节点。

如果实现了这2个组件,只要配置文件统一变化,所有的连接全部实施变化了,那么旧的节点就可以下架,新增的节点负载马上就均衡了。这是第二个阶段。配置的管理。这个阶段我刚刚提到对原有架构体系的冲击非常小。如果大家现在还是使用的配置市场阶段,如果还在这个阶段,每个上游和自己的配置文件里去写下游的配置文件,强烈建议快速升级到这个阶段。

FVVVJjV.jpg!mobile

3)阶段三:配置中心

当然这个方案的缺点就是我还是不知道谁依赖了我。终极解决方案其实还是配置中心,配置中心也是现在行业内聊的非常多的话题,在运维领域,在配置架构领域都有很多的话题。方案就是由配置文件升级成配置中心,当然配置中心本身也是一个非常复杂的架构。

QVBFBz6.jpg!mobile

如果升级配置中心,那么所有业务要调用下游,会去配置中心,问要调用下游,它的配置是多少,配置中心就告诉他,然后他再给下游,那么如果我的配置会发生变化,我会在配置中心的后台进行操作,说IP 1要下调,增加IP 3和IP 4,然后配置中心它记录了依赖关系,它能够反向的通知所有调用了的它的人说增加IP 3和IP 4减去IP 1,再配合动态连接词组建。

首先配置中心它本身就是一个注册加反向通知的机制,所有人要依赖于它,你去配置中心注册后,我发生变化的时候要通知你,然后再配合动态连接词,只要我扩容缩容,通过配置中心,一修改则通知所有的,这么做了之后,整个全局的依赖关系图就极其容易了,因为所有人都在配置中心,需要配置时,谁依赖谁,谁调用了谁,谁依赖了数据库,谁调用了缓存,整个全局的依赖关系图就呈现了,但是它是一个全局的关系,它并不能够精确到每一个请求。

配置中心,它对原有的架构体系和架构的冲击比较大,可以慢慢调整往这个方向尝试,但是上述的,全局配置文件则对现有的一个架构体系的冲击应该是比较小。

4、分布式调用链追踪系统的核心难点

前面说的分布式调用链的一个核心难点,如何跨进程的串联请求,如何跨进程的标识调用时序,如何跨进程的标识调用深度,只要你做到了前面我们说的统一组件,统一框架,其实这个事情是相对比较容易做的,因为如果你统一框架的话,你的内部RPC的协议你是自己可以控制的。你在自有的协议里要增加三个字段:

  • 第一个字段是请求的ID,来做全链路的请求标识;

  • 第二个字段是时序的ID,来做全链路的时序的标识;

  • 第三个字段是深度的ID,来做调用链的深度调用数是广度还是深度的一个标识。

但是前提是上文所述协议是可控了,如果用的是Double,而Double不只是这样的功能,则可能要去做二开,我们的整个站点和服务都是自研的,所以加相关的字段相对比较容易。

jmEBzir.jpg!mobile

1)请求ID

比如说你们统一用了Double框架,那么Double的定长包头变长包体的二进制协议,我可以在包头里面加一些字段,比如说加请求ID字段,我再请求第一次进入到我们的系统,比如说进入到我们的外部层的时候,我生成一个全局的、唯一的分布式的ID来标识,这一个请求在系统中的总串联。

然后在站点层通过RPC Client调RPC Server的时候,把这个请求ID放到协议的头部里去,就可以由上游传到下游。如果还递归调别的服务,我还可以放在包头里传给下游。这样的话只要我们的协议是可控可扩展的,这一全局的请求ID,我就可以在系统里跨机器跨进程的到处传,在数据上报的时候我就统一的传全局请求ID,就能够将众多收集数据的一些信息,一些日志里,去将整个信息从请求ID给拿出来。

2)时序ID

同理,我们不能够用本地的时间来做调用链追踪的时序偏序。因为每个服务器它的本地时间会不一样,但是你可以用一个时序ID,比如说我在请求进入到系统的时候,我生成一个时序ID 11,我调第1个的时候,时序ID 2,时序ID 3,时序ID 4,都不用传到这边来,在这边就可以进入时序ID 4、5、6、7、8。因为我们的协议是可控的,我们可以在协议的包头里加一个字段,叫做时序ID,来标识数据上报的整体的时间偏序,本地的执行时间只做参考。即我们不能够用这一台机器的本地时间和这一台机器的本地时间来标识哪一个请求执行的前,哪一个数据上报在后,这个时间只能参考。绝对的偏序时间,是用你的时序ID,对于同一个请求01234来做时间,同一个请求的时间偏序。

3)深度ID

同理,深度ID也是完全相同的。比如说这一层它有一个深度ID是1,一旦往下调用,深度ID变成2,成第二层,往下调,深度变成3,但一旦请求调回来,深度ID则变成2,再调回,深度ID变成1,这个就是持续ID的特点,它只增不减。持续ID是1、2、3,深度ID 4往下一层调用会涨,返回则会减,所以通过三个自有协议的字段增加,可以解决刚才说的难点。

5、分布式调用链追踪系统的数据收集

如何收集数据和展现数据?收集数据常见的方式有两种,实时的和相对异步的,根据数据量大小来用。我们公司比较常见有这两种方式:

baeI3ez.jpg!mobile

  • 第一种UDP上报,每一个要收集数据的点,签一个UDP的SDK,然后往UDP 的Server去发数据。在机房内网 UDP的可靠性还是不错的,而且性能是比较高的。然后UDP收集到日志数据之后,再往日志中心或者叫搜索中心去做二次同步,然后后台要做检索,可以把它存到ES里,然后用这个调用链追踪后台,调相关的服务去做数据可视化;

  • 收集数据的第二种方式,对原有的架构体系的冲击会比较小,也是有一个SDK,嵌入到我们的框架,我们的组件的某些需要收日志的地方,先打本地日志,先落盘,然后通过一个异步的,将日志再同步到我们的调用链追踪系统的后台系统里面。如果要支持检索的话,落到到ES里,后台来查询。这个数据的收集无非是这两种方式,我们用的是这种方式,先打本地日志,对性能的影响也会比较小。

BjqQbiz.jpg!mobile

最后是要修改哪些地方?其实上文也基本上说了,如果我们的框架统一,组件统一,只要修改这10个地方,每个地方加1到2行代码就可以解决了。刚刚说不管是站点框架还是RPC的框架,在请求进来和请求出去的时候,会记时间,做时间差,传递参数,上报日志。在缓存组件开启Client的组件,发请求和回请求的两个地方,要记做时间差,以及要上报缓存访问的Key。数据库调用的发出请求、收到请求的地方要做时间差,并且上报所有相关的信息。RPC Client和RPC Server在发出请求,收到请求时,要上报时间以及上报占用函数以及函数传递的参数。为什么Client和Server也要收集?因为对于RPC调用来说有客户端视角和服务端视角,有时候有些请求的性能在网络上,我们客户端视角看的是100毫秒,服务端的视角会同一个请求是80毫秒,很有可能网络上爆了20毫秒,可能网络是主要矛盾。

所以需要组建统一框架,只要在这10个地方埋入上文说的数据上报的小SDK就是上报一些时间差及重要参数的日志就行了,所以修改点其实非常的小。最后都是后台的事情,数据集中上报,就看后台是怎么做检索,怎么样做方便查询了,所以上述说的统一框架组件其实非常重要。

6、分布式调用链追踪系统的可视化

最后是可视化,跟大家呈现一下我们的后台,大概是下图这样的。首先进来是一个HTTP,它的总消耗时间可能是101毫秒,整个过程中它是Passport的外部,它的类型是到家外部Framework,它是站点层,它是到家Service Framework,这就是DSF的客户端调用方和服务端,调用了什么样的函数,调了服务的什么函数,调用了什么样的函数,传进去什么样的参数,消耗了多少的时间以及这个Service它通过DAO调了哪些,调了多少次数据库,数据库的Server语句是什么,执行时间是什么,整个递归关系,按时序按地位一目了然。

QJBVram.jpg!mobile

对于我来说,如果我要快速定位问题,一看上图哪一块执行的时间最长,这个Service语句怎么执行了600毫秒,我们就去找Service语句在哪个地方出了什么问题。如果看某一台机器挂了,某一个地方,它就是红色的,包括调用。这个地方怎么某一个Service调用了50次,原来是因为写在For循环里了,很快能够帮助我们定位线上问题,找到不合理的调用,找到新的瓶颈来优化。所以最后的系统的产出大概是这样的,这是其中的一个视角。这是另外一个视角。这个视角的话详细信息会比较多,定义到我前面10个节点, Dao节点、 Service节点等等,详细信息会比较多。

yuumuaj.jpg!mobile

上图是一个依赖关系视图,所以进来的是我们Passport的一个Web,它的类型是DWF,我们这边是到家Web Framework,它调了3个Service,他通过第1个到家Service Framework的Client,它调了3个Service,调的都是到家 Service,这是DUS Service,上面是客户端视角,中间是服务端视角。

第一个Service通过DAO调了一次数据库,中间的Service通过DAO调了好多次数据库,最后一个Service通过DAO调了两次数据库,这个图可能是一个用户注册的请求或是用户登录的,通过图我们会发现它其实调了10多次数据库,但是每一次数据库的访问耗时是很短的毫秒级的,绝大部分的时间可能是在CPU处理上,虽然一个请求它访问了10多次数据库,但是其实它的最后总执行时间只有101毫秒。

六、总结

最后一个是总结,根据我的经验,今天讲的内容,明天能够记住的只有5%,如果只有5%,我希望是这5%。

6jiAbai.png!mobile

1、分布式调用链追踪系统解决什么问题?

1)快速发现线上问题。

2)快速定位性能瓶颈。

3)快速找到不合理的调用。

2、我们怎么低成本的来干这个事情?

用我们的最佳实践

1)统一框架,包括站点框架和服务框架。如果不在早期做规划,要统一是很难的,越大的公司统一越难,像BAT他们要统一公司,所有的站点,所有的服务用同一个框架,几乎不可能。但是我能够自信地说,如果到家发展到5年,能够统一战略框架和服务框架,因为我们从一开始就这么规划的。

2)统一组件,前面有很多,比如说数据库的访问,有时候我们只是浅浅的封装了一层,但是浅浅的封装了一层,也非常的重要,能够方便我们统一的来做很多统一的事情。比如说调用链,比如说日志收集,比如说告警,比如说调用Release的组件,我们可能就是使用的Redis,但是我们在外面包装了一层命名空间,叫到家Cache Client或者叫到家KV Client。这个到家KV Client。里面具体是访问的memcache还是访问的Redis,还是未来我们要切其他的,对于组件的使用方是透明的。未来如果我们要切,要统一切成Release,可能我们在框架层面统一的做升级,所有调用方只要依赖到我们最新的到家KV的包,到家数据库访问的包,它就能够完成升级。

3)我们统一的在架构部,在组建层面,统一的支持日志的上报,超时的告警,调用链追踪也只需要你们依赖于我们最新的包,就能够统一的支持所有的功能,而不是所有的地方,不知道是哪个请求要记录去埋点。这样的技术体系的推进是很慢的。

4)自有协议。

5)数据的统一上报。

6)图形化展示。

以上这些是能够在很快的时间内去解决我们的很多问题的。最后就是配置的升级,大家如果有相关的痛点,在配置市场阶段,可以升级到全局配置文件阶段,升级到配置中心阶段。这个是我们的四大技术战略方向上的第一块,解决我们的这些痛点,我们用了这样的一个实践,希望大家有收获,谢谢大家。

2QbUV3R.jpg!mobile

↓点这里可下载本文PPT,提取码:o83a


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK