随着业务发展,系统拆分导致系统调用链路愈发复杂一个前端请求可能最终需要调用很多次后端服务才能完成,当整个请求变慢或不可用时,我们是无法得知该请求是由某个或某些后端服务引起的,这时就需要解决如何快读定位服务故障点,以对症下药。于是就有了分布式系统调用跟踪的诞生。
现今业界分布式服务跟踪的理论基础主要来自于 Google 的一篇论文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》,使用最为广泛的开源实现是 Twitter 的 Zipkin,为了实现平台无关、厂商无关的分布式服务跟踪,CNCF 发布了布式服务跟踪标准 Open Tracing。国内,阿里的Tracing “鹰眼”、京东的 “Hydra”、大众点评的 “CAT”、新浪的 “Watchman”、唯品会的 “Microscope” 都是这样的系统。
Spring Cloud Sleuth 也为我们提供了一套完整的解决方案。在本章中,我们将详细介绍如何使用 Spring Cloud Sleuth + Zipkin 来为我们的微服务架构增加分布式服务跟踪的能力。
一般的,一个分布式服务跟踪系统主要由三部分构成:
根据系统大小不同,每一部分的结构又有一定变化。譬如,对于大规模分布式系统,数据存储可分为实时数据和全量数据两部分,实时数据用于故障排查(Trouble Shooting),全量数据用于系统优化;数据收集除了支持平台无关和开发语言无关系统的数据收集,还包括异步数据收集(需要跟踪队列中的消息,保证调用的连贯性),以及确保更小的侵入性;数据展示又涉及到数据挖掘和分析。虽然每一部分都可能变得很复杂,但基本原理都类似。
服务追踪的追踪单元是从客户发起请求(request)抵达被追踪系统的边界开始,到被追踪系统向客户返回响应(response)为止的过程,称为一个 trace。每个 trace 中会调用若干个服务,为了记录调用了哪些服务,以及每次调用的消耗时间等信息,在每次调用服务时,埋入一个调用记录,称为一个 span。这样,若干个有序的 span 就组成了一个 trace。在系统向外界提供服务的过程中,会不断地有请求和响应发生,也就会不断生成 trace,把这些带有 span 的 trace 记录下来,就可以描绘出一幅系统的服务拓扑图。附带上 span 中的响应时间,以及请求成功与否等信息,就可以在发生问题的时候,找到异常的服务;根据历史数据,还可以从系统整体层面分析出哪里性能差,定位性能优化的目标。
Spring Cloud Sleuth 为服务之间调用提供链路追踪。通过 Sleuth 可以很清楚的了解到一个服务请求经过了哪些服务,每个服务处理花费了多长。从而让我们可以很方便的理清各微服务间的调用关系。此外 Sleuth 可以帮助我们:
Spring Cloud Sleuth 可以结合 Zipkin,将信息发送到 Zipkin,利用 Zipkin 的存储来存储信息,利用 Zipkin UI 来展示数据。
zipkin 的原理是基于2010年谷歌发表了其内部使用的分布式跟踪系统Dapper的论文(PDF地址、译文地址),讲述了Dapper在谷歌内部两年的演变和设计、运维经验,Twitter也根据该论文开发了自己的分布式跟踪系统Zipkin,并将其开源。它致力于收集服务的定时数据,以解决微服务架构中的延迟问题,包括数据的收集、存储、查找和展现。
我们可以使用它来收集各个服务器上请求链路的跟踪数据,并通过它提供的 REST API 接口来辅助我们查询跟踪数据以实现对分布式系统的监控程序,从而及时地发现系统中出现的延迟升高问题并找出系统性能瓶颈的根源。除了面向开发的 API 接口之外,它也提供了方便的 UI 组件来帮助我们直观的搜索跟踪信息和分析请求链路明细,比如:可以查询某段时间内各用户请求的处理时间等。
Zipkin 提供了可插拔数据存储方式:In-Memory、MySql、Cassandra 以及 Elasticsearch。接下来的测试为方便直接采用 In-Memory 方式进行存储,生产推荐 Elasticsearch。
上图展示了 Zipkin 的基础架构,它主要由 4 个核心组件构成:
ELK提供的是日志的管理,包含了收集、存储、搜索等功能,但是它缺乏实时服务链路跟踪。而zipkin刚好凝补了它这个缺陷。
Zipkin 分为两端,一个是 Zipkin 服务端,一个是 Zipkin 客户端,客户端也就是微服务的应用。
客户端会配置服务端的 URL 地址,一旦发生服务间的调用的时候,会被配置在微服务里面的 Sleuth 的监听器监听,并生成相应的 Trace 和 Span 信息发送给服务端。
发送的方式主要有两种:
不论哪种方式,我们都需要:
eureka
项目来当注册中心。trace-a
和 trace-b
,其中 trace-a
中有一个 REST 接口 /trace-a
,调用该接口后将触发对 trace-b
应用的调用。关于 Zipkin 的服务端,在使用 Spring Boot 2.x 版本后,官方就不推荐自行定制编译了,反而是直接提供了编译好的 jar 包来给我们使用,详情请看 upgrade to Spring Boot 2.0 NoClassDefFoundError UndertowEmbeddedServletContainerFactory · Issue #1962 · openzipkin/zipkin · GitHub
并且以前的 @EnableZipkinServer
也已经被打上了 @Deprecated
If you decide to make a custom server, you accept responsibility for troubleshooting your build or configuration problems, even if such problems are a reaction to a change made by the OpenZipkin maintainers. In other words, custom servers are possible, but not supported.
EnableZipkinServer.javagithub.com/openzipkin/zipkin/blob/master/zipkin-server/src/main/java/zipkin/server/EnableZipkinServer.java
简而言之就是:私自改包,后果自负。
所以官方提供了一键脚本
curl -sSL https://zipkin.io/quickstart.sh | bash -s
java -jar zipkin.jar
如果用 Docker 的话,直接
docker run -d -p 9411:9411 openzipkin/zipkin
启动后访问 http://IP:9411/zipkin/
服务端搭建完毕。
两个服务均需要引入以下依赖
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-sleuth
org.springframework.cloud
spring-cloud-starter-zipkin
两个服务还需要添加以下配置
eureka.client.service-url.defaultZone=http://192.168.xxx.xxx:8761/eureka/,http://192.168.xxx.xxx:8762/eureka/
# 将采样比例设置为 1.0,也就是全部都需要。默认是 0.1
spring.sleuth.sampler.probability=1.0
# 指定 Zipkin 服务器的地址
spring.zipkin.base-url=http://192.168.xxx.xxx:9411/
Spring Cloud Sleuth 有一个 Sampler 策略,可以通过这个实现类来控制采样算法。采样器不会阻碍 span 相关 id 的产生,但是会对导出以及附加事件标签的相关操作造成影响。 Sleuth 默认采样算法的实现是 Reservoir sampling,具体的实现类是 PercentageBasedSampler,默认的采样比例为: 0.1 (即 10%)。不过我们可以通过 spring.sleuth.sampler.percentage
来设置,所设置的值介于 0.0 到 1.0 之间,1.0 则表示全部采集。
与 HTTP方式 相比,两个服务都增加了一个依赖
org.springframework.cloud
spring-cloud-stream-binder-rabbit
同时,配置文件也有所改变,主要的不同是新增了 rabbitmq 相关配置,并删除了 spring.zipkin.base-url=http://106.15.120.126:9411/
。
eureka.client.service-url.defaultZone=http://192.168.xxx.xxx:8761/eureka/,http://192.168.xxx.xxx:8762/eureka/
# 将采样比例设置为 1.0,也就是全部都需要。默认是 0.1
spring.sleuth.sampler.probability=1.0
# 设置为rabbitmq收集信息,也可以设置为kafka或者web
spring.zipkin.sender.type=rabbit
# 设置zipkin所用队列,默认是zipkin
spring.zipkin.rabbit.queue=zipkin
#rabbitmq配置
spring.rabbitmq.host=106.15.120.126
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
spring.rabbitmq.virtual-host=/
zipkin 服务端启动的变化
因为之前说的 Zipkin 不再推荐我们来自定义 Server 端了,所以在最新版本的 Spring Cloud 依赖管理里已经找不到 zipkin-server 了。
那么如果直接用官方提供的 jar 包怎么从 RabbitMQ 中获取 trace 信息呢?
我们可以通过环境变量让 Zipkin 从 RabbitMQ 中读取信息,就像这样:
RABBIT_ADDRESSES=localhost java -jar zipkin.jar
可配置的环境变量如下表所示:
属性 | 环境变量 | 描述 |
---|---|---|
zipkin.collector.rabbitmq.concurrency | RABBIT_CONCURRENCY | 并发消费者数量,默认为 1 |
zipkin.collector.rabbitmq.connection-timeout | RABBIT_CONNECTION_TIMEOUT | 建立连接时的超时时间,默认为 60000 毫秒,即 1 分钟 |
zipkin.collector.rabbitmq.queue | RABBIT_QUEUE | 从中获取 span 信息的队列,默认为 zipkin |
zipkin.collector.rabbitmq.uri | RABBIT_URI | 符合 RabbitMQ URI 规范 的 URI,例如 amqp://user:pass@host:10000/vhost |
如果设置了 URI,则以下属性将被忽略。
属性 | 环境变量 | 描述 |
---|---|---|
zipkin.collector.rabbitmq.addresses | RABBIT_ADDRESSES | 用逗号分隔的 RabbitMQ 地址列表,例如 localhost:5672,localhost:5673 |
zipkin.collector.rabbitmq.password | RABBIT_PASSWORD | 连接到 RabbitMQ 时使用的密码,默认为 guest |
zipkin.collector.rabbitmq.username | RABBIT_USER | 连接到 RabbitMQ 时使用的用户名,默认为 guest |
zipkin.collector.rabbitmq.virtual-host | RABBIT_VIRTUAL_HOST | 使用的 RabbitMQ virtual host,默认为 / |
zipkin.collector.rabbitmq.use-ssl | RABBIT_USE_SSL | 设置为 true 则用 SSL 的方式与 RabbitMQ 建立链接 |
zipkin.jar的yml配置文件内容可点此查看
zipkin
默认是将数据存储在内存中的,如果数据量过大,或者系统运行时间太长内存就会爆掉,所以可以将记录的数据存储到 MySQL 中。
只需在启动 zipkin 服务端时添加相应环境变量即可。
属性 | 环境变量 | 描述 |
---|---|---|
zipkin.collector.storage.type | STORAGE_TYPE | 数据类型,默认为 mem,即存储在内存中 |
zipkin.collector.storage.type.mysql.db | MYSQL_DB | 所用数据库,默认为 zipkin |
zipkin.collector.storage.type.mysql.username | MYSQL_USER | 数据库用户 |
zipkin.collector.storage.type.mysql.password | MYSQL_PASS | 数据库用户密码 |
zipkin.collector.storage.type.mysql.host | MYSQL_HOST | 数据库地址,默认为 localhost |
zipkin.collector.storage.type.mysql.port | MYSQL_TCP_PORT | 数据库端口,默认为 3306 |
zipkin.collector.storage.type.mysql.max-active | MYSQL_MAX_CONNECTIONS | 最大连接数,默认值为 10 |
zipkin.collector.storage.type.mysql.use-ssl | MYSQL_USE_SSL | 是否使用ssl,默认值为 false |
官方数据库脚本地址 ,这里是从GitHub官方复制的。
--
-- Copyright 2015-2019 The OpenZipkin Authors
--
-- Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
-- in compliance with the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software distributed under the License
-- is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
-- or implied. See the License for the specific language governing permissions and limitations under
-- the License.
--
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,
`remote_service_name` VARCHAR(255),
`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',
PRIMARY KEY (`trace_id_high`, `trace_id`, `id`)
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
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(`remote_service_name`) COMMENT 'for getTraces and getRemoteServiceNames';
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,
PRIMARY KEY (`day`, `parent`, `child`)
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
访问 http://192.168.xxx.xxx:9411/zipkin/
若是采用 rabbitmq 的方式,访问 rabbitmq web页面可以看到名为 zipkin
的队列
参考:https://windmt.com/2018/04/24/spring-cloud-12-sleuth-zipkin/