在前面几篇文章中,我们主要介绍了基于 Sentinel 如何对微服务架构提供限流、熔断保护。从本篇开始,我们继续完善微服务架构,通过介绍链路跟踪原理和基于SpringCloud Sleuth实现链路跟踪。
一、微服务链路跟踪原理
我们先看一个图,大家都知道在微服务架构下,系统的功能是由大量的微服务协调组成的,例如:电商创建订单业务就需要订单服务、库存服务、支付服务、短信通知服务逐级调用才能完成。而每个服务可能是由不同的团队进行开发,部署在成百上千台服务器上。
如此复杂的消息传递过程,当系统发生故障的时候,就需要一种机制对故障点进行快速定位,确认是哪个服务出了问题,链路追踪技术由此而生。
**所谓的链路追踪,就是运行时通过某种方式记录下服务之间的调用过程,在通过可视化的 UI 界面帮研发运维人员快速定位到出错点。**引入链路追踪,是微服务架构运维的底层基础,没有它,运维人员就像盲人摸象一样,根本无法了解服务间通信过程。
在 Spring Cloud 标准生态下内置了 Sleuth 这个组件,它通过扩展 Logging 日志的方式实现微服务的链路追踪。说起来比较晦涩,咱们看一个实例就明白了,在标准的微服务下日志产生的格式是:
2024-03-21 17:00:33.441 INFO [nio-7000-exec-2] c.netflix.config.ChainedDynamicProperty : Flipping property: b-service.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
但是当引入 Spring Cloud Sleuth 链路追踪组件后就会变成下面的格式:
2024-03-21 17:00:33.441 INFO [a-service,5f70945e0eefa832,5f70945e0eefa832,true] 18404 --- [nio-7000-exec-2] c.netflix.config.ChainedDynamicProperty : Flipping property: b-service.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647//比较后会发现,在原有日志中额外附加了下面的文本[a-service,5f70945e0eefa832,5f70945e0eefa832,true]
这段文本就是 Sleuth 在微服务日志中附加的链路调用数据,它的格式是固定的,包含以下四部分:
[微服务 Id,TraceId,SpanId,isExport]
我们来介绍一下这四部分的含义:
-
微服务ID:指明日志是由哪个微服务产生的。
-
TraceId:轨迹编号。一次完整的业务处理过程被称为轨迹,例如:实现登录功能需要从服务 A 调用服务 B,服务B再调用服务 C,那这一次登录处理的过程就是一个轨迹,从前端应用发来请求到接收到响应,每一次完整的业务功能处理过程都对应唯一的 TraceId。
-
SpanId:步骤编号。刚才要实现登录功能需要从服务 A 到服务 C 涉及 3 个微服务处理,按处理前后顺序,每一个微服务处理时日志都被赋予不同的 SpanId。一个 TraceId 拥有多个 SpanId,而 SpanId 只能隶属于某一个 TraceId。
-
导出标识:当前这个日志是否被导出,该值为 true 的时候说明当前轨迹数据允许被其他链路追踪可视化服务收集展现。
知道含义之后,我们来模拟一下上述业务逻辑执行的数据跟踪实例:
服务 A -> 服务 B -> 服务 C的调用链路,下面是分别产生的日志
#服务 A 应用控制台日志2024-03-21 22:16:54.394 DEBUG [a-service,e8ca7047a782568b,e8ca7047a782568b,true] 21320 --- [nio-7000-exec-1] org.apache.tomcat.util.http.Parameters : ...#服务 B 应用控制台日志2024-03-21 22:16:54.402 DEBUG [b-service,e8ca7047a782568b,b6aa80fb33e71de6,true] 21968 --- [nio-8000-exec-2] org.apache.tomcat.util.http.Parameters : ...#服务 C 应用控制台日志2024-03-21 22:16:54.405 DEBUG [c-service,e8ca7047a782568b,537098c59827a242,true] 17184 --- [nio-9000-exec-2] org.apache.tomcat.util.http.Parameters : ...
可以发现,在 DEBUG 级别下链路追踪数据被打印出来,按调用时间先后顺序分别是 A 到 C 依次出现。因为是一次完整业务处理,TraceId 都是相同的,SpanId 却各不相同,这些日志都已经被 Sleuth 导出,可以被 ZipKin 收集展示(ZipKin我们后面会聊到,感兴趣的记得关注哦)。
下面咱们通过实例讲解如何在微服务架构中进行链路追踪。
二、微服务整合Sleuth
1、创建微服务
创建 a-service、b-service、c-service 三个 Spring Boot 工程,pom.xml 依赖如下:
<!--Spring Web应用 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--Nacos 客户端 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!--服务间通信组件OpenFeign --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId><version>2.2.6.RELEASE</version></dependency>c-service的pom.xml依赖如下:<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency>
因为调用关系是服务 A 调用服务 B,服务 B 调用服务 C,所以在 A、B 两个服务中需要额外依赖 OpenFeign 实现服务间通信。
2、配置微服务的application.yml文件
server:port: 7000 #a:7000/b:8000/c:9000 spring:cloud:nacos:discovery:server-addr: 106.14.221.171:8848username: nacospassword: nacosapplication:name: a-service #a-service/b-service/c-servicelogging:level:root: debug #为演示需要,开启debug级别日志
3、模拟业务逻辑
这里贴出来三个微服务的业务逻辑:
- c-service:
SampleController,methodC方法产生响应字符串“-> Service C”,方法映射地址“/c”@RestControllerpublic class SampleController {@GetMapping("/c")public String methodC(){String result = " -> Service C";return result;}}
- b-service:
CServiceFeignClient 通过 OpenFeign 实现了 C 服务的通信客户端,方法名为 methodC。
@FeignClient("c-service")public interface CServiceFeignClient {@GetMapping("/c")public String methodC();}
SampleController 通过 methodB 方法调用 methodC 的同时为响应附加的字符串“-> Service B”,方法映射地址“/b”。
@Controllerpublic class SampleController {@Resourceprivate CServiceFeignClient cService;@GetMapping("/b")@ResponseBodypublic String methodB(){String result = cService.methodC();result = " -> Service B" + result;return result;}}
- a-service:
BServiceFeignClient通过OpenFeign实现了B服务的通信客户端,方法名为methodB@FeignClient("b-service")public interface BServiceFeignClient {@GetMapping("/b")public String methodB();}
SampleController 通过 methodA 方法调用 methodB 的同时,成为响应附加的字符串“-> Service A”,方法映射地址“/a”。
@RestControllerpublic class SampleController {@Resourceprivate BServiceFeignClient bService;@GetMapping("/a")public String methodA(){String result = bService.methodB();result = "-> Service A" + result;return result;}}
这样一个完整的调用链路已形成。在 3 个服务实例启动后,访问 A 实例
http://localhost:7000/a 得到运行结果-> ServiceA -> Service B -> Service C
4、引入Sleuth
可以看到 ABC 三个服务按前后顺序依次产生结果,但目前在日志中并没有包含任何链路追踪数据,那如何引入 Sleuth 呢?
只需要打开三个服务工程的 pom.xml 文件分别引入 spring-cloud-starter-sleuth 依赖。
<!--添加Sleuth依赖 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-sleuth</artifactId><version>2.2.6.RELEASE</version></dependency>
加入依赖后,重启服务,无须做任何额外设置,Spring Cloud Sleuth 便自动为日志增加了链路追踪数据
#服务 A 应用控制台日志,已附加链路追踪数据2024-03-21 22:16:54.394 DEBUG [a-service,e8ca7047a782568b,e8ca7047a782568b,true] 21320 --- [nio-7000-exec-1] org.apache.tomcat.util.http.Parameters : ...#服务 B 应用控制台日志2024-03-21 22:16:54.402 DEBUG [b-service,e8ca7047a782568b,b6aa80fb33e71de6,true] 21968 --- [nio-8000-exec-2] org.apache.tomcat.util.http.Parameters : ...#服务 C 应用控制台日志2024-03-21 22:16:54.405 DEBUG [c-service,e8ca7047a782568b,537098c59827a242,true] 17184 --- [nio-9000-exec-2] org.apache.tomcat.util.http.Parameters : ...
虽然数据已产生,但如果在生产环境靠人工组织数以万计的链路日志显然不现实,我们还需要部署**链路追踪数据的分析工具 ZipKin **来简化这个过程。
ZipKin部署及使用,我们下篇文章再接着聊。
文章将持续更新,欢迎关注公众号:服务端技术精选。欢迎点赞、关注、转发。