说明
在微服务架构下,随着业务的发展,系统规模也会变得越来越大,个微服务之间的调用关系也变得越来越错综复杂。通常由一个客户端发起的请求在后端会经过多个不同的微服务调用来协同产生最后的请求结果。在复杂的微服务架构系统中,几乎每一个前端请求都会形成一个复杂的分布式服务调用链路。这时候,对于每一个请求,全链路的调用的跟踪就变得越来越重要,通过实现对请求调用的跟踪可以帮助我们快速发现错误根源以及监控分析每条请求链路上的性能瓶颈等。
ZipKin:Twitter开源项目,可以实现对于请求链路的延迟监控
目标
使用SpringCloud Sleuth、RabbitMQ、ZipKin,实现异步的跟踪链路监控
快速开始
1 构建服务中心Eureka与消息中间件RabbitMQ
(这一部分省略,RabbitMQ的使用可以参照我的另一篇博客->RabbitMQ使用)
2 构建Zipkin服务端
首先给出项目结构:
1)更新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>yunlingfly</groupId>
<artifactId>springcloudzipkin</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springcloudzipkin</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Edgware.SR3</spring-cloud.version>
</properties>
<dependencies>
<!-- 如果不指定版本可能会找不到该maven -->
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
<version>2.4.6</version>
</dependency>
<!-- 以下依赖将实现通过消息中间件异步接收跟踪消息 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2)更新application.yml
server:
port: 9411
spring:
application:
name: zipkin-server
rabbitmq:
host: xxx.xxx.xxx.xxx
username: xxxxxxx
password: xxxxxxx
port: 5672
info:
name: 一个zipkin服务端
version: 0.0.1
3)编写启动类
package yunlingfly.springcloudzipkin;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.sleuth.zipkin.stream.EnableZipkinStreamServer;
import zipkin.server.EnableZipkinServer;
//@EnableZipkinServer
// 因为使用RabbitMQ异步接收,改为@EnableZipkinStreamServer
@EnableZipkinStreamServer
@SpringBootApplication
public class SpringcloudzipkinApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudzipkinApplication.class, args);
}
}
3 构建链式调用的服务track-1 -> track-2
track-1:首先给出项目结构:
1)更新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>yunlingfly</groupId>
<artifactId>springcloudsleuth</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springcloudsleuth</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Dalston.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<!-- 如果使用RabbitMQ异步的话注意将下面的依赖注释掉,否则报错HostLocator bean找不到 -->
<!--<dependency>-->
<!--<groupId>org.springframework.cloud</groupId>-->
<!--<artifactId>spring-cloud-sleuth-zipkin</artifactId>-->
<!--</dependency>-->
<!-- 将跟踪消息发送到RabbitMQ,使用sleuth-zipkin-stream将自动引入sleuth-stream和相关依赖包 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2)创建bootstrap.yml
spring:
application:
name: trace-1
3)更新application.yml
server:
port: 8766
info:
app:
name: 分布式服务跟踪SpringCloudSleuth
version: 0.0.1
eureka:
client:
serviceUrl:
defaultZone: http://xxx.xxx.xxx.xxx:8761/eureka/
# instance:
# preferIpAddress: true
# instance-id: ${spring.cloud.client.ipAddress}:${server.port}
# hostname: ${spring.cloud.client.ipAddress}
# ipAddress: 120.79.66.240
spring:
application:
name: trace-1
# zipkin:
# base-url: http://120.79.66.240:9411
rabbitmq:
host: xxx.xxx.xxx.xxx
username: xxxxxx
password: xxxxxx
port: 5672
4)更新启动类
package yunlingfly.springcloudsleuth;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@EnableDiscoveryClient
@SpringBootApplication
public class SpringcloudsleuthApplication {
@Bean
// @LoadBalanced注解表明这个restRemplate开启负载均衡的功能
@LoadBalanced
RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(SpringcloudsleuthApplication.class, args);
}
}
5)controller层
package yunlingfly.springcloudsleuth.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.servlet.http.HttpServletRequest;
@RestController
public class HelloController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/hello",method = RequestMethod.GET)
public String hello(){
return restTemplate.getForEntity("http://trace-2/trace-2",String.class).getBody();
}
@RequestMapping(value = "/trace-2",method = RequestMethod.GET)
public String trace(HttpServletRequest request){
System.out.println("[INFO追踪服务]call trace-2 traceId="+request.getHeader("X-B3-TraceId")+" SpanId="+request.getHeader("X-B3-SpanId"));
return "success";
}
}
track-2:为了方便演示track-2与track-1其实是一样的,可以将track-1的application.yml和bootstrap.yml配置的spring.application.name属性改成track-2,然后server.port换成另一个不冲突的端口即可
运行
分别启动Eureka、RabbitMQ、ZipKin、track-2、track-1,浏览器访问你开启的ZipKin的地址:http://xxx.xxx.xxx.xxx:9411,
然后访问:http://127.0.0.1:8766/hello
返回ZipKin页面点击Find Tracks即可查看调用链:
控制台也可查看traceId:(该ID就是链式跟踪的关键,使用该ID来传递和标识调用)
注意事项:我bootstrap.yml与application.yml都配置了name属性,不这样貌似不行;使用RabbitMQ异步跟踪时,不用spring-cloud-sleuth-zipkin得注释掉,注意@EnableZipkinServer与@EnableZipkinStreamServer的不同。由于SpringCloud Sleuth与RabbitMQ集成的很好,所以我们不用自己创建异步的消息队列,其实Sleuth会自己创建一个名为sleuth的Exchange与Queues: