第 1 章:Zipkin 概述与核心概念
1.1 分布式追踪的基本原理
| 概念名称 | 说明 | 注意事项 |
|---|
| 分布式系统 | 由多个独立服务构成,通过网络通信协同完成业务逻辑的系统架构。 | 服务间调用链复杂,传统日志难以定位问题。 |
| 调用链路(Call Chain) | 用户请求经过多个微服务形成的逻辑执行路径。 | 路径可能涉及同步、异步、消息队列等多种通信方式。 |
| 上下文传播(Context Propagation) | 在服务调用过程中传递追踪信息(如 TraceId、SpanId)的机制。 | 必须确保跨进程调用时上下文不丢失,通常通过 HTTP Header 或消息头传递。 |
| 追踪数据采集 | 收集每个服务执行过程中的时间戳、标签、事件等信息。 | 采集应轻量,避免对业务性能造成显著影响;支持采样机制以降低开销。 |
| 可视化分析 | 将采集到的调用链数据以图形化方式展示,帮助开发者分析延迟和错误。 | UI 需支持按服务、时间、状态等维度查询,并能下钻查看具体 Span 的详细信息。 |
1.2 Zipkin 简介与架构组成
| 组件名称 | 说明 | 注意事项 |
|---|
| Collector | 接收客户端上报的追踪数据(Span),并写入存储层。支持 HTTP、Kafka、RabbitMQ 等方式接收。 | 应确保 Collector 具备一定的缓冲和容错能力,避免因存储延迟导致数据丢失。 |
| Storage Backend | 存储追踪数据的持久化系统,支持内存、MySQL、Cassandra、Elasticsearch 等。 | 生产环境推荐使用 Cassandra 或 Elasticsearch,以支持高吞吐和快速查询。 |
| API | 提供 RESTful 接口供 UI 或外部系统查询追踪数据。 | 查询接口支持按 TraceId、服务名、时间范围等条件检索。 |
| Web UI | 提供图形化界面展示调用链、服务依赖图、延迟分布等信息。 | UI 是只读的,不支持修改数据;可通过反向代理暴露给内网用户访问。 |
| Instrumentation Clients | 嵌入在应用中的库(如 Brave、Spring Cloud Sleuth),负责生成和上报 Span 数据。 | 客户端需轻量且对业务无侵入或低侵入,支持自动埋点和手动埋点两种模式。 |
1.3 核心术语:Trace、Span、Annotation、BinaryAnnotation 解析
| 术语名称 | 说明 | 注意事项 |
|---|
| Trace | 表示一个完整的请求调用链,由多个 Span 组成,通过唯一的 TraceId 标识。 | 从用户发起请求开始,贯穿所有服务节点,直到返回响应为止。 |
| Span | 表示一个操作的基本单元,如一次方法调用或一次数据库查询,包含开始时间、结束时间和元数据。 | 每个 Span 有唯一的 SpanId,并可包含父 SpanId(ParentSpanId)表示层级关系。 |
| Annotation | 记录 Span 内部的关键事件及其时间戳,用于表示操作的生命周期阶段。 | 常见的 Annotation 类型包括 cs(Client Send)、sr(Server Receive)、ss(Server Send)、cr(Client Receive)。 |
| BinaryAnnotation | 用于记录键值对形式的附加信息,如 HTTP URL、状态码、SQL 语句等。 | 适合记录非时间戳类的元数据,可用于过滤和分析;注意避免记录敏感信息(如密码)。 |
| TraceId | 全局唯一标识一个 Trace 的字符串或数字。 | 通常为 16 或 32 位十六进制字符串,保证全局唯一性。 |
| SpanId | 标识当前 Span 的唯一 ID。 | 在同一个 Trace 中必须唯一。 |
| ParentSpanId | 指向上一级 Span 的 ID,用于构建调用树结构。 | 根 Span 没有 ParentSpanId。 |
第 2 章:Zipkin 服务端搭建
2.1 使用官方 JAR 包快速启动 Zipkin Server
| 方法/参数 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
java -jar | java -jar zipkin-server-x.x.x-exec.jar | 启动 Zipkin Server 可执行 JAR 包 | java -jar zipkin-server-2.23.16-exec.jar | 确保已安装 JDK 8 或以上版本;文件需具有可执行权限。 |
--server.port | --server.port=9411 | 指定 Zipkin 服务监听端口 | java -jar zipkin-server-2.23.16-exec.jar --server.port=9411 | 默认端口为 9411,若被占用需更改;防火墙需开放对应端口。 |
--zipkin.storage.type | --zipkin.storage.type=mem | 设置存储类型为内存(默认) | java -jar zipkin-server-2.23.16-exec.jar --zipkin.storage.type=mem | 数据不持久化,重启后丢失;仅适用于测试环境。 |
| 下载地址 | https://github.com/openzipkin/zipkin/releases | 获取最新版本的 zipkin-server JAR 包 | wget https://github.com/openzipkin/zipkin/releases/download/2.23.16/zipkin-server-2.23.16-exec.jar | 建议使用稳定版本;注意校验文件完整性(SHA)。 |
2.2 基于 Docker 部署 Zipkin(单机与持久化)
| 方法/参数 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
docker run(基础) | docker run -d -p 9411:9411 openzipkin/zipkin | 启动 Zipkin 容器(内存存储) | docker run -d -p 9411:9411 openzipkin/zipkin | 最简单的部署方式,适合本地开发测试。 |
-p | -p 9411:9411 | 映射主机 9411 端口到容器端口 | 同上 | 确保主机端口未被占用。 |
-e(环境变量) | -e KEY=VALUE | 设置容器运行时环境变量 | -e STORAGE_TYPE=mem | 环境变量控制存储类型、数据库连接等配置。 |
| 使用 Elasticsearch 持久化 | -e STORAGE_TYPE=elasticsearch -e ES_HOSTS=http://es-host:9200 | 配置 Zipkin 使用外部 Elasticsearch 存储 | docker run -d -p 9411:9411 -e STORAGE_TYPE=elasticsearch -e ES_HOSTS=http://192.168.1.100:9200 openzipkin/zipkin | 需提前部署并启动 Elasticsearch 服务;网络连通性需保障。 |
| 使用 Kafka 作为数据源 | -e KAFKA_BOOTSTRAP_SERVERS=host:9092 | 让 Zipkin 从 Kafka 消费 Span 数据 | docker run -d -p 9411:9411 -e KAFKA_BOOTSTRAP_SERVERS=kafka:9092 openzipkin/zipkin | 适用于高吞吐场景;需确保 Kafka 集群可用。 |
--name | --name zipkin-server | 为容器指定名称 | docker run --name zipkin-server -d -p 9411:9411 openzipkin/zipkin | 便于后续管理(如日志查看、重启、删除)。 |
2.3 配置存储后端(In-Memory、MySQL、Cassandra、Elasticsearch)
| 存储类型 | 配置参数/语法 | 用途 | 代码示例(Docker 环境变量形式) | 注意事项 |
|---|
| In-Memory | -e STORAGE_TYPE=mem | 使用内存作为存储后端 | -e STORAGE_TYPE=mem | 数据重启即丢,仅用于测试;不支持大规模数据查询。 |
| MySQL | -e STORAGE_TYPE=mysql -e MYSQL_HOST=localhost -e MYSQL_TCP_PORT=3306 -e MYSQL_DB=zipkin -e MYSQL_USER=root -e MYSQL_PASS=password | 配置 Zipkin 使用 MySQL 存储 | -e STORAGE_TYPE=mysql -e MYSQL_HOST=db -e MYSQL_USER=zipkin -e MYSQL_PASS=secret | 需提前创建数据库和表结构(Zipkin 提供 DDL 脚本);性能不如 Cassandra/Elasticsearch。 |
| Cassandra | -e STORAGE_TYPE=cassandra -e CASSANDRA_CONTACT_POINTS=node1,node2 | 配置 Zipkin 使用 Cassandra 集群存储 | -e STORAGE_TYPE=cassandra -e CASSANDRA_CONTACT_POINTS=cass1,cass2 | 推荐生产环境使用;需提前初始化 schema(使用 zipkin-cassandra-core 工具)。 |
| Elasticsearch | -e STORAGE_TYPE=elasticsearch -e ES_HOSTS=http://es1:9200,http://es2:9200 | 配置 Zipkin 使用 Elasticsearch 集群存储 | -e STORAGE_TYPE=elasticsearch -e ES_HOSTS=http://es-node:9200 -e ES_INDEX=zipkin | 支持高效全文检索和聚合分析;需配置索引策略和生命周期管理。 |
| 初始化 Schema(Cassandra) | java -Djava.security.egd=file:/dev/./urandom -jar zipkin-server.jar --cassandra.schema-create-if-missing=true | 自动创建缺失的表结构 | 在启动命令中添加该参数或使用专用工具执行 | 建议在首次部署时手动执行 schema 初始化脚本以控制权限。 |
| 连接超时设置 | -e CASSANDRA_CONNECT_TIMEOUT_MS=5000 -e CASSANDRA_SOCKET_TIMEOUT_MS=20000 | 调整 Cassandra 连接和读取超时时间 | -e CASSANDRA_CONNECT_TIMEOUT_MS=5000 | 网络不稳定时需适当调大超时值,避免连接失败。 |
提示: Elasticsearch 和 Cassandra 更适合生产环境,因其具备高可用、可扩展和高性能查询能力。MySQL 虽然常见,但在大数据量下查询性能较差。内存存储仅用于演示或临时测试。
第 3 章:Spring Boot 集成 Sleuth + Zipkin
3.1 引入 Spring Cloud Sleuth 与 Zipkin 依赖
| 依赖名称 | Maven 坐标 | 用途 | 代码示例(pom.xml 片段) | 注意事项 |
|---|
spring-cloud-starter-sleuth | org.springframework.cloud:spring-cloud-starter-sleuth | 提供分布式追踪能力,自动为请求生成 Trace 和 Span | <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-sleuth</artifactId></dependency> | 必须引入的基础依赖,无需额外配置即可在日志中看到 traceId 和 spanId。 |
spring-cloud-starter-zipkin | org.springframework.cloud:spring-cloud-starter-zipkin | 启用 Sleuth 与 Zipkin 的集成,支持将 Span 数据上报至 Zipkin Server | <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-zipkin</artifactId></dependency> | 引入后需配置 zipkin.base-url 才能启用上报功能。 |
spring-boot-starter-web | org.springframework.boot:spring-boot-starter-web | 构建 Web 应用,Sleuth 自动拦截 HTTP 请求进行埋点 | <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency> | 常见于微服务应用;Sleuth 对 RestTemplate、WebClient 等自动增强。 |
spring-kafka | org.springframework.kafka:spring-kafka | 支持通过 Kafka 上报 Span 数据(当使用 Kafka 作为传输通道时) | <dependency><groupId>org.springframework.kafka</groupId><artifactId>spring-kafka</artifactId></dependency> | 需配置 spring.zipkin.sender.type=kafka 并指定 Kafka 地址。 |
spring-rabbit | org.springframework.boot:spring-boot-starter-amqp | 支持通过 RabbitMQ 上报 Span 数据 | <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency> | 需配置 spring.zipkin.sender.type=rabbit 并确保 RabbitMQ 服务可用。 |
| 版本对齐 | 使用 Spring Cloud BOM 管理版本 | 确保各组件版本兼容,避免冲突 | <dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>2023.0.0</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement> | 建议使用 BOM(Bill of Materials)统一管理版本,避免手动指定版本导致不兼容。 |
提示: spring-cloud-starter-zipkin 内部已包含 spring-cloud-starter-sleuth,因此只需引入前者即可同时获得追踪和上报能力。
3.2 配置 application.yml 启用消息发送(HTTP / Kafka / RabbitMQ)
| 配置项 | 语法示例 | 用途 | 代码示例(application.yml) | 注意事项 |
|---|
spring.zipkin.base-url | http://localhost:9411 | 指定 Zipkin Server 的地址(HTTP 上报时必需) | spring.zipkin.base-url: http://zipkin-server:9411 | 确保网络可达;生产环境建议使用域名或服务发现方式。 |
spring.sleuth.sampler.probability | 0.1 | 设置采样率(0.0 ~ 1.0),决定上报 Span 的比例 | spring.sleuth.sampler.probability: 0.1 | 生产环境建议设为 0.1 或更低以减少性能开销;调试时可设为 1.0。 |
spring.zipkin.sender.type | web / kafka / rabbit | 指定 Span 数据的传输方式 | spring.zipkin.sender.type: web | 默认为 web(HTTP);若使用消息中间件需显式指定。 |
spring.kafka.bootstrap-servers | localhost:9092 | 配置 Kafka 集群地址(当 sender.type=kafka 时) | spring.kafka.bootstrap-servers: kafka1:9092,kafka2:9092 | 需确保 Kafka 集群正常运行,且 topic zipkin 存在(自动创建需开启)。 |
spring.rabbitmq.addresses | amqp://localhost:5672 | 配置 RabbitMQ 地址列表(当 sender.type=rabbit 时) | spring.rabbitmq.addresses: amqp://user:pass@rabbitmq:5672 | 需确保 RabbitMQ 已安装并启动,且用户有权限访问。 |
spring.zipkin.rabbitmq.queue | zipkin | 指定 RabbitMQ 中用于接收 Span 的队列名称 | spring.zipkin.rabbitmq.queue: zipkin | 队列需存在或允许自动声明。 |
logging.level.org.springframework.cloud.sleuth | DEBUG | 开启 Sleuth 调试日志,便于排查上报问题 | logging.level.org.springframework.cloud.sleuth: DEBUG | 可查看 Span 生成和发送过程;生产环境建议关闭。 |
3.3 验证追踪数据是否上报至 Zipkin Server
| 验证方法 | 说明 | 操作步骤 | 注意事项 |
|---|
| 查看 Zipkin Web UI | 通过浏览器访问 Zipkin Server 的 UI 界面,查看是否有追踪数据展示 | 1. 访问 http://zipkin-server:9411 2. 在搜索栏选择服务名或输入时间范围 3. 点击 “Find Traces” 查看结果 | 若无数据,检查服务名是否正确、时间范围是否匹配、采样率是否过低。 |
| 检查应用日志 | 查看应用启动日志中是否包含 Sleuth 和 Zipkin 初始化信息 | 搜索日志中的关键词:Tracing、Zipkin、Reporter、Span | 正常应看到类似 Reporting spans with HTTP ZipkinSpanReporter 的日志。 |
| 使用 curl 发送测试请求 | 手动触发一个 HTTP 请求,观察是否生成 Trace | curl http://your-service/hello | 确保端点被 Sleuth 拦截(如使用 Web 依赖)。 |
| 查看 Zipkin API 响应 | 直接调用 Zipkin 的 REST API 查询追踪数据 | curl http://zipkin-server:9411/api/v2/traces?serviceName=your-service-name | 返回 JSON 格式 Trace 列表;为空则说明未上报或服务名不匹配。 |
| 启用 DEBUG 日志 | 开启 Sleuth 的 DEBUG 日志级别,查看 Span 上报过程 | 在 application.yml 中设置:logging.level.org.springframework.cloud.sleuth=DEBUG | 可看到 Span 创建、采样、序列化、发送的详细过程;用于排查网络或序列化问题。 |
| 检查网络连通性 | 确认应用能访问 Zipkin Server 或消息中间件 | 使用 telnet 或 ping 测试 Zipkin Server 端口(如 9411)或 Kafka/RabbitMQ 端口 | 网络隔离或防火墙可能导致上报失败。 |
第 4 章:Sleuth 核心机制解析
4.1 Trace 与 Span 的自动生成规则
| 触发场景 | 自动生成规则 | 注意事项 |
|---|
| HTTP 请求进入(服务端) | 每个 incoming HTTP 请求生成一个新的 Span(若无 TraceId 则创建新 Trace) | 使用 X-B3-TraceId、X-B3-SpanId 等 B3 Header 进行上下文传播。 |
| 发起 HTTP 调用(客户端) | 使用 RestTemplate 或 WebClient 时,自动创建 Client Span 并注入 B3 Header | 需通过 @LoadBalanced 注解或手动包装才能被 Sleuth 拦截。 |
| 消息消费者(Kafka/RabbitMQ) | 消费消息时,从消息头中提取 B3 信息,创建新的 Span 并加入现有 Trace | 消息生产者需启用 Sleuth 才能传递上下文。 |
异步方法(@Async) | 若线程池支持上下文传递(如使用 TaskExecutor 装饰),可继承父 Span 上下文 | 默认线程池会丢失上下文,需使用 LazyTraceExecutor 或自定义包装。 |
定时任务(@Scheduled) | 每次执行生成独立的 Trace,不继承调用上下文 | 被视为外部触发事件,无法关联到用户请求链。 |
| Feign 客户端调用 | 自动创建 Span 并注入 Header,无需额外配置 | 需引入 spring-cloud-starter-openfeign 和 spring-cloud-starter-zipkin。 |
| 手动抛出异常 | 异常被捕获并记录为 Span 的 tag(如 error=true) | 有助于在 Zipkin UI 中识别失败请求。 |
4.2 日志中 MDC 的自动注入(traceId、spanId)
| MDC 键名 | 值来源 | 用途 | 代码示例(logback-spring.xml) | 注意事项 |
|---|
traceId | 当前 Span 所属 Trace 的唯一标识 | 在日志中标识请求的全局链路 | %X{traceId} | 多个服务共享同一 traceId,可用于日志聚合分析。 |
parentId | 当前 Span 的父 Span ID | 表示调用层级关系 | %X{parentId} | 根 Span 无 parentId。 |
spanId | 当前 Span 的唯一标识 | 标识当前操作单元 | %X{spanId} | 同一 Trace 中 spanId 唯一。 |
sampled | 是否被采样(true/false) | 判断该请求是否会上报 Zipkin | %X{sampled} | 未采样请求不会生成完整追踪链。 |
| logback 配置支持 | 使用 %X{key} 或 %mdc{key} 输出 MDC 内容 | 将追踪信息嵌入日志输出 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - [%X{traceId}/%X{spanId}] %msg%n | 建议统一日志格式,便于 ELK 等系统解析。 |
4.3 自定义 Span 的创建与注解添加
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
Tracer.nextSpan() | tracer.nextSpan() | 创建一个新的 Span | try (Span span = tracer.nextSpan().name("custom-operation").start()) { ... } finally { span.finish(); } | 需手动管理 Span 的生命周期(start/finish)。 |
Span.name(String) | span.name("operation-name") | 设置 Span 的名称 | span.name("database-query") | 名称应具有业务含义,便于在 UI 中识别。 |
Span.tag(String key, String value) | span.tag("sql.query", "SELECT * FROM users") | 添加键值对标签(Tag) | span.tag("user.id", "123") | 避免记录敏感信息;可用于过滤和分析。 |
Span.annotate(String value) | span.annotate("cache-hit") | 添加时间戳类注解(Annotation) | span.annotate("method-entry") | 通常用于标记关键事件点,如方法入口、缓存命中等。 |
Span.start() | span.start() | 显式启动 Span(开始计时) | span.start() | 若使用 try-with-resources,可自动 start。 |
Span.finish() | span.finish() | 结束 Span(记录结束时间并上报) | span.finish() | 必须调用,否则 Span 不完整;建议使用 try-finally 或 try-with-resources。 |
Tracer.currentSpan() | tracer.currentSpan() | 获取当前线程的活动 Span | Span current = tracer.currentSpan() | 若无当前 Span,返回 null;可用于在方法内部添加 tag 或 annotate。 |
Tracer.createSpan(Span) | tracer.createSpan(parentSpan) | 创建子 Span 并关联父 Span | Span child = tracer.createSpan(parent).name("child-op") | 用于构建调用树结构。 |
Tracer.detach(Span) | tracer.detach(span) | 从当前线程分离指定 Span | tracer.detach(span) | 多线程环境下手动控制上下文传递时使用。 |
第 5 章:客户端上报方式详解
5.1 使用 HTTP 直接上报(RestTemplate / WebClient)
| 方法/配置项 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
spring.zipkin.sender.type=web | web | 指定使用 HTTP 方式上报 Span 数据 | spring.zipkin.sender.type: web | 默认方式,无需额外依赖;适用于低吞吐场景。 |
spring.zipkin.base-url | http://zipkin-server:9411 | 设置 Zipkin Server 的 HTTP 接收地址 | spring.zipkin.base-url: http://zipkin.example.com:9411 | 必须可访问;生产环境建议使用负载均衡或服务发现。 |
RestTemplate 自动增强 | 使用 @LoadBalanced 注解 | Sleuth 自动为 RestTemplate 添加拦截器 | @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } | 必须使用此方式创建的 RestTemplate 才能自动传播上下文并生成 Client Span。 |
WebClient 自动增强 | WebClient.builder().build() | Sleuth 自动为 WebClient 添加过滤器 | WebClient webClient = WebClient.builder().filter(sleuthWebFilter).build(); | 需引入 spring-webflux;适用于响应式编程模型。 |
| 同步上报行为 | Reporter.sync() | 将 Span 立即通过 HTTP 发送到 Zipkin | 默认行为,由 ZipkinRestTemplateSender 实现 | 阻塞当前线程,可能影响性能;高并发下不推荐。 |
| 连接超时配置 | spring.zipkin.sender.connect-timeout / read-timeout | 设置 HTTP 连接和读取超时时间 | spring.zipkin.sender.connect-timeout: 500ms
spring.zipkin.sender.read-timeout: 1000ms | 网络不稳定时需调大,避免因超时导致请求失败。 |
| 批量上报(有限支持) | spring.sleuth.reporter.max-operations | 设置批量上报的最大操作数 | spring.sleuth.reporter.max-operations: 10 | HTTP 上报通常为单条发送,批量能力弱;建议使用消息中间件实现高效批量。 |
5.2 基于 Kafka 的异步上报配置与验证
| 方法/配置项 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
spring.zipkin.sender.type=kafka | kafka | 启用 Kafka 作为 Span 数据传输通道 | spring.zipkin.sender.type: kafka | 需引入 spring-kafka 依赖。 |
spring.kafka.bootstrap-servers | host:9092 | 指定 Kafka 集群地址 | spring.kafka.bootstrap-servers: kafka1:9092,kafka2:9092 | 确保 Kafka 集群可用且网络连通。 |
spring.zipkin.kafka.topic | zipkin | 设置 Span 数据写入的 Kafka 主题 | spring.zipkin.kafka.topic: my-traces | 主题需存在或允许自动创建;多个服务可共用同一主题。 |
| Producer 配置(acks) | spring.kafka.producer.acks=1 | 设置消息确认机制 | spring.kafka.producer.acks: 1 | 推荐设为 1 或 all,避免数据丢失。 |
| Producer 配置(retries) | spring.kafka.producer.retries=3 | 设置发送失败重试次数 | spring.kafka.producer.retries: 3 | 提高可靠性;注意重试间隔。 |
| 异步非阻塞上报 | Reporter.composite(AsyncReporter.create(KafkaSender.create(...))) | 使用异步上报器,提升性能 | 自动由 Sleuth 集成实现 | 不阻塞业务线程,适合高吞吐场景。 |
| 验证 Kafka 消息 | kafka-console-consumer.sh --topic zipkin --bootstrap-server localhost:9092 | 查看 Kafka 主题中的原始 Span 消息 | ./bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic zipkin --from-beginning | 消息为 Avro 或 JSON 格式,可验证是否成功发送。 |
| Kafka 消费组监控 | Kafka Manager / Prometheus + Kafka Exporter | 监控消费者 Lag 和吞吐量 | - | 确保 Zipkin Collector 能及时消费,避免消息积压。 |
5.3 基于 RabbitMQ 的上报机制与调优
| 方法/配置项 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
spring.zipkin.sender.type=rabbit | rabbit | 启用 RabbitMQ 作为 Span 传输通道 | spring.zipkin.sender.type: rabbit | 需引入 spring-boot-starter-amqp 依赖。 |
spring.rabbitmq.addresses | amqp://host:5672 | 设置 RabbitMQ 服务器地址 | spring.rabbitmq.addresses: amqp://user:pass@rabbitmq:5672 | 支持集群地址列表,用逗号分隔。 |
spring.zipkin.rabbitmq.queue | zipkin | 指定用于接收 Span 的队列名称 | spring.zipkin.rabbitmq.queue: sleuth-traces | 队列需存在或允许自动声明。 |
| 消息持久化配置 | spring.rabbitmq.publisher-confirm-type=correlated | 启用发布确认机制,确保消息不丢失 | spring.rabbitmq.publisher-confirm-type: correlated | 推荐开启,提高可靠性。 |
| 消息序列化格式 | Encoding.SPAN_V2 / Encoding.JSON_V2 | 设置 Span 消息的编码格式 | 默认使用 JSON_V2 | 可通过自定义 Sender 修改编码方式。 |
| 连接池配置 | spring.rabbitmq.cache.channel.size | 设置 Channel 缓存大小 | spring.rabbitmq.cache.channel.size: 10 | 提高并发性能,避免频繁创建连接。 |
| 死信队列(DLX)配置 | x-dead-letter-exchange | 为队列配置死信交换机,处理失败消息 | 在 RabbitMQ 控制台或声明队列时设置 | 便于排查上报失败的消息。 |
| 验证队列消息 | rabbitmqctl list_queues | 查看队列中是否有待消费的 Span 消息 | rabbitmqctl list_queues | grep zipkin | 若消息堆积,需检查 Zipkin Collector 是否正常消费。 |
第 6 章:自定义追踪与埋点
6.1 手动创建和结束 Span
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
Tracer.nextSpan() | tracer.nextSpan() | 创建一个新的独立 Span | Span span = tracer.nextSpan(); | 返回的 Span 未启动,需调用 start()。 |
Span.start() | span.start() | 启动 Span,记录开始时间 | span.start(); | 可多次调用无副作用;建议与 try-finally 配合使用。 |
Span.finish() | span.finish() | 结束 Span,记录结束时间并触发上报 | try { span.start(); ... } finally { span.finish(); } | 必须调用,否则 Span 不完整且占用资源。 |
| try-with-resources | try (SpanInScope ws = tracer.withSpanInScope(span)) | 自动管理 Span 生命周期(推荐方式) | try (Tracer.SpanInScope ws = tracer.withSpanInScope(span.start())) { ... } | Java 7+ 特性,自动 finish;更安全。 |
Tracer.currentSpan() | tracer.currentSpan() | 获取当前线程绑定的活动 Span | Span current = tracer.currentSpan(); | 若无当前 Span,返回 null;可用于扩展当前操作。 |
Span.isNoop() | span.isNoop() | 判断 Span 是否为”空操作”(未被采样) | if (!span.isNoop()) { span.tag("debug", "value"); } | 未采样的 Span 不会上报,添加 tag 可跳过以节省开销。 |
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
Span.tag(String, String) | span.tag("key", "value") | 添加字符串类型的标签(Tag) | span.tag("user.id", "12345"); span.tag("http.url", "/api/users"); | 用于记录元数据,支持索引和查询;避免敏感信息。 |
Span.tag(String, boolean) | span.tag("cache.hit", true) | 添加布尔类型标签 | span.tag("cache.hit", cacheHit); | 自动转换为字符串 “true”/“false”。 |
Span.annotate(long, String) | span.annotate(System.currentTimeMillis(), "event") | 在指定时间点添加注解 | span.annotate(System.currentTimeMillis(), "file.processed"); | 精确控制事件时间戳;一般使用无参 annotate()。 |
Span.annotate(String) | span.annotate("event-name") | 在当前时间添加事件注解 | span.annotate("method.entry"); span.annotate("db.query.start"); | 用于标记关键执行点,如方法入口、SQL 开始等。 |
| 支持的 Tag 前缀(约定) | error, http.status_code, db.instance 等 | 特殊标签影响 Zipkin UI 显示行为 | span.tag("error", "SQLSyntaxError"); span.tag("http.status_code", "500"); | error 标签会让 Span 在 UI 中标红;标准标签提升可读性。 |
| 自定义复杂对象序列化 | ObjectMapper.writeValueAsString(obj) | 将对象转为 JSON 字符串后作为 Tag 值 | span.tag("request.payload", objectMapper.writeValueAsString(request)); | 谨慎使用,避免过大或敏感数据;可能影响性能。 |
6.3 在非 Web 请求场景中使用 Tracer
| 场景类型 | 使用方式 | 代码示例 | 注意事项 |
|---|
定时任务(@Scheduled) | 在方法内手动创建 Span 并 finish | @Scheduled(fixedRate=5000) public void cronJob() { Span span = tracer.nextSpan().name("cron.cleanup").start(); try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) { ... } finally { span.finish(); } } | 每次执行生成独立 Trace,无法关联外部请求。 |
| 消息生产者 | 在发送消息前创建 Span 并注入上下文 | Span span = tracer.nextSpan().name("produce.message").start(); messagingTemplate.convertAndSend("topic", message, msg -> { /* 注入 B3 Header */ return msg; }); span.finish(); | 需确保消息中间件支持 Header 传递(如 Kafka/RabbitMQ)。 |
| 多线程任务 | 手动传递 Span 上下文到子线程 | Span parent = tracer.currentSpan(); executor.submit(() -> { try (Tracer.SpanInScope ws = tracer.withSpanInScope(parent)) { /* 子任务 */ } }); | 默认线程池会丢失上下文,必须手动传递。 |
| 批处理作业(Batch Job) | 为每个 Job 或 Step 创建独立 Span | JobExecutionListener 中 start Span,结束时 finish | 可结合 Spring Batch 使用,为每个 Step 创建 Span。 |
| 异步回调 | 在回调函数中恢复父 Span 上下文 | 使用 Future 或 CompletableFuture 时,在回调中 attach 原始 Span | 需保存 Span 引用并在回调中重新绑定。 |
| gRPC 调用(非 HTTP) | 使用 Brave 的 gRPC 拦截器自动处理上下文 | ManagedChannel channel = NettyChannelBuilder.forAddress("localhost", 50051).intercept(TracingClientInterceptor.create(tracing)).build(); | 需引入 brave-instrumentation-grpc 模块。 |
第 7 章:采样策略与性能优化
7.1 默认采样策略分析(ProbabilityBasedSampler)
| 属性/概念 | 说明 | 默认值 | 影响 | 备注 |
|---|
| 采样器类型 | 基于概率的采样器,随机决定是否追踪请求 | ProbabilityBasedSampler | 控制整体追踪覆盖率 | Sleuth 默认使用此策略 |
| 采样率 (sample-rate) | 每秒最多采样请求数 | 10 | 高频请求可能被限流 | 与 percentage 不同,是速率限制 |
| 采样百分比 (percentage) | 被追踪请求的百分比 | 0.1(即 10%) | 决定 Span 生成频率 | spring.sleuth.sampler.probability=0.5 表示 50% 采样 |
| 决策时机 | 在请求进入时立即决定是否创建 Trace | 请求入口(如 Filter) | 性能开销极小 | 一旦决定不追踪,则不创建任何 Span |
| Noop Span | 未被采样的请求返回的”空操作” Span | True(对未采样请求) | 不记录任何数据,不发送上报 | 调用 span.isNoop() 可判断 |
| 性能影响 | 低 | - | 仅增加轻微的随机数计算开销 | 适合生产环境长期开启 |
最佳实践: 默认 10% 采样率适用于大多数场景,平衡了数据量与性能。
7.2 配置自定义采样率(包括按请求路径过滤)
| 配置方式 | 配置项 | 示例 | 说明 | 注意事项 |
|---|
| 全局采样率 | spring.sleuth.sampler.probability | spring.sleuth.sampler.probability=0.05 | 设置全局采样比例为 5% | 推荐生产环境 0.01~0.1,测试环境可调高 |
| 每秒最大采样数 | spring.sleuth.sampler.rate | spring.sleuth.sampler.rate=5 | 每秒最多采集 5 个请求,防止突发流量打满 | 与 probability 结合使用更安全 |
| 按路径采样(自定义 Bean) | 实现 SamplerFunction<HttpRequest> | 见下方代码示例 | 可根据 URL 路径、Header 等动态决定是否采样 | 需注册为 @Bean |
| 排除健康检查路径 | 自定义逻辑 | /actuator/health, /info | 避免高频探针请求占用采样配额 | 建议排除所有监控类接口 |
| 高优先级路径全量采样 | 如 /api/order/create | 强制对核心交易路径 100% 采样 | 保障关键链路可观测性 | 可结合业务标识判断 |
代码示例:按路径自定义采样
@Bean
public SamplerFunction<HttpRequest> customSampler() {
return request -> {
String path = request.path();
// 健康检查不采样
if (path.startsWith("/actuator/health")) {
return false;
}
// 核心订单接口全量采样
if (path.startsWith("/api/order/create")) {
return true;
}
// 其他请求按 10% 概率采样
return Math.random() < 0.1;
};
}
注意: SamplerFunction<HttpRequest> 来自 brave.http.HttpRequest,需引入 brave-instrumentation-http。
7.3 关闭采样的场景与配置方法
| 场景 | 是否建议关闭采样 | 配置方式 | 说明 |
|---|
| 本地开发调试 | 否,建议调高采样率 | spring.sleuth.sampler.probability=1.0 | 全量采样便于排查问题 |
| 性能压测环境 | 是 | spring.sleuth.enabled=false | 完全关闭 Sleuth,消除一切开销 |
| 老旧微服务迁移中 | 是 | 同上 | 避免与旧监控系统冲突 |
| 资源极度受限服务 | 是 | spring.sleuth.enabled=false | 如 IoT 边缘设备 |
| 仅需日志 MDC 追踪 | 否 | 保持开启但设采样率为 0 | 仍可输出 traceId 到日志 |
关闭采样配置方法:
spring:
sleuth:
enabled: false # 完全禁用 Sleuth
# 或仅关闭采样但保留 traceId 生成
sampler:
probability: 0.0 # 不生成 Span,但仍可在日志中看到 traceId
建议: 若只需 traceId 用于日志关联,设 probability=0.0 更佳,仍可保持上下文传播。
第 8 章:与其他系统集成
8.1 Feign 客户端的透明追踪支持
| 特性 | 说明 | 配置要求 | 注意事项 |
|---|
| 自动注入 Trace 上下文 | Sleuth 自动为 Feign 请求添加 B3 Header(如 X-B3-TraceId) | 引入 spring-cloud-starter-openfeign 和 spring-cloud-starter-sleuth | 无需额外编码 |
| 生成 Client Span | 每次 Feign 调用自动生成 http.client 类型的 Span | 默认行为 | Span 名为 HTTP POST /api/users |
| 异常处理 | 调用失败时自动添加 error Tag | 自动 | 包括超时、4xx/5xx 等 |
| 自定义拦截器兼容 | 若使用 RequestInterceptor,需确保不覆盖 Sleuth Header | 正确写法见示例 | 错误写法会丢失上下文 |
| 禁用 Feign 追踪 | 特殊情况下可关闭 | spring.sleuth.feign.enabled=false | 一般不推荐 |
自定义 Feign Interceptor 正确写法:
@Bean
public RequestInterceptor customInterceptor() {
return template -> {
// 添加自定义 Header,不要 setHeader,以免覆盖
template.header("X-Custom-Header", "value");
// Sleuth 会自动处理 B3 头,无需手动添加
};
}
8.2 Spring Cloud Gateway 中的追踪传递
| 组件 | 支持情况 | 配置方式 | 说明 |
|---|
| Reactor Netty | 自动支持 | 引入 spring-cloud-gateway + sleuth | WebFlux 基于 Reactor,Sleuth 自动集成 |
| Filter 中传递上下文 | 支持 | 使用 tracing.tracer().currentSpan() | 可在自定义 GatewayFilter 中获取 Span |
| 路由转发自动注入 Header | 是 | 默认行为 | 请求下游服务时自动携带 B3 头 |
| 全局 Filter 添加 Tag | 支持 | 自定义 GlobalFilter | 可为网关层添加统一 Tag,如 gateway.route.id |
| 跨线程传播 | 支持 | Sleuth 自动处理 Reactor 上下文 | 使用 Mono.subscriberContext() 无需手动传递 |
代码示例:在 Gateway 中添加自定义 Tag
@Bean
public GlobalFilter addTagFilter(Tracer tracer) {
return (exchange, chain) -> {
Span gatewaySpan = tracer.currentSpan();
if (gatewaySpan != null) {
gatewaySpan.tag("gateway.route", exchange.getAttribute(GATEWAY_ROUTE_ATTR));
}
return chain.filter(exchange);
};
}
8.3 多线程环境下的上下文传播问题与解决方案
| 问题现象 | 原因 | 解决方案 | 示例 |
|---|
| 子线程无 traceId | 线程切换导致 MDC/Span 上下文丢失 | 使用 LazyTraceExecutor 包装线程池 | new LazyTraceExecutor(executorService) |
| Reactor 异步操作丢失上下文 | publishOn / subscribeOn 切换线程 | 使用 subscriberContext() 传递 | mono.subscriberContext(Context.of(TraceContext.class, span.context())) |
CompletableFuture 中丢失 | 默认 ForkJoinPool 不传播 | 使用 CompletableFuture.runAsync(Runnable, Executor) | 提供包装后的 Executor |
@Async 方法无追踪 | Spring AOP 代理未集成 Sleuth | 配置 TaskExecutor 为 LazyTraceExecutor | 见下方配置 |
手动 new Thread() | 无法自动传播 | 手动传递 Span 并绑定 | 不推荐,应使用线程池 |
解决方案代码示例:
// 1. 包装线程池
@Bean
public ExecutorService traceExecutor(Tracing tracing) {
ExecutorService delegate = Executors.newFixedThreadPool(5);
return new LazyTraceExecutor(tracing, delegate);
}
// 2. 配置 @Async 使用追踪线程池
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public Executor asyncExecutor(Tracing tracing) {
return new LazyTraceExecutor(tracing, Executors.newFixedThreadPool(3));
}
}
核心原则: 避免使用原始线程池,始终通过 LazyTraceExecutor 或 Reactor 的 Context 机制传播上下文。
第 9 章:监控与告警
9.1 在 Zipkin UI 中分析延迟瓶颈
| 功能/操作 | 使用方法 | 用途 | 最佳实践 |
|---|
| 按服务/接口搜索 Trace | 在搜索栏输入服务名(如 user-service)或 Span 名称(如 http:/api/users) | 快速定位特定服务的调用链 | 结合时间范围缩小排查范围 |
| 查看完整调用链(Trace) | 点击某条 Trace 记录,查看所有关联 Span 的时间轴 | 分析跨服务调用的完整路径 | 关注跨服务边界的时间间隔 |
| 识别高延迟 Span | 观察各 Span 的持续时间(Duration),颜色越深表示耗时越长 | 定位性能瓶颈点 | 红色 Span 优先排查(通常为 error 或超时) |
| 查看 Span 详情 | 点击具体 Span,查看 Tags、Annotations、时间戳等 | 分析错误原因或上下文信息 | 检查 error、http.status_code、db.statement 等关键 Tag |
| 对比多个 Trace | 使用”Compare”功能并列查看多个相似 Trace | 识别性能波动或异常模式 | 用于 A/B 测试或版本对比 |
| 依赖关系图(Dependencies) | 点击顶部”Dependencies”标签页 | 查看服务间调用拓扑 | 识别循环依赖、单点故障风险 |
| 筛选异常 Trace | 添加 Tag 过滤:error=true | 快速定位失败请求 | 常用于故障复盘 |
技巧: 使用 minDuration 参数过滤出耗时超过阈值的 Trace(如 minDuration=500ms)。
9.2 结合 Prometheus 与 Grafana 展示追踪指标
| 指标类型 | 暴露方式 | 示例指标 | Grafana 展示建议 |
|---|
| Span 数量(按状态) | Brave + Micrometer → Prometheus | spans_started_total{status="error"} | 柱状图 + 告警 |
| 平均延迟(P95/P99) | Micrometer Timer 记录 Span Duration | integration_call_duration_seconds{quantile="0.95"} | 折线图,多服务对比 |
| 采样率监控 | 自定义 Counter 记录采样决策 | trace_sampled_total{sampled="true"} | 饼图展示采样比例 |
| 上报成功率 | 监控 Reporter 发送失败次数 | zipkin_reporter_spans_failed_total | 告警触发条件 |
| 服务调用频率 | 记录 Span 开始次数 | spans_started_total | 热力图或趋势图 |
配置步骤:
- 引入依赖:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
-
暴露 /actuator/prometheus 端点
-
Prometheus 配置抓取:
scrape_configs:
- job_name: 'sleuth-services'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['service-a:8080', 'service-b:8080']
- Grafana 导入仪表板(推荐 ID:2588 或自定义)
建议: 将关键接口的 P95 延迟作为 SLO 核心指标进行监控。
9.3 基于异常 Span 触发告警的实践
| 告警类型 | 触发条件 | 技术实现 | 告警渠道 |
|---|
| 高频错误 Span | 单服务 error=true 的 Span 数 5 分钟内 > 100 | Prometheus + Alertmanager:rate(spans_started_total{error="true"}[5m]) > 100 | 钉钉、企业微信、邮件 |
| 核心接口超时 | /api/order/create P99 > 2s | 自定义 Timer + 告警规则 | 同上 |
| Zipkin 上报失败 | zipkin_reporter_spans_failed_total 增加 | 监控 Reporter 内部指标 | 优先级高,影响可观测性 |
| 服务无 Trace 上报 | 某服务连续 5 分钟无任何 Span 上报 | up{job="sleuth-services"} == 0 OR absent(spans_started_total) | 可能服务宕机或 Sleuth 故障 |
| 采样率异常波动 | 采样率突降至 0 | 监控 trace_sampled_total 增长率 | 配置错误或环境问题 |
Prometheus 告警示例:
groups:
- name: tracing-alerts
rules:
- alert: HighErrorRateInTraces
expr: rate(spans_started_total{error="true"}[5m]) > 50
for: 2m
labels:
severity: warning
annotations:
summary: "High error rate in traces ({{ $value }} errors/min)"
description: "Service {{ $labels.application }} has high trace error rate."
最佳实践: 告警应结合日志、Metrics、Tracing 三者联动(Golden Signals)。
第 10 章:生产环境最佳实践
10.1 数据脱敏与敏感信息过滤
| 敏感数据类型 | 风险 | 过滤方案 | 实现方式 |
|---|
| 用户密码 | 泄露账户安全 | 不记录或打码 | 自定义 HttpRequestParser |
| 身份证/手机号 | 个人隐私泄露 | 正则替换为 *** | 在 Tag 中处理 |
| 支付信息 | 金融安全风险 | 完全禁止记录 | 拦截器中丢弃敏感字段 |
| 请求 Body | 可能含敏感数据 | 选择性记录或哈希 | 配置 spring.sleuth.http.legacy.enabled=false 并自定义 |
| Header 中 Token | 如 Authorization: Bearer ... | 仅记录是否存在,不记录值 | 使用 TraceKeys 配置 |
代码示例:自定义 HTTP 解析器过滤敏感信息
@Bean
public HttpRequestParser httpRequestParser() {
return (request, span) -> {
// 不记录 Authorization Header 全文
if (request.header("Authorization") != null) {
span.tag("http.header.authorization", "present");
}
// 过滤请求路径中的手机号
String path = request.path();
if (path.matches(".*/\\d{11}.*")) {
span.tag("http.path", path.replaceAll("\\d{11}", "****"));
}
};
}
合规建议: 遵循 GDPR、网络安全法等要求,敏感字段默认不采集。
10.2 高并发下的性能影响评估与调优
| 性能影响项 | 默认开销 | 调优建议 | 监控指标 |
|---|
| Span 创建/销毁 | ~0.1ms/请求 | 使用异步上报(Kafka/RabbitMQ) | GC 频率、CPU 使用率 |
| MDC 上下文切换 | 极低 | 避免在日志中频繁输出 traceId | 日志写入延迟 |
| 采样率过高 | 显著增加 CPU 和网络 | 生产环境设为 0.01~0.1 | 上报延迟、Zipkin 吞吐 |
| 同步 HTTP 上报 | 阻塞主线程 | 切换为消息队列异步上报 | 请求 P99 延迟 |
| 过度埋点 | 生成大量 Span | 仅在关键路径埋点 | Span 数/请求 |
性能压测建议:
- 开启 Sleuth 前后进行 JMeter 压测对比
- 监控:
/actuator/metrics/jvm.memory.used、/actuator/metrics/http.server.requests
- 目标:P99 延迟增加 < 5%
结论: 合理配置下,Sleuth 对性能影响通常 < 3%,可接受。
10.3 升级到 OpenTelemetry 的过渡路径
| 阶段 | 目标 | 实施步骤 | 注意事项 |
|---|
| 阶段一:共存运行 | Sleuth 与 OTel 并行采集 | 使用 opentelemetry-spring-boot-starter | 避免冲突,可先在非核心服务试点 |
| 阶段二:上报兼容 | OTel 数据发送至 Zipkin | 配置 OTel Exporter → Zipkin | OpenTelemetry 支持 Zipkin v2 格式 |
| 阶段三:逐步迁移 | 替换 Sleuth 为 OTel API | 将 Tracer 调用替换为 OpenTelemetry.getTracer() | API 不兼容,需代码改造 |
| 阶段四:统一后端 | 切换至 OTLP 协议 + Tempo/Jaeger | 使用 OTLP gRPC 上报 | 提升性能与功能 |
| 阶段五:完全下线 | 移除 Spring Cloud Sleuth 依赖 | 验证所有追踪功能正常 | 回滚预案准备 |
依赖替换示例:
<!-- 旧 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<!-- 新 -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-spring-boot-starter</artifactId>
</dependency>
建议: Spring Cloud 2022+ 已推荐使用 OpenTelemetry,新项目直接采用 OTel。