第9天:分布式链路追踪 - 微服务里的"侦探",请求去哪儿了一清二楚🔍
一、先白话白话为啥需要链路追踪
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
场景:用户下单慢,该找谁?
用户反馈:"我下单等了10秒才成功!"
没有链路追踪时:
- 开发A:"我订单服务没问题啊!"
- 开发B:"我商品服务响应很快!"
- 开发C:"我库存服务1秒就返回了!"
- 运维:"网关看着也正常啊..."
- 结果:互相甩锅,问题定位像无头苍蝇
有链路追踪时:
- 看链路图:
网关(1s) -> 订单服务(6s) -> 商品服务(0.5s) -> 库存服务(0.5s) -> 用户服务(2s) - 一眼看出:订单服务花了6秒,卡在数据库查询
- 精准定位:优化订单服务的SQL语句
- 解决问题:响应时间降到2秒
二、链路追踪是啥?
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
就像快递追踪系统:
- 每个包裹有个唯一单号(Trace ID)
- 经过每个站点都扫码记录(Span)
- 你能看到包裹的完整路线(Trace)
- 知道在哪个站点停留多久(耗时)
核心概念:
- Trace:一次完整的请求链路(比如一次下单)
- Span:链路中的每个环节(比如调用订单服务)
- Trace ID:整个链路的唯一ID
- Span ID:每个环节的唯一ID
- Parent Span ID:父环节ID,形成调用树
三、Sleuth + Zipkin 黄金搭档
1. Sleuth(侦探)
- Spring Cloud的"侦探"
- 自动给请求打标签(Trace ID、Span ID)
- 把追踪信息传递给下游服务
- 不存储数据,只收集和传递
2. Zipkin(档案馆)
- 存储和展示追踪数据
- 漂亮的Web界面
- 支持多种存储:内存、MySQL、Elasticsearch
- 分析工具,看链路、查问题
工作流程:
rust
用户请求 -> Sleuth生成Trace ID -> 经过各个服务 ->
每个服务Sleuth记录Span -> 数据发送到Zipkin ->
Zipkin存储展示 -> 我们在界面查看
四、搭建Zipkin服务端
方式1:Docker(最简单)
bash
# 拉取Zipkin镜像
docker pull openzipkin/zipkin
# 运行(用内存存储,适合测试)
docker run -d \
--name zipkin \
-p 9411:9411 \
openzipkin/zipkin
# 或者用MySQL存储(生产环境)
docker run -d \
--name zipkin \
-p 9411:9411 \
-e STORAGE_TYPE=mysql \
-e MYSQL_HOST=localhost \
-e MYSQL_PORT=3306 \
-e MYSQL_USER=root \
-e MYSQL_PASS=123456 \
-e MYSQL_DB=zipkin \
openzipkin/zipkin
方式2:Java直接运行
bash
# 下载jar包
curl -sSL https://zipkin.io/quickstart.sh | bash -s
# 运行
java -jar zipkin.jar
方式3:Spring Boot项目集成
xml
<!-- 新建zipkin-server项目 -->
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
</dependency>
访问:http://localhost:9411,看到Zipkin界面就中了!
五、给微服务加链路追踪
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
步骤1:所有服务加依赖
xml
<!-- Sleuth(链路追踪) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<!-- Zipkin客户端(上报数据) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
步骤2:配置文件
yaml
spring:
application:
name: order-service # 服务名很重要,Zipkin上显示这个
sleuth:
sampler:
probability: 1.0 # 采样率,1.0表示100%采样(生产环境可以设0.1)
# 可以自定义一些标签
tags:
application: ${spring.application.name}
environment: ${spring.profiles.active:dev}
zipkin:
base-url: http://localhost:9411 # Zipkin服务器地址
sender:
type: web # 使用HTTP上报(也可以用RabbitMQ、Kafka)
# 连接配置
connect-timeout: 5000
read-timeout: 10000
# 压缩数据,减少网络传输
compression:
enabled: true
# 本地服务名
service:
name: ${spring.application.name}
# 定位信息(IP)
locator:
discovery:
enabled: true
步骤3:启动服务测试
- 启动Zipkin(9411端口)
- 启动user-service、order-service
- 访问几次order-service接口
- 打开Zipkin界面:
http://localhost:9411
六、看看追踪效果
1. 查看服务依赖图
在Zipkin首页:
- 点Dependencies(依赖关系)
- 看到服务之间的调用关系
- 像下面这样:
scss
gateway (网关)
├── order-service (订单服务)
│ ├── user-service (用户服务)
│ └── product-service (商品服务)
└── product-service (商品服务)
2. 查看具体链路
- 点Find Traces(查找追踪)
- 选择服务名:
order-service - 点Run Query(运行查询)
- 看到一堆追踪记录,点一个进去
看到的信息:
yaml
Trace ID: 7a1b3c4d5e6f7g8h
Duration: 1.234s (总耗时)
Services: 3 (涉及3个服务)
Timeline (时间轴):
├── [0ms-50ms] gateway: /order/1
│ └── [10ms-45ms] order-service: OrderController.getOrder
│ ├── [15ms-30ms] user-service: UserClient.getUserById
│ └── [35ms-40ms] product-service: ProductClient.getProduct
└── [60ms-80ms] gateway: 返回响应
3. 看详细信息
点开一个Span,能看到:
- 基本信息:服务名、接口、耗时
- 标签:HTTP方法、状态码、URL
- 时间线:开始时间、结束时间
- 日志:如果有额外日志
七、实际项目中的应用
场景1:慢查询定位
java
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/slow/{id}")
public Order getOrderSlow(@PathVariable Long id) {
// Sleuth会自动给这个请求加上Trace ID
log.info("开始查询订单,订单ID:{}", id);
// 模拟慢查询
simulateSlowQuery();
Order order = orderService.getOrderById(id);
log.info("订单查询完成:{}", order);
return order;
}
private void simulateSlowQuery() {
try {
// 模拟耗时操作
Thread.sleep(3000);
log.warn("查询耗时3秒,需要优化!");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
在Zipkin里能看到:
- order-service有个3秒的Span
- 日志里有"查询耗时3秒,需要优化!"
- 轻松定位到慢接口
场景2:异常追踪
java
@RestController
@RequestMapping("/product")
@Slf4j
public class ProductController {
@GetMapping("/{id}")
public Product getProduct(@PathVariable Long id) {
log.info("查询商品,ID:{}", id);
if (id == 999) {
// 模拟异常
log.error("商品不存在,ID:{}", id);
throw new RuntimeException("商品不存在");
}
return productService.getProductById(id);
}
}
在Zipkin里:
- 看到失败的请求(红色标记)
- 点开看到错误信息
- 知道是哪个参数引起的
场景3:自定义Span
java
@Service
@Slf4j
public class OrderService {
@Autowired
private Tracer tracer; // Sleuth的Tracer
public Order createOrder(OrderDTO orderDTO) {
// 创建自定义Span
ScopedSpan dbSpan = tracer.startScopedSpan("database-operation");
try {
dbSpan.tag("operation", "insert-order");
dbSpan.event("start-db-insert");
// 数据库操作
Order order = saveOrderToDB(orderDTO);
dbSpan.event("end-db-insert");
return order;
} catch (Exception e) {
dbSpan.error(e); // 记录错误
throw e;
} finally {
dbSpan.end(); // 结束Span
}
}
public List<Order> batchProcessOrders(List<Long> orderIds) {
// 批量处理,每个订单一个Span
return orderIds.stream()
.map(orderId -> {
ScopedSpan span = tracer.startScopedSpan("process-order-" + orderId);
try {
return processSingleOrder(orderId);
} finally {
span.end();
}
})
.collect(Collectors.toList());
}
}
八、高级功能
1. 采样率控制(生产环境重要!)
yaml
spring:
sleuth:
sampler:
# 按比例采样(0.1表示10%的请求被追踪)
probability: 0.1
# 或者按速率采样(每秒最多10个)
# rate: 10
为啥不全量采样?
- 数据量太大,存储成本高
- 对性能有影响(约3-5%性能损耗)
- 10%采样率足够发现问题
2. 集成Logback(在日志中显示Trace ID)
logback-spring.xml:
xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<!-- 在日志中显示Trace ID和Span ID -->
<property name="CONSOLE_LOG_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId:-},%X{spanId:-}] [%thread] %-5level %logger{36} - %msg%n"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
日志输出:
ini
2024-01-13 10:00:00.123 [7a1b3c4d5e6f7g8h,9i0j1k2l3m4n5o6] [http-nio-8081-exec-1] INFO c.e.OrderController - 开始处理订单
好处:
- 根据Trace ID搜日志,一找一个准
- 看整个请求链路的日志
3. 集成ELK(日志分析)
yaml
spring:
sleuth:
# 把Trace ID、Span ID加到MDC(Mapped Diagnostic Context)
# Logstash会自动收集
propagation-keys: traceId,spanId
# Logstash配置
logging:
logstash:
enabled: true
host: localhost
port: 5000
4. 集成OpenTelemetry(新标准)
xml
<!-- OpenTelemetry是新一代追踪标准 -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
</dependency>
九、实际项目最佳实践
1. 给网关加追踪
java
@Configuration
public class GatewayTraceConfig {
@Bean
public GlobalFilter traceFilter(Tracer tracer) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
// 获取或生成Trace ID
String traceId = tracer.currentSpan().context().traceId();
// 添加到响应头(方便前端追踪)
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().add("X-Trace-Id", traceId);
return chain.filter(exchange);
};
}
}
2. 数据库调用追踪
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
java
@Configuration
public class DataSourceTraceConfig {
@Bean
public DataSource dataSource(DataSource dataSource, Tracer tracer) {
// 包装DataSource,追踪SQL执行
return new TraceDataSource(dataSource, tracer);
}
}
3. HTTP客户端追踪
java
@Configuration
public class RestTemplateTraceConfig {
@Bean
public RestTemplate restTemplate(Tracer tracer) {
RestTemplate restTemplate = new RestTemplate();
// 添加追踪拦截器
restTemplate.getInterceptors().add((request, body, execution) -> {
// 传递Trace ID
String traceId = tracer.currentSpan().context().traceId();
request.getHeaders().add("X-B3-TraceId", traceId);
return execution.execute(request, body);
});
return restTemplate;
}
}
4. 异步调用追踪
java
@Service
public class AsyncService {
@Autowired
private Tracer tracer;
@Async
public CompletableFuture<String> asyncMethod() {
// 异步方法中需要手动传递Trace Context
try (Tracer.SpanInScope ws = tracer.withSpan(tracer.currentSpan())) {
// 异步操作
return CompletableFuture.completedFuture("done");
}
}
}
十、生产环境部署方案
方案1:Zipkin + MySQL(小规模)
bash
# Zipkin用MySQL存储
docker run -d \
--name zipkin \
-p 9411:9411 \
-e STORAGE_TYPE=mysql \
-e MYSQL_HOST=mysql-host \
-e MYSQL_TCP_PORT=3306 \
-e MYSQL_USER=zipkin \
-e MYSQL_PASS=zipkin \
-e MYSQL_DB=zipkin \
openzipkin/zipkin
方案2:Zipkin + Elasticsearch(大规模)
bash
# 先启动Elasticsearch
docker run -d --name elasticsearch -p 9200:9200 elasticsearch:7.17.0
# 启动Zipkin
docker run -d \
--name zipkin \
-p 9411:9411 \
-e STORAGE_TYPE=elasticsearch \
-e ES_HOSTS=http://elasticsearch:9200 \
openzipkin/zipkin
方案3:通过消息队列上报(解耦)
yaml
spring:
zipkin:
sender:
type: rabbit # 或kafka
rabbitmq:
host: localhost
port: 5672
username: admin
password: admin123
十一、常见问题解决
1. Zipkin看不到数据
检查:
- 服务连上Zipkin没(
spring.zipkin.base-url) - 采样率是不是0(
spring.sleuth.sampler.probability) - 网络通不通
- Zipkin服务正常不
2. Trace ID不连续
yaml
# 在网关统一生成Trace ID
spring:
sleuth:
# 使用128位Trace ID(默认是64位)
trace-id128: true
# 自定义ID生成器
id-generator:
simple:
# 使用更随机的ID
random:
enabled: true
3. 性能影响太大
yaml
# 调整采样率
spring:
sleuth:
sampler:
probability: 0.01 # 1%采样
# 或者用速率限制
sampler:
rate: 100 # 每秒最多100个
4. 数据保留时间
bash
# Zipkin用Elasticsearch可以设置保留策略
# 在Elasticsearch创建生命周期策略
PUT _ilm/policy/zipkin-retention-policy
{
"policy": {
"phases": {
"hot": {
"actions": {}
},
"delete": {
"min_age": "30d",
"actions": {
"delete": {}
}
}
}
}
}
十二、监控告警
1. 慢请求告警
java
@Component
public class SlowRequestAlert {
@Autowired
private Tracer tracer;
@EventListener
public void handleSpanExported(SpanExportedEvent event) {
Span span = event.getSpan();
// 检查耗时
long duration = span.getDurationMicros() / 1000; // 转成毫秒
if (duration > 3000) { // 超过3秒
// 发送告警
sendAlert("慢请求警告",
"服务: " + span.getLocalServiceName() +
", 接口: " + span.getName() +
", 耗时: " + duration + "ms" +
", Trace ID: " + span.getTraceId());
}
}
private void sendAlert(String title, String message) {
// 发送到钉钉、企业微信、邮件等
System.out.println("告警: " + title + " - " + message);
}
}
2. 错误率监控
java
@Component
public class ErrorRateMonitor {
private Map<String, AtomicInteger> errorCounts = new ConcurrentHashMap<>();
private Map<String, AtomicInteger> totalCounts = new ConcurrentHashMap<>();
@EventListener
public void handleSpanExported(SpanExportedEvent event) {
Span span = event.getSpan();
String serviceName = span.getLocalServiceName();
// 统计总数
totalCounts.computeIfAbsent(serviceName, k -> new AtomicInteger(0))
.incrementAndGet();
// 检查是否有错误标签
if (span.getTags().containsKey("error")) {
errorCounts.computeIfAbsent(serviceName, k -> new AtomicInteger(0))
.incrementAndGet();
// 计算错误率
double errorRate = (double) errorCounts.get(serviceName).get()
/ totalCounts.get(serviceName).get();
if (errorRate > 0.05) { // 错误率超过5%
sendAlert("错误率过高",
"服务: " + serviceName +
", 错误率: " + String.format("%.2f", errorRate * 100) + "%");
}
}
}
}
十三、今儿个总结
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
学会了啥?
- ✅ 链路追踪的重要性(不再甩锅)
- ✅ Sleuth + Zipkin 工作原理
- ✅ 搭建Zipkin服务端
- ✅ 集成到Spring Cloud微服务
- ✅ 查看和分析链路数据
- ✅ 生产环境最佳实践
关键点
- Trace ID贯穿整个请求链路
- Zipkin 存储展示,Sleuth收集传递
- 采样率控制性能影响
- 日志集成方便问题定位
- 自定义Span细化追踪
十四、明儿个学啥?
明天咱学服务监控!
- 服务健康状态怎么监控?
- 内存、CPU、线程池怎么看?
- 用Spring Boot Admin一站式监控
- 出问题自动告警,运维不再"救火"
明天咱给微服务装个"健康手环"!🩺
SpringCloud分布式追踪深度实战:Sleuth+Zipkin从入门到生产部署全攻略
【关注】 🔔 关注我,获取更多SpringCloud实战技巧!
📚 专栏《SpringCloud从零到一》持续更新中
💡 每天一个核心技术点,15天掌握微服务架构
👨💻 私信回复"源码"获取完整Demo项目
🚀 实战问题欢迎评论区讨论,有问必答!
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。