打造立体化监控体系与APM最佳实践系列 --Zipkin部署与使用

一、背景


近年来在云计算、大数据等快速发展的时代下,产生了很多新的业务场景,同时很多企业传统业务开始向互联网的转移。随着企业业务的发展,规模扩大,业务越来越多,所采用的组件也越来越多开始走向分布式化,如微服务、消息收发、分布式数据库、分布式对象存储、分布式缓存、跨域调用等,这些组件共同构成了繁杂的分布式网络,一个业务请求可能会涉及到几个、几十个服务的协同处理,如何动态展示服务的链路?如何分析服务链路的瓶颈并对其进行调优?如何快速进行服务链路的故障发现?如何保障产品服务的用户体验?企业需要一个从代码端的视角来监控自己的应用进而确保自身的IT支撑系统得到高效的运行,同时需要一个强大的IT运维管理体系时刻监督IT环境各组件的性能质量,通过多维度实时分析异常并进行诊断以解决产品的问题。因此,应用性能管理(APM)将逐渐成为推广中国IT技术进步与用户体验提升的标配。


当前大的互联网公司都有自己的分布式跟踪系统,比如Google的Dapper,Twitter的zipkin,Naver的pinpoint,淘宝的鹰眼,新浪的Watchman,京东的Hydra等,本文主要介绍zipkin的设计、安装部署,并以一个简单的案例演示zipkin的使用方法。

打造立体化监控体系与APM最佳实践系列 --Zipkin部署与使用


二、概述


zipkin是一款开源的分布式实时数据追踪系统(Distributed Tracking System),基于 Google Dapper的论文设计而来,由 Twitter 公司开发贡献。其主要功能是聚集来自各个异构系统的实时监控数据。



三、使用场景


3.1 故障快速定位


通过分析调用链,可以将一次请求的逻辑轨迹完整清晰的展示出来,通过在开发中在业务日志中添加调用链ID,可以通过调用链结合业务日志快速定位错误信息。


3.2 性能分析


在调用链的各个环节分别添加调用时延,可以分析系统的性能瓶颈,进行有针对性的优化。

3.3 服务可用性


通过分析各个环节的平均时延,QPS等信息,可以找到系统的薄弱环节,对一些模块做调整,例如数据冗余、链路可用等。


四、zipkin架构


4.1 架构

打造立体化监控体系与APM最佳实践系列 --Zipkin部署与使用

如上图所示,zipkin主要包括四个模块

Ø Collector接收各service传输的数据

Ø Storage存储收集过来的数据,当前支持Cassandra,Redis,HBase,MySQL,PostgreSQL, SQLite等,默认存储在内存中。

Ø API(Query)负责查询Storage中存储的数据,提供简单的JSON API获取数据,主要提供给web UI使用

Ø Web 提供简单的web界面


各个异构的服务向zipkin报告数据的架构如下图:

打造立体化监控体系与APM最佳实践系列 --Zipkin部署与使用

上图中的S表示发送跟踪数据的客户端SDK或者Scribe客户端(twitter内部采用scirbe来采集跟踪数据)。


4.2 Span


     Zipkin 以 Trace 结构表示对一次请求的追踪,又把每个 Trace 拆分为若干个有依赖关系的 Span。在微服务架构中,一次用户请求可能会由后台若干个服务负责处理,那么每个处理请求的服务就可以理解为一个 Span(可以包括 API 服务,缓存服务,数据库服务以及报表服务等)。当然这个服务也可能继续请求其他的服务,因此 Span 是一个树形结构,以体现服务之间的调用关系。


Zipkin的Span模型几乎完全仿造了Dapper中Span模型的设计,我们知道,Span用来描述一次RPC调用,所以一个RPC调用只应该关联一个spanId,Zipkin中的Span主要包含三个数据部分:


Ø 基础数据,包括traceId、spanId、parentId、name、timestamp和duration,主要用于跟踪树中节点的关联和界面展示。

u traceId:全局跟踪ID,用它来标记一次完整服务调用,所以和一次服务调用相关的span中的traceId都是相同的,Zipkin将具有相同traceId的span组装成跟踪树来直观的将调用链路图展现在我们面前。

u spanid:span的id,理论上来说,span的id只要做到一个traceId下唯一就可以。

u parentId:父span的id,调用有层级关系,所以span作为调用节点的存储结构,也有层级关系,跟踪链是采用跟踪树的形式来展现的,树的根节点就是调用的顶点,其中parentId为null的Span将成为跟踪树的根节点来展示,当然它也是调用链的起点。

u name:span的名称,主要用于在界面上展示,一般是接口方法名,name的作用是让人知道它是哪里采集的span。

u timestamp:span创建时的时间戳,用来记录采集的时刻。

