在微服务架构的系统中,一次客户端请求,可能会引起数十次、上百次服务端服务之间的调用。一旦请求出问题了,我们需要考虑很多东西:
要解决这些问题,就涉及到分布式链路追踪。分布式链路追踪系统主要用来跟踪服务调用记录的,一般来说,一个分布式链路追踪系统,有三个部分:数据收集、数据存储、数据展示。
Spring Cloud Sleuth 是 Spring Cloud 提供的一套分布式链路追踪系统。它可以直观地展示出一次请求的调用过程。
创建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.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.1.RELEASEversion>
<relativePath/>
parent>
<modelVersion>4.0.0modelVersion>
<groupId>top.javahaigroupId>
<artifactId>sleuthartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-sleuthartifactId>
<version>2.2.8.RELEASEversion>
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);
/**
* 异步任务
* @return
*/
@Async
public 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 都是一致的,这
个和普通的调用不一样。
Zipkin是Twitter的一个开源项目,可以用来获取和分析Spring Cloud Sleuth 中产生的请求链路跟踪日志,它提供了Web界面来帮助我们直观地查看请求链路跟踪信息。
安装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服务器已成功启动。
接下来Elasticsearch可视化工具es-head ,方便查看数据,可视化工具 es-head 有三种安装方式:
拉取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
https://blog.csdn.net/aiwangtingyun/article/details/123616
直接运行如下命令安装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:[email protected]:5672 openzipkin/zipkin
创建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.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.6.3version>
<relativePath/>
parent>
<modelVersion>4.0.0modelVersion>
<groupId>top.javahaigroupId>
<artifactId>zipkinartifactId>
<properties>
<java.version>1.8java.version>
<spring-cloud.version>2021.0.0spring-cloud.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-sleuthartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-streamartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-stream-binder-rabbitartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-sleuth-zipkinartifactId>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
project>
application.properties添加如下配置:
#服务名
spring.application.name=zipkin01
spring.sleuth.web.client.enabled=true
# 配置采样比例,默认为 0.1
spring.sleuth.sampler.probability=1
# zipkin 地址
spring.zipkin.base-url=http://101.43.30.7:9411
# 开启 zipkin
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
# 配置采样比例,默认为 0.1
spring.sleuth.sampler.probability=1
# zipkin 地址
spring.zipkin.base-url=http://101.43.30.7:9411
# 开启 zipkin
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接口