1. 简介
在微服务架构的系统中,一次客户端请求,可能会引起数十次、上百次服务端服务之间的调用。一旦请求出问题了,我们需要考虑很多东西:
- 如何快速定位问题?
- 如果快速确定此次客户端请求都涉及到哪些服务?
- 到底是哪一个服务出问题了?
要解决这些问题,就涉及到分布式链路追踪。分布式链路追踪系统主要用来跟踪服务调用记录的,一般来说,一个分布式链路追踪系统,有三个部分:数据收集、数据存储、数据展示。
Spring Cloud Sleuth 是 Spring Cloud 提供的一套分布式链路追踪系统。它可以直观地展示出一次请求的调用过程。
- trace:从请求到达系统开始,到给请求做出响应,这样一个过程成为 trace
- span:每次调用服务时,埋入的一个调用记录,成为 span
- annotation:相当于 span 的语法,描述 span 所处的状态
2. 基本应用
创建Maven项目,引入如下依赖:
<?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">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
<relativePath/>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>top.javahai</groupId>
<artifactId>sleuth</artifactId>
<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>
<version>2.2.8.RELEASE</version>
</dependency>
</dependencies>
</project>
创建HelloController,测试日志打印:
@RestController
public class HelloController {
private static final Log log = LogFactory.getLog(HelloController.class);
@GetMapping("/hello")
public String hello() {
log.info("hello spring cloud sleuth");
return "hello spring cloud sleuth";
}
}
启动应用,浏览器请求/hello接口控制台输出:
定义hello2和hello3接口,实现hello2调用hello3接口,形成调用链。
@RestController
public class HelloController {
@Autowired
RestTemplate restTemplate;
@Autowired
HelloService helloService;
private static final Log log = LogFactory.getLog(HelloController.class);
@GetMapping("/hello")
public String hello() {
log.info("hello spring cloud sleuth");
return "hello spring cloud sleuth";
}
@GetMapping("/hello2")
public String hello2() throws InterruptedException {
log.info("hello2");
Thread.sleep(500);
return restTemplate.getForObject("http://localhost:8080/hello3",
String.class);
}
@GetMapping("/hello3")
public String hello3() throws InterruptedException {
log.info("hello3");
Thread.sleep(500);
return "hello 3";
}
}
控制台输出如下 一个 trace 由多个 span 组成,一个trace 相当于就是一个调用链,而一个 span 则是这个链中的每一次 调用过程。
Spring Cloud Sleuth 中也可以收集到异步任务和定时任务中的信息。 开启异步任务和定时任务:
@SpringBootApplication
@EnableAsync
@EnableScheduling
public class SleuthApplication {
public static void main(String[] args) {
SpringApplication.run(SleuthApplication.class, args);
}
@Bean
RestTemplate restTemplate(){
return new RestTemplate();
}
}
创建HelloService类,提供异步任务和定时任务方法:
@Service
public class HelloService {
private static final Log log = LogFactory.getLog(HelloService.class);
@Async
public String asyncFun() {
log.info("asyncFun");
return "asyncFun";
}
@Scheduled(cron = "0/10 * * * * ?")
public void scheduleTask() {
log.info("scheduleTask start");
asyncFun();
log.info("scheduleTask end");
}
}
在HelloController方法中定义hello4接口,调用异步方法helloService.asyncFun:
@GetMapping("/hello4")
public String hello4() throws InterruptedException {
log.info("hello4");
return helloService.asyncFun();
}
重启项目,调用/hello4接口,查看控制台输出日志。可以看到在异步任务中,异步任务是单独的 spanid。 而次定时任务都会产生一个新的 TraceId,并且在调用过程中,SpanId 都是一致的,这 个和普通的调用不一样。
3. 整合Zipkin
Zipkin是Twitter的一个开源项目,可以用来获取和分析Spring Cloud Sleuth 中产生的请求链路跟踪日志,它提供了Web界面来帮助我们直观地查看请求链路跟踪信息。
3.1 Zipkin安装
3.1.1 安装Elasticsearch
安装Zipkin前首先需要 安装Elasticsearch,使用Elasticsearch作为数据存储工具,用于存储服务链路的跟踪信息。下面都使用Docker方式进行安装。
es 安装命令:
docker run -d --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms128m -Xmx512m" elasticsearch:7.1.0
浏览器访问服务器的9200端口返回如下信息则说明ES服务器已成功启动。
3.1.2 安装Elasticsearch-head
接下来Elasticsearch可视化工具es-head ,方便查看数据,可视化工具 es-head 有三种安装方式:
- 直接下载软件安装
- 通过 Docker 安装
- 安装 Chrome/Firefox 插件
拉取elasticsearch-head镜像
docker pull mobz/elasticsearch-head:5
运行镜像
docker run -d -p 9100:9100 mobz/elasticsearch-head:5
进入elasticsearch容器中
docker exec -it 160327b0a4fa /bin/bash
打开config目录下elasticsearch.yml配置文件
镜像一般不会配置有vim,CentOS的软件安装工具是yum,我们使用yum先下载vim
yum install -y vim
elasticsearch.yml配置文件中添加跨域配置
http.cors.enabled: true
http.cors.allow-origin: '*'
重启elasticsearch容器
docker restart 160327b0a4fa
浏览器访问,如果集群健康显示green则说明成功连接了elasticsearch
3.1.3 安装RabbitMQ
https://blog.csdn.net/aiwangtingyun/article/details/123616
3.1.4 安装Zipkin
直接运行如下命令安装Zipkin
docker run -d -p 9411:9411 --name zipkin -e ES_HOSTS=10.0.16.9 -e STORAGE_TYPE=elasticsearch -e ES_HTTP_LOGGING=BASIC -e RABBIT_URI=amqp://guest:guest@10.0.16.9:5672 openzipkin/zipkin
3.2 测试使用
创建Maven项目,添加如下依赖:
<?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">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
<relativePath/>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>top.javahai</groupId>
<artifactId>zipkin</artifactId>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2021.0.0</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-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</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>
</project>
application.properties添加如下配置:
spring.application.name=zipkin01
spring.sleuth.web.client.enabled=true
spring.sleuth.sampler.probability=1
spring.zipkin.base-url=http://101.43.30.7:9411
spring.zipkin.enabled=true
spring.zipkin.sender.type=rabbit
spring.rabbitmq.host=101.43.30.7
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
启动类配置:
@SpringBootApplication
public class ZipkinApplication {
public static void main(String[] args) {
SpringApplication.run(ZipkinApplication.class, args);
}
@Bean
RestTemplate restTemplate(){
return new RestTemplate();
}
}
创建HelloController类,提供接口/hello
@RestController
public class HelloController {
private static final Logger logger =
LoggerFactory.getLogger(HelloController.class);
@GetMapping("/hello")
public String hello(String name) {
logger.info("zipkin01-hello");
return "hello " + name + " !";
}
}
复制上面创建的项目,新建一个zipkin02项目,修改配置文件
server.port=8082
spring.application.name=zipkin02
spring.sleuth.web.client.enabled=true
spring.sleuth.sampler.probability=1
spring.zipkin.base-url=http://101.43.30.7:9411
spring.zipkin.enabled=true
spring.zipkin.sender.type=rabbit
spring.rabbitmq.host=101.43.30.7
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
同样创建HelloController类,提供接口/hello,调用zipkin01的hello接口
@RestController
public class HelloController {
@Autowired
RestTemplate restTemplate;
private static final Logger logger =
LoggerFactory.getLogger(HelloController.class);
@GetMapping("/hello")
public String hello() {
logger.info("zipkin02-hello");
String s = restTemplate.getForObject("http://localhost:8080/hello?name={1}",String.class, "hello world");
logger.info(s);
return s;
}
}
启动两个项目,浏览器访问zipkin02的hello接口
打开zipkin查看这条链路调用,我们可以通过zipkin直观地看到请求调用链路和通过每个服务的耗时等信息。
|