## 概述
一个完整的微服务系统包含多个微服务单元,各个微服务子系统存在互相调用的情况,形成一个 调用链。一个客户端请求从发出到被响应 经历了哪些组件、哪些微服务、请求总时长、每个组件所花时长 等信息我们有必要了解和收集,以帮助我们定位性能瓶颈、进行性能调优,因此监控整个微服务架构的调用链十分有必要,本文将阐述如何使用 Zipkin 搭建微服务调用链追踪中心。
Zipkin 初摸
正如 Ziplin 官网 所描述,Zipkin 是一款分布式的追踪系统,其可以帮助我们收集微服务架构中用于解决延时问题的时序数据,更直白地讲就是可以帮我们追踪调用的轨迹。
Zipkin 的设计架构如下图所示:

要理解这张图,需要了解一下 Zipkin 的几个核心概念:
- Reporter
在某个应用中安插的用于发送数据给 Zipkin 的组件称为 Report,目的就是用于追踪数据收集
- Span
微服务中调用一个组件时,从发出请求开始到被响应的过程会持续一段时间,将这段跨度称为 Span
- Trace
从 Client 发出请求到完成请求处理,中间会经历一个调用链,将这一个整个过程称为一个追踪( Trace )。一个 Trace 可能包含多个 Span,反之每个 Span 都有一个上级的 Trace。
- Transport
一种数据传输的方式,比如最简单的 HTTP 方式,当然在高并发时可以换成 Kafka 等消息队列
看了一下基本概念后,再结合上面的架构图,可以试着理解一下,只有装配有 Report 组件的 Client 才能通过 Transport 来向 Zipkin 发送追踪数据。追踪数据由 Collector 收集器进行手机然后持久化到 Storage 之中。最后需要数据的一方,可以通过 UI 界面调用 API 接口,从而最终取到 Storage 中的数据。可见整体流程不复杂。
Zipkin 官网给出了各种常见语言支持的 OpenZipkin libraries:

本文接下来将 构造微服务追踪的实验场景 并使用 Brave 来辅助完成微服务调用链追踪中心搭建!
部署 Zipkin 服务
利用 Docker 来部署 Zipkin 服务再简单不过了:
docker run -d -p 9411:9411 \
--name zipkin \
docker.io/openzipkin/zipkin
完成之后浏览器打开:localhost:9411可以看到 Zipkin 的可视化界面:

模拟微服务调用链
我们来构造一个如下图所示的调用链:

图中包含 一个客户端 + 三个微服务:
-
Client:使用 /servicea 接口消费 ServiceA 提供的服务
-
ServiceA:使用 /serviceb 接口消费 ServiceB 提供的服务,端口 8881
-
ServiceB:使用 /servicec 接口消费 ServiceC 提供的服务,端口 8882
-
ServiceC:提供终极服务,端口 8883
为了模拟明显的延时效果,准备在每个接口的响应中用代码加入 3s 的延时。
简单起见,我们用 SpringBt 来实现三个微服务。
ServiceA 的控制器代码如下:
@RestController
public class ServiceAContorller {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/servicea ”)
public String servicea() {
try {
Thread.sleep( 3000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
return restTemplate.getForObject("http://localhost:8882/serviceb", String.class);
}
}
ServiceB 的代码如下:
@RestController
public class ServiceBContorller {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/serviceb ”)
public String serviceb() {
try {
Thread.sleep( 3000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
return restTemplate.getForObject("http://localhost:8883/servicec", String.class);
}
}
ServiceC 的代码如下:
@RestController
public class ServiceCContorller {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/servicec ”)
public String servicec() {
try {
Thread.sleep( 3000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Now, we reach the terminal call: servicec !”;
}
}
我们将三个微服务都启动起来,然后浏览器中输入localhost:8881/servicea来发出请求,过了 9s 之后,将取到 ServiceC 中提供的微服务接口所返回的内容,如下图所示:

很明显,调用链可以正常 work 了!
那么接下来我们就要引入 Zipkin 来追踪这个调用链的信息!
编写与 Zipkin 通信的工具组件
从 Zipkin 官网我们可以知道,借助 OpenZipkin 库 Brave,我们可以开发一个封装 Brave 的公共组件,让其能十分方便地嵌入到 ServiceA,ServiceB,ServiceC 服务之中,完成与 Zipkin 的通信。
为此我们需要建立一个新的基于 Maven 的 Java 项目:ZipkinTool
- pom.xml 中加入如下依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.hansonwang99</groupId>
<artifactId>ZipkinTool</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>6</source>
<target>6</target>
</configuration>
</plugin>
</plugins>
</build>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>2.0.1.RELEASE</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.7.RELEASE</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-spring-web-servlet-interceptor</artifactId>
<version>4.0.6</version>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-spring-resttemplate-interceptors</artifactId>
<version>4.0.6</version>
</dependency>
<dependency>
<groupId>io.zipkin.reporter</groupId>
<artifactId>zipkin-sender-okhttp3</artifactId>
<version>0.6.12</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
- 编写 ZipkinProperties 类
其包含 endpoint 和 service 两个属性,我们最后是需要将该两个参数提供给 ServiceA、ServiceB、ServiceC 微服务作为其 application.properties 中的 Zipkin 配置
@Data
@Component
@ConfigurationProperties("zipkin")
public class ZipkinProperties {
private String endpoint;
private String service;
}
用了 lombok 之后,这个类异常简单!
[注意:关于 lombok 的用法,可以看这里]
- 编写 ZipkinConfiguration 类
这个类很重要,在里面我们将 Brave 的 BraveClientHttpRequestInterceptor 拦截器注册到 RestTemplate 的拦截器调用链中来收集请求数据到 Zipkin 中;同时还将 Brave 的 ServletHandlerInterceptor 拦截器注册到调用链中来收集响应数据到 Zipkin 中
上代码吧:
@Configuration
@Import({RestTemplate.class, BraveClientHttpRequestInterceptor.class, ServletHandlerInterceptor.class})
public class ZipkinConfiguration extends WebMvcConfigurerAdapter {
@Autowired
private ZipkinProperties zipkinProperties;
@Autowired
private RestTemplate restTemplate;
@Autowired
private BraveClientHttpRequestInterceptor clientInterceptor;
@Autowired
private ServletHandlerInterceptor serverInterceptor;
@Bean
public Sender sender() {
return OkHttpSender.create( zipkinProperties.getEndpoint() );
}
@Bean
public Reporter<Span> reporter() {
return AsyncReporter.builder(sender()).build();
}
@Bean
public Brave brave() {
return new Brave.Builder(zipkinProperties.getService()).reporter(reporter()).build();
}
@Bean
public SpanNameProvider spanNameProvider() {
return new SpanNameProvider() {
@Override
public String spanName(HttpRequest httpRequest) {
return String.format(
"%s %s",
httpRequest.getHttpMethod(),
httpRequest.getUri().getPath()
);
}
};
}
@PostConstruct
public void init() {
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
interceptors.add(clientInterceptor);
restTemplate.setInterceptors(interceptors);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(serverInterceptor);
}
}
ZipkinTool 完成以后,我们需要在 ServiceA、ServiceB、ServiceC 三个 SpringBt 项目的 application.properties 中加入 Zipkin 的配置:
以 ServiceA 为例:
server.port=8881
zipkin.endpoint=http://你 Zipkin 服务所在机器的 IP:9411/api/v1/spans
zipkin.service=servicea
我们最后依次启动 ServiceA、ServiceB、和 ServiceC 三个微服务,并开始实验来收集链路追踪数据 !
## 实际实验
1. 依赖分析
浏览器打开 Zipkin 的 UI 界面,可以查看 依赖分析:

图中十分清晰地展示了 ServiceA、ServiceB 和 ServiceC 三个服务之间的调用关系! 注意,该图可缩放,并且每一个元素均可以点击,例如点击 ServiceB 这个微服务,可以看到其调用链的上下游!

2. 查找调用链
接下来我们看一下调用链相关,点击 服务名,可以看到 Zipkin 监控到个所有服务:

同时可以查看 Span,如以 ServiceA 为例,其所有 REST 接口都再下拉列表中:

以 ServiceA 为例,点击 Find Traces,可以看到其所有追踪信息:

点击某个具体 Trace,还能看到详细的每个 Span 的信息,如下图中,可以看到 A B C 调用过程中每个 REST 接口的详细时间戳:

点击某一个 REST 接口进去还能看到更详细的信息,如查看 /servicec 这个 REST 接口,可以看到从发送请求到收到响应信息的所有详细步骤:
