与单体应用相比,在微服务架构下,一次用户调用会因为服务化拆分后,变成多个不同服务之间的相互调用,每个服务可能是由不同的团队开发,使用了不同的编程语言,还有可能部署在不同的机器上,分布在不同的数据中心,这也就需要对拆分后的每个服务做监控并追踪服务调用。
实现服务监控,我们需要搞清楚三个问题:监控的对象是什么?具体监控哪些指标?从哪些维度进行监控?
对于微服务系统来说,监控对象可以分为四个层次,由上到下可归纳为:
通常有以下几个业务指标需要重点监控:
一般来说,要从多个维度来对业务进行监控,具体来讲可以包括下面几个维度:
对于一个微服务来说,必须明确要监控哪些对象、哪些指标,并且还要从不同的维度进行监控,才能掌握微服务的调用情况。明确了这几个关键的问题后,那么该如何搭建一个监控系统,来完成上面这些监控功能呢?
显然,我们要对服务调用进行监控,首先要能收集到每一次调用的详细信息,包括调用的响应时间、调用是否成功、调用的发起者和接收者分别是谁,这个过程叫作数据采集。采集到数据之后,要把数据通过一定的方式传输给数据处理中心进行处理,这个过程叫作数据传输。数据传输过来后,数据处理中心再按照服务的维度进行聚合,计算出不同服务的请求量、响应时间以及错误率等信息并存储起来,这个过程叫作数据处理。最后再通过接口或者Dashboard的形式对外展示服务的调用情况,这个过程叫作数据展示。
可见, 监控系统主要包括四个环节:数据采集、数据传输、数据处理和数据展示
1. 数据采集
通常有两种数据收集方式:
无论哪种数据采集方式,首先要考虑的问题就是采样率,也就是采集数据的频率。采样率决定了监控的实时性与精确度,一般来说,采样率越高,监控的实时性就越高,精确度也越高。但采样对系统本身的性能也会有一定的影响,尤其是采集后的数据需要写到本地磁盘的时候,过高的采样率会导致系统写入磁盘的I/O过高,进而会影响到正常的服务调用。所以设置合理的采用率是数据采集的关键,最好是可以动态控制采样率,在系统比较空闲的时候加大采样率,追求监控的实时性与精确度;在系统负载比较高的时候减小采样率,追求监控的可用性与系统的稳定性。
2. 数据传输
数据传输最常用的方式有两种:
无论采用哪种传输方式,数据格式都十分重要,尤其是对带宽敏感以及解析性能要求比较高的场景,一般数据传输时采用的数据格式有两种:
3. 数据处理
数据处理是对收集来的原始数据进行聚合并存储。数据聚合通常有两个维度:
聚合后的数据需要持久化到数据库中存储,所选用的数据库一般分为两种:
4. 数据展示
数据展示是把处理后的数据以Dashboard的方式展示给用户。数据展示有多种方式,比如曲线图、饼状图、格子图展示等。
在微服务架构下,由于进行了服务拆分,一次请求往往需要涉及多个服务,你可以想象如果这次请求失败了,要想查清楚到底是哪个应用导致,会是多么复杂的一件事情。如果有一个系统,可以跟踪记录一次用户请求都发起了哪些调用,经过哪些服务处理,并且记录每一次调用所涉及的服务的详细信息,这时候如果发生调用失败,你就可以通过这个日志快速定位是在哪个环节出了问题,讲解如何实现服务追踪之前我们先来了解服务追踪的作用
通过记录调用经过的每一条链路上的耗时,我们能快速定位整个系统的瓶颈点在哪里。比如你访问微博首页发现很慢,肯定是由于某种原因造成的,有可能是运营商网络延迟,有可能是网关系统异常,有可能是某个服务异常,还有可能是缓存或者数据库异常。通过服务追踪,可以从全局视角上去观察,找出整个系统的瓶颈点所在,然后做出针对性的优化。
通过服务追踪可以分析调用所经过的路径,然后评估是否合理。比如一个服务调用下游依赖了多个服务,通过调用链分析,可以评估是否每个依赖都是必要的,是否可以通过业务优化来减少服务依赖。
还有就是,一般业务都会在多个数据中心都部署服务,以实现异地容灾,这个时候经常会出现一种状况就是服务A调用了另外一个数据中心的服务B,而没有调用同处于一个数据中心的服务B。
根据我的经验,跨数据中心的调用视距离远近都会有一定的网络延迟,像北京和广州这种几千公里距离的网络延迟可能达到30ms以上,这对于有些业务几乎是不可接受的。通过对调用链路进行分析,可以找出跨数据中心的服务调用,从而进行优化,尽量规避这种情况出现。
通过服务追踪系统中记录的链路信息,可以生成一张系统的网络调用拓扑图,它可以反映系统都依赖了哪些服务,以及服务之间的调用关系是什么样的,可以一目了然。除此之外,在网络拓扑图上还可以把服务调用的详细信息也标出来,也能起到服务监控的作用。
除了服务追踪,业务上经常有一种需求,期望能把一些用户数据,从调用的开始一直往下传递,以便系统中的各个服务都能获取到这个信息。比如业务想做一些A/B测试,这时候就想通过服务追踪系统,把A/B测试的开关逻辑一直往下传递,经过的每一层服务都能获取到这个开关值,就能够统一进行A/B测试。
Google发布的一篇的论文 Dapper, a Large-Scale Distributed Systems Tracing Infrastructure
,里面详细讲解了服务追踪系统的实现原理。它的核心理念就是 调用链:通过一个全局唯一的ID将分布在各个服务节点上的同一次请求串联起来,从而还原原有的调用关系,可以追踪系统问题、分析调用数据并统计各种系统指标。
可以说后面的诞生各种服务追踪系统都是基于Dapper衍生出来的,比较有名的有Twitter的 Zipkin、阿里的 鹰眼、美团的 MTrace 等。
要理解服务追踪的原理,首先必须搞懂一些基本概念:traceId、spanId、annonation等。Dapper这篇论文讲得比较清楚,但对初学者来说理解起来可能有点困难,美团的MTrace的原理介绍理解起来相对容易一些,下面我就以MTrace为例,给你详细讲述服务追踪系统的实现原理。虽然原理有些晦涩,但却是你必须掌握的,只有理解了服务追踪的基本概念,才能更好地将其实现出来。
首先看下面这张图,我来给你讲解下服务追踪系统中几个最基本概念。
上面这三段内容我用通俗语言再给你小结一下,traceId是用于串联某一次请求在系统中经过的所有路径,spanId是用于区分系统不同服务之间调用的先后关系,而annotation是用于业务自定义一些自己感兴趣的数据,在上传traceId和spanId这些基本信息之外,添加一些自己感兴趣的信息。
讲到这里,你应该已经理解服务追踪系统里最重要的概念和原理了,我们先来看看服务追踪系统的架构,让你了解一下系统全貌。
上面是服务追踪系统架构图,你可以看到一个服务追踪系统可以分为三层。
下面来看看具体每一层的实现方式是什么样的。
1. 数据采集层
数据采集层的作用就是在系统的各个不同模块中进行埋点,采集数据并上报给数据处理层进行处理。
那么该如何进行数据埋点呢?结合下面这张图来了解一下数据埋点的流程。
以红色方框里圈出的A调用B的过程为例,一次RPC请求可以分为四个阶段。
2. 数据处理层
数据处理层的作用就是把数据采集层上报的数据按需计算,然后落地存储供查询使用。
据我所知,数据处理的需求一般分为两类,一类是实时计算需求,一类是离线计算需求。
实时计算需求对计算效率要求比较高,一般要求对收集的链路数据能够在秒级别完成聚合计算,以供实时查询。而离线计算需求对计算效率要求就没那么高了,一般能在小时级别完成链路数据的聚合计算即可,一般用作数据汇总统计。针对这两类不同的数据处理需求,采用的计算方法和存储也不相同。
针对实时数据处理,一般采用Storm或者Spark Streaming来对链路数据进行实时聚合加工,存储一般使用OLTP数据仓库,比如HBase,使用traceId作为RowKey,能天然地把一整条调用链聚合在一起,提高查询效率。
针对离线数据处理,一般通过运行MapReduce或者Spark批处理程序来对链路数据进行离线计算,存储一般使用Hive。
3. 数据展示层
数据展示层的作用就是将处理后的链路信息以图形化的方式展示给用户。
根据我的经验,实际项目中主要用到两种图形展示,一种是调用链路图,一种是调用拓扑图。
下面以一张Zipkin的调用链路图为例,通过这张图可以看出下面几个信息。
服务整体情况:服务总耗时、服务调用的网络深度、每一层经过的系统,以及多少次调用。下图展示的一次调用,总共耗时209.323ms,经过了5个不同的系统模块,调用深度为7层,共发生了24次系统调用。
每一层的情况:每一层发生了几次调用,以及每一层调用的耗时。
根据我的经验,调用链路图在实际项目中,主要是被用来做故障定位,比如某一次用户调用失败了,可以通过调用链路图查询这次用户调用经过了哪些环节,到底是哪一层的调用失败所导致。
下面是一张Pinpoint的调用拓扑图,通过这张图可以看出系统内都包含哪些应用,它们之间是什么关系,以及依赖调用的QPS、平均耗时情况。
调用拓扑图是一种全局视野图,在实际项目中,主要用作全局监控,用于发现系统中异常的点,从而快速做出决策。比如,某一个服务突然出现异常,那么在调用链路拓扑图中可以看出对这个服务的调用耗时都变高了,可以用红色的图样标出来,用作监控报警。
服务监控在微服务改造过程中的重要性不言而喻,没有强大的监控能力,改造成微服务架构后,就无法掌控各个不同服务的情况,在遇到调用失败时,如果不能快速发现系统的问题,对于业务来说就是一场灾难,服务追踪也是分布式系统中必不可少的功能,它能够帮助我们查询一次用户请求在系统中的具体执行路径,以及每一条路径的上下游的详细情况,对于追查问题十分有用。