在微服务架构的系统中,一次客户端请求,可能会引起数十次、上百次服务端服务之间的调用。一旦请求出问题了,我们需要考虑很多东西:
- 如何快速定位问题?
- 如果快速确定此次客户端请求都涉及到哪些服务?
- 到底是哪一个服务出问题了?
要解决这些问题,就涉及到分布式链路追踪。分布式链路追踪系统主要用来跟踪服务调用记录的,一般来说,一个分布式链路追踪系统,有三个部分:数据收集、数据存储、数据展示。
Spring Cloud Sleuth 是 Spring Cloud 提供的一套分布式链路追踪系统。它可以直观地展示出一次请求的调用过程。
- trace:从请求到达系统开始,到给请求做出响应,这样一个过程成为 trace
- span:每次调用服务时,埋入的一个调用记录,成为 span
- annotation:相当于 span 的语法,描述 span 所处的状态
创建Maven项目,引入如下依赖:
org.springframework.boot spring-boot-starter-parent 2.3.1.RELEASE 4.0.0 top.javahai sleuth org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-starter-sleuth 2.2.8.RELEASE
创建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 都是一致的,这
个和普通的调用不一样。
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服务器已成功启动。
接下来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
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/zipkin3.2 测试使用
创建Maven项目,添加如下依赖:
org.springframework.boot spring-boot-starter-parent 2.6.3 4.0.0 top.javahai zipkin 1.8 2021.0.0 org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-starter-sleuth org.springframework.cloud spring-cloud-stream org.springframework.cloud spring-cloud-stream-binder-rabbit org.springframework.cloud spring-cloud-sleuth-zipkin org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} pom import
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接口
打开zipkin查看这条链路调用,我们可以通过zipkin直观地看到请求调用链路和通过每个服务的耗时等信息。