u duration:持续时间,即span的创建到span完成最终的采集所经历的时间,除去span自己逻辑处理的时间,该时间段可以理解成对于该跟踪埋点来说服务调用的总耗时,timestamp+duration将表示成调用的结束时间。

Ø Annotation,注解,用来记录请求特定事件相关信息(例如时间),通常包含四个注解信息

cs - Client Start,表示客户端发起请求

sr - Server Receive,表示服务端收到请求

ss - Server Send,表示服务端完成处理,并将结果发送给客户端

cr - Client Received,表示客户端获取到服务端返回信息


Ø BinaryAnnotation,提供一些额外信息,一般以key-value对出现。

如下图是一个调用链路示例:

打造立体化监控体系与APM最佳实践系列 --Zipkin部署与使用

在本文后续章节中将会对该实例进行详细介绍。


4.3 客户端SDK


在上一节中我们知道对于一个APM来说,提供多种类型的客户端SDK(instrument)是很重要的,支持的客户端SDK越多,推广起来也越方便,使用人群也会越多。


跟踪信息是使用instrument库进行收集并发送给zipkin,截止目前,zipkin官方支持的客户端SDK如下:


Language

Library

Framework

Propagation Supported

Transports Supported

Sampling Supported?

Other notes

Go

zipkin-go-opentracing

Go kit, or roll your own with OpenTracing

Http (B3), gRPC (B3)

Http, Kafka, Scribe

Yes


Java

brave

Jersey, RestEASY, JAXRS2, Apache HttpClient, Mysql

Http (B3), gRPC (B3)

Http, Kafka, Scribe

Yes


JavaScript

zipkin-js

cujoJS, express, restify

Http (B3)

Http, Kafka, Scribe

Yes


Ruby

zipkin-ruby

Rack

Http (B3)

Http, Kafka, Scribe

Yes


Scala

zipkin-finagle

Finagle

Http (B3), Thrift

Http, Kafka, Scribe

Yes



除了官方库之后,社区也提供了instrument支持,社区支持库如下:


Language

Library

Framework

Propagation Supported

Transports

Supported

Sampling Supported

Other notes

C#

ZipkinTracerModule

OWIN, HttpHandler

Http (B3)

Http

Yes


C#

Zipkin4net

Any

Http (B3)

Any

Yes


Go

go-zipkin

x/net Context


Kafka



Go

monkit-zipkin

Monkit

Http (B3), easy to add others

Scribe, UDP, easy to add others

Yes


Java

cassandra-zipkin-tracing

Apache Cassandra





Java

Dropwizard Zipkin

Dropwizard





Java

htrace

HDFS, HBase





Java

Spring Cloud Sleuth

Spring, Spring Cloud (e.g. Stream, Netflix)

Http (B3), Messaging (B3)




Java

Wingtips


Http (B3)




Python

py_zipkin


Http (B3)




Python

pyramid_zipkin


Http (B3)




Python

swagger_zipkin


Http (B3)




Python

flask_zipkin

Flask

Http (B3)




Scala

akka-tracing






Scala

play-zipkin-tracing


Http (B3)






五、zipkin部


5.1 web UI部署


zipkin支持两种方式部署,docker容器以及jar包运行,

docker容器方式运行命令如下:

打造立体化监控体系与APM最佳实践系列 --Zipkin部署与使用

jar包直接运行命令如下(要求java8及以上版本):

打造立体化监控体系与APM最佳实践系列 --Zipkin部署与使用

5.2 服务开发


本文中以java语言开发一个简单demo来演示zipkin的使用,Brave 是用来装备 Java 程序的类库,提供了面向 Standard Servlet、Spring MVC、Http Client、JAX RS、Jersey、Resteasy 和 MySQL 等接口的装备能力,可以通过编写简单的配置和代码,让基于这些框架构建的应用可以向 Zipkin 报告数据。同时 Brave 也提供了非常简单且标准化的接口,在以上封装无法满足要求的时候可以方便扩展与定制。

服务调用关系如下:

打造立体化监控体系与APM最佳实践系列 --Zipkin部署与使用

新建名为service1、service2、service3、service4四个spring boot类型的项目,下面以service1项目来描述项目详细配置,其他项目的配置类似,在此不一一详述。

