一、简介

1、概述

Zipkin是一款开源的分布式追踪系统,它可以收集和分析分布式系统中服务调用的耗时数据,直观地展示请求在微服务链路中的完整路径,快速定位性能瓶颈和问题根因。

2、核心概念

  • Trace

Trace代表一次完整的请求链路,具有全局唯一的Trace ID;一个Trace由多个Span构成一棵树状结构。

  • Span

Span代表一个服务内部的基本工作单元(例如一次方法调用),其中包含操作名称、时间戳、耗时情况等;Span之间通过Parent ID建立父子关系,形成调用链。

  • Annotation

用于记录关键事件的时间点,主要包括cs, sr, ss, cr四种,可用来计算网络延迟和服务处理时间;属于Span的一部分。

注解 全称 含义
cs Client Sent 客户端发起请求,标志着一个Span的开始
sr Server Received 服务端接收到请求,准备开始处理
ss Server Sent 服务端处理完成,并将响应发送回客户端
cr Client Received 客户端成功接收到服务器的响应,标志着该Span的结束
  • Tags

Tags是以键值对形式附加在Span上的自定义元数据(metadata),用于记录业务上下文,例如:HTTP方法、URL、状态码等;是Span的补充信息。

3、内部模块

  • Collector

任何组件发送的Trace数据都会到达收集器守护程序处理。

  • Storage

存储、索引和查找数据,支持Cassandra、ElasticSearch和MySQL。

  • Search

搜索模块提供了一个简单的JSON API,用于查找和检索存储在后端的Trace数据;此API主要被Web UI使用。

  • Web UI

用于查看Trace的UI界面。

4、工作流程

  • 数据写入

使用集成了Zipkin的客户端(例如:Spring Cloud Sleuth)处理请求时,客户端会拦截调用,生成Trace和Span信息;这些数据会通过HTTP或消息队列(如Kafka, RabbitMQ)​ 等传输方式异步发送到Zipkin服务端的收集器(Collector)处理并存储。

  • 数据存储与查询

Zipkin支持多种存储,包括内存(仅用于测试)、MySQL、Elasticsearch和Cassandra;生产环境推荐使用Elasticsearch以满足大规模数据存储和查询需求。存储的数据可以通过查询服务(Query Service)API进行检索,最终在Web UI上可视化展示。

下面是用户请求/foo资源的顺序图:

5、下载安装

下载zipkin-server-*exec.jar

下载之后使用java -jar运行:

java -jar zipkin-server-2.24.3-exec.jar

访问http://localhost:9411/zipkin/

如果使用MySQL存储,可以使用以下命令启动:

java -jar zipkin-server-2.24.3-exec.jar --zipkin.storage.type=mysql --zipkin.storage.mysql.host=localhost --zipkin.storage.mysql.tcp.port=3306 --zipkin.storage.mysql.db=test --zipkin.storage.mysql.username=root --zipkin.storage.mysql.password=123456

启动之前需要在数据库中运行zipkin提供的建表脚本:

zipkin-master\zipkin-storage\mysql-v1\src\main\resources\mysql.sql

二、样例

1、pom.xml

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.7.18</version>
	<relativePath /> <!-- lookup parent from repository -->
</parent>

<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-dependencies</artifactId>
			<version>2021.0.9</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>

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

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

</dependencies>

2、Java类

  • 配置类

定义RestTemplate和采样方式为一直采样。

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

import brave.sampler.Sampler;

@Configuration
public class WebConfig {

	@Bean
	@ConditionalOnMissingBean
	public RestTemplate getRestTemplate() {
		return new RestTemplate();
	}
	
	@Bean
	public Sampler alwaysSampler() {
		return Sampler.ALWAYS_SAMPLE;
	}
}
  • 服务一

服务一运行在8081端口,访问/zipkin1时会调用服务二的服务/zipkin2

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@SpringBootApplication
public class ZipkinServiceOneApp {

	@Autowired
	private RestTemplate restTemplate;
	
	public static void main(String[] args) {
		SpringApplicationBuilder builder = new SpringApplicationBuilder(ZipkinServiceOneApp.class);
		builder.properties("server.port=8081","spring.application.name=server1");
		builder.run(args);
	}

	@GetMapping(value="/zipkin1")
	public String zipkinServerOne() {
		System.out.println("zipkin service one...");
		String response = restTemplate.exchange("http://localhost:8082/zipkin2", HttpMethod.GET, null, new ParameterizedTypeReference<String>() {}).getBody();
		return "Service2 response: " + response;
	}
}
  • 服务二

服务二运行在8082端口,访问/zipkin2时会调用服务三的服务/zipkin3

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@SpringBootApplication
public class ZipkinServiceTwoApp {

	@Autowired
	private RestTemplate restTemplate;
	
	public static void main(String[] args) {
		SpringApplicationBuilder builder = new SpringApplicationBuilder(ZipkinServiceTwoApp.class);
		builder.properties("server.port=8082", "spring.application.name=server2");
		builder.run(args);
	}
	
	@GetMapping(value="/zipkin2")
	public String zipkinServerTwo() {
		System.out.println("zipkin service two...");
		String response = restTemplate.exchange("http://localhost:8083/zipkin3", HttpMethod.GET, null, new ParameterizedTypeReference<String>() {}).getBody();
		return "Service3 response: " + response;
	}
}
  • 服务三

服务三运行在8083端口,服务执行时会sleep 3秒(模拟耗时)后返回Hello World。

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@SpringBootApplication
public class ZipkinServiceThreeApp {

	public static void main(String[] args) {
		SpringApplicationBuilder builder = new SpringApplicationBuilder(ZipkinServiceThreeApp.class);
		builder.properties("server.port=8083", "spring.application.name=server3");
		builder.run(args);
	}
	
	@GetMapping(value="/zipkin3")
	public String zipkinServerThree() {
		System.out.println("zipkin service three...");
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return "Hello World!";
	}
}

3、运行

分别启动服务一、服务二、服务三后,访问:http://localhost:8081/zipkin1,大概等待3秒后可以看到响应:

Service2 response: Service3 response: Hello World!

在Zipkin Web UI中查询,可以看到请求的调用情况以及每个请求的耗时情况:

参考资料