写在前面

默认情况下,Zipkin Server会将跟踪信息存储在内存中,但是当用户每次重启Zipkin Server时,都会将之前收集的跟踪信息进行删除,并且当有大量跟踪信息时存储在内存中肯定是不行的,因此正确的做法是将数据持久化到外部存储磁盘中,使用MySQL来进行存储,也可以将其输出到ElasticSearch中存储存储,这两种方式本文都会详细介绍。

数据持久化

持久化到MySQL中

MySQL简介

MySQL是一款优秀的开源关系型数据库,因其速度、可靠性和适应性而备受关注。大多数人都认为在不需要事务化处理的情况下,MySQL是管理内容最好的选择。

持久化到MySQL流程

请注意zipkin默认是支持HTTP实现的收集,因此需要将现在基于RabbitMQ消息中间件实现的方式注释掉,后期会介绍如何将基于消息中间件的收集持久化到MySQL数据库中。

第一步,在MySQL中创建用于Zipkin存储的Schema。由于笔者使用的zipkin-server版本是2.12.6,因此首先需要点击 这里选择自己对应版本的readme.md文档进行阅读,往下看到Applying the schema部分,找到最后两行代码:

1
2
$ mysql -uroot -e "create database if not exists zipkin"
$ mysql -uroot -Dzipkin < zipkin-storage/mysql-v1/src/main/resources/mysql.sql

这两行代码告诉我们两个信息,第一需要创建名称为zipkin的数据库,第二需要使用zipkin-storage/mysql-v1/src/main/resources/mysql.sql文件来创建zipkin数据库中对应的表结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
CREATE TABLE IF NOT EXISTS zipkin_spans (
`trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
`trace_id` BIGINT NOT NULL,
`id` BIGINT NOT NULL,
`name` VARCHAR(255) NOT NULL,
`parent_id` BIGINT,
`debug` BIT(1),
`start_ts` BIGINT COMMENT 'Span.timestamp(): epoch micros used for endTs query and to implement TTL',
`duration` BIGINT COMMENT 'Span.duration(): micros used for minDuration and maxDuration query'
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;

ALTER TABLE zipkin_spans ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `id`) COMMENT 'ignore insert on duplicate';
ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`, `id`) COMMENT 'for joining with zipkin_annotations';
ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTracesByIds';
ALTER TABLE zipkin_spans ADD INDEX(`name`) COMMENT 'for getTraces and getSpanNames';
ALTER TABLE zipkin_spans ADD INDEX(`start_ts`) COMMENT 'for getTraces ordering and range';

CREATE TABLE IF NOT EXISTS zipkin_annotations (
`trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
`trace_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.trace_id',
`span_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.id',
`a_key` VARCHAR(255) NOT NULL COMMENT 'BinaryAnnotation.key or Annotation.value if type == -1',
`a_value` BLOB COMMENT 'BinaryAnnotation.value(), which must be smaller than 64KB',
`a_type` INT NOT NULL COMMENT 'BinaryAnnotation.type() or -1 if Annotation',
`a_timestamp` BIGINT COMMENT 'Used to implement TTL; Annotation.timestamp or zipkin_spans.timestamp',
`endpoint_ipv4` INT COMMENT 'Null when Binary/Annotation.endpoint is null',
`endpoint_ipv6` BINARY(16) COMMENT 'Null when Binary/Annotation.endpoint is null, or no IPv6 address',
`endpoint_port` SMALLINT COMMENT 'Null when Binary/Annotation.endpoint is null',
`endpoint_service_name` VARCHAR(255) COMMENT 'Null when Binary/Annotation.endpoint is null'
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;

ALTER TABLE zipkin_annotations ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `span_id`, `a_key`, `a_timestamp`) COMMENT 'Ignore insert on duplicate';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`, `span_id`) COMMENT 'for joining with zipkin_spans';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTraces/ByIds';
ALTER TABLE zipkin_annotations ADD INDEX(`endpoint_service_name`) COMMENT 'for getTraces and getServiceNames';
ALTER TABLE zipkin_annotations ADD INDEX(`a_type`) COMMENT 'for getTraces and autocomplete values';
ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT 'for getTraces and autocomplete values';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id`, `span_id`, `a_key`) COMMENT 'for dependencies job';

CREATE TABLE IF NOT EXISTS zipkin_dependencies (
`day` DATE NOT NULL,
`parent` VARCHAR(255) NOT NULL,
`child` VARCHAR(255) NOT NULL,
`call_count` BIGINT,
`error_count` BIGINT
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;

ALTER TABLE zipkin_dependencies ADD UNIQUE KEY(`day`, `parent`, `child`);

将下来按照上述要求执行上述两行命令即可创建对于的Schema,创建后的三个表的数据结构如下所示:

第二步,配置参数启动zipkin-server。由于我们使用的是官方推荐的zipkin-server,且已经打包成了jar包,因此直接修改其代码是不现实的,但是通常而言我们只是对于一些配置信息会发生修改,而对于项目依赖jar包几乎不会修改,因此可以通过在启动的时候设置参数来达到自定义配置这一目的。下面是mysql相关的一些参数,如下表所示,这些都是可以配置的参数:

参数 描述
STORAGE_TYPE 存储类型,支持mysql和elasticsearch
MYSQL_HOST mysql的主机IP
MYSQL_TCP_PORT mysql的端口号
MYSQL_DB mysql对应的数据库,注意名称必须为zipkin
MYSQL_USER mysql的用户名
MYSQL_PASS mysql的用户密码

因此开发者可以使用诸如如下命令来手动启动zipkin-server:

1
start /min java -jar D:\SpringCloud\cloudgo\zipkin-server\zipkin-server-2.12.6-exec.jar --STORAGE_TYPE=mysql --MYSQL_HOST=127.0.0.1 --MYSQL_TCP_PORT=3306 --MYSQL_DB=zipkin --MYSQL_USER=root --MYSQL_PASS=root

第三步,测试。依次启动eureka-server服务注册中心、zipkin-server、trace-one项目、trace-two项目,然后在浏览器地址栏中访问http://localhost:5001/trace-one,可以发现trace-one和trace-two项目的控制台中都已经输出了相应的信息,最后查看数据库,可以发现zipkin_annotations和zipkin_spans这两张数据表中都有了相应的数据:

zipkin_annotations和zipkin_spans这两张表内的数据在前面收集原理分析的时候进行了研究,也验证了前面原理分析的正确性。

持久化到ElasticSearch中

ElasticSearch简介

Elasticsearch是一个基于Lucene构建的开源、分布式、RESTful 接口全文搜索引擎,同时Elasticsearch还是一个分布式文档数据库,其中每个字段均可被索引,而且每个字段的数据均可被搜索,ES能够横向扩展至数以百计的服务器存储以及处理PB级的数据。可以在极短的时间内存储、搜索和分析大量的数据。总它具有以下功能:
(1)分布式实时文件存储,并将每一个字段都编入索引,使其可以被搜索;(2)实时分析的分布式搜索引擎;(3)可以扩展到上百台服务器,处理PB级别的结构化或非结构化数据。

由于它比较突出的三个功能,因此ElasticSearch主要的使用场景有以下三个:(1)适用于大型分布式日志分析系统ELK,其中ELasticSearch用于提供搜索和存储日志,Logstash用于收集日志;Kibana用于展示数据。(2)大型电商系统中的搜索功能;(3)网盘的搜索引擎等。

在Elasticsearch中存储的是文档,它是最小的存储单位,使用JSON作为文档序列化的格式,Elasticsearch与关系型数据库之间的对应关系如下表所示:

类型 名词1 名词2 名词3 名词4
关系型数据库 数据库 数据表
Elasticsearch 索引(Index) 类型(Type) 文档(Docments) 字段(Fields)

通过了解以上一些信息对于后续学习Zipkin Server如何将基于HTTP实现的收集信息持久化到Elasticsearch中。

持久化到Elasticsearch流程

第一步,阅读文档获取信息。由于笔者使用的zipkin-server版本是2.12.6,因此首先需要点击 这里选择elasticsearch对应版本的readme.md文档进行阅读,首先看到storage-elasticsearch-http部分,它要求ElasticSearch的版本为2.x,5.x and 6.x,因此不能选择7.x版本进行安装。

第二步,安装ElasticSearch。关于ELasticSearch的安装这里就忽略,建议选择使用6.x版本,笔者选择使用6.7.0的版本。

第三步,安装elasticsearch-head插件。为了能直观感受到ElasticSearch中的数据,此处需要安装elasticsearch-head插件,请注意该插件要求nodejs版本大于6,可以使用node -v命令来查看当前系统nodejs版本的信息。之后依次执行如下命令来安装和启动csearch-head插件:

1
2
3
4
git clone git://github.com/mobz/elasticsearch-head.git
cd elasticsearch-head
npm install
npm run start

之后打开浏览器,访问http://localhost:9100/链接即可:

但是目前Web页面显示集群未连接,但是此时ElasticSearch和Head插件都已经启动了,所以问题在于两者还未完成通信(存在跨域问题),因此需要停掉正在运行的ElasticSearch,同时进入到其config包内,修改其elasticsearch.yml配置文件,在其末尾添加如下两行代码:

1
2
http.cors.enabled: true
http.cors.allow-origin: "*"

最后保存退出,重启ElasticSearch和Head插件,注意顺序不能搞错,然后再去浏览器访问http://localhost:9100地址,此时页面如下所示:

第四步,下载zipkin-server-2.x.x-exec.jar包。接下来开发者需要点击 这里,然后选择对应的zip版本进行下载之后会得到对应的jar包。

第五步,配置参数启动zipkin-server。由于我们使用的是官方推荐的zipkin-server,且已经打包成了jar包,因此直接修改其代码是不现实的,但是通常而言我们只是对于一些配置信息会发生修改,而对于项目依赖jar包几乎不会修改,因此可以通过在启动的时候设置参数来达到自定义配置这一目的。下面是ElasticSearch相关的一些参数,如下表所示,这些都是可以配置的参数:

参数 描述
STORAGE_TYPE 存储类型,支持mysql和elasticsearch
ES_HOSTS elasticsearch的主机IP和端口号
ES_USERNAME elasticsearch的用户名
ES_PASSWORD elasticsearch的用户密码

关于MySQL和ElasticSearch的更多相关配置参数,可以点击 这里进行查看。

由于笔者的ELasticSearch全都是采用默认配置,因此是没有用户和密码等信息,开发者可以使用诸如如下命令来手动启动zipkin-server:

1
start /min java -jar D:\SpringCloud\cloudgo\zipkin-server\zipkin-server-2.12.6-exec.jar --STORAGE_TYPE=elasticsearch ES_HOSTS=http://127.0.0.1:9200

第六步,测试。依次启动eureka-server服务注册中心、zipkin-server、trace-one项目、trace-two项目,然后在浏览器地址栏中访问http://localhost:5001/trace-one,可以发现trace-one和trace-two项目的控制台中都已经输出了相应的信息,最后通过查看ElasticSearch的head插件,可以确认数据已经都存入到ElasticSearch中:

API接口

zipkin不仅提供了UI模块让用户可以使用Web页面来方便地查看跟踪信息,它还提供了丰富的RESTful API接口供用户在第三方系统中调用来定制自己的跟踪信息展示或监控。我们可以在Zipkin Server启动时的控制台或日志中找到Zipkin服务端提供的RESTful API定义,由于笔者使用的是2.12.6,因此也可以从trace-one或者trace-two项目的输出控制台中得到:

也就是说Zipkin Server提供的API接口都是以/api/v2路径作为前缀,具体的功能如下所示:

接口路径 请求方式 接口描述
/dependencies GET 用于获取通过收集到的Span分析出的依赖关系
/services GET 用于获取服务列表
/spans GET 根据服务名来获取所有的Span名
/spans POST 向Zipkin Server上传Span
/trace/{traceId} GET 根据TraceID获取指定跟踪信息的Span列表
/traces GET 根据指定条件查询并返回符合条件的trace清单

更多关于接口的请求参数和请求返回格式等细节说明可以访问Zipkin的 官方API页面来查看,以帮助我们根据自身系统架构来访问Zipkin Server以定制自己的Dashboard或者监控系统。在前面也提到过Zipkin的UI模块也是基于RESTful API接口来实现的,可以通过浏览器的开发者模式来查看每个页面发起的请求,并以此作为调用样例来参考。

这样关于SpringCloud Sleuth分布式服务跟踪,数据存储的相关内容就学习到这里。