Zipkin链路追踪技术分享
- 什么是Zipkin
Zipkin 是 Twitter 的一个开源项目,它基于 Google Dapper 实现,它致力于收集服务的定时数据,以 解决微服务架构中的延迟问题,包括数据的收集、存储、查找和展现。 我们可以使用它来收集各个服务 器上请求链路的跟踪数据,并通过它提供的 REST API 接口来辅助我们查询跟踪数据以实现对分布式系 统的监控程序,从而及时地发现系统中出现的延迟升高问题并找出系统性能瓶颈的根源。除了面向开发 的 API 接口之外,它也提供了方便的 UI 组件来帮助我们直观的搜索跟踪信息和分析请求链路明细,比 如:可以查询某段时间内各用户请求的处理时间等。 Zipkin 提供了可插拔数据存储方式:InMemory、MySql、Cassandra 以及 Elasticsearch。
扩展:Google Dapper是什么?
Dapper是谷歌内部使用的分布式链路追踪系统,虽然没有开源,但是Google在其2010年发布的一篇论文中对其进行了详细的介绍。可以说,Dapper是链路追踪领域的始祖,其提出的概念和理念一致影响着后来所有的分布式系统链路追踪系统,包括阿里的鹰眼系统,大众点评的cat系统,Twitter的Zipkin以及开源的Jaeger等等。
- 设计初衷及应用场景
现代的大型应用系统一般是复杂的分布式系统,他们由许多的软件模块构成,这些软件模块可能由不同的团队用不同的编程语言编写而成,因此那些传统的用于理解系统行为,分析性能问题的工具,在这种复杂环境下变得失效。
- Zipkin的核心组件
- Collector:收集器组件,它主要用于处理从外部系统发送过来的跟踪信息,将这些信息转换为 Zipkin 内部处理的 Span 格式,以支持后续的存储、分析、展示等功能。
- Storage:存储组件,它主要对处理收集器接收到的跟踪信息,默认会将这些信息存储在内存中, 我们也可以修改此存储策略,通过使用其他存储组件将跟踪信息存储到数据库中。
- RESTful API:API 组件,它主要用来提供外部访问接口。比如给客户端展示跟踪信息,或是外接 系统访问以实现监控等。
- Web UI:UI 组件,基于 API 组件实现的上层应用。通过 UI 组件用户可以方便而有直观地查询和 分析跟踪信息。
Zipkin 分为两端,一个是 Zipkin 服务端,一个是 Zipkin 客户端,客户端也就是微服务的应用。
- Zipkin中的术语
- Span:基本工作单元,
- 那么Span中都有什么呢?
{
"traceId": "bd7a977555f6b982",(标记一次请求的跟踪,相关的Spans都有相同的traceId)
"name": "get-traces",(span的名称,一般是接口方法的名称)
"id": "ebf33e1a81dc6f71",(Span的id)
"parentId": "bd7a977555f6b982",(上一个span的id)
"timestamp": 1458702548478000,(开始的时间戳,微秒级)
"duration": 354374,(持续时间,微秒级)
"annotations": [
{
"endpoint": {
"serviceName": "zipkin-query",
"ipv4": "192.168.1.2",
"port": 9411
},
"timestamp": 1458702548786000,
"value": "cs"
}
],
"binaryAnnotations": [(二进制注释,旨在提供有关RPC的额外信息)
{
"key": "lc",
"value": "JDBCSpanStore",
"endpoint": {
"serviceName": "zipkin-query",
"ipv4": "192.168.1.2",
"port": 9411
}
}
]
}
如下图:
刚发起调用时traceId和spanId是一致,parentId不存在。 被调用者的traceId和调用者的traceId时一致的,被调用者会产生自己的spanId,并且被调用者的parentId是调用者的spanId
- Trace(追溯):一系列spans组成的一个树状结构,例如,如果你正在跑一个分布式大数据工程,你可能需要创建一个trace。
- Annotation:用来及时记录一个事件的存在,一些核心annotations用来定义一个请求的开始和结束
- cs - Client Sent -客户端发起一个请求,这个annotion描述了这个span的开始
- sr - Server Received -服务端获得请求并准备开始处理它,如果将其sr减去cs时间戳便可得到网络延迟
- ss - Server Sent -注解表明请求处理的完成(当请求返回客户端),如果ss减去sr时间戳便可得到服务端需要的处理请求时间
- cr - Client Received -表明span的结束,客户端成功接收到服务端的回复,如果cr减去cs时间戳便可得到客户端从服务端获取回复的所有所需时间
下面一张大图能够清晰展示其中的关系和事件
Reporter:将数据发送到Zipkin的仪器化应用程序中的组件;有三个主要的传输方式:HTTP, Kafka和Scribe;
然后才是上面提到的Zipkin客户端的收集器等
- 安装与使用
官方提供了多种安装方式
一.docker安装启动
docker run -d -p 9411:9411 openzipkin/zipkin
- jar包启动(jdk1.8以上,默认端口9411)
java -jar zipkin-server-2.24.0-exec.jar
如果端口被占用,也可切换端口 java -jar zipkin-server-2.24.0-exec.jar --server.port=8080
启动之后我们就可以直接通过路径访问Zipkin的页面
- 如何使用
通过jar方式,已经把Zipkin服务器启动了,下面需要启动客户端,
客户端:服务的生产者及服务的调用者:
服务的生产者、调用者是相对的,两者之间可以互相调用,即可以同时作为生产者和调用者,两者都是Eureka Client;
两者都要注册到注册中心上,这样才可以相互可见,才能通过服务名来调用指定服务,才能使用Feign或RestTemplate+Ribbon来达到负载均衡
及两者都要注册到Zipkin服务器上,这样Zipkin才能追踪服务的调用链路
1.准备
准备两个项目:op-aouth和op-basic,在basic项目中调用查询人员基本信息接口通过Feign,再调用aouth中的服务
- 服务中的配置
添加mvn
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
添加配置
spring.zipkin.base-url=http://localhost:9411
spring.sleuth.sampler.percentage=1
注:base-url不配置的话,是默认向9411端口注册的
- 启动服务
启动顺序:注册中心(可选)->Zipkin服务器->服务生产者及调用者(op-aouth和op-basic)
顺利启动后,就可在Zipkin的web页面看到注册进来的服务
这是我们就可以调用一个接口,对接口进行追踪
我们可以看到,这个接口整个的链路,以及每一步所使用的时间,就可以有针对性代码优化
- 更改通信方式
Zipkin默认是http的通信方式,在发生问题的,就可能造成数据丢失,生产环境一般是需要添加MQ来辅助
Zipkin是支持MQ的,从jar中就可以看出
zipkin.jar中的yml配置可以参考: https://github.com/openzipkin/zipkin/blob/master/zipkin-server/src/main/resources/zipkin-server-shared.yml
也可指定MQ的地址
java -jar zipkin-server-2.24.0-exec.jar --zipkin.collector.rabbitmq.addresses=192.168.8.215:5672
MQ访问页面:192.168.8.215:15672
客户端也配置上MQ
spring.zipkin.sender.type=rabbit
spring.zipkin.rabbitmq.addresses=192.168.8.215
加了MQ之后,通信过程如下图所示
可以看到如下效果:
当ZipkinServer不可用时(比如关闭、网络不通等),追踪信息不会丢失,因为这些信息会保存在Rabbitmq服务器上,直到Zipkin服务器可用时,再从Rabbitmq中取出这段时间的信息
看一下效果
- Zipkin数据持久化
这时候我们看到的所有数据,都是存在内存中,在实际生产中,数据肯定是要进行持久化的
所以,前面也说到,Zipkin支持多种数据库,下面以mysql为例
命令看着很长,其实仔细看发现很简单,都是见名知义,不必死记硬背。
java -jar zipkin-server-2.24.0-exec.jar --zipkin.collector.rabbitmq.addresses=192.168.8.215:5672 --zipkin.storage.type=mysql --zipkin.storage.mysql.host=192.168.8.21 --zipkin.storage.mysql.port=3306 --zipkin.storage.mysql.username=***** --zipkin.storage.mysql.password=****** --zipkin.storage.mysql.db=op_basic
还需要创建一下几张表
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;
下面看看效果
6配置参数含义
https://www.dandelioncloud.cn/article/details/1592875941275815938