分布式追踪:Spring Cloud Sleuth 与 Zipkin 实践 —— 像侦探一样追踪你的微服务
大家好,我是你们的老朋友,一个在代码世界里摸爬滚打多年的老兵。今天,咱们来聊聊一个在微服务架构中至关重要,却常常被忽略的话题:分布式追踪。
想象一下,你正在侦破一个复杂的案件,线索分散在各个角落,而你必须把它们拼凑起来才能找到真相。微服务架构就像这个案件,不同的服务就像不同的嫌疑人,请求就像线索,而分布式追踪就像你手中的放大镜和笔记本,帮你理清头绪,找到性能瓶颈,揪出幕后黑手。
如果没有分布式追踪,你的微服务就像一群无头苍蝇,嗡嗡嗡地飞来飞去,出了问题你却不知道从何查起。所以,今天我们就来学习如何使用 Spring Cloud Sleuth 和 Zipkin 这两个利器,让你的微服务变得透明,让问题无处遁形。
1. 为什么要用分布式追踪?
在单体应用时代,我们debug就像在自己家后院散步,随便打个断点,就能看到整个调用链。但是到了微服务时代,一个请求可能要经过十几个甚至几十个服务,每个服务都可能由不同的团队维护,使用不同的技术栈。这时候,如果某个请求出了问题,你可能需要:
- 登录所有服务器,查看日志,大海捞针。
- 询问所有相关团队,互相甩锅,扯皮半天。
- 祈祷问题能够自动消失,听天由命。
这种场景想想都让人头皮发麻。分布式追踪就是来解决这个问题的,它可以:
- 监控服务调用链: 让你知道一个请求经过了哪些服务,每个服务花费了多少时间。
- 定位性能瓶颈: 找出哪个服务是瓶颈,哪个服务拖慢了整体速度。
- 分析错误原因: 快速定位错误发生的位置,减少排查时间。
- 优化系统性能: 通过分析追踪数据,找到可以优化的地方,提升系统性能。
- 建立服务依赖关系: 了解服务之间的依赖关系,方便进行容量规划和故障隔离。
简单来说,分布式追踪就是给你的微服务装上了一个“摄像头”,让你能够随时监控它们的一举一动,出了问题也能第一时间发现并解决。
2. Spring Cloud Sleuth:追踪的“传感器”
Spring Cloud Sleuth就像一个精密的传感器,它能够自动地为你的 Spring Boot 应用添加追踪信息。它主要做了以下几件事:
- 生成 Trace ID: 每个请求都会被分配一个唯一的 Trace ID,用来标识整个调用链。
- 生成 Span ID: 每个服务调用都会被分配一个 Span ID,用来标识该服务在调用链中的位置。
- 注入追踪信息: 将 Trace ID 和 Span ID 注入到 HTTP Header 中,传递给下游服务。
- 收集追踪数据: 将追踪数据发送给追踪系统(比如 Zipkin)。
使用 Sleuth 非常简单,只需要在你的 Spring Boot 项目中添加以下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
添加依赖后,Sleuth 就会自动生效,不需要做任何额外的配置。它会自动为你的应用添加一个 TraceFilter
,拦截所有的 HTTP 请求,并生成 Trace ID 和 Span ID。
示例:一个简单的 REST API
假设我们有一个简单的 REST API,提供一个 /hello
接口:
@RestController
public class HelloController {
private static final Logger logger = LoggerFactory.getLogger(HelloController.class);
@GetMapping("/hello")
public String hello() {
logger.info("Received request to /hello");
return "Hello, world!";
}
}
启动这个应用后,访问 /hello
接口,你会在日志中看到类似这样的信息:
2023-10-27 10:00:00.000 INFO [your-application,e4a2b3c1d5f6g7h8,9c8b7a6f5e4d3c2b,false] 12345 --- [nio-8080-exec-1] com.example.HelloController : Received request to /hello
your-application
是你的应用名称。e4a2b3c1d5f6g7h8
是 Trace ID。9c8b7a6f5e4d3c2b
是 Span ID。false
表示是否是抽样出来的,如果是true,表明是被抽样的。抽样是为了减少数据量,避免对性能产生影响。
3. Zipkin:追踪数据的“存储和展示中心”
有了 Sleuth 这个“传感器”,我们还需要一个“存储和展示中心”来收集和分析追踪数据。Zipkin 就是这样一个工具,它可以:
- 收集追踪数据: 接收来自各个服务的追踪数据。
- 存储追踪数据: 将追踪数据存储在数据库中(比如 MySQL、Cassandra)。
- 展示追踪数据: 提供一个 Web 界面,让你能够方便地查看和分析追踪数据。
你可以使用 Docker 来快速启动 Zipkin:
docker run -d -p 9411:9411 openzipkin/zipkin
启动后,访问 http://localhost:9411
,你就可以看到 Zipkin 的 Web 界面了。
4. 将 Sleuth 和 Zipkin 连接起来
现在,我们需要将 Sleuth 和 Zipkin 连接起来,让 Sleuth 将追踪数据发送给 Zipkin。只需要在你的 application.properties
或 application.yml
文件中添加以下配置:
spring.zipkin.baseUrl=http://localhost:9411
spring.sleuth.sampler.probability=1.0
spring.zipkin.baseUrl
指定 Zipkin 的地址。spring.sleuth.sampler.probability
指定采样率。设置为 1.0 表示所有请求都进行追踪。在生产环境中,通常会将采样率设置为一个较小的数值(比如 0.1),以减少对性能的影响。
重启你的应用后,再次访问 /hello
接口,你就可以在 Zipkin 的 Web 界面中看到追踪数据了。
5. 跨服务追踪:让追踪信息流动起来
上面的例子只是一个简单的单服务应用。在微服务架构中,一个请求通常会经过多个服务。为了能够追踪整个调用链,我们需要让追踪信息在服务之间流动起来。
Sleuth 会自动将 Trace ID 和 Span ID 注入到 HTTP Header 中,传递给下游服务。只要下游服务也使用了 Sleuth,它就会自动读取这些信息,并生成新的 Span ID,将自己加入到调用链中。
示例:服务 A 调用服务 B
假设我们有两个服务:服务 A 和服务 B。服务 A 调用服务 B 的 /hello
接口。
服务 A (application.properties):
spring.application.name=service-a
spring.zipkin.baseUrl=http://localhost:9411
spring.sleuth.sampler.probability=1.0
server.port=8081
服务 B (application.properties):
spring.application.name=service-b
spring.zipkin.baseUrl=http://localhost:9411
spring.sleuth.sampler.probability=1.0
server.port=8082
服务 A 的代码:
@RestController
public class ServiceAController {
private static final Logger logger = LoggerFactory.getLogger(ServiceAController.class);
@Autowired
private RestTemplate restTemplate;
@GetMapping("/hello")
public String hello() {
logger.info("Received request to /hello in service A");
String response = restTemplate.getForObject("http://localhost:8082/hello", String.class);
logger.info("Received response from service B: {}", response);
return "Hello from service A! " + response;
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
服务 B 的代码:
@RestController
public class ServiceBController {
private static final Logger logger = LoggerFactory.getLogger(ServiceBController.class);
@GetMapping("/hello")
public String hello() {
logger.info("Received request to /hello in service B");
return "Hello from service B!";
}
}
启动服务 A 和服务 B 后,访问 http://localhost:8081/hello
,你会在 Zipkin 的 Web 界面中看到一个包含两个 Span 的调用链:
- 一个 Span 代表服务 A 的
/hello
接口。 - 另一个 Span 代表服务 B 的
/hello
接口。
通过这个调用链,你可以清楚地看到请求从服务 A 流向服务 B 的过程,以及每个服务花费的时间。
6. 自定义 Span:精细化追踪
有时候,你可能需要追踪一些非 HTTP 请求的操作,比如数据库查询、消息队列发送等。这时候,你可以使用 Sleuth 提供的 API 来创建自定义 Span。
@Autowired
private Tracer tracer;
public void doSomething() {
Span newSpan = tracer.nextSpan().name("doSomething");
try (Tracer.SpanInScope ws = tracer.withSpan(newSpan.start())) {
// Your code here
logger.info("Doing something important...");
} finally {
newSpan.end();
}
}
tracer.nextSpan().name("doSomething")
创建一个新的 Span,并设置 Span 的名称为 "doSomething"。tracer.withSpan(newSpan.start())
将新的 Span 设置为当前线程的活动 Span。newSpan.end()
结束 Span。
在 try-with-resources
语句块中,你可以执行任何你想要追踪的操作。Sleuth 会自动将这些操作记录到 Span 中,并发送给 Zipkin。
示例:追踪数据库查询
@Autowired
private Tracer tracer;
@Autowired
private JdbcTemplate jdbcTemplate;
public List<User> getUsers() {
Span newSpan = tracer.nextSpan().name("getUsersFromDatabase");
try (Tracer.SpanInScope ws = tracer.withSpan(newSpan.start())) {
logger.info("Querying database for users...");
List<User> users = jdbcTemplate.query("SELECT * FROM users", new BeanPropertyRowMapper<>(User.class));
return users;
} finally {
newSpan.end();
}
}
7. Baggage:传递上下文信息
有时候,你需要在服务之间传递一些上下文信息,比如用户 ID、请求 ID 等。Sleuth 提供了 Baggage 功能,让你能够方便地传递这些信息。
@Autowired
private BaggageField userIdField;
public void doSomething(String userId) {
userIdField.updateValue(userId);
// ...
}
public String getUserId() {
return userIdField.getValue();
}
首先,你需要定义一个 BaggageField
:
@Configuration
public class BaggageConfig {
@Bean
public BaggageField userIdField() {
return BaggageField.create("user-id");
}
}
然后,你就可以使用 BaggageField
来设置和获取 Baggage 的值。Baggage 的值会自动传递给下游服务。
8. 注意事项和最佳实践
- 采样率: 在生产环境中,不要将采样率设置为 1.0,否则会对性能产生影响。建议根据实际情况设置一个合适的采样率。
- 数据存储: Zipkin 默认使用内存存储数据,不适合生产环境。建议使用 MySQL、Cassandra 等数据库来存储数据。
- 数据清理: 定期清理 Zipkin 中的数据,避免数据量过大。
- 日志格式: 统一日志格式,方便分析日志。
- 监控报警: 设置监控报警,及时发现问题。
- 安全性: 确保 Zipkin 的安全性,避免未经授权的访问。
9. 总结
分布式追踪是微服务架构中不可或缺的一部分。Spring Cloud Sleuth 和 Zipkin 是两个强大的工具,可以帮助你轻松地实现分布式追踪。通过使用 Sleuth 和 Zipkin,你可以:
- 监控服务调用链,定位性能瓶颈,分析错误原因。
- 优化系统性能,建立服务依赖关系,方便进行容量规划和故障隔离。
- 让你的微服务变得透明,让问题无处遁形。
希望这篇文章能够帮助你更好地理解和使用 Spring Cloud Sleuth 和 Zipkin。记住,优秀的程序员就像优秀的侦探,总是能够找到问题的根源,解决问题,让世界变得更加美好。
祝你编码愉快!