1. 简介

在微服务架构的系统中,一次客户端请求,可能会引起数十次、上百次服务端服务之间的调用。一旦请求出问题了,我们需要考虑很多东西:

  • 如何快速定位问题?
  • 如果快速确定此次客户端请求都涉及到哪些服务?
  • 到底是哪一个服务出问题了?

要解决这些问题,就涉及到分布式链路追踪。分布式链路追踪系统主要用来跟踪服务调用记录的,一般来说,一个分布式链路追踪系统,有三个部分:数据收集、数据存储、数据展示。

Spring Cloud Sleuth 是 Spring Cloud 提供的一套分布式链路追踪系统。它可以直观地展示出一次请求的调用过程。

  • trace:从请求到达系统开始,到给请求做出响应,这样一个过程成为 trace
  • span:每次调用服务时,埋入的一个调用记录,成为 span
  • annotation:相当于 span 的语法,描述 span 所处的状态

2. 基本应用

创建Maven项目,引入如下依赖:

<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,测试日志打印:

@RestControllerpublic 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接口,形成调用链。

@RestControllerpublic class HelloController {@AutowiredRestTemplate restTemplate;@AutowiredHelloService 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//开启定时任务@EnableSchedulingpublic class SleuthApplication {public static void main(String[] args) {SpringApplication.run(SleuthApplication.class, args);}@BeanRestTemplate restTemplate(){return new RestTemplate();}}

创建HelloService类,提供异步任务和定时任务方法:

@Servicepublic class HelloService {private static final Log log = LogFactory.getLog(HelloService.class);/** * 异步任务 * @return */@Asyncpublic String asyncFun() {log.info("asyncFun");return "asyncFun";}/** * 定时任务,每10秒调用一次 */@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 有三种安装方式:

  1. 直接下载软件安装
  2. 通过 Docker 安装
  3. 安装 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: truehttp.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项目,添加如下依赖:

<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=zipkin01spring.sleuth.web.client.enabled=true# 配置采样比例,默认为 0.1spring.sleuth.sampler.probability=1# zipkin 地址spring.zipkin.base-url=http://101.43.30.7:9411# 开启 zipkinspring.zipkin.enabled=true# 追踪消息的发送类型spring.zipkin.sender.type=rabbitspring.rabbitmq.host=101.43.30.7spring.rabbitmq.port=5672spring.rabbitmq.username=guestspring.rabbitmq.password=guest

启动类配置:

@SpringBootApplicationpublic class ZipkinApplication {public static void main(String[] args) {SpringApplication.run(ZipkinApplication.class, args);}@BeanRestTemplate restTemplate(){return new RestTemplate();}}

创建HelloController类,提供接口/hello

@RestControllerpublic 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=8082spring.application.name=zipkin02spring.sleuth.web.client.enabled=true# 配置采样比例,默认为 0.1spring.sleuth.sampler.probability=1# zipkin 地址spring.zipkin.base-url=http://101.43.30.7:9411# 开启 zipkinspring.zipkin.enabled=true# 追踪消息的发送类型spring.zipkin.sender.type=rabbitspring.rabbitmq.host=101.43.30.7spring.rabbitmq.port=5672spring.rabbitmq.username=guestspring.rabbitmq.password=guest

同样创建HelloController类,提供接口/hello,调用zipkin01的hello接口

@RestControllerpublic class HelloController {@AutowiredRestTemplate 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直观地看到请求调用链路和通过每个服务的耗时等信息。