微服务之链路追踪
1.搭建Zipkin服务器
同时也作为Eureka的客户端注册到Eureka的服务中心。
引入依赖
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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>spring.cloud</groupId>
<artifactId>sleuth-zipkin</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>sleuth-zipkin</name>
<description>sleuth-zipkin project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.RELEASE</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-netflix-eureka-client</artifactId>
</dependency>
<!-- sleuth -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<!-- zipkin服务端包 -->
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
<version>2.12.3</version>
</dependency>
<!-- Zipkin用户配置包 -->
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
<version>2.12.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</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>
application.yml
# 定义Spring应用名称,它是一个微服务的名称,一个微服务可拥有多个实例
spring:
application:
name: sleuth-zipkin
# 服务治理中心相互注册
eureka:
client:
serviceUrl:
defaultZone: http://localhost:1001/eureka,http://localhost:1002/eureka
management:
metrics:
web:
server:
# 取消自动定时,不设置为false可能时间序列数量增长过大,导致异常
auto-time-requests: false
server:
port: 5001
启动类 SleuthZipkinApplication.java
package com.spring.cloud.sleuth.zipkin.main;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import zipkin2.server.internal.EnableZipkinServer;
@SpringBootApplication
// 驱动Zipkin服务器
@EnableZipkinServer
public class SleuthZipkinApplication {
public static void main(String[] args) {
SpringApplication.run(SleuthZipkinApplication.class, args);
}
}
2.搭建Zipkin客户端
同时也是Eureka的客户端。
sleuth-provider (服务提供模块)
引入依赖
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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>spring.cloud</groupId>
<artifactId>sleuth-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>sleuth-provider</name>
<description>sleuth-provider project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.RELEASE</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-netflix-eureka-client</artifactId>
</dependency>
<!-- sleuth -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</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>
application.yml配置文件
# 定义Spring应用名称,他是一个微服务的名称,一个微服务可以拥有多个实例
spring:
application:
name: sleuth-provider
sleuth:
sampler: # 样本配置
# 百分比,默认0.1
probability: 1.0
# 速率,每秒追踪30次
# rate: 30
zipkin:
base-url: http://localhost:5001
# 注册到服务治理中心
eureka:
client:
serviceUrl:
defaultZone: http://localhost:1001/eureka,http://localhost:1002/eureka
server:
port: 2001
启动类 SleuthProviderApplication.java
package com.spring.cloud.sleuth.provider.main;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages ="com.spring.cloud.sleuth.provider")
public class SleuthProviderApplication {
public static void main(String[] args) {
SpringApplication.run(SleuthProviderApplication.class, args);
}
}
控制器 SleuthProviderController.java
package com.spring.cloud.sleuth.provider.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**** imports ****/
@RestController
public class SleuthProviderController {
// 日志对象(org.slf4j.Logger)
private static final Logger logger
= LoggerFactory.getLogger(SleuthProviderController.class);
// 提供的服务
@GetMapping("/hello/{name}")
public String sayHello(@PathVariable("name") String name) {
logger.info("请求参数:{}" , name);
String helloResult = "hello " + name;
logger.info("请求结果:{}", helloResult);
return helloResult;
}
}
sleuth-consumer (服务消费模块)
引入依赖
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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>spring.cloud</groupId>
<artifactId>sleuth-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>sleuth-consumer</name>
<description>sleuth-consumer project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.RELEASE</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-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- sleuth -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</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>
application.yml配置文件
# 定义Spring应用名称,他是一个微服务的名称,一个微服务可以拥有多个实例
spring:
application:
name: sleuth-consumer
sleuth:
sampler: # 样本配置
# 百分比,默认0.1
probability: 1.0
# 速率,每秒追踪30次
# rate: 30
zipkin:
base-url: http://localhost:5001
# 注册到服务治理中心
eureka:
client:
serviceUrl:
defaultZone: http://localhost:1001/eureka,http://localhost:1002/eureka
server:
port: 3001
启动类 SleuthConsumerApplication.java
package com.spring.cloud.sleuth.consumer.main;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
/**** imports ****/
@SpringBootApplication( // 定义扫描包
scanBasePackages = "com.spring.cloud.sleuth.consumer")
@EnableFeignClients( //扫描装配OpenFeign接口到IoC容器中 ①
basePackages="com.spring.cloud.sleuth.consumer")
public class SleuthConsumerApplication {
@Bean
@LoadBalanced // 负载均衡
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(SleuthConsumerApplication.class, args);
}
}
测试OpenFeign客户端,所以引入@FeignClient
SleuthFeign.java
package com.spring.cloud.sleuth.consumer.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
// 声明为OpenFeign客户端
@FeignClient("sleuth-provider")
public interface SleuthFeign {
@GetMapping("/hello/{name}")
public String sayHello(@PathVariable("name") String name);
}
控制器 SleuthController.java
package com.spring.cloud.sleuth.consumer.controller;
import com.spring.cloud.sleuth.consumer.feign.SleuthFeign;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**** imports ****/
@RestController
public class SleuthController {
// 日志
private static final Logger logger
= LoggerFactory.getLogger(SleuthController.class);
@Autowired
private RestTemplate restTemplate = null;
@Autowired
private SleuthFeign sleuthFeign = null;
// Ribbon的调用
@GetMapping("/hello/rest/{name}")
public String testResTemplate(@PathVariable("name") String name) {
logger.info("使用RestTemplate,请求参数:{}", name );
String url = "http://sleuth-provider/hello/{name}";
logger.info("使用RestTemplate,请求URL:{}", url );
String result = restTemplate.getForObject(url, String.class, name);
logger.info("使用RestTemplate,请求结果:{}", result );
return result;
}
// OpenFeign的调用
@GetMapping("/hello/feign/{name}")
public String testFeign(@PathVariable("name") String name) {
logger.info("使用Open Feign,请求参数:{}", name );
String result = sleuthFeign.sayHello(name);
logger.info("使用Open Feign,请求结果:{}", result );
return result;
}
}
3.搭建Eureka服务治理中心
请参考Eureka篇。只需修改下端口号。
链路追踪需要从网关开始追踪,所以需要搭建网关,前面有搭建Getway的文章了,这里结束zuul网关的搭建
4.搭建网关
sleuth-zuul (微服务实例,同时也是Eureka的客户端和Zipkin客户端)
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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>spring.cloud</groupId>
<artifactId>sleuth-zuul</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>sleuth-zuul</name>
<description>sleuth-zuul project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<!-- Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- sleuth -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<!-- 服务发现 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- Zipkin客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<!-- Zuul网关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</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>
application.yml
# 定义Spring应用名称,他是一个微服务的名称,一个微服务可以拥有多个实例
spring:
application:
name: sleuth-zuul
sleuth:
sampler: # 样本配置
# 百分比,默认0.1
probability: 1.0
# 速率,每秒追踪30次
# rate: 30
zipkin:
base-url: http://localhost:5001
# 注册到服务治理中心
eureka:
client:
serviceUrl:
defaultZone: http://localhost:1001/eureka,http://localhost:1002/eureka
# Zuul 路由配置
zuul:
routes:
provider:
path: /provider/**
service-id: sleuth-provider
consumer:
path: /consumer/**
service-id: sleuth-consumer
server:
port: 8001
启动类 SleuthZuulApplication.java
package com.spring.cloud.sleuth.zuul.main;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication(scanBasePackages="com.spring.cloud.sleuth.zuul")
@EnableZuulProxy // 驱动Zuul网关服务
public class SleuthZuulApplication {
public static void main(String[] args) {
SpringApplication.run(SleuthZuulApplication.class, args);
}
}
自定义过滤器 TraceFilter.java
package com.spring.cloud.sleuth.zuul.filter;
import brave.Tracer;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
/**** imports ****/
@Component
public class TraceFilter extends ZuulFilter {
private static final Logger logger
= LoggerFactory.getLogger(TraceFilter.class);
// 注入Brave的Tracer对象
@Autowired
private Tracer tracer = null; // ①
@Override
public Object run() throws ZuulException {
// 添加一个span的属性标记
tracer.currentSpan().tag("1001", "GET请求");
// 当前trace id
String traceId = tracer.currentSpan().context().traceIdString();
// 当前 span id
String spanId = tracer.currentSpan().context().spanIdString();
// 日志打印
logger.info("当前追踪参数:traceId={},spanId={}", traceId, spanId);
return null;
}
@Override
public boolean shouldFilter() {
// 获取请求上下文
RequestContext ctx = RequestContext.getCurrentContext();
// 判断是否GET请求
return "GET".equalsIgnoreCase(ctx.getRequest().getMethod());
}
// 过滤器顺序
@Override
public int filterOrder() {
return FilterConstants.PRE_DECORATION_FILTER_ORDER + 10;
}
// 过滤器类型为“pre”
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
}
启动各个微服务实例
浏览器访问http://localhost:8001/consumer/hello/feign/jim 和http://localhost:8001/consumer/hello/rest/jim
然后登录http://localhost:5001/zipkin/
就可以看到请求记录了。
同时sleuth-consumer的后台日志也可以看到请求日志。