一、前言
我们在使用微服务的时候,往往涉及到各个微服务之间的调用,肯定会存在深度的调用链路,如果出现BUG或者异常,就会让问题定位和处理效率非常低。有了Sleuth ,就可以帮助我们记录、跟踪应用程序中的请求和操作。通常与 Zipkin 配合使用,从而提供更全面的可视化应用程序跟踪和分析功能。
就像ElasticSearch和Kibana一样!
复杂的链路调用如下图所示:
在继续往下看的同时,需要你具备Springboot整合Nacos构建一个聚合项目的能力。
当然如果不想自己来,小编也给大家准备好了。大家可以下载运行一下,开始下面的实战!
防止Github访问不了,这里把代码提交到了Gitee。
cloud-sleuth-zipkin-demo代码下载地址:https://gitee.com/wang-zhenjun/cloud-sleuth-zipkin-demo
二、Spring Cloud Sleuth 介绍
1、简介
Spring Cloud Sleuth 是 Spring Cloud 生态系统的一部分,它是一个分布式追踪解决方案,用于监视微服务架构中的请求流程,并帮助开发者跟踪请求在不同微服务之间的传播路径。
Sleuth主要用于解决微服务架构中的分布式系统跟踪和调试问题。
官网文档:https://docs.spring.io/spring-cloud-sleuth/docs/2.2.8.RELEASE/reference/html/。
2、核心概念
我们可以看一下官网的图片:
简单名词介绍:
❝
cs:「客户端发送」,客户已提出请求。该注释指示跨度的开始。sr:「服务器已接收」,服务器端收到请求并开始处理。从此时间戳中减去cs时间戳即可得出网络延迟。ss:「服务器发送」,在请求处理完成时(当响应发送回客户端时)进行注释。从这个时间戳中减去sr时间戳就可以得出服务器端处理请求所需的时间。cr:「客户端已收到」,表示跨度的结束。客户端已成功收到服务器端的响应。从此时间戳中减去cs时间戳即可得出客户端从服务器接收响应所需的整个时间。
❞
详细信息可以看官网介绍,总结一下:
名词 | 翻译 | 解释 |
Trace | 追踪 | Trace 是一个请求的整体追踪。它代表了从请求的起始点到结束点的完整路径,经过多个微服务。每个 Trace 都有一个唯一的 Trace ID。 |
Span | 跨度 | Span 是 Trace 中的一个小段,它代表了请求在某个特定微服务上的处理过程。Spans 之间有父子关系,它们可以形成一个层次结构,以表示请求的处理路径。 |
Trace ID | 追踪标识 | Trace ID 是唯一标识一个 Trace 的标识符。它在整个 Trace 中保持不变,用于将不同的 Span 关联到同一个 Trace 上。 |
Span ID | 跨度标识 | Span ID 是唯一标识一个 Span 的标识符。它用于在不同 Span 之间建立父子关系。 |
Parent Span ID | 父 Span 标识 | 父 Span ID 是标识一个 Span 的父 Span 的标识符。它用于建立 Span 之间的关系。 |
Annotations | 注解 | Annotations 是关于 Span 的额外信息,通常用于记录 Span 的开始和结束时间、操作名称、以及其他相关信息。Annotations 可以帮助你更好地理解请求的处理过程。 |
Binary Annotations | 二进制注解 | Binary Annotations 是键值对形式的信息,用于记录与 Span 相关的自定义信息,例如请求的状态、错误信息等。 |
Collector | 收集器 | Collector 是用于收集追踪信息的组件,它将追踪数据发送到后端存储或可视化工具(如Zipkin或Jaeger)。Collector 可以将 Span 数据持久化,以供分析和监视使用。 |
Sampler | 采样器 | Sampler 用于确定是否对一个请求进行追踪。它决定是否为请求创建一个 Trace。Sampler 可以根据策略决定是否记录某个请求的 Trace 数据,以避免记录过多的追踪信息,从而降低性能开销。 |
「官网的图,每一个代表一个组件,他们之间进行调用,画的少了Trace Id,加上就好了。」
每个组件都会生成一个 Trace Id(全局唯一),还会有 Span Id、Parent Id 三部分组成。链路上的所有组件组成一个完整的 Trace。
「注意:」
「头链路Parent Id = null,其余的都指向上一个组件的Span Id,从而形成链路。」
「一次链路调用所有的组件Trace Id都是一样的。」
「这里说的组件就是一个个的微服务!」
三、 Zipkin介绍和搭建
1、定义
Zipkin 是一个分布式追踪系统。它有助于收集解决服务架构中的延迟问题所需的计时数据。功能包括该数据的收集和查找。
Zipkin官网地址:https://zipkin.io/。
2、核心概念
名词 | 翻译 | 解释 |
Trace | 追踪 | Trace 代表整个请求的追踪路径,跨越不同的服务。 |
Span | 跨度 | Span 是基本工作单位,代表了请求在单个服务中的处理过程。 |
Trace ID | 追踪标识 | Trace ID 是唯一标识一个 Trace 的标识符,用于将不同的 Span 关联到同一个 Trace 上。 |
Annotations | 注解 | Annotations 用于记录 Span 的关键事件,通常包括开始和结束时间、操作名称等。 |
Binary Annotations | 二进制注解 | Binary Annotations 用于记录额外的自定义信息,例如请求状态、错误信息等。 |
Collector | 收集器 | Collector 负责接收和存储从不同服务发送的 Span 数据,以便后续的检查和分析。 |
Query and Visualization | 查询和可视化 | 提供了查询和可视化界面,允许用户查看和分析跟踪数据,以帮助故障排查和性能优化。 |
尽管Sleuth 和 Zipkin有些术语和概念中有相似之处,但它们是两个不同的工具,各自有自己的实现和用途。
「Spring Cloud Sleuth 用于生成和传播跟踪信息,而 Zipkin 用于收集、存储、查询和可视化这些信息。它们可以协同工作,但也可以独立使用。」
3、docker搭建
官方有三种方式搭建,推荐使用:「如果您熟悉 Docker,这是首选的启动方法。」
Docker Zipkin项目能够构建 docker 镜像、提供脚本和docker-compose.yml 用于启动预构建镜像的脚本。
https://github.com/openzipkin/docker-zipkin/blob/master/docker-compose.yml。
最快的启动方式是直接运行最新的镜像:
docker run -d -p 9411:9411 openzipkin/zipkin
我们启动成功,在Windows下访问看是否成功!
http://192.168.239.130:9411/zipkin/
「注意:」
「Zipkin默认将追踪数据信息保存到内存,重启服务后追踪数据丢失,Zipkin支持将追踪数据持久化到MySQL或ES。」
可以直接使用docker-componse运行:
docker-componse运行脚本:https://github.com/openzipkin/zipkin/tree/master/docker/examples
可以自行试一下,这里就不带大家演示了!
四、Springboot整合
今天我们来进行简单的链路模拟:
「service-order模块调用service-stock模块调用service-message模块」
「通信我们使用openFeign来进行调用,三个模块统一使用nacos进行注册」
大家可以下载一下项目体验一下,可以自己搭建,就是一个聚合项目!
结构如下:
1、导入依赖
这是父依赖。
<properties>
<spring.boot.version>2.7.3</spring.boot.version>
<spring.cloud.dependencies.version>2021.0.1</spring.cloud.dependencies.version>
<spring.cloud.alibaba.version>2021.0.1.0</spring.cloud.alibaba.version>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<org.projectlombok.lombok>1.18.26</org.projectlombok.lombok>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.dependencies.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.lombok}</version>
</dependency>
</dependencies>
</dependencyManagement>
spring-cloud-dependencies里包含了sleuth、zipkin的依赖,父不需要再定义管理版本。
子依赖要比父依赖多了sleuth、zipkin两个,还有openFeign的包!
<!-- Sleuth依赖项 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<!--Zipkin 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</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-loadbalancer</artifactId>
</dependency>
2、yml配置
三份自己改一下端口和应用名称:service-order、service-stock、service-message;端口分别为:9000、9001、9002
server:
port: 9000
spring:
application:
# 应用名称
name: service-order
cloud:
nacos:
discovery:
# 服务注册地址
server-addr: localhost:8848
zipkin:
base-url: http://192.168.239.130:9411
sender:
type: web # 设置使用 http 的方式传输数据
3、详细代码
记得在启动类上添加注解:@EnableFeignClients,表示开启feign调用。
完整的结构如下:
下面把具体代码给大家:OrderController :
/**
* @author wangzhenjun
* @date 2023/10/31 14:25
*/
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/order")
@RestController
public class OrderController {
private final RemoteStockFeignService remoteStockFeignService;
/**
* 模拟下单流程
* @param userId
* @param productId
* @return
*/
@GetMapping("/createOrder")
public String createOrder(@RequestParam("userId") Integer userId, @RequestParam("productId") Integer productId) {
log.info("====>订单模块<========");
// 调用库存服务进行库存扣减
String stockResult = remoteStockFeignService.subtractStock(userId,productId,1);
log.info("扣减库存结果:{}", stockResult);
// 还有其他。。。。。
return "下单成功!";
}
}
RemoteStockFeignService :
/**
* @author wangzhenjun
* @date 2023/10/31 14:29
*/
@FeignClient(value = "service-stock")
public interface RemoteStockFeignService {
@GetMapping(value = "/stock/subtractStock")
String subtractStock(@RequestParam(value = "userId") Integer userId,@RequestParam(value = "productId") Integer productId,@RequestParam(value = "num") Integer num);
}
StockController :
/**
* @author wangzhenjun
* @date 2023/10/31 14:40
*/
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/stock")
@RestController
public class StockController {
private final RemoteMessageFeignService remoteMessageFeignService;
@GetMapping(value = "/subtractStock")
public String subtractStock(@RequestParam(value = "userId") Integer userId,@RequestParam(value = "productId") Integer productId, @RequestParam(value = "num") Integer num) {
log.info("====>库存模块<========");
if (productId < 1) {
throw new RuntimeException("商品不存在,请重新请求!");
}
// 调用短信模块给用户发下单成功短信
String messageResult = remoteMessageFeignService.sendMessage(userId);
log.info("发送短信结果:{}", messageResult);
return "扣减库存成功!";
}
}
RemoteMessageFeignService:
/**
* @author wangzhenjun
* @date 2023/10/31 14:29
*/
@FeignClient(value = "service-message")
public interface RemoteMessageFeignService {
@GetMapping(value = "/message/sendMessage/{userId}")
String sendMessage(@PathVariable(value = "userId") Integer userId);
}
MessageController :
/**
* @author wangzhenjun
* @date 2023/10/31 14:40
*/
@Slf4j
@RequestMapping("/message")
@RestController
public class MessageController {
@GetMapping(value = "/sendMessage/{userId}")
public String sendMessage(@PathVariable(value = "userId") Integer userId) {
log.info("====>短信模块<========");
if (userId < 1 || userId > 999999) {
throw new RuntimeException("用户不存在,请重新请求!");
}
return "发送短信成功!";
}
}
4、启动nacos
Windows下启动nacos,找到nacos下的bin目录执行命令:
startup.cmd -m standalone
找到地址,访问。用户名密码都是nacos。可以在配置文件中修改!
5、注册成功
我们把三个模块进行启动!nacos上已经可以看到我们的服务注册上了,可以通过服务名进行调用了!
五、调试
我们以订单模块为入口进行链路调用:
http://localhost:9000/order/createOrder?userId=2&productId=89。
1、查看日志
我们看一下订单模块的日志。
可以总结出Sleuth 日志格式:
[service-order,e36ebe859a7473e7,e36ebe859a7473e7]
[服务名称,Trace ID,Span ID]
在最开始的链路上,Trace ID 和 Span ID 的值通常是相同的,这是因为它们都代表了整个请求的追踪。
「一条链路使用一个相同的Trace ID。」
在日志没有体现出Parent Span ID,不过不应该,我们可以通过Zipkin来看链路!
2、Zipkin查看
前面我们已经进入了Zipkin页面了,只需要刷新一下就可以看到每次链路的记录了!
点击SHOW按钮,可以看到详细链路信息:
耗时,深度,Trance ID 还是挺好的。
3、模拟异常
我们来把商品修改一下:
http://localhost:9000/order/createOrder?userId=2&productId=-89。
此时是库存服务出现的问题,就不会展示下一个消息模块,自然而然的找到了出现问题的链路和根源!
在模拟一个三级错误的,就会看到链路的最后一级!
下面还可以查询依赖关系:
点击节点,可以查看汇总,调用次数和失败次数的统计分析!
六、总结
分布式链路追踪已经成为现代微服务架构中不可或缺的工具之一。
通过它,我们可以清晰地跟踪请求的调用路径,了解系统的性能,诊断潜在问题,并不断优化我们的应用程序。
Spring Cloud Sleuth让我们轻松生成和传播跟踪信息,使我们的微服务能够协同工作,无缝地捕捉每个请求的处理路径。
Zipkin作为一个流行的分布式追踪系统,为我们提供了可视化界面,使我们能够以图形化的方式查看和分析跟踪数据。
「当然简单系统上这个大材小用,但是我们可以在项目中试试,加了也不会影响程序的正常运行,做一个简单的知识储备!」