pom.xml新增如下依赖:

      <dependency>

            <groupId>io.zipkin.brave</groupId>

            <artifactId>brave-core</artifactId>

            <version>4.0.6</version>

        </dependency>

        <!-- https://mvnrepository.com/artifact/io.zipkin.brave/brave-http -->

        <dependency>

            <groupId>io.zipkin.brave</groupId>

            <artifactId>brave-http</artifactId>

            <version>4.0.6</version>

        </dependency>

        <dependency>

            <groupId>io.zipkin.brave</groupId>

            <artifactId>brave-spancollector-http</artifactId>

            <version>4.0.6</version>

        </dependency>

        <dependency>

            <groupId>io.zipkin.brave</groupId>

            <artifactId>brave-web-servlet-filter</artifactId>

            <version>4.0.6</version>

        </dependency>


        <dependency>

            <groupId>io.zipkin.brave</groupId>

            <artifactId>brave-okhttp</artifactId>

            <version>4.0.6</version>

        </dependency>


        <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->

        <dependency>

            <groupId>org.slf4j</groupId>

            <artifactId>slf4j-api</artifactId>

            <version>1.7.13</version>

        </dependency>

        <dependency>

            <groupId>org.apache.httpcomponents</groupId>

            <artifactId>httpclient</artifactId>

            <version>4.5.1</version>

        </dependency>

application.properties中增加如下配置:

com.zipkin.serviceName=service1

com.zipkin.url=http://127.0.0.1:9411

com.zipkin.connectTimeout=6000

com.zipkin.readTimeout=6000

com.zipkin.flushInterval=1

com.zipkin.compressionEnabled=true

server.port=8080


配置文件中指定了本服务的服务名以及服务端口,zipkin服务的地址。


服务定义:

public class HomeController {


@Autowired

private OkHttpClient client;


private  Random random = new Random();


@RequestMapping("start")

    public String start() throws InterruptedException, IOException {

        int sleep= random.nextInt(100);

        TimeUnit.MILLISECONDS.sleep(sleep);

        Request request = new Request.Builder().url("http://localhost:9090/foo").get().build();

        Response response = client.newCall(request).execute();

        return " [service1 sleep " + sleep+" ms]" + response.body().toString();

    }


}



当请求该服务(请求/start时),服务会请求下级的localhost:9090/foo服务-对应service2服务。


5.3界面展示


UI 启动后主界面如下:


打造立体化监控体系与APM最佳实践系列 --Zipkin部署与使用

启动服务service1、service2、service3、service4,并通过浏览器请求服务service1。


打造立体化监控体系与APM最佳实践系列 --Zipkin部署与使用

zipkin页面查看服务


打造立体化监控体系与APM最佳实践系列 --Zipkin部署与使用


服务调用链如下:


打造立体化监控体系与APM最佳实践系列 --Zipkin部署与使用


查看服务依赖关系:


打造立体化监控体系与APM最佳实践系列 --Zipkin部署与使用


完整的服务跟踪链信息如下:


[

  {

    "traceId": "122ecddc1769c0da",

    "id": "122ecddc1769c0da",

    "name": "get",

    "timestamp": 1494383123630139,

    "duration": 2832405,

    "annotations": [

      {

        "timestamp": 1494383123630139,

        "value": "sr",

        "endpoint": {

          "serviceName": "service1",

          "ipv4": "192.168.1.10"

        }

      },

      {

        "timestamp": 1494383126462544,

        "value": "ss",

        "endpoint": {

          "serviceName": "service1",

          "ipv4": "192.168.1.10"

        }

      }

    ],

    "binaryAnnotations": [

      {

        "key": "http.status_code",

        "value": "200",

        "endpoint": {

          "serviceName": "service1",

          "ipv4": "192.168.1.10"

        }

      },

      {

        "key": "http.url",

        "value": "/start",

        "endpoint": {

          "serviceName": "service1",

          "ipv4": "192.168.1.10"

        }

      }

    ]

  },

  {

    "traceId": "122ecddc1769c0da",

    "id": "d92eb6cbae9b4787",

    "name": "get",

    "parentId": "122ecddc1769c0da",

    "timestamp": 1494383123974246,

    "duration": 2475470,

    "annotations": [

      {

        "timestamp": 1494383123974246,

        "value": "cs",

        "endpoint": {

          "serviceName": "service1",

          "ipv4": "192.168.1.10"

        }

      },

      {

        "timestamp": 1494383124351184,

        "value": "sr",

        "endpoint": {

          "serviceName": "service2",

          "ipv4": "192.168.1.10"

        }

      },

      {

        "timestamp": 1494383126443649,

        "value": "ss",

        "endpoint": {

          "serviceName": "service2",

          "ipv4": "192.168.1.10"

        }

      },

      {

        "timestamp": 1494383126449716,

        "value": "cr",

        "endpoint": {

          "serviceName": "service1",

          "ipv4": "192.168.1.10"

        }

      }

    ],

    "binaryAnnotations": [

      {

        "key": "http.status_code",

        "value": "200",

        "endpoint": {

          "serviceName": "service2",

          "ipv4": "192.168.1.10"

        }

      },

      {

        "key": "http.url",

        "value": "/foo",

        "endpoint": {

          "serviceName": "service2",

          "ipv4": "192.168.1.10"

        }

      },

      {

        "key": "http.url",

        "value": "http://localhost:9090/foo",

        "endpoint": {

          "serviceName": "service1",

          "ipv4": "192.168.1.10"

        }

      }

    ]

  },

  {

    "traceId": "122ecddc1769c0da",

    "id": "eba2687430a3f56c",

    "name": "get",

    "parentId": "d92eb6cbae9b4787",

    "timestamp": 1494383124477917,

    "duration": 558367,

    "annotations": [

      {

        "timestamp": 1494383124477917,

        "value": "cs",

        "endpoint": {

          "serviceName": "service2",

          "ipv4": "192.168.1.10"

        }

      },

      {

        "timestamp": 1494383124881960,

        "value": "sr",

        "endpoint": {

          "serviceName": "service3",

          "ipv4": "192.168.1.10"

        }

      },

      {

        "timestamp": 1494383125033410,

        "value": "ss",

        "endpoint": {

          "serviceName": "service3",

          "ipv4": "192.168.1.10"

        }

      },

      {

        "timestamp": 1494383125036284,

        "value": "cr",

        "endpoint": {

          "serviceName": "service2",

          "ipv4": "192.168.1.10"

        }

      }

    ],

    "binaryAnnotations": [

      {

        "key": "http.status_code",

        "value": "200",

        "endpoint": {

          "serviceName": "service3",

          "ipv4": "192.168.1.10"

        }

      },

      {

        "key": "http.url",

        "value": "http://localhost:9091/bar",

        "endpoint": {

          "serviceName": "service2",

          "ipv4": "192.168.1.10"

        }

      },

      {

        "key": "http.url",

        "value": "/bar",

        "endpoint": {

          "serviceName": "service3",

          "ipv4": "192.168.1.10"

        }

      }

    ]

  },

  {

    "traceId": "122ecddc1769c0da",

    "id": "3b0df0a2f1ea18b2",

    "name": "get",

    "parentId": "d92eb6cbae9b4787",

    "timestamp": 1494383125298903,

    "duration": 1117321,

    "annotations": [

      {

        "timestamp": 1494383125298903,

        "value": "cs",

        "endpoint": {

          "serviceName": "service2",

          "ipv4": "192.168.1.10"

        }

      },

      {

        "timestamp": 1494383125529695,

        "value": "sr",

        "endpoint": {

          "serviceName": "service4",

          "ipv4": "192.168.1.10"

        }

      },

      {

        "timestamp": 1494383126416224,

        "value": "cr",

        "endpoint": {

          "serviceName": "service2",

          "ipv4": "192.168.1.10"

        }

      },

      {

        "timestamp": 1494383126424903,

        "value": "ss",

        "endpoint": {

          "serviceName": "service4",

          "ipv4": "192.168.1.10"

        }

      }

    ],

    "binaryAnnotations": [

      {

        "key": "http.status_code",

        "value": "200",

        "endpoint": {

          "serviceName": "service4",

          "ipv4": "192.168.1.10"

        }

      },

      {

        "key": "http.url",

        "value": "http://localhost:9092/tar",

        "endpoint": {

          "serviceName": "service2",

          "ipv4": "192.168.1.10"

        }

      },

      {

        "key": "http.url",

        "value": "/tar",

        "endpoint": {

          "serviceName": "service4",

          "ipv4": "192.168.1.10"

        }

      }

    ]

  }

]


六、结束语


目前市面上已知APM包括zipkin、pinpoint、appdash、cat、hydra、EagleEye等,其中cat、hydra、EagleEye分别为美团、京东以及阿里内部使用的系统,有些未开源或者开源后不再更新,pinpoint由韩国的naver开源,zipkin由Twitter开源,当前zipkin以及pinpoint社区均非常活跃,版本发布也比较频繁,用户较多,生态系统也较为完善。


相对pinpoint的部署来说,zipkin部署比较简单-运行jar包即可,pinpoint主要通过Plugin来支持众多的模块,包括tomcat、okhttpclient等中间件,基本不用修改源码和配置文件,对于运维人员来讲最为方便,zipkin通过instrument libraries来支持众多的中间件,并且有Twitter等大公司的支持,但是开发时需要对Spring、web.xml之类的配置文件做修改。


打造立体化监控体系与APM最佳实践系列 --Zipkin部署与使用

参考资料  

http://zipkin.io/pages/existing_instrumentations.html

http://bigbully.github.io/Dapper-translation/

http://blog.csdn.net/manzhizhen/article/details/52811600


上一篇:SAP Cloud Application Programming bookshop 例子 Vue页面不能正常显示的原因分析


下一篇:阿里云OSS云存储(一)文件上传