SpringCloud分布式追踪深度实战:Sleuth+Zipkin从入门到生产部署全攻略

第9天:分布式链路追踪 - 微服务里的"侦探",请求去哪儿了一清二楚🔍

一、先白话白话为啥需要链路追踪

零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目

资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。

场景:用户下单慢,该找谁?

用户反馈:"我下单等了10秒才成功!"

没有链路追踪时

  • 开发A:"我订单服务没问题啊!"
  • 开发B:"我商品服务响应很快!"
  • 开发C:"我库存服务1秒就返回了!"
  • 运维:"网关看着也正常啊..."
  • 结果:互相甩锅,问题定位像无头苍蝇

有链路追踪时

  • 看链路图:网关(1s) -> 订单服务(6s) -> 商品服务(0.5s) -> 库存服务(0.5s) -> 用户服务(2s)
  • 一眼看出:订单服务花了6秒,卡在数据库查询
  • 精准定位:优化订单服务的SQL语句
  • 解决问题:响应时间降到2秒

二、链路追踪是啥?

零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目

资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。

就像快递追踪系统

  • 每个包裹有个唯一单号(Trace ID)
  • 经过每个站点都扫码记录(Span)
  • 你能看到包裹的完整路线(Trace)
  • 知道在哪个站点停留多久(耗时)

核心概念

  1. Trace:一次完整的请求链路(比如一次下单)
  2. Span:链路中的每个环节(比如调用订单服务)
  3. Trace ID:整个链路的唯一ID
  4. Span ID:每个环节的唯一ID
  5. 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:启动服务测试

  1. 启动Zipkin(9411端口)
  2. 启动user-service、order-service
  3. 访问几次order-service接口
  4. 打开Zipkin界面:http://localhost:9411

六、看看追踪效果

1. 查看服务依赖图

在Zipkin首页:

  • Dependencies(依赖关系)
  • 看到服务之间的调用关系
  • 像下面这样:
scss 复制代码
gateway (网关)
  ├── order-service (订单服务)
  │     ├── user-service (用户服务)
  │     └── product-service (商品服务)
  └── product-service (商品服务)

2. 查看具体链路

  1. Find Traces(查找追踪)
  2. 选择服务名:order-service
  3. Run Query(运行查询)
  4. 看到一堆追踪记录,点一个进去

看到的信息

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看不到数据

检查

  1. 服务连上Zipkin没(spring.zipkin.base-url
  2. 采样率是不是0(spring.sleuth.sampler.probability
  3. 网络通不通
  4. 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 ,获取本文所有示例代码、配置模板及导出工具。

学会了啥?

  1. ✅ 链路追踪的重要性(不再甩锅)
  2. ✅ Sleuth + Zipkin 工作原理
  3. ✅ 搭建Zipkin服务端
  4. ✅ 集成到Spring Cloud微服务
  5. ✅ 查看和分析链路数据
  6. ✅ 生产环境最佳实践

关键点

  1. Trace ID贯穿整个请求链路
  2. Zipkin 存储展示,Sleuth收集传递
  3. 采样率控制性能影响
  4. 日志集成方便问题定位
  5. 自定义Span细化追踪

十四、明儿个学啥?

明天咱学服务监控

  • 服务健康状态怎么监控?
  • 内存、CPU、线程池怎么看?
  • Spring Boot Admin一站式监控
  • 出问题自动告警,运维不再"救火"

明天咱给微服务装个"健康手环"!🩺


SpringCloud分布式追踪深度实战:Sleuth+Zipkin从入门到生产部署全攻略

【关注】 🔔 关注我,获取更多SpringCloud实战技巧!

📚 专栏《SpringCloud从零到一》持续更新中

💡 每天一个核心技术点,15天掌握微服务架构

👨💻 私信回复"源码"获取完整Demo项目

🚀 实战问题欢迎评论区讨论,有问必答!

零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目

资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。


相关推荐
陈随易2 小时前
Bun v1.3.6发布,内置tar解压缩,各方面提速又提速
前端·后端
武子康2 小时前
大数据-212 K-Means 聚类实战指南:从无监督概念到 Inertia、K 值选择与避坑
大数据·后端·机器学习
lewis_lk2 小时前
docker-compose部署nacos
后端
lewis_lk2 小时前
docker-compose部署mysql&redis
后端·docker
天天摸鱼的java工程师2 小时前
工作中七天免登录如何实现
java·后端
小杨同学492 小时前
C 语言实战:水果总价计算程序(结构体应用 + 细节优化)
后端·算法·程序员
用户948357016512 小时前
《自动化埋点:利用 AOP 统一记录接口入参、出参及执行耗时》
后端
undsky2 小时前
【RuoYi-SpringBoot3-Pro】:多租户功能上手指南
spring boot·后端·mybatis