背景

本文是《Java 后端从小白到大神》修仙系列之框架学习,Spring Cloud 微服务实战系列的 第七篇。Spring Cloud 是构建微服务架构的基石,拥有完整的服务治理生态,在云原生架构中广泛应用。本系列将从架构认知到实际工程,逐步构建一套企业级 Spring Cloud 微服务项目。若想详细学习请点击首篇博文开始,现在开始学习。

文章概览

链路追踪体系构建:Micrometer Tracing + Zipkin + 日志关联:

  1. 链路追踪演进:从 Sleuth 到 Micrometer Tracing
  2. Micrometer Tracing 原理与 Zipkin 可视化接入
  3. Gateway + Feign 链路自动传递
  4. 日志注入 TraceId / SpanId
  5. 异步线程链路追踪传播

1. 链路追踪演进:从 Sleuth 到 Micrometer Tracing

旧的项目可能在使用 Spring Cloud Sleuth 来实现链路追踪功能,但自 2022 年起已不再维护,Spring 官方推荐使用 Micrometer Tracing 替代。

Micrometer Tracing 更加轻量、模块化,并支持多种 backend,如 Zipkin、Jaeger、OpenTelemetry,是现代云原生环境下更优的选择。

2. Micrometer Tracing 集成与 Zipkin 可视化

在所有需要链路追踪的服务中加入以下依赖:

1
2
3
4
5
6
7
8
<dependency>
  <groupId>io.micrometer</groupId>
  <artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
  <groupId>io.zipkin.reporter2</groupId>
  <artifactId>zipkin-reporter-brave</artifactId>
</dependency>

application.yaml 配置

1
2
3
4
management:
  tracing:
    sampling:
      probability: 1.0  # 100%采样,生产可调整为0.1或动态配置

启动 Zipkin 服务

使用 docker-compose:

1
2
3
4
zipkin:
  image: openzipkin/zipkin
  ports:
    - "9411:9411"

访问:http://localhost:9411 查看完整的请求调用链!

3. Gateway + Feign 链路追踪自动传递

Micrometer Tracing 与 Spring Cloud Gateway、OpenFeign 自动集成:

  • Gateway:生成 traceId,传递至下游服务
  • Feign:自动携带上下文,无需手动注入

无需额外配置,只需在 gateway-service、user-service、task-service 中接入依赖和配置即可。

4. 日志注入 TraceId / SpanId

通过日志 MDC 机制,自动注入链路 ID 信息:

1
2
3
logging:
  pattern:
    level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"

输出示例:

1
INFO [gateway-service,9fabc1234,3bcd4567] 请求进入网关

方便结合 ELK、SkyWalking 等日志分析系统进行统一追踪。

通过 TraceId 可以在日志聚合平台(如 ELK)中快速定位某一次请求的所有日志记录。

5. 异步线程上下文传播支持

Micrometer Tracing 支持异步传播:

1
2
3
4
5
@Async
public CompletableFuture<Void> runAsync() {
    log.info("异步调用,依旧包含TraceId");
    return CompletableFuture.completedFuture(null);
}

无需额外操作,自动传递上下文信息。

6. 搭建两个微服务

  • user-service:提供用户信息接口(Feign 被调用方)
  • task-service:通过 FeignClient 调用 user-service 获取用户信息

user-service

1
2
3
4
5
6
7
8
9
user-service
├── controller/UserController.java
├── service/UserService.java
├── repository/UserRepository.java
├── entity/User.java
├── dto/UserDTO.java
├── resources/application.yaml
├── resources/bootstrap.yaml
└── pom.xml
user-service 源代码
1
2
3
4
5
6
7
8
@RestController
@RequestMapping("/users")
public class UserController {
    @GetMapping("/{id}")
    public UserDTO getUser(@PathVariable Long id) {
        return new UserDTO(id, "用户" + id);
    }
}
1
2
3
4
5
6
7
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserDTO {
    private Long id;
    private String name;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Entity
@Table(name = "users")
@Data
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;
    private String email;
}
1
2
public interface UserRepository extends JpaRepository<User, Long> {
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    /**
     * 根据用户ID获取用户信息
     */
    public UserDTO getUserById(Long id) {
        User user = userRepository.findById(id).orElse(null);
        if (user == null) return null;
        return new UserDTO(user.getId(), user.getUsername());
    }
}
项目配置文件

application.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
server:
  port: 9002

spring:
  application:
    name: user-service
  profiles:
    active: dev
  
  datasource:
    url: jdbc:mysql://localhost:3306/cloud_task?useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root

  jpa:
    hibernate:
      ddl-auto: update  # 或 none、create、validate(根据你需求)
    show-sql: true
    database-platform: org.hibernate.dialect.MySQL8Dialect  

  cloud:
    nacos:
      discovery:
        namespace: 47bd187a-3446-43a7-8a56-573dc8abc147

bootstrap.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
server:
  port: 9002

spring:
  application:
    name: user-service
  profiles:
    active: dev
  
  datasource:
    url: jdbc:mysql://localhost:3306/cloud_task?useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root

  jpa:
    hibernate:
      ddl-auto: update  # 或 none、create、validate(根据你需求)
    show-sql: true
    database-platform: org.hibernate.dialect.MySQL8Dialect  

  cloud:
    nacos:
      discovery:
        namespace: 47bd187a-3446-43a7-8a56-573dc8abc147

pom.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.yutao</groupId>
    <artifactId>cloudtask2</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>

  <groupId>com.yutao</groupId>
  <artifactId>user-service</artifactId>
  <version>1.0-SNAPSHOT</version>

  <dependencies>
    <!-- Spring Boot 核心 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <!-- Spring Boot Web -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Spring Cloud Nacos Config -->
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>

    <!-- 可选:Nacos Discovery(如果需要注册自身) -->
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>

    <!-- 动态刷新配置所需 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

    <!-- 可选:暴露刷新端点 -->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-bootstrap</artifactId>
    </dependency>

    <!-- 链路追踪 -->
    <dependency>
      <groupId>io.micrometer</groupId>
      <artifactId>micrometer-tracing-bridge-brave</artifactId>
    </dependency>
    <dependency>
      <groupId>io.zipkin.reporter2</groupId>
      <artifactId>zipkin-reporter-brave</artifactId>
    </dependency>

    <!-- Spring Data JPA -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!-- MySQL 驱动 -->
    <dependency>
      <groupId>com.mysql</groupId>
      <artifactId>mysql-connector-j</artifactId>
      <scope>runtime</scope>
    </dependency>

  </dependencies>

</project>

task-service

1
2
3
4
5
6
7
8
task-service
├── controller/TaskController.java
├── feign/UserClient.java
├── dto/TaskDTO.java
├── service/TaskService.java
├── resources/application.yaml
├── resources/bootstrap.yaml
└── pom.xml
task-service 源代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@RestController
@RequestMapping("/tasks")
public class TaskController {
    @Autowired
    private UserClient userClient;

    @GetMapping("/create/{userId}")
    public String createTask(@PathVariable Long userId) {
        UserDTO user = userClient.getUserById(userId);
        return "为用户 " + user.getUsername() + " 创建任务成功";
    }
}
1
2
3
4
5
@FeignClient(name = "user-service")
public interface UserClient {
    @GetMapping("/users/{id}")
    UserDTO getUser(@PathVariable("id") Long id);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@Service
public class TaskService {

    // 假设调用 FeignClient 获取用户信息
    @Autowired
    private UserClient userClient;

    public TaskDTO getTaskWithUser(Long taskId) {
        // 模拟任务查询
        TaskDTO task = new TaskDTO(taskId, "测试任务", 1001L);

        // 通过 Feign 调用 user-service 获取用户信息
        UserDTO user = userClient.getUserById(task.getUserId());
        task.setUser(user);
        return task;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TaskDTO {
    private Long id;
    private String name;
    private Long userId;

    private UserDTO user; // 用户信息


    public TaskDTO(Long id, String name, long userId) {
        this.id = id;
        this.name = name;
        this.userId = userId;
    }
}
项目配置文件

application.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
server:
  port: 9001

spring:
  application:
    name: task-service
  profiles:
    active: dev

  cloud:
    nacos:
      discovery:
        namespace: 47bd187a-3446-43a7-8a56-573dc8abc147

bootstrap.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
spring:
  application:
    name: task-service
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yaml
        namespace: 47bd187a-3446-43a7-8a56-573dc8abc147
        group: DEFAULT_GROUP
      discovery:
        namespace: 47bd187a-3446-43a7-8a56-573dc8abc147
  profiles:
    active: dev

pom.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.yutao</groupId>
        <artifactId>cloudtask2</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <groupId>com.yutao</groupId>
    <artifactId>task-service</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- Spring Boot 核心 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Spring Cloud Nacos Config -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

        <!-- 可选:Nacos Discovery(如果需要注册自身) -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!-- 动态刷新配置所需 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- 可选:暴露刷新端点 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>

        <!-- 链路追踪 -->
        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-tracing-bridge-brave</artifactId>
        </dependency>
        <dependency>
            <groupId>io.zipkin.reporter2</groupId>
            <artifactId>zipkin-reporter-brave</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>com.yutao</groupId>
            <artifactId>user-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

    </dependencies>
</project>

总结

从 Sleuth 到 Zipkin 的链路收集与可视化。服务间的 TraceId 自动传递,为我们后续日志追踪、接口耗时分析、异步问题定位提供了扎实基础。