微服务之间通过网络进行通信。在我们提供服务的同时,我们不能保证网络一定是畅通的,相反,网络是很脆弱的,网络资源也有限。因此,我们有必要追踪每个网络请求,了解其经过了哪些微服务,延迟多少,每个请求所耗费的时间等。只有这样,我们才能更好的分析系统拼劲,解决系统问题。

下面主要探讨服务追踪组件 Zipkin, SpringCloudSleuth 集成了 Zipkin。

Spring Cloud Sleuth简介

通过 SpringCloud 来构建微服务架构,我们可以通过 SpringCloudSleuth 实现分布式追踪,它集成了 Zipkin。

Sleuth 术语
  • span(跨度):基本工作单元。例如,在一个新建的 span 中发送一个 RPC 等同于发送一个回应请求给 RPC,span 通过一个 64 位 ID 唯一标识,trace 以另一个 64 位 ID 表示,span 还有其他数据信息,比如摘要、时间戳事件、关键值注释(tags)、span 的 ID,以及进度 ID(通常是 IP 地址)。span 在不断的启动和停止,同时记录了时间信息,当你创建了一个 span,你必须在未来的某个时刻停止它。

  • trace(追踪):一组共享“root span”的 span 组成的树状结构成为 trace。trace 也用一个 64 位的 ID 唯一标识,trace 中的所有 span 都共享该 trace 的 ID。

  • annotation(标注):用来及时记录一个事件的存在,一些核心 annotations 用来定义一个请求的开始和结束。
    1)、CS,即 Client Sent,客户端发起一个请求,这个 annotion 描述了这个 span 的开始。
    2)、SR,即 Server Received,服务端获得请求并准备开始处理它,如果将其 SR 减去 CS 时间戳便可得到网络延迟。
    3)、SS,即 Server Sent,注解表明请求处理的完成(当请求返回客户端),如果 SS 减去 SR 时间戳便可得到服务端需要的处理请求时间。
    4)、CR,即 Client Received,表明 span 的结束,客户端成功接收到服务端的回复,如果 CR 减去 CS 时间戳便可得到客户端从服务端获取回复的所有所需时间。

     下图演示了请求依次经过 SERVICE1 -> SERVICE2 -> SERVICE3 -> SERVICE4 时,span、trace、annotation 的变化:
    

在这里插入图片描述

zipkin 简介

Zipkin 是 Twitter 开源的分布式跟踪系统,基于 Dapper 的论文设计而来。它的主要功能是收集系统的时序数据,从而追踪微服务架构的系统延时等问题。Zipkin 还提供了一个非常友好的界面,便于我们分析追踪数据。

zipkin服务端安装

要实现完整的服务器链路,需要分为服务端和客户端,下面我们来分别介绍服务端和客户端的实现: 在 Spring Boot 2.0 以前,我们需要自己实现 Zipkin 服务端,从 Spring Boot 2.0 以后,其推出了官方 Zipkin 服务端,我们只需要下载服务端 jar 包,放到服务器上,启动即可。具体操作如下:
(1)从网络上下载 Zipkin 服务端的可执行 jar 包,下载地址可点击这里获取

(2)将 zipkin-server-2.9.4-exec.jar 修改为 zipkin.jar;

(3)命令行终端进入 zipkin.jar 所在目录,执行命令:java -jar zipkin.jar。

启动成功后,如图所示:
在这里插入图片描述
Zipkin 服务端的默认启动端口为 9411,浏览器访问 http://localhost:9411 即可进入 Zipkin 服务端管理界面,如图:

enter image description here

单纯集成 zipkinServer 还达不到追踪的目的,还必须使我们的微服务客户端集成 Zipkin 才能跟踪微服务,下面是集成步骤。
(1)在 EurekaClient 服务工程的 pom 文件中添加以下依赖:

  <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-sleuth</artifactId>
    </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth-zipkin</artifactId>
        </dependency>

也可以只导入如下依赖,下面依赖包含了上面两个依赖:

    <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>

(2) 配置文件进行配置

spring:
    zipkin:
        base-url: http://localhost:9411
        sender:
            type: web
    sleuth:
        sampler:
          probability : 1     # 1表示百分百进行采样

其中,spring.zipkin.base-url 用来指定 zipkinServer 的地址。
spring.sleutch.sampler.probability 用来指定采样请求的百分比(默认为 0.1,即 10%)。
(3)依次启动注册中心、配置中心、Zipkin、eurekaclient,依次访问 http://localhost:8763/index,http://localhost:9411,进入 Zipkin 界面后,点击 Find a trace 按钮,可以看到 trace 列表:
在这里插入图片描述
(4) 自定义日志级别:

# 修改日志级别
logging:
  level:
    org.springframework.cloud.openfeign: debug

通过消息中间件实现链路追踪

在之前的实例中,我们使用 HTTP 来收集数据,如果 zipkinServer 的网络地址发生了变化,每个微服务的 base-url 都需要改变,因此,我们还可以通过消息队列来收集追踪数据。

我以 RabbitMQ 作为消息中间件进行演示。

(1)命令行启动官网提供的 zipkin.jar,注意,启动时需要指定 RabbitMQ 的 host 地址,如: java -jar zipkin.jar --RABBIT_ADDRESSES=127.0.0.1

其中,–RABBIT_ADDRESSES 即为 RabbitMQ 的 host 地址。

(2)启动完成后,我们访问 RabbitMQ 的 Web 管理界面,可以看到 Zipkin Server 已经为我们创建了一个名叫 zipkin 的队列,如图:
在这里插入图片描述
(3)改造 EurekaClient服务 ,将 pom.xml 添加如下内容:

 <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>

(4)配置文件中 去掉 spring.zipkin.base-url 配置。
(5)依次启动相应工程,我们发现依然可以正常跟踪微服务。

存储追踪数据

前面的示例中,ZipkinServer 是默认将数据存储在内存中,一旦 ZipkinServer 重启或发生故障,将会导致历史数据丢失,因此我们需要将跟踪数据保存到硬盘中。

ZipkinServer 支持多种后端数据存储,比如 MySQL、ElasticSearch、Cassandra 等。

以 MySQL 为例来演示如何将历史数据存储在 MySQL 中。

(1)首先创建一个名为 zipkin_db 的数据库,并执行以下脚本:

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';
ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT 'for getTraces';

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

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

(2)重新启动 zipkin.jar,这次启动需要指定数据库连接信息,如:

java -jar zipkin.jar --RABBIT_ADDRESSES=127.0.0.1 --MYSQL_HOST=127.0.0.1 --MYSQL_TCP_PORT=3306 --MYSQL_USER=root --MYSQL_PASS=1qaz2wsx --MYSQL_DB=zipkin_db --STORAGE_TYPE=mysql

注:如果启动失败,可能的原因有:

  • 数据库无法连接
  • MySQL 版本过高(大于等于 8.0),请降低版本,如果是 MariaDB,则最好安装其官网最新版本。
  • 重启工程,可以看到数据库已经存储了追踪数据,如图:

在这里插入图片描述
且重启 Zipkin Server 后,也能通过 http://localhost:9411 查询到追踪数据。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