我们在使用微服务的时候,往往涉及到各个微服务之间的调用,肯定会存在深度的调用链路,如果出现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 是 Spring Cloud 生态系统的一部分,它是一个分布式追踪解决方案,用于监视微服务架构中的请求流程,并帮助开发者跟踪请求在不同微服务之间的传播路径。
Sleuth主要用于解决微服务架构中的分布式系统跟踪和调试问题。
官网文档:https://docs.spring.io/spring-cloud-sleuth/docs/2.2.8.RELEASE/reference/html/。
我们可以看一下官网的图片:
简单名词介绍:
❝
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 是一个分布式追踪系统。它有助于收集解决服务架构中的延迟问题所需的计时数据。功能包括该数据的收集和查找。
Zipkin官网地址:https://zipkin.io/。
名词 | 翻译 | 解释 |
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 用于收集、存储、查询和可视化这些信息。它们可以协同工作,但也可以独立使用。」
官方有三种方式搭建,推荐使用:「如果您熟悉 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
可以自行试一下,这里就不带大家演示了!
今天我们来进行简单的链路模拟:
「service-order模块调用service-stock模块调用service-message模块」
「通信我们使用openFeign来进行调用,三个模块统一使用nacos进行注册」
大家可以下载一下项目体验一下,可以自己搭建,就是一个聚合项目!
结构如下:
这是父依赖。
<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>
三份自己改一下端口和应用名称:service-order、service-stock、service-message;端口分别为:9000、9001、9002
server: port: 9000spring: application: # 应用名称 name: service-order cloud: nacos: discovery: # 服务注册地址 server-addr: localhost:8848 zipkin: base-url: http://192.168.239.130:9411 sender: type: web # 设置使用 http 的方式传输数据
记得在启动类上添加注解:@EnableFeignClients,表示开启feign调用。
完整的结构如下:
下面把具体代码给大家:OrderController :
/** * @author wangzhenjun * @date 2023/10/31 14:25 */@Slf4j@RequiredArgsConstructor@RequestMapping("/order")@RestControllerpublic 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")@RestControllerpublic 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")@RestControllerpublic 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 "发送短信成功!"; }}
Windows下启动nacos,找到nacos下的bin目录执行命令:
startup.cmd -m standalone
找到地址,访问。用户名密码都是nacos。可以在配置文件中修改!
我们把三个模块进行启动!nacos上已经可以看到我们的服务注册上了,可以通过服务名进行调用了!
我们以订单模块为入口进行链路调用:
http://localhost:9000/order/createOrder?userId=2&productId=89。
我们看一下订单模块的日志。
可以总结出Sleuth 日志格式:
[service-order,e36ebe859a7473e7,e36ebe859a7473e7]
[服务名称,Trace ID,Span ID]
在最开始的链路上,Trace ID 和 Span ID 的值通常是相同的,这是因为它们都代表了整个请求的追踪。
「一条链路使用一个相同的Trace ID。」
在日志没有体现出Parent Span ID,不过不应该,我们可以通过Zipkin来看链路!
前面我们已经进入了Zipkin页面了,只需要刷新一下就可以看到每次链路的记录了!
点击SHOW按钮,可以看到详细链路信息:
耗时,深度,Trance ID 还是挺好的。
我们来把商品修改一下:
http://localhost:9000/order/createOrder?userId=2&productId=-89。
此时是库存服务出现的问题,就不会展示下一个消息模块,自然而然的找到了出现问题的链路和根源!
在模拟一个三级错误的,就会看到链路的最后一级!
下面还可以查询依赖关系:
点击节点,可以查看汇总,调用次数和失败次数的统计分析!
分布式链路追踪已经成为现代微服务架构中不可或缺的工具之一。
通过它,我们可以清晰地跟踪请求的调用路径,了解系统的性能,诊断潜在问题,并不断优化我们的应用程序。
Spring Cloud Sleuth让我们轻松生成和传播跟踪信息,使我们的微服务能够协同工作,无缝地捕捉每个请求的处理路径。
Zipkin作为一个流行的分布式追踪系统,为我们提供了可视化界面,使我们能够以图形化的方式查看和分析跟踪数据。
「当然简单系统上这个大材小用,但是我们可以在项目中试试,加了也不会影响程序的正常运行,做一个简单的知识储备!」
本文链接:http://www.28at.com/showinfo-26-32008-0.html分布式进阶-链路追踪SpringCloudSleuth、Zipkin【实战篇】
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: 一文搞懂微服务架构演进
下一篇: Go 内存分配:结构体中的优化技巧