Skip to content

Spring Cloud 微服务 ​

基于 Spring Cloud Alibaba 的微服务架构全面指南,涵盖服务注册发现、配置管理、网关、熔断降级、分布式事务及链路追踪等核心组件。


目录 ​

  1. 微服务概述
  2. Nacos 服务发现与配置中心
  3. Spring Cloud Gateway 网关
  4. Sentinel 流量防卫兵
  5. OpenFeign 声明式调用
  6. Seata 分布式事务
  7. 分布式链路追踪
  8. 其他组件
  9. 项目结构实践

一、微服务概述 ​

1.1 单体架构 vs 微服务架构 ​

维度单体架构微服务架构
定义所有功能模块打包在一个进程中部署每个功能模块独立部署为小型服务
开发效率项目初期开发快,后期耦合严重每个服务独立开发,技术栈灵活
部署发布即停服,影响全部功能独立部署,灰度发布
伸缩性只能整体水平扩展,资源浪费按需伸缩,精细化资源分配
容错性一个模块 OOM 拖垮整个进程故障隔离,熔断降级保护
团队协作代码冲突频繁,沟通成本高每个团队负责一个或几个服务
运维成本低,仅需维护一个应用高,需要服务发现/配置中心/网关/监控等基础设施

1.2 微服务的核心挑战 ​

┌─────────────────────────────────────────────────────┐
│                  微服务架构挑战                      │
├─────────────────────────────────────────────────────┤
│  1. 服务发现 —— 服务如何找到彼此                     │
│  2. 配置管理 —— 配置变更如何实时生效                 │
│  3. API 网关 —— 统一入口、认证、路由、限流            │
│  4. 熔断降级 —— 防止级联故障(雪崩效应)              │
│  5. 负载均衡 —— 客户端/服务端请求分发                │
│  6. 分布式事务 —— 跨服务数据一致性                   │
│  7. 链路追踪 —— 全链路调用可视化                     │
│  8. 日志聚合 —— 分散日志的统一采集与检索               │
│  9. 监控告警 —— 服务健康状态实时感知                  │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│                  微服务架构挑战                      │
├─────────────────────────────────────────────────────┤
│  1. 服务发现 —— 服务如何找到彼此                     │
│  2. 配置管理 —— 配置变更如何实时生效                 │
│  3. API 网关 —— 统一入口、认证、路由、限流            │
│  4. 熔断降级 —— 防止级联故障(雪崩效应)              │
│  5. 负载均衡 —— 客户端/服务端请求分发                │
│  6. 分布式事务 —— 跨服务数据一致性                   │
│  7. 链路追踪 —— 全链路调用可视化                     │
│  8. 日志聚合 —— 分散日志的统一采集与检索               │
│  9. 监控告警 —— 服务健康状态实时感知                  │
└─────────────────────────────────────────────────────┘

1.3 Spring Cloud 版本选型 ​

Spring Cloud 与 Spring Boot 以及 Spring Cloud Alibaba 之间存在严格的版本兼容关系,选型不当将导致启动失败或运行时异常。

Spring Cloud 2023.x 系列(推荐最新稳定版)

Spring Cloud 版本Spring Boot 版本Spring Cloud Alibaba说明
2023.0.x3.2.x2023.0.x最新主线,JDK 17+ 推荐
2022.0.x3.1.x / 3.0.x2022.0.x较成熟的版本线
2021.0.x2.7.x / 2.6.x2021.0.xLTS 版本,JDK 11 推荐

版本命名说明:Spring Cloud 从 2020.0.x 开始改用日历版本命名(之前是伦敦地铁站名如 Hoxton / Greenwich)。

Alibaba 组件版本对照(以 2022.x 为例)

xml
<properties>
    <spring-cloud.version>2022.0.4</spring-cloud.version>
    <spring-cloud-alibaba.version>2022.0.0.0</spring-cloud-alibaba.version>
    <spring-boot.version>3.1.5</spring-boot.version>
</properties>
<properties>
    <spring-cloud.version>2022.0.4</spring-cloud.version>
    <spring-cloud-alibaba.version>2022.0.0.0</spring-cloud-alibaba.version>
    <spring-boot.version>3.1.5</spring-boot.version>
</properties>

版本选型建议:

  • 新项目建议直接上 Spring Boot 3.2 + Spring Cloud 2023.x + JDK 17,Loom / Record / Pattern Matching 等新特性对微服务开发体验提升明显。
  • 存量项目升级需注意:Spring Boot 3.x 移除了 spring.factories 自动配置机制,改用 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports;同时 Jakarta EE 取代了 Java EE(javax.* -> jakarta.*)。
  • 生产环境应以 Spring Cloud Alibaba 官方发布的推荐版本组合为准,避免使用 Pre-release 版本。

1.4 整体架构预览 ​

                          ┌─────────────┐
                          │  客户端/APP   │
                          └──────┬──────┘
                                 │
                          ┌──────▼──────┐
                          │  Gateway    │  ← 认证 / 限流 / 路由 / 跨域
                          └──────┬──────┘
                                 │
             ┌───────────────────┼───────────────────┐
             │                   │                   │
       ┌─────▼─────┐      ┌─────▼─────┐      ┌─────▼─────┐
       │  Auth 服务 │      │ Business  │      │  Order    │
       │           │      │   服务     │      │  服务      │
       └─────┬─────┘      └─────┬─────┘      └─────┬─────┘
             │                   │                   │
             └───────────────────┼───────────────────┘
                                 │
                    ┌────────────▼────────────┐
                    │       Nacos Server       │ ← 注册中心 + 配置中心
                    └────────────▲────────────┘
                                 │
                    ┌────────────▼────────────┐
                    │     Sentinel Dashboard   │ ← 流控 / 熔断监控
                    └────────────▲────────────┘
                                 │
                    ┌────────────▼────────────┐
                    │     Zipkin / SkyWalking  │ ← 链路追踪
                    └─────────────────────────┘
                          ┌─────────────┐
                          │  客户端/APP   │
                          └──────┬──────┘
                                 │
                          ┌──────▼──────┐
                          │  Gateway    │  ← 认证 / 限流 / 路由 / 跨域
                          └──────┬──────┘
                                 │
             ┌───────────────────┼───────────────────┐
             │                   │                   │
       ┌─────▼─────┐      ┌─────▼─────┐      ┌─────▼─────┐
       │  Auth 服务 │      │ Business  │      │  Order    │
       │           │      │   服务     │      │  服务      │
       └─────┬─────┘      └─────┬─────┘      └─────┬─────┘
             │                   │                   │
             └───────────────────┼───────────────────┘
                                 │
                    ┌────────────▼────────────┐
                    │       Nacos Server       │ ← 注册中心 + 配置中心
                    └────────────▲────────────┘
                                 │
                    ┌────────────▼────────────┐
                    │     Sentinel Dashboard   │ ← 流控 / 熔断监控
                    └────────────▲────────────┘
                                 │
                    ┌────────────▼────────────┐
                    │     Zipkin / SkyWalking  │ ← 链路追踪
                    └─────────────────────────┘

二、Nacos 服务发现与配置中心 ​

Nacos(Dynamic Naming and Configuration Service)是阿里巴巴开源的服务发现与配置管理平台,在 Spring Cloud Alibaba 体系中充当着注册中心和配置中心的双重角色。

2.1 Nacos 核心概念 ​

概念说明
Service服务实例的逻辑分组
Instance提供服务的具体进程实例(IP:Port)
Cluster同一服务下的实例集群(如北京/上海机房)
Data ID配置文件的唯一标识,通常为 ${spring.application.name}-${profile}.${file-extension}
Group配置分组,默认 DEFAULT_GROUP
Namespace命名空间,用于多环境隔离(dev / test / prod)
Protect Threshold保护阈值(0-1),当健康实例比例低于该值时触发保护模式,将不健康实例也返回给调用方

2.2 搭建 Nacos Server ​

Docker 方式(推荐开发环境使用) ​

yaml
# docker-compose.yml
version: '3.8'
services:
  nacos:
    image: nacos/nacos-server:v2.2.3
    container_name: nacos-server
    environment:
      MODE: standalone                # 单机模式
      JVM_XMS: 256m
      JVM_XMX: 512m
      PREFER_HOST_MODE: hostname
    ports:
      - "8848:8848"                   # 客户端通信端口
      - "9848:9848"                   # 客户端 gRPC 端口(v2 新增)
      - "9849:9849"                   # 服务端 gRPC 端口
    volumes:
      - ./nacos/logs:/home/nacos/logs
      - ./nacos/data:/home/nacos/data
# docker-compose.yml
version: '3.8'
services:
  nacos:
    image: nacos/nacos-server:v2.2.3
    container_name: nacos-server
    environment:
      MODE: standalone                # 单机模式
      JVM_XMS: 256m
      JVM_XMX: 512m
      PREFER_HOST_MODE: hostname
    ports:
      - "8848:8848"                   # 客户端通信端口
      - "9848:9848"                   # 客户端 gRPC 端口(v2 新增)
      - "9849:9849"                   # 服务端 gRPC 端口
    volumes:
      - ./nacos/logs:/home/nacos/logs
      - ./nacos/data:/home/nacos/data

启动命令:

bash
docker-compose up -d
# 访问控制台 http://localhost:8848/nacos  默认用户名/密码: nacos/nacos
docker-compose up -d
# 访问控制台 http://localhost:8848/nacos  默认用户名/密码: nacos/nacos

使用外部 MySQL(生产环境推荐) ​

Nacos 默认使用内嵌 Derby 数据库,仅适用于单机演示。生产环境必须切换为 MySQL:

yaml
services:
  nacos:
    image: nacos/nacos-server:v2.2.3
    environment:
      MODE: standalone
      SPRING_DATASOURCE_PLATFORM: mysql
      MYSQL_SERVICE_HOST: 10.0.0.1
      MYSQL_SERVICE_DB_NAME: nacos_config
      MYSQL_SERVICE_USER: nacos
      MYSQL_SERVICE_PASSWORD: nacos123
      MYSQL_SERVICE_DB_PARAM: characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false
services:
  nacos:
    image: nacos/nacos-server:v2.2.3
    environment:
      MODE: standalone
      SPRING_DATASOURCE_PLATFORM: mysql
      MYSQL_SERVICE_HOST: 10.0.0.1
      MYSQL_SERVICE_DB_NAME: nacos_config
      MYSQL_SERVICE_USER: nacos
      MYSQL_SERVICE_PASSWORD: nacos123
      MYSQL_SERVICE_DB_PARAM: characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false

需提前执行 Nacos 官方提供的 mysql-schema.sql 脚本初始化数据库。

2.3 服务注册与发现 ​

引入依赖 ​

xml
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

配置 application.yml ​

yaml
spring:
  application:
    name: user-service                     # 服务名,将注册到 Nacos
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848        # Nacos 地址
        namespace: dev                     # 命名空间 ID(可选)
        group: DEFAULT_GROUP               # 分组(可选)
        weight: 1.0                        # 权重,越高的实例被调用越多
        cluster-name: BJ                   # 集群名称(可选,用于同城优先)
        ephemeral: true                    # 是否为临时实例(默认 true)
        # metadata 可以携带自定义信息
        metadata:
          version: v1
          region: beijing
spring:
  application:
    name: user-service                     # 服务名,将注册到 Nacos
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848        # Nacos 地址
        namespace: dev                     # 命名空间 ID(可选)
        group: DEFAULT_GROUP               # 分组(可选)
        weight: 1.0                        # 权重,越高的实例被调用越多
        cluster-name: BJ                   # 集群名称(可选,用于同城优先)
        ephemeral: true                    # 是否为临时实例(默认 true)
        # metadata 可以携带自定义信息
        metadata:
          version: v1
          region: beijing

启用服务发现 ​

java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient      // 启用服务发现(Spring Cloud 通用注解)
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient      // 启用服务发现(Spring Cloud 通用注解)
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}

注意:Spring Cloud Alibaba 中,引入 nacos-discovery-starter 后,即使不写 @EnableDiscoveryClient,服务也会自动注册到 Nacos。该注解更多是标记性的,但在代码可读性和跨平台兼容性上仍然推荐保留。

使用 DiscoveryClient 获取服务实例 ​

java
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/discovery")
public class DiscoveryController {

    private final DiscoveryClient discoveryClient;

    public DiscoveryController(DiscoveryClient discoveryClient) {
        this.discoveryClient = discoveryClient;
    }

    @GetMapping("/instances/{serviceId}")
    public List<?> getInstances(@PathVariable String serviceId) {
        // 获取某个服务的所有实例
        return discoveryClient.getInstances(serviceId);
    }

    @GetMapping("/services")
    public List<String> getServices() {
        // 获取所有已注册的服务名
        return discoveryClient.getServices();
    }
}
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/discovery")
public class DiscoveryController {

    private final DiscoveryClient discoveryClient;

    public DiscoveryController(DiscoveryClient discoveryClient) {
        this.discoveryClient = discoveryClient;
    }

    @GetMapping("/instances/{serviceId}")
    public List<?> getInstances(@PathVariable String serviceId) {
        // 获取某个服务的所有实例
        return discoveryClient.getInstances(serviceId);
    }

    @GetMapping("/services")
    public List<String> getServices() {
        // 获取所有已注册的服务名
        return discoveryClient.getServices();
    }
}

健康检查机制 ​

Nacos 客户端默认每 5 秒 向服务端发送一次心跳。服务端在 15 秒 内未收到心跳则将该实例标记为不健康,30 秒 未收到则删除实例。

yaml
spring:
  cloud:
    nacos:
      discovery:
        heart-beat-interval: 5000          # 心跳间隔(毫秒)
        heart-beat-timeout: 15000          # 心跳超时(毫秒)
        ip-delete-timeout: 30000           # 实例删除超时(毫秒)
spring:
  cloud:
    nacos:
      discovery:
        heart-beat-interval: 5000          # 心跳间隔(毫秒)
        heart-beat-timeout: 15000          # 心跳超时(毫秒)
        ip-delete-timeout: 30000           # 实例删除超时(毫秒)

2.4 Nacos 配置中心 ​

引入依赖 ​

xml
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

注意:配置中心的启动优先于应用自身的 application.yml,因此相关配置需要放到 bootstrap.yml(Spring Boot 3.x 中如使用了 Spring Cloud 2022+,应使用 spring.config.import 替代传统 bootstrap 方式,下文会说明)。

bootstrap.yml 方式(传统) ​

yaml
# bootstrap.yml — 优先级最高,在 application.yml 之前加载
spring:
  application:
    name: user-service
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        namespace: dev
        group: DEFAULT_GROUP
        file-extension: yaml               # 配置文件格式
        refresh-enabled: true              # 启用动态刷新
        # 共享配置(多个服务共用)
        shared-configs:
          - data-id: common-database.yaml
            group: SHARED_GROUP
            refresh: true
          - data-id: common-redis.yaml
            group: SHARED_GROUP
            refresh: true
        # 扩展配置
        extension-configs:
          - data-id: user-service-ext.yaml
            group: DEFAULT_GROUP
            refresh: true
# bootstrap.yml — 优先级最高,在 application.yml 之前加载
spring:
  application:
    name: user-service
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        namespace: dev
        group: DEFAULT_GROUP
        file-extension: yaml               # 配置文件格式
        refresh-enabled: true              # 启用动态刷新
        # 共享配置(多个服务共用)
        shared-configs:
          - data-id: common-database.yaml
            group: SHARED_GROUP
            refresh: true
          - data-id: common-redis.yaml
            group: SHARED_GROUP
            refresh: true
        # 扩展配置
        extension-configs:
          - data-id: user-service-ext.yaml
            group: DEFAULT_GROUP
            refresh: true

Spring Boot 3.x 推荐方式(不使用 bootstrap) ​

在 application.yml 中通过 spring.config.import 导入 Nacos 配置:

yaml
spring:
  application:
    name: user-service
  config:
    import:
      - nacos:user-service.yaml?group=DEFAULT_GROUP&refreshEnabled=true
      - nacos:common-database.yaml?group=SHARED_GROUP
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        namespace: dev
spring:
  application:
    name: user-service
  config:
    import:
      - nacos:user-service.yaml?group=DEFAULT_GROUP&refreshEnabled=true
      - nacos:common-database.yaml?group=SHARED_GROUP
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        namespace: dev

配置在 Nacos 控制台的格式 ​

在 Nacos 配置列表新建配置:

  • Data ID:user-service-dev.yaml(对应 ${spring.application.name}-${profile}.${file-extension})
  • Group:DEFAULT_GROUP
  • 格式:YAML
yaml
# Data ID: user-service-dev.yaml
server:
  port: 8081

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/user_db?useSSL=false&serverTimezone=Asia/Shanghai
    username: dev_user
    password: dev_pass
    driver-class-name: com.mysql.cj.jdbc.Driver

logging:
  level:
    com.example: DEBUG
# Data ID: user-service-dev.yaml
server:
  port: 8081

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/user_db?useSSL=false&serverTimezone=Asia/Shanghai
    username: dev_user
    password: dev_pass
    driver-class-name: com.mysql.cj.jdbc.Driver

logging:
  level:
    com.example: DEBUG

动态刷新配置 ​

java
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/config")
@RefreshScope                          // 标注在类上,配置变更时刷新 Bean
public class ConfigController {

    @Value("${spring.datasource.url}")
    private String datasourceUrl;

    @Value("${logging.level.com.example:INFO}")
    private String logLevel;

    @GetMapping("/show")
    public String show() {
        return "Datasource URL: " + datasourceUrl + ", Log Level: " + logLevel;
    }
}
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/config")
@RefreshScope                          // 标注在类上,配置变更时刷新 Bean
public class ConfigController {

    @Value("${spring.datasource.url}")
    private String datasourceUrl;

    @Value("${logging.level.com.example:INFO}")
    private String logLevel;

    @GetMapping("/show")
    public String show() {
        return "Datasource URL: " + datasourceUrl + ", Log Level: " + logLevel;
    }
}

当 Nacos 上的配置发生变化并点击发布后,Nacos 会推送变更通知给客户端,@RefreshScope 标注的 Bean 会自动重建,从而实现配置热更新。

Data ID 匹配规则 ​

Nacos Config Starter 加载配置时,会按以下顺序查找 Data ID(优先级从高到低):

${spring.application.name}-${profile}.${file-extension}    # 带 profile 的配置
${spring.application.name}.${file-extension}               # 不带 profile 的配置
application                                          # 应用级默认配置
${spring.application.name}-${profile}.${file-extension}    # 带 profile 的配置
${spring.application.name}.${file-extension}               # 不带 profile 的配置
application                                          # 应用级默认配置

如果应用激活了 dev profile,则优先加载 user-service-dev.yaml,未匹配到再尝试 user-service.yaml。

多环境管理方案 ​

使用 Namespace 实现环境级别的完全隔离:

Namespace: dev
  └── Data ID: user-service.yaml  (数据库配测试库)
  └── Data ID: order-service.yaml

Namespace: prod
  └── Data ID: user-service.yaml  (数据库配生产库)
  └── Data ID: order-service.yaml
Namespace: dev
  └── Data ID: user-service.yaml  (数据库配测试库)
  └── Data ID: order-service.yaml

Namespace: prod
  └── Data ID: user-service.yaml  (数据库配生产库)
  └── Data ID: order-service.yaml

应用启动时通过 spring.cloud.nacos.config.namespace 指定所属环境:

yaml
# application-dev.yml
spring:
  cloud:
    nacos:
      config:
        namespace: dev

# application-prod.yml
spring:
  cloud:
    nacos:
      config:
        namespace: prod
# application-dev.yml
spring:
  cloud:
    nacos:
      config:
        namespace: dev

# application-prod.yml
spring:
  cloud:
    nacos:
      config:
        namespace: prod

启动命令:

bash
# 开发环境
java -jar user-service.jar --spring.profiles.active=dev

# 生产环境
java -jar user-service.jar --spring.profiles.active=prod
# 开发环境
java -jar user-service.jar --spring.profiles.active=dev

# 生产环境
java -jar user-service.jar --spring.profiles.active=prod

配置优先级(从高到低) ​

1. 启动命令行参数(--key=value)
2. 操作系统环境变量
3. application-{profile}.yml(本地)
4. application.yml(本地)
5. Nacos 带 profile 的配置(user-service-dev.yaml)
6. Nacos 不带 profile 的配置(user-service.yaml)
7. Nacos 共享配置(shared-configs)
8. Nacos 扩展配置(extension-configs)
1. 启动命令行参数(--key=value)
2. 操作系统环境变量
3. application-{profile}.yml(本地)
4. application.yml(本地)
5. Nacos 带 profile 的配置(user-service-dev.yaml)
6. Nacos 不带 profile 的配置(user-service.yaml)
7. Nacos 共享配置(shared-configs)
8. Nacos 扩展配置(extension-configs)

三、Spring Cloud Gateway 网关 ​

Spring Cloud Gateway 是基于 Spring WebFlux 的 API 网关,底层使用 Reactor 框架和 Netty 运行时,非阻塞 IO 支撑高并发。注意:Gateway 依赖 Netty,因此不能在传统的 Servlet 容器(Tomcat)中运行,也不兼容 spring-boot-starter-web。

3.1 核心概念 ​

概念说明
Route(路由)网关的基本构建块,由 ID、目标 URI、断言集合、过滤器集合组成
Predicate(断言)Java 8 Predicate 的实现,匹配 HTTP 请求条件,决定是否路由
Filter(过滤器)对请求和响应进行修改的拦截器,分为 GatewayFilter(局部)和 GlobalFilter(全局)

3.2 基础依赖与配置 ​

xml
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 配合 Nacos 服务发现 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 配合 Nacos 服务发现 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
yaml
server:
  port: 8080

spring:
  application:
    name: api-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      routes:
        - id: user-service                         # 路由 ID,唯一
          uri: lb://user-service                   # lb:// 表示从 Nacos 负载均衡获取服务
          predicates:
            - Path=/api/user/**                    # 匹配 /api/user/xxx 的请求
          filters:
            - StripPrefix=1                        # 去掉第一段路径前缀(/api)
            - AddRequestHeader=X-Request-Source, Gateway

        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/order/**
          filters:
            - StripPrefix=1

        - id: auth-service
          uri: lb://auth-service
          predicates:
            - Path=/api/auth/**

      # 全局默认过滤器(作用于所有路由)
      default-filters:
        - AddResponseHeader=X-Response-Gateway, Spring-Cloud-Gateway
server:
  port: 8080

spring:
  application:
    name: api-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      routes:
        - id: user-service                         # 路由 ID,唯一
          uri: lb://user-service                   # lb:// 表示从 Nacos 负载均衡获取服务
          predicates:
            - Path=/api/user/**                    # 匹配 /api/user/xxx 的请求
          filters:
            - StripPrefix=1                        # 去掉第一段路径前缀(/api)
            - AddRequestHeader=X-Request-Source, Gateway

        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/order/**
          filters:
            - StripPrefix=1

        - id: auth-service
          uri: lb://auth-service
          predicates:
            - Path=/api/auth/**

      # 全局默认过滤器(作用于所有路由)
      default-filters:
        - AddResponseHeader=X-Response-Gateway, Spring-Cloud-Gateway

路由匹配流程:

请求: GET /api/user/100
  ↓
遍历路由表:
  1. user-service: Path=/api/user/**  →  匹配成功
  2. order-service: Path=/api/order/**  →  不匹配
  3. auth-service: Path=/api/auth/**  →  不匹配
  ↓
选择 user-service 路由
  ↓
执行 filters: StripPrefix=1  →  /user/100
  ↓
转发到: lb://user-service/user/100
请求: GET /api/user/100
  ↓
遍历路由表:
  1. user-service: Path=/api/user/**  →  匹配成功
  2. order-service: Path=/api/order/**  →  不匹配
  3. auth-service: Path=/api/auth/**  →  不匹配
  ↓
选择 user-service 路由
  ↓
执行 filters: StripPrefix=1  →  /user/100
  ↓
转发到: lb://user-service/user/100

3.3 内置断言工厂(Predicate Factories) ​

Gateway 提供了丰富的内置断言工厂,支持按请求的不同维度进行路由匹配。

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: predicates-demo
          uri: http://localhost:8082
          predicates:
            # 路径匹配
            - Path=/api/**, /public/**

            # 请求头匹配(值支持正则)
            - Header=X-Request-Id, \d+

            # 请求方法匹配
            - Method=GET, POST

            # 请求参数匹配(必须包含参数名,值可选)
            - Query=page
            - Query=size, \d+

            # Cookie 匹配(名称 + 正则)
            - Cookie=sessionId, [a-f0-9]{32}

            # Host 匹配(域名通配)
            - Host=**.example.com, api.example.org

            # 请求时间匹配
            - After=2024-01-01T00:00:00+08:00[Asia/Shanghai]
            - Before=2025-12-31T23:59:59+08:00[Asia/Shanghai]
            - Between=2024-06-01T00:00:00+08:00[Asia/Shanghai], 2024-09-01T00:00:00+08:00[Asia/Shanghai]

            # 权重路由(用于灰度发布,两个相同权重路由的百分比总和应为 100)
            - Weight=group1, 80
spring:
  cloud:
    gateway:
      routes:
        - id: predicates-demo
          uri: http://localhost:8082
          predicates:
            # 路径匹配
            - Path=/api/**, /public/**

            # 请求头匹配(值支持正则)
            - Header=X-Request-Id, \d+

            # 请求方法匹配
            - Method=GET, POST

            # 请求参数匹配(必须包含参数名,值可选)
            - Query=page
            - Query=size, \d+

            # Cookie 匹配(名称 + 正则)
            - Cookie=sessionId, [a-f0-9]{32}

            # Host 匹配(域名通配)
            - Host=**.example.com, api.example.org

            # 请求时间匹配
            - After=2024-01-01T00:00:00+08:00[Asia/Shanghai]
            - Before=2025-12-31T23:59:59+08:00[Asia/Shanghai]
            - Between=2024-06-01T00:00:00+08:00[Asia/Shanghai], 2024-09-01T00:00:00+08:00[Asia/Shanghai]

            # 权重路由(用于灰度发布,两个相同权重路由的百分比总和应为 100)
            - Weight=group1, 80

组合断言(and/or/negate) ​

yaml
predicates:
  - Path=/api/user/**
  - Header=X-Version, v2
  # Gateway 中多个谓词默认是 AND 关系
predicates:
  - Path=/api/user/**
  - Header=X-Version, v2
  # Gateway 中多个谓词默认是 AND 关系

路由 -> Path 匹配 AND Header 匹配,两个条件都满足才会生效。

3.4 内置过滤器工厂(GatewayFilter Factories) ​

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: filter-demo
          uri: lb://backend-service
          predicates:
            - Path=/**
          filters:
            # ======== 请求/响应头操作 ========
            - AddRequestHeader=X-Trace-Id, ${cachedRandom.int}
            - AddRequestParameter=source, gateway
            - AddResponseHeader=X-Response-Time, ${currentTime.millis()}

            # ======== 路径重写 ========
            - StripPrefix=2                         # 去掉前 2 段路径
            - PrefixPath=/api                       # 添加路径前缀
            - SetPath=/internal/{segment}           # 使用模板重写路径
            - RewritePath=/api/old/(?<segment>.*), /new/$\{segment}

            # ======== 重试 ========
            - name: Retry
              args:
                retries: 3
                statuses: BAD_GATEWAY, SERVICE_UNAVAILABLE
                methods: GET
                series: SERVER_ERROR

            # ======== 请求限流 ========
            - name: RequestRateLimiter
              args:
                redis-rate-limiter:
                  replenishRate: 10        # 令牌桶填充速率(每秒)
                  burstCapacity: 20         # 令牌桶容量
                  requestedTokens: 1        # 每次请求消耗令牌数
                key-resolver: "#{@userKeyResolver}"

            # ======== 熔断 ========
            - name: CircuitBreaker
              args:
                name: backendCircuitBreaker
                fallbackUri: forward:/fallback
spring:
  cloud:
    gateway:
      routes:
        - id: filter-demo
          uri: lb://backend-service
          predicates:
            - Path=/**
          filters:
            # ======== 请求/响应头操作 ========
            - AddRequestHeader=X-Trace-Id, ${cachedRandom.int}
            - AddRequestParameter=source, gateway
            - AddResponseHeader=X-Response-Time, ${currentTime.millis()}

            # ======== 路径重写 ========
            - StripPrefix=2                         # 去掉前 2 段路径
            - PrefixPath=/api                       # 添加路径前缀
            - SetPath=/internal/{segment}           # 使用模板重写路径
            - RewritePath=/api/old/(?<segment>.*), /new/$\{segment}

            # ======== 重试 ========
            - name: Retry
              args:
                retries: 3
                statuses: BAD_GATEWAY, SERVICE_UNAVAILABLE
                methods: GET
                series: SERVER_ERROR

            # ======== 请求限流 ========
            - name: RequestRateLimiter
              args:
                redis-rate-limiter:
                  replenishRate: 10        # 令牌桶填充速率(每秒)
                  burstCapacity: 20         # 令牌桶容量
                  requestedTokens: 1        # 每次请求消耗令牌数
                key-resolver: "#{@userKeyResolver}"

            # ======== 熔断 ========
            - name: CircuitBreaker
              args:
                name: backendCircuitBreaker
                fallbackUri: forward:/fallback

3.5 自定义过滤器 ​

全局过滤器(GlobalFilter)— 自动应用于所有路由 ​

java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.UUID;

/**
 * 全局请求日志过滤器
 * 为每个请求添加 Trace ID,便于链路追踪
 */
@Component
public class TraceIdGlobalFilter implements GlobalFilter, Ordered {

    private static final Logger log = LoggerFactory.getLogger(TraceIdGlobalFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String traceId = UUID.randomUUID().toString().replace("-", "").substring(0, 16);

        ServerHttpRequest request = exchange.getRequest().mutate()
                .header("X-Trace-Id", traceId)
                .build();

        ServerWebExchange mutatedExchange = exchange.mutate().request(request).build();

        // 记录请求日志
        log.info("[{}] {} {}",
                traceId,
                request.getMethod(),
                request.getURI().getPath());

        return chain.filter(mutatedExchange);
    }

    /**
     * 返回 order 值,数值越小优先级越高
     */
    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.UUID;

/**
 * 全局请求日志过滤器
 * 为每个请求添加 Trace ID,便于链路追踪
 */
@Component
public class TraceIdGlobalFilter implements GlobalFilter, Ordered {

    private static final Logger log = LoggerFactory.getLogger(TraceIdGlobalFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String traceId = UUID.randomUUID().toString().replace("-", "").substring(0, 16);

        ServerHttpRequest request = exchange.getRequest().mutate()
                .header("X-Trace-Id", traceId)
                .build();

        ServerWebExchange mutatedExchange = exchange.mutate().request(request).build();

        // 记录请求日志
        log.info("[{}] {} {}",
                traceId,
                request.getMethod(),
                request.getURI().getPath());

        return chain.filter(mutatedExchange);
    }

    /**
     * 返回 order 值,数值越小优先级越高
     */
    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

局部过滤器(GatewayFilter)— 需在路由配置中显式引用 ​

java
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;

/**
 * 自定义过滤器工厂:移除指定请求头
 * 使用方式:- RemoveRequestHeaderSuffix=suffix1,suffix2
 */
@Component
public class RemoveRequestHeaderSuffixGatewayFilterFactory
        extends AbstractGatewayFilterFactory<RemoveRequestHeaderSuffixGatewayFilterFactory.Config> {

    public RemoveRequestHeaderSuffixGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            HttpHeaders headers = request.getHeaders();

            headers.keySet().stream()
                    .filter(key -> config.getSuffixes().stream().anyMatch(key::endsWith))
                    .forEach(key -> {
                        request.mutate().headers(h -> h.remove(key));
                    });

            return chain.filter(exchange.mutate()
                    .request(request.mutate().build())
                    .build());
        };
    }

    public static class Config {
        private String suffixes;

        public String getSuffixes() {
            return suffixes;
        }

        public void setSuffixes(String suffixes) {
            this.suffixes = suffixes;
        }
    }
}
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;

/**
 * 自定义过滤器工厂:移除指定请求头
 * 使用方式:- RemoveRequestHeaderSuffix=suffix1,suffix2
 */
@Component
public class RemoveRequestHeaderSuffixGatewayFilterFactory
        extends AbstractGatewayFilterFactory<RemoveRequestHeaderSuffixGatewayFilterFactory.Config> {

    public RemoveRequestHeaderSuffixGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            HttpHeaders headers = request.getHeaders();

            headers.keySet().stream()
                    .filter(key -> config.getSuffixes().stream().anyMatch(key::endsWith))
                    .forEach(key -> {
                        request.mutate().headers(h -> h.remove(key));
                    });

            return chain.filter(exchange.mutate()
                    .request(request.mutate().build())
                    .build());
        };
    }

    public static class Config {
        private String suffixes;

        public String getSuffixes() {
            return suffixes;
        }

        public void setSuffixes(String suffixes) {
            this.suffixes = suffixes;
        }
    }
}

在配置中使用:

yaml
filters:
  - RemoveRequestHeaderSuffix=-sensitive
filters:
  - RemoveRequestHeaderSuffix=-sensitive

3.6 熔断降级集成 Sentinel ​

xml
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
yaml
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8081       # Sentinel 控制台地址
      filter:
        enabled: false                  # 关闭 SentinelFilter(网关场景需特殊配置)
    gateway:
      routes:
        - id: sentinel-demo
          uri: lb://backend-service
          predicates:
            - Path=/api/**
          filters:
            - name: SentinelGatewayFilter  # 启用 Sentinel 网关过滤器
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8081       # Sentinel 控制台地址
      filter:
        enabled: false                  # 关闭 SentinelFilter(网关场景需特殊配置)
    gateway:
      routes:
        - id: sentinel-demo
          uri: lb://backend-service
          predicates:
            - Path=/api/**
          filters:
            - name: SentinelGatewayFilter  # 启用 Sentinel 网关过滤器

网关限流配置可以实时通过 Sentinel 控制台推送,也支持代码方式进行 API 分组管理:

java
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import jakarta.annotation.PostConstruct;
import org.springframework.context.annotation.Configuration;

import java.util.HashSet;
import java.util.Set;

@Configuration
public class GatewaySentinelConfig {

    @PostConstruct
    public void initGatewayRules() {
        // 1. 定义 API 分组
        Set<ApiDefinition> apis = new HashSet<>();
        apis.add(new ApiDefinition("user_api")
                .setPredicateItems(Set.of(
                        new ApiPathPredicateItem().setPattern("/api/user/**")
                )));
        apis.add(new ApiDefinition("order_api")
                .setPredicateItems(Set.of(
                        new ApiPathPredicateItem().setPattern("/api/order/**")
                )));
        GatewayApiDefinitionManager.loadApiDefinitions(apis);

        // 2. 定义网关流控规则
        Set<GatewayFlowRule> rules = new HashSet<>();
        rules.add(new GatewayFlowRule("user_api")
                .setCount(100)                      // QPS 阈值
                .setIntervalSec(1));                // 统计时间窗口(秒)
        rules.add(new GatewayFlowRule("order_api")
                .setCount(50)
                .setIntervalSec(1));
        GatewayRuleManager.loadRules(rules);
    }
}
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import jakarta.annotation.PostConstruct;
import org.springframework.context.annotation.Configuration;

import java.util.HashSet;
import java.util.Set;

@Configuration
public class GatewaySentinelConfig {

    @PostConstruct
    public void initGatewayRules() {
        // 1. 定义 API 分组
        Set<ApiDefinition> apis = new HashSet<>();
        apis.add(new ApiDefinition("user_api")
                .setPredicateItems(Set.of(
                        new ApiPathPredicateItem().setPattern("/api/user/**")
                )));
        apis.add(new ApiDefinition("order_api")
                .setPredicateItems(Set.of(
                        new ApiPathPredicateItem().setPattern("/api/order/**")
                )));
        GatewayApiDefinitionManager.loadApiDefinitions(apis);

        // 2. 定义网关流控规则
        Set<GatewayFlowRule> rules = new HashSet<>();
        rules.add(new GatewayFlowRule("user_api")
                .setCount(100)                      // QPS 阈值
                .setIntervalSec(1));                // 统计时间窗口(秒)
        rules.add(new GatewayFlowRule("order_api")
                .setCount(50)
                .setIntervalSec(1));
        GatewayRuleManager.loadRules(rules);
    }
}

3.7 跨域配置(CORS) ​

yaml
spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowed-origins:
              - "http://localhost:3000"
              - "https://admin.example.com"
            allowed-methods:
              - GET
              - POST
              - PUT
              - DELETE
              - OPTIONS
            allowed-headers: "*"
            allow-credentials: true
            max-age: 3600
spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowed-origins:
              - "http://localhost:3000"
              - "https://admin.example.com"
            allowed-methods:
              - GET
              - POST
              - PUT
              - DELETE
              - OPTIONS
            allowed-headers: "*"
            allow-credentials: true
            max-age: 3600

Java 配置方式:

java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

@Configuration
public class CorsConfig {

    @Bean
    public CorsWebFilter corsWebFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("http://localhost:3000");
        config.addAllowedMethod("*");
        config.addAllowedHeader("*");
        config.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return new CorsWebFilter(source);
    }
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

@Configuration
public class CorsConfig {

    @Bean
    public CorsWebFilter corsWebFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("http://localhost:3000");
        config.addAllowedMethod("*");
        config.addAllowedHeader("*");
        config.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return new CorsWebFilter(source);
    }
}

3.8 网关限流(RequestRateLimiter) ​

Gateway 配合 Redis 实现令牌桶限流:

yaml
spring:
  redis:
    host: localhost
    port: 6379
  cloud:
    gateway:
      routes:
        - id: rate-limit-demo
          uri: lb://backend-service
          predicates:
            - Path=/api/**
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter:
                  replenishRate: 10        # 每秒填充令牌数
                  burstCapacity: 20        # 桶最大容量
                  requestedTokens: 1       # 每次请求消耗令牌数
                # 限流 Key 解析器 Bean 名称
                key-resolver: "#{@ipKeyResolver}"
spring:
  redis:
    host: localhost
    port: 6379
  cloud:
    gateway:
      routes:
        - id: rate-limit-demo
          uri: lb://backend-service
          predicates:
            - Path=/api/**
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter:
                  replenishRate: 10        # 每秒填充令牌数
                  burstCapacity: 20        # 桶最大容量
                  requestedTokens: 1       # 每次请求消耗令牌数
                # 限流 Key 解析器 Bean 名称
                key-resolver: "#{@ipKeyResolver}"

限流 Key 解析器(支持多种维度):

java
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;

@Configuration
public class RateLimiterConfig {

    /**
     * 按请求 IP 限流
     */
    @Bean
    public KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(
                exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
        );
    }

    /**
     * 按用户 ID 限流(需从 Token 解析)
     */
    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> Mono.just(
                exchange.getRequest().getHeaders().getFirst("X-User-Id")
        );
    }

    /**
     * 按请求路径限流
     */
    @Bean
    public KeyResolver pathKeyResolver() {
        return exchange -> Mono.just(
                exchange.getRequest().getURI().getPath()
        );
    }
}
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;

@Configuration
public class RateLimiterConfig {

    /**
     * 按请求 IP 限流
     */
    @Bean
    public KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(
                exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
        );
    }

    /**
     * 按用户 ID 限流(需从 Token 解析)
     */
    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> Mono.just(
                exchange.getRequest().getHeaders().getFirst("X-User-Id")
        );
    }

    /**
     * 按请求路径限流
     */
    @Bean
    public KeyResolver pathKeyResolver() {
        return exchange -> Mono.just(
                exchange.getRequest().getURI().getPath()
        );
    }
}

四、Sentinel 流量防卫兵 ​

Sentinel 是阿里巴巴开源的流量控制、熔断降级组件,以"资源"为粒度提供全方位的流量治理能力。相比于 Hystrix,Sentinel 的线程模型更轻量(不隔离线程池),且支持实时监控和动态规则推送。

4.1 Sentinel 核心概念 ​

                  ┌──────────────────────┐
                  │     Sentinel          │
                  ├──────────────────────┤
                  │  Resource (资源)      │ ← 被保护的代码块/方法/接口
                  │  Rule (规则)          │ ← 流量控制规则 / 熔断降级规则 / 热点规则
                  │  Slot Chain (槽链)    │ ← 规则校验的责任链
                  │  Metric (指标)        │ ← 滑动窗口统计数据
                  │  Dashboard (控制台)    │ ← 可视化监控与管理
                  └──────────────────────┘
                  ┌──────────────────────┐
                  │     Sentinel          │
                  ├──────────────────────┤
                  │  Resource (资源)      │ ← 被保护的代码块/方法/接口
                  │  Rule (规则)          │ ← 流量控制规则 / 熔断降级规则 / 热点规则
                  │  Slot Chain (槽链)    │ ← 规则校验的责任链
                  │  Metric (指标)        │ ← 滑动窗口统计数据
                  │  Dashboard (控制台)    │ ← 可视化监控与管理
                  └──────────────────────┘

4.2 引入依赖 ​

xml
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

4.3 基础配置 ​

yaml
spring:
  application:
    name: user-service
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8081          # Sentinel 控制台地址
        port: 8719                         # 客户端与 Dashboard 通信端口
      eager: true                          # 取消懒加载,项目启动即连接控制台
      log:
        dir: ./logs/sentinel               # 日志目录

# 配置 Sentinel 会在日志和监控中显示的来源应用名
# 需在 VM options 中设置,或通过环境变量:
# -Dproject.name=user-service
spring:
  application:
    name: user-service
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8081          # Sentinel 控制台地址
        port: 8719                         # 客户端与 Dashboard 通信端口
      eager: true                          # 取消懒加载,项目启动即连接控制台
      log:
        dir: ./logs/sentinel               # 日志目录

# 配置 Sentinel 会在日志和监控中显示的来源应用名
# 需在 VM options 中设置,或通过环境变量:
# -Dproject.name=user-service

启动 Sentinel 控制台:

bash
# 下载最新 release JAR
java -Dserver.port=8081 -Dcsp.sentinel.dashboard.server=localhost:8081 -jar sentinel-dashboard.jar
# 下载最新 release JAR
java -Dserver.port=8081 -Dcsp.sentinel.dashboard.server=localhost:8081 -jar sentinel-dashboard.jar

4.4 流量控制规则 ​

QPS 流量控制 ​

java
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/user")
public class UserController {

    /**
     * 使用 @SentinelResource 定义资源
     * value: 资源名称(唯一)
     * blockHandler: 流控/熔断触发时的回调(处理 BlockException)
     * fallback: 所有异常的回调(包括业务异常)
     */
    @GetMapping("/{id}")
    @SentinelResource(
            value = "getUserById",
            blockHandler = "getUserByIdBlockHandler",
            fallback = "getUserByIdFallback"
    )
    public User getUserById(@PathVariable Long id) {
        if (id == null || id <= 0) {
            throw new IllegalArgumentException("invalid id");
        }
        // 模拟业务调用
        return new User(id, "name_" + id);
    }

    /**
     * blockHandler 方法签名必须与资源方法一致,末尾增加 BlockException 参数
     */
    public User getUserByIdBlockHandler(Long id, BlockException e) {
        return new User(-1L, "系统繁忙,请稍后再试 [blocked]");
    }

    /**
     * fallback 处理业务异常
     */
    public User getUserByIdFallback(Long id, Throwable t) {
        return new User(-1L, "服务异常:" + t.getMessage());
    }
}
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/user")
public class UserController {

    /**
     * 使用 @SentinelResource 定义资源
     * value: 资源名称(唯一)
     * blockHandler: 流控/熔断触发时的回调(处理 BlockException)
     * fallback: 所有异常的回调(包括业务异常)
     */
    @GetMapping("/{id}")
    @SentinelResource(
            value = "getUserById",
            blockHandler = "getUserByIdBlockHandler",
            fallback = "getUserByIdFallback"
    )
    public User getUserById(@PathVariable Long id) {
        if (id == null || id <= 0) {
            throw new IllegalArgumentException("invalid id");
        }
        // 模拟业务调用
        return new User(id, "name_" + id);
    }

    /**
     * blockHandler 方法签名必须与资源方法一致,末尾增加 BlockException 参数
     */
    public User getUserByIdBlockHandler(Long id, BlockException e) {
        return new User(-1L, "系统繁忙,请稍后再试 [blocked]");
    }

    /**
     * fallback 处理业务异常
     */
    public User getUserByIdFallback(Long id, Throwable t) {
        return new User(-1L, "服务异常:" + t.getMessage());
    }
}

规则配置方式 ​

方式一:控制台动态配置(推荐生产使用)

在 Sentinel 控制台 → 流控规则 → 新增流控规则,填入资源名、阈值等。规则会推送到客户端 JVM 内存中生效。

方式二:代码硬编码(适合测试)

java
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import jakarta.annotation.PostConstruct;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class SentinelFlowRuleConfig {

    @PostConstruct
    public void initFlowRules() {
        List<FlowRule> rules = new ArrayList<>();

        FlowRule rule = new FlowRule();
        rule.setResource("getUserById");            // 资源名
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 限流阈值类型:QPS
        rule.setCount(100);                         // 阈值
        rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); // 流控效果:快速失败
        // rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP);   // 预热
        // rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER); // 排队等待

        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }
}
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import jakarta.annotation.PostConstruct;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class SentinelFlowRuleConfig {

    @PostConstruct
    public void initFlowRules() {
        List<FlowRule> rules = new ArrayList<>();

        FlowRule rule = new FlowRule();
        rule.setResource("getUserById");            // 资源名
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 限流阈值类型:QPS
        rule.setCount(100);                         // 阈值
        rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); // 流控效果:快速失败
        // rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP);   // 预热
        // rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER); // 排队等待

        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }
}

流控模式详解 ​

流控模式说明适用场景
直接资源达到阈值直接限流接口级别的直接保护
关联当关联资源达到阈值时,对当前资源限流读写分离场景:写操作过高时限制读操作
链路根据调用链入口限流控制某个调用来源对资源的访问频率
yaml
# 控制台配置 - 关联模式示例
资源名: createOrder
流控模式: 关联
关联资源: queryOrder
阈值: 50
说明: 当 queryOrder 的 QPS 超过 50 时,createOrder 被限流
# 控制台配置 - 关联模式示例
资源名: createOrder
流控模式: 关联
关联资源: queryOrder
阈值: 50
说明: 当 queryOrder 的 QPS 超过 50 时,createOrder 被限流

流控效果 ​

效果说明典型阈值场景
快速失败直接抛出 BlockException对延迟敏感的核心接口
Warm Up阈值从 coldFactor(默认 3)逐渐增加到设定值系统刚启动时的保护
排队等待请求以固定速率通过,超时则放弃削峰填谷,如消息推送

4.5 熔断降级规则 ​

Sentinel 的熔断降级会维护一个断路器的状态机:

Closed (关闭)  →  Open (打开)  →  Half-Open (半开)  →  Closed (恢复正常)
                      ↑                                       │
                      └───────────────────────────────────────┘
Closed (关闭)  →  Open (打开)  →  Half-Open (半开)  →  Closed (恢复正常)
                      ↑                                       │
                      └───────────────────────────────────────┘
熔断策略说明判定条件
慢调用比例响应时间超过 RT 的请求比例达到阈值maxRt > 阈值ms 且比例 > ratio
异常比例异常数占总请求数的比例达到阈值errorCount / totalCount > ratio
异常数单位时间内的异常数量达到阈值errorCount > count
java
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import jakarta.annotation.PostConstruct;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class SentinelDegradeRuleConfig {

    @PostConstruct
    public void initDegradeRules() {
        List<DegradeRule> rules = new ArrayList<>();

        // 规则1:慢调用比例熔断
        DegradeRule slowRule = new DegradeRule("getUserById");
        slowRule.setGrade(RuleConstant.DEGRADE_GRADE_RT);       // 慢调用
        slowRule.setCount(500);                                  // RT 超过 500ms
        slowRule.setTimeWindow(10);                              // 熔断时长 10 秒
        slowRule.setMinRequestAmount(5);                         // 触发熔断的最小请求数
        slowRule.setSlowRatioThreshold(0.5);                     // 慢调用比例阈值 50%
        slowRule.setStatIntervalMs(1000);                        // 统计时间窗口 1s
        rules.add(slowRule);

        // 规则2:异常比例熔断
        DegradeRule exceptionRatioRule = new DegradeRule("createOrder");
        exceptionRatioRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
        exceptionRatioRule.setCount(0.2);                        // 异常比例 20%
        exceptionRatioRule.setTimeWindow(30);                    // 熔断 30 秒
        exceptionRatioRule.setMinRequestAmount(10);
        rules.add(exceptionRatioRule);

        // 规则3:异常数熔断
        DegradeRule exceptionCountRule = new DegradeRule("payOrder");
        exceptionCountRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
        exceptionCountRule.setCount(10);                         // 10 个异常
        exceptionCountRule.setTimeWindow(60);
        exceptionCountRule.setMinRequestAmount(5);
        rules.add(exceptionCountRule);

        DegradeRuleManager.loadRules(rules);
    }
}
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import jakarta.annotation.PostConstruct;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class SentinelDegradeRuleConfig {

    @PostConstruct
    public void initDegradeRules() {
        List<DegradeRule> rules = new ArrayList<>();

        // 规则1:慢调用比例熔断
        DegradeRule slowRule = new DegradeRule("getUserById");
        slowRule.setGrade(RuleConstant.DEGRADE_GRADE_RT);       // 慢调用
        slowRule.setCount(500);                                  // RT 超过 500ms
        slowRule.setTimeWindow(10);                              // 熔断时长 10 秒
        slowRule.setMinRequestAmount(5);                         // 触发熔断的最小请求数
        slowRule.setSlowRatioThreshold(0.5);                     // 慢调用比例阈值 50%
        slowRule.setStatIntervalMs(1000);                        // 统计时间窗口 1s
        rules.add(slowRule);

        // 规则2:异常比例熔断
        DegradeRule exceptionRatioRule = new DegradeRule("createOrder");
        exceptionRatioRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
        exceptionRatioRule.setCount(0.2);                        // 异常比例 20%
        exceptionRatioRule.setTimeWindow(30);                    // 熔断 30 秒
        exceptionRatioRule.setMinRequestAmount(10);
        rules.add(exceptionRatioRule);

        // 规则3:异常数熔断
        DegradeRule exceptionCountRule = new DegradeRule("payOrder");
        exceptionCountRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
        exceptionCountRule.setCount(10);                         // 10 个异常
        exceptionCountRule.setTimeWindow(60);
        exceptionCountRule.setMinRequestAmount(5);
        rules.add(exceptionCountRule);

        DegradeRuleManager.loadRules(rules);
    }
}

4.6 热点参数限流 ​

热点参数限流可以针对方法调用中的特定参数值进行精细化限流,例如对某个高频商品 ID 单独限制。

java
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/product")
public class ProductController {

    /**
     * 热点参数限流
     * 在控制台配置热点规则:参数索引 0(productId),阈值 100
     * 可以设置例外:参数值为 1001 时阈值提升到 1000
     */
    @GetMapping("/{productId}")
    @SentinelResource(
            value = "getProductById",
            blockHandler = "hotBlockHandler"
    )
    public String getProduct(@PathVariable String productId) {
        return "Product: " + productId;
    }

    public String hotBlockHandler(String productId, BlockException e) {
        return "商品 " + productId + " 请求过于频繁,请稍后再试";
    }
}
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/product")
public class ProductController {

    /**
     * 热点参数限流
     * 在控制台配置热点规则:参数索引 0(productId),阈值 100
     * 可以设置例外:参数值为 1001 时阈值提升到 1000
     */
    @GetMapping("/{productId}")
    @SentinelResource(
            value = "getProductById",
            blockHandler = "hotBlockHandler"
    )
    public String getProduct(@PathVariable String productId) {
        return "Product: " + productId;
    }

    public String hotBlockHandler(String productId, BlockException e) {
        return "商品 " + productId + " 请求过于频繁,请稍后再试";
    }
}

在 Sentinel 控制台配置热点规则:

参数值
资源名getProductById
参数索引0
单机阈值100
统计窗口时长1
参数例外项参数值=1001, 限流阈值=1000

4.7 系统保护规则 ​

系统规则从 Load / RT / 线程数 / 入口 QPS / CPU 使用率 五个维度对整体系统进行保护,当系统指标超过阈值时触发限流。

java
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
import jakarta.annotation.PostConstruct;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class SentinelSystemRuleConfig {

    @PostConstruct
    public void initSystemRules() {
        List<SystemRule> rules = new ArrayList<>();

        SystemRule rule = new SystemRule();
        // rule.setHighestSystemLoad(10.0);             // Load 阈值(仅 Linux/Mac)
        rule.setAvgRt(1000);                           // 所有入口流量的平均 RT 阈值(ms)
        rule.setMaxThread(500);                         // 入口总线程数阈值
        rule.setQps(10000);                             // 入口总 QPS 阈值
        // rule.setHighestCpuUsage(0.8);                // CPU 使用率阈值(仅 Linux/Mac)

        rules.add(rule);
        SystemRuleManager.loadRules(rules);
    }
}
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
import jakarta.annotation.PostConstruct;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class SentinelSystemRuleConfig {

    @PostConstruct
    public void initSystemRules() {
        List<SystemRule> rules = new ArrayList<>();

        SystemRule rule = new SystemRule();
        // rule.setHighestSystemLoad(10.0);             // Load 阈值(仅 Linux/Mac)
        rule.setAvgRt(1000);                           // 所有入口流量的平均 RT 阈值(ms)
        rule.setMaxThread(500);                         // 入口总线程数阈值
        rule.setQps(10000);                             // 入口总 QPS 阈值
        // rule.setHighestCpuUsage(0.8);                // CPU 使用率阈值(仅 Linux/Mac)

        rules.add(rule);
        SystemRuleManager.loadRules(rules);
    }
}

4.8 规则持久化(推模式 + Nacos) ​

Sentinel 控制台配置的规则默认保存在客户端内存中,服务重启后丢失。生产环境必须实现规则持久化。推荐使用 推模式:配置变更 -> Nacos -> Sentinel DataSource -> 客户端内存。

java
import com.alibaba.cloud.sentinel.datasource.RuleType;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@Configuration
public class SentinelNacosDataSourceConfig {

    private static final String NACOS_SERVER_ADDR = "127.0.0.1:8848";
    private static final String GROUP_ID = "SENTINEL_GROUP";
    private static final String FLOW_DATA_ID = "user-service-flow-rules";
    private static final String DEGRADE_DATA_ID = "user-service-degrade-rules";

    @Bean
    public ReadableDataSource<String, List<FlowRule>> flowRuleDataSource() {
        ReadableDataSource<String, List<FlowRule>> dataSource =
                new NacosDataSource<>(NACOS_SERVER_ADDR, GROUP_ID, FLOW_DATA_ID,
                        source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
        FlowRuleManager.register2Property(dataSource.getProperty());
        return dataSource;
    }

    @Bean
    public ReadableDataSource<String, List<DegradeRule>> degradeRuleDataSource() {
        ReadableDataSource<String, List<DegradeRule>> dataSource =
                new NacosDataSource<>(NACOS_SERVER_ADDR, GROUP_ID, DEGRADE_DATA_ID,
                        source -> JSON.parseObject(source, new TypeReference<List<DegradeRule>>() {}));
        DegradeRuleManager.register2Property(dataSource.getProperty());
        return dataSource;
    }
}
import com.alibaba.cloud.sentinel.datasource.RuleType;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@Configuration
public class SentinelNacosDataSourceConfig {

    private static final String NACOS_SERVER_ADDR = "127.0.0.1:8848";
    private static final String GROUP_ID = "SENTINEL_GROUP";
    private static final String FLOW_DATA_ID = "user-service-flow-rules";
    private static final String DEGRADE_DATA_ID = "user-service-degrade-rules";

    @Bean
    public ReadableDataSource<String, List<FlowRule>> flowRuleDataSource() {
        ReadableDataSource<String, List<FlowRule>> dataSource =
                new NacosDataSource<>(NACOS_SERVER_ADDR, GROUP_ID, FLOW_DATA_ID,
                        source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
        FlowRuleManager.register2Property(dataSource.getProperty());
        return dataSource;
    }

    @Bean
    public ReadableDataSource<String, List<DegradeRule>> degradeRuleDataSource() {
        ReadableDataSource<String, List<DegradeRule>> dataSource =
                new NacosDataSource<>(NACOS_SERVER_ADDR, GROUP_ID, DEGRADE_DATA_ID,
                        source -> JSON.parseObject(source, new TypeReference<List<DegradeRule>>() {}));
        DegradeRuleManager.register2Property(dataSource.getProperty());
        return dataSource;
    }
}

Nacos 中对应 Data ID 的内容示例:

json
// Data ID: user-service-flow-rules  Group: SENTINEL_GROUP
[
    {
        "resource": "getUserById",
        "limitApp": "default",
        "grade": 1,
        "count": 100,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]
// Data ID: user-service-flow-rules  Group: SENTINEL_GROUP
[
    {
        "resource": "getUserById",
        "limitApp": "default",
        "grade": 1,
        "count": 100,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]

规则持久化三种模式对比:

模式原理优点缺点
原始模式规则保存在客户端内存简单重启丢失,无一致性
Pull 模式客户端定期从文件/数据库拉取实现简单一致性差,有延迟
Push 模式控制台 -> Nacos/ZK -> 客户端 DataSource实时一致,支持管理架构复杂

4.9 Sentinel + Feign 集成 ​

xml
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- 开启 Feign 的 Sentinel 支持 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- 开启 Feign 的 Sentinel 支持 -->
yaml
feign:
  sentinel:
    enabled: true      # 开启 Feign 对 Sentinel 的支持
feign:
  sentinel:
    enabled: true      # 开启 Feign 对 Sentinel 的支持
java
import com.alibaba.cloud.sentinel.annotation.SentinelRestTemplate;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class SentinelRestTemplateConfig {

    @Bean
    @LoadBalanced
    @SentinelRestTemplate(
            blockHandler = "handleBlock",
            blockHandlerClass = ExceptionUtil.class,
            fallback = "handleFallback",
            fallbackClass = ExceptionUtil.class
    )
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
import com.alibaba.cloud.sentinel.annotation.SentinelRestTemplate;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class SentinelRestTemplateConfig {

    @Bean
    @LoadBalanced
    @SentinelRestTemplate(
            blockHandler = "handleBlock",
            blockHandlerClass = ExceptionUtil.class,
            fallback = "handleFallback",
            fallbackClass = ExceptionUtil.class
    )
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

五、OpenFeign 声明式调用 ​

OpenFeign 将 HTTP 调用抽象为 Java 接口,开发者只需声明接口和方法注解,Feign 会自动完成请求发送、结果解码等过程。结合 Spring Cloud LoadBalancer 实现客户端负载均衡。

5.1 引入依赖 ​

xml
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 负载均衡 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 负载均衡 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

5.2 启用 Feign ​

java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients(basePackages = "com.example.feign")  // 扫描 FeignClient
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients(basePackages = "com.example.feign")  // 扫描 FeignClient
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

5.3 声明 Feign 客户端 ​

java
import com.example.common.dto.UserDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

/**
 * 调用 user-service 的 Feign 客户端
 * name/value: 目标服务名(对应 Nacos 注册的服务名)
 * path: 目标服务统一的上下文路径(可选)
 */
@FeignClient(
        name = "user-service",
        path = "/api/user",
        configuration = UserFeignConfig.class           // 自定义配置类
)
public interface UserFeignClient {

    @GetMapping("/{id}")
    UserDTO getUserById(@PathVariable("id") Long id);

    @PostMapping("/batch")
    List<UserDTO> getUsersByIds(@RequestBody List<Long> ids);

    @GetMapping("/search")
    List<UserDTO> searchUsers(
            @RequestParam("keyword") String keyword,
            @RequestParam("page") int page,
            @RequestParam("size") int size
    );
}
import com.example.common.dto.UserDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

/**
 * 调用 user-service 的 Feign 客户端
 * name/value: 目标服务名(对应 Nacos 注册的服务名)
 * path: 目标服务统一的上下文路径(可选)
 */
@FeignClient(
        name = "user-service",
        path = "/api/user",
        configuration = UserFeignConfig.class           // 自定义配置类
)
public interface UserFeignClient {

    @GetMapping("/{id}")
    UserDTO getUserById(@PathVariable("id") Long id);

    @PostMapping("/batch")
    List<UserDTO> getUsersByIds(@RequestBody List<Long> ids);

    @GetMapping("/search")
    List<UserDTO> searchUsers(
            @RequestParam("keyword") String keyword,
            @RequestParam("page") int page,
            @RequestParam("size") int size
    );
}

5.4 自定义配置 ​

日志级别配置 ​

java
import feign.Logger;
import org.springframework.context.annotation.Bean;

/**
 * Feign 配置类
 * 注意:不要添加 @Configuration 注解,避免变成全局配置
 */
public class UserFeignConfig {

    @Bean
    public Logger.Level feignLoggerLevel() {
        // NONE: 不记录(默认)
        // BASIC: 仅记录请求方法、URL、响应状态码、执行时间
        // HEADERS: 在 BASIC 基础上记录请求和响应头
        // FULL: 记录所有(请求/响应头、体、元数据)
        return Logger.Level.FULL;
    }
}
import feign.Logger;
import org.springframework.context.annotation.Bean;

/**
 * Feign 配置类
 * 注意:不要添加 @Configuration 注解,避免变成全局配置
 */
public class UserFeignConfig {

    @Bean
    public Logger.Level feignLoggerLevel() {
        // NONE: 不记录(默认)
        // BASIC: 仅记录请求方法、URL、响应状态码、执行时间
        // HEADERS: 在 BASIC 基础上记录请求和响应头
        // FULL: 记录所有(请求/响应头、体、元数据)
        return Logger.Level.FULL;
    }
}
yaml
logging:
  level:
    com.example.feign.UserFeignClient: DEBUG   # 开启 Feign 客户端日志
logging:
  level:
    com.example.feign.UserFeignClient: DEBUG   # 开启 Feign 客户端日志

编码器与解码器 ​

java
import feign.codec.Decoder;
import feign.codec.Encoder;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;

public class FeignCodecConfig {

    @Bean
    public Encoder feignEncoder() {
        return new SpringEncoder(() -> new HttpMessageConverters());
    }

    @Bean
    public Decoder feignDecoder() {
        return new SpringDecoder(() -> new HttpMessageConverters());
    }
}
import feign.codec.Decoder;
import feign.codec.Encoder;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;

public class FeignCodecConfig {

    @Bean
    public Encoder feignEncoder() {
        return new SpringEncoder(() -> new HttpMessageConverters());
    }

    @Bean
    public Decoder feignDecoder() {
        return new SpringDecoder(() -> new HttpMessageConverters());
    }
}

5.5 请求拦截器 ​

拦截器可以统一向 Feign 请求添加公共信息,如认证 Token、Trace ID 等。

java
import feign.RequestInterceptor;
import feign.RequestTemplate;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Configuration
public class FeignInterceptorConfig {

    /**
     * 全局 OAuth2 Token 传递拦截器
     * 将当前请求的 Authorization 头传递到下游服务
     */
    @Bean
    public RequestInterceptor oauth2TokenInterceptor() {
        return (RequestTemplate template) -> {
            ServletRequestAttributes attributes =
                    (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes != null) {
                HttpServletRequest request = attributes.getRequest();
                String token = request.getHeader("Authorization");
                if (token != null) {
                    template.header("Authorization", token);
                }
            }
        };
    }

    /**
     * Trace ID 传递拦截器
     */
    @Bean
    public RequestInterceptor traceIdInterceptor() {
        return (RequestTemplate template) -> {
            ServletRequestAttributes attributes =
                    (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes != null) {
                String traceId = attributes.getRequest().getHeader("X-Trace-Id");
                if (traceId != null) {
                    template.header("X-Trace-Id", traceId);
                }
            }
        };
    }
}
import feign.RequestInterceptor;
import feign.RequestTemplate;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Configuration
public class FeignInterceptorConfig {

    /**
     * 全局 OAuth2 Token 传递拦截器
     * 将当前请求的 Authorization 头传递到下游服务
     */
    @Bean
    public RequestInterceptor oauth2TokenInterceptor() {
        return (RequestTemplate template) -> {
            ServletRequestAttributes attributes =
                    (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes != null) {
                HttpServletRequest request = attributes.getRequest();
                String token = request.getHeader("Authorization");
                if (token != null) {
                    template.header("Authorization", token);
                }
            }
        };
    }

    /**
     * Trace ID 传递拦截器
     */
    @Bean
    public RequestInterceptor traceIdInterceptor() {
        return (RequestTemplate template) -> {
            ServletRequestAttributes attributes =
                    (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes != null) {
                String traceId = attributes.getRequest().getHeader("X-Trace-Id");
                if (traceId != null) {
                    template.header("X-Trace-Id", traceId);
                }
            }
        };
    }
}

5.6 超时与重试配置 ​

yaml
spring:
  cloud:
    openfeign:
      client:
        config:
          default:                                   # 默认配置,对所有 FeignClient 生效
            connect-timeout: 5000                    # 连接超时(毫秒)
            read-timeout: 10000                      # 读取超时(毫秒)
            logger-level: BASIC
          user-service:                              # 单独对 user-service 配置
            connect-timeout: 3000
            read-timeout: 5000
      compression:
        request:
          enabled: true
          mime-types: application/json
          min-request-size: 2048
        response:
          enabled: true
spring:
  cloud:
    openfeign:
      client:
        config:
          default:                                   # 默认配置,对所有 FeignClient 生效
            connect-timeout: 5000                    # 连接超时(毫秒)
            read-timeout: 10000                      # 读取超时(毫秒)
            logger-level: BASIC
          user-service:                              # 单独对 user-service 配置
            connect-timeout: 3000
            read-timeout: 5000
      compression:
        request:
          enabled: true
          mime-types: application/json
          min-request-size: 2048
        response:
          enabled: true

自定义重试器 ​

java
import feign.RetryableException;
import feign.Retryer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FeignRetryConfig {

    @Bean
    public Retryer feignRetryer() {
        // period: 初始间隔(毫秒)
        // maxPeriod: 最大间隔(毫秒)
        // maxAttempts: 最大尝试次数
        return new Retryer.Default(100, 1000, 3);
    }
}
import feign.RetryableException;
import feign.Retryer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FeignRetryConfig {

    @Bean
    public Retryer feignRetryer() {
        // period: 初始间隔(毫秒)
        // maxPeriod: 最大间隔(毫秒)
        // maxAttempts: 最大尝试次数
        return new Retryer.Default(100, 1000, 3);
    }
}

5.7 异常处理(FallbackFactory) ​

FallbackFactory 可以获取熔断/降级时的异常原因,相比 fallback 属性更加强大。

java
import com.example.common.dto.UserDTO;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;

@FeignClient(
        name = "user-service",
        path = "/api/user",
        fallbackFactory = UserFeignClientFallbackFactory.class
)
public interface UserFeignClient {

    @GetMapping("/{id}")
    UserDTO getUserById(@PathVariable("id") Long id);
}

/**
 * FallbackFactory:可以获取异常原因
 */
@Component
class UserFeignClientFallbackFactory implements FallbackFactory<UserFeignClient> {

    @Override
    public UserFeignClient create(Throwable cause) {
        return new UserFeignClient() {
            @Override
            public UserDTO getUserById(Long id) {
                // 可以记录异常日志
                System.err.println("Feign 调用失败,原因:" + cause.getMessage());
                // 返回默认值或兜底数据
                return new UserDTO(-1L, "fallback_user", "熔断降级");
            }
        };
    }
}
import com.example.common.dto.UserDTO;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;

@FeignClient(
        name = "user-service",
        path = "/api/user",
        fallbackFactory = UserFeignClientFallbackFactory.class
)
public interface UserFeignClient {

    @GetMapping("/{id}")
    UserDTO getUserById(@PathVariable("id") Long id);
}

/**
 * FallbackFactory:可以获取异常原因
 */
@Component
class UserFeignClientFallbackFactory implements FallbackFactory<UserFeignClient> {

    @Override
    public UserFeignClient create(Throwable cause) {
        return new UserFeignClient() {
            @Override
            public UserDTO getUserById(Long id) {
                // 可以记录异常日志
                System.err.println("Feign 调用失败,原因:" + cause.getMessage());
                // 返回默认值或兜底数据
                return new UserDTO(-1L, "fallback_user", "熔断降级");
            }
        };
    }
}

FallbackFactory 与 fallback 的区别:

方式能否获取异常适用场景
fallback = XXX.class否简单兜底,不需要知道失败原因
fallbackFactory = XXX.class是需要根据异常类型做不同处理

5.8 负载均衡(Spring Cloud LoadBalancer) ​

Spring Cloud 2020.x 之后移除了 Netflix Ribbon,默认使用 Spring Cloud LoadBalancer 作为负载均衡客户端。LoadBalancer 支持两种负载均衡策略:

java
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.web.client.RestTemplate;

/**
 * 为 user-service 配置随机负载均衡策略
 */
@Configuration
@LoadBalancerClient(name = "user-service", configuration = UserServiceLoadBalancerConfig.class)
public class LoadBalancerConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

class UserServiceLoadBalancerConfig {

    @Bean
    public ReactorServiceInstanceLoadBalancer reactorServiceInstanceLoadBalancer(
            Environment environment,
            LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RandomLoadBalancer(
                loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
                name
        );
    }
}
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.web.client.RestTemplate;

/**
 * 为 user-service 配置随机负载均衡策略
 */
@Configuration
@LoadBalancerClient(name = "user-service", configuration = UserServiceLoadBalancerConfig.class)
public class LoadBalancerConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

class UserServiceLoadBalancerConfig {

    @Bean
    public ReactorServiceInstanceLoadBalancer reactorServiceInstanceLoadBalancer(
            Environment environment,
            LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RandomLoadBalancer(
                loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
                name
        );
    }
}

内置负载均衡策略:

策略说明
RoundRobinLoadBalancer轮询(默认)
RandomLoadBalancer随机
自定义实现 ReactorServiceInstanceLoadBalancer 接口

六、Seata 分布式事务 ​

Seata(Simple Extensible Autonomous Transaction Architecture)是阿里巴巴开源的分布式事务解决方案。在微服务架构中,单一事务可能跨多个服务、多个数据库,Seata 保证了这一过程的 ACID 特性。

6.1 Seata 核心架构 ​

                     ┌──────────────────────┐
                     │    TC (Transaction    │
                     │    Coordinator)       │ ← 事务协调器,独立部署
                     │    维护全局事务状态    │
                     └──────┬───────────────┘
                            │
             ┌──────────────┼──────────────┐
             │              │              │
      ┌──────▼──────┐ ┌────▼───────┐ ┌────▼───────┐
      │ TM (Transaction   │ RM (Resource │ RM (Resource │
      │ Manager)    │ Manager)    │ Manager)    │
      │             │ │             │ │             │
      │ @GlobalTransactional│ │  管理分支事务  │ │  管理分支事务  │
      │  开启/提交/回滚 │ │  报告分支状态 │ │  报告分支状态 │
      └─────────────┘ └─────────────┘ └─────────────┘
                     ┌──────────────────────┐
                     │    TC (Transaction    │
                     │    Coordinator)       │ ← 事务协调器,独立部署
                     │    维护全局事务状态    │
                     └──────┬───────────────┘
                            │
             ┌──────────────┼──────────────┐
             │              │              │
      ┌──────▼──────┐ ┌────▼───────┐ ┌────▼───────┐
      │ TM (Transaction   │ RM (Resource │ RM (Resource │
      │ Manager)    │ Manager)    │ Manager)    │
      │             │ │             │ │             │
      │ @GlobalTransactional│ │  管理分支事务  │ │  管理分支事务  │
      │  开启/提交/回滚 │ │  报告分支状态 │ │  报告分支状态 │
      └─────────────┘ └─────────────┘ └─────────────┘
角色说明
TC事务协调器,维护全局事务的运行状态,负责协调分支事务的提交或回滚
TM事务管理器,定义全局事务的边界,开启、提交、回滚全局事务
RM资源管理器,管理分支事务的资源,与 TC 通信报告分支状态

6.2 部署 Seata Server ​

yaml
# docker-compose.yml
version: '3.8'
services:
  seata-server:
    image: seataio/seata-server:1.7.0
    container_name: seata-server
    ports:
      - "8091:8091"           # 服务端口
      - "7091:7091"           # 管理控制台端口
    environment:
      SEATA_IP: 192.168.1.100
      SEATA_PORT: 8091
      STORE_MODE: db          # 使用数据库存储事务日志
    volumes:
      - ./seata/resources:/seata-server/resources
# docker-compose.yml
version: '3.8'
services:
  seata-server:
    image: seataio/seata-server:1.7.0
    container_name: seata-server
    ports:
      - "8091:8091"           # 服务端口
      - "7091:7091"           # 管理控制台端口
    environment:
      SEATA_IP: 192.168.1.100
      SEATA_PORT: 8091
      STORE_MODE: db          # 使用数据库存储事务日志
    volumes:
      - ./seata/resources:/seata-server/resources

建表脚本(Seata Server 需要以下表来存储全局事务和分支事务):

sql
-- 全局事务表
CREATE TABLE IF NOT EXISTS `global_table` (
    `xid` VARCHAR(128) NOT NULL,
    `transaction_id` BIGINT,
    `status` TINYINT NOT NULL,
    `application_id` VARCHAR(32),
    `transaction_service_group` VARCHAR(32),
    `transaction_name` VARCHAR(128),
    `timeout` INT,
    `begin_time` BIGINT,
    `application_data` VARCHAR(2000),
    `gmt_create` DATETIME,
    `gmt_modified` DATETIME,
    PRIMARY KEY (`xid`),
    KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

-- 分支事务表
CREATE TABLE IF NOT EXISTS `branch_table` (
    `branch_id` BIGINT NOT NULL,
    `xid` VARCHAR(128) NOT NULL,
    `transaction_id` BIGINT,
    `resource_group_id` VARCHAR(32),
    `resource_id` VARCHAR(256),
    `branch_type` VARCHAR(8),
    `status` TINYINT,
    `client_id` VARCHAR(64),
    `application_data` VARCHAR(2000),
    `gmt_create` DATETIME(6),
    `gmt_modified` DATETIME(6),
    PRIMARY KEY (`branch_id`),
    KEY `idx_xid` (`xid`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
-- 全局事务表
CREATE TABLE IF NOT EXISTS `global_table` (
    `xid` VARCHAR(128) NOT NULL,
    `transaction_id` BIGINT,
    `status` TINYINT NOT NULL,
    `application_id` VARCHAR(32),
    `transaction_service_group` VARCHAR(32),
    `transaction_name` VARCHAR(128),
    `timeout` INT,
    `begin_time` BIGINT,
    `application_data` VARCHAR(2000),
    `gmt_create` DATETIME,
    `gmt_modified` DATETIME,
    PRIMARY KEY (`xid`),
    KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

-- 分支事务表
CREATE TABLE IF NOT EXISTS `branch_table` (
    `branch_id` BIGINT NOT NULL,
    `xid` VARCHAR(128) NOT NULL,
    `transaction_id` BIGINT,
    `resource_group_id` VARCHAR(32),
    `resource_id` VARCHAR(256),
    `branch_type` VARCHAR(8),
    `status` TINYINT,
    `client_id` VARCHAR(64),
    `application_data` VARCHAR(2000),
    `gmt_create` DATETIME(6),
    `gmt_modified` DATETIME(6),
    PRIMARY KEY (`branch_id`),
    KEY `idx_xid` (`xid`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

每个业务数据库也需要 undo_log 表(AT 模式使用):

sql
-- 业务库中创建 undo_log 表(每个业务数据库都要执行)
CREATE TABLE IF NOT EXISTS `undo_log` (
    `branch_id` BIGINT NOT NULL,
    `xid` VARCHAR(128) NOT NULL,
    `context` VARCHAR(128) NOT NULL,
    `rollback_info` LONGBLOB,
    `log_status` INT NOT NULL,
    `log_created` DATETIME NOT NULL,
    `log_modified` DATETIME NOT NULL,
    `ext` VARCHAR(100),
    PRIMARY KEY (`branch_id`),
    KEY `idx_union_key` (`xid`, `branch_id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
-- 业务库中创建 undo_log 表(每个业务数据库都要执行)
CREATE TABLE IF NOT EXISTS `undo_log` (
    `branch_id` BIGINT NOT NULL,
    `xid` VARCHAR(128) NOT NULL,
    `context` VARCHAR(128) NOT NULL,
    `rollback_info` LONGBLOB,
    `log_status` INT NOT NULL,
    `log_created` DATETIME NOT NULL,
    `log_modified` DATETIME NOT NULL,
    `ext` VARCHAR(100),
    PRIMARY KEY (`branch_id`),
    KEY `idx_union_key` (`xid`, `branch_id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

6.3 引入依赖与配置 ​

xml
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
yaml
seata:
  enabled: true
  application-id: ${spring.application.name}
  tx-service-group: my_test_tx_group           # 事务服务分组
  service:
    vgroup-mapping:
      my_test_tx_group: default                # 分组映射到 Seata 集群
    grouplist:
      default: 127.0.0.1:8091                  # Seata Server 地址
  registry:
    type: nacos                                 # 使用 Nacos 注册 Seata Server
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace:
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
seata:
  enabled: true
  application-id: ${spring.application.name}
  tx-service-group: my_test_tx_group           # 事务服务分组
  service:
    vgroup-mapping:
      my_test_tx_group: default                # 分组映射到 Seata 集群
    grouplist:
      default: 127.0.0.1:8091                  # Seata Server 地址
  registry:
    type: nacos                                 # 使用 Nacos 注册 Seata Server
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace:
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP

6.4 AT 模式(自动补偿) ​

AT 模式是 Seata 最核心的模式,对业务代码的侵入性极低。其核心机制是自动生成逆向 SQL 实现回滚。

一阶段:TM 开始全局事务 -> RM 执行业务 SQL -> 生成 undo_log -> 提交本地事务 -> 上报分支状态
                    ↓
二阶段提交:TC 通知所有 RM 删除 undo_log(全局提交)
                    ↓
二阶段回滚:TC 通知 RM -> RM 根据 undo_log 生成逆向 SQL 恢复数据
一阶段:TM 开始全局事务 -> RM 执行业务 SQL -> 生成 undo_log -> 提交本地事务 -> 上报分支状态
                    ↓
二阶段提交:TC 通知所有 RM 删除 undo_log(全局提交)
                    ↓
二阶段回滚:TC 通知 RM -> RM 根据 undo_log 生成逆向 SQL 恢复数据
java
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {

    private final OrderMapper orderMapper;
    private final AccountFeignClient accountFeignClient;
    private final InventoryFeignClient inventoryFeignClient;

    public OrderService(OrderMapper orderMapper,
                        AccountFeignClient accountFeignClient,
                        InventoryFeignClient inventoryFeignClient) {
        this.orderMapper = orderMapper;
        this.accountFeignClient = accountFeignClient;
        this.inventoryFeignClient = inventoryFeignClient;
    }

    /**
     * 创建订单的分布式事务
     * 涉及三个服务:订单服务、账户服务、库存服务
     * @GlobalTransactional 开启全局事务
     * @Transactional     本地事务(用于写 undo_log)
     */
    @GlobalTransactional(name = "create-order-tx", rollbackFor = Exception.class)
    @Transactional(rollbackFor = Exception.class)
    public Order createOrder(Order order) {
        // 1. 创建订单(本地事务)
        orderMapper.insert(order);

        // 2. 扣减账户余额(远程调用)
        accountFeignClient.deduct(order.getUserId(), order.getAmount());

        // 3. 扣减库存(远程调用)
        inventoryFeignClient.deduct(order.getProductId(), order.getQuantity());

        // 任一远程调用失败,全局事务回滚
        return order;
    }
}
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {

    private final OrderMapper orderMapper;
    private final AccountFeignClient accountFeignClient;
    private final InventoryFeignClient inventoryFeignClient;

    public OrderService(OrderMapper orderMapper,
                        AccountFeignClient accountFeignClient,
                        InventoryFeignClient inventoryFeignClient) {
        this.orderMapper = orderMapper;
        this.accountFeignClient = accountFeignClient;
        this.inventoryFeignClient = inventoryFeignClient;
    }

    /**
     * 创建订单的分布式事务
     * 涉及三个服务:订单服务、账户服务、库存服务
     * @GlobalTransactional 开启全局事务
     * @Transactional     本地事务(用于写 undo_log)
     */
    @GlobalTransactional(name = "create-order-tx", rollbackFor = Exception.class)
    @Transactional(rollbackFor = Exception.class)
    public Order createOrder(Order order) {
        // 1. 创建订单(本地事务)
        orderMapper.insert(order);

        // 2. 扣减账户余额(远程调用)
        accountFeignClient.deduct(order.getUserId(), order.getAmount());

        // 3. 扣减库存(远程调用)
        inventoryFeignClient.deduct(order.getProductId(), order.getQuantity());

        // 任一远程调用失败,全局事务回滚
        return order;
    }
}

AT 模式优缺点:

优点缺点
代码侵入性低,几乎无感知性能较差(一阶段锁定资源)
支持绝大多数 SQL(INSERT/UPDATE/DELETE/SELECT FOR UPDATE)不支持所有 SQL 类型(如 DDL)
对已有系统改造工作量小事务隔离级别需要额外处理(全局写锁)

6.5 TCC 模式(Try-Confirm-Cancel) ​

TCC 模式需要业务方自己实现 Try、Confirm、Cancel 三个方法,相比 AT 模式更灵活,性能更好。

Try:     资源预留(如冻结库存、预扣金额)
Confirm: 确认执行业务操作(使用 Try 阶段预留的资源)
Cancel:  取消业务操作(释放 Try 阶段预留的资源)
Try:     资源预留(如冻结库存、预扣金额)
Confirm: 确认执行业务操作(使用 Try 阶段预留的资源)
Cancel:  取消业务操作(释放 Try 阶段预留的资源)
java
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;

/**
 * TCC 接口定义
 */
@LocalTCC
public interface AccountTccService {

    /**
     * Try 阶段:预扣金额
     * name: TCC 步骤名称(在 Seata 中唯一标识)
     * commitMethod: Confirm 方法名
     * rollbackMethod: Cancel 方法名
     */
    @TwoPhaseBusinessAction(
            name = "accountTccAction",
            commitMethod = "confirm",
            rollbackMethod = "cancel"
    )
    boolean prepare(BusinessActionContext context,
                    @BusinessActionContextParameter(paramName = "userId") Long userId,
                    @BusinessActionContextParameter(paramName = "amount") BigDecimal amount);

    /**
     * Confirm 阶段:确认扣减
     */
    boolean confirm(BusinessActionContext context);

    /**
     * Cancel 阶段:释放预扣金额
     */
    boolean cancel(BusinessActionContext context);
}
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;

/**
 * TCC 接口定义
 */
@LocalTCC
public interface AccountTccService {

    /**
     * Try 阶段:预扣金额
     * name: TCC 步骤名称(在 Seata 中唯一标识)
     * commitMethod: Confirm 方法名
     * rollbackMethod: Cancel 方法名
     */
    @TwoPhaseBusinessAction(
            name = "accountTccAction",
            commitMethod = "confirm",
            rollbackMethod = "cancel"
    )
    boolean prepare(BusinessActionContext context,
                    @BusinessActionContextParameter(paramName = "userId") Long userId,
                    @BusinessActionContextParameter(paramName = "amount") BigDecimal amount);

    /**
     * Confirm 阶段:确认扣减
     */
    boolean confirm(BusinessActionContext context);

    /**
     * Cancel 阶段:释放预扣金额
     */
    boolean cancel(BusinessActionContext context);
}
java
import io.seata.rm.tcc.api.BusinessActionContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class AccountTccServiceImpl implements AccountTccService {

    private final AccountMapper accountMapper;
    private final AccountFreezeMapper freezeMapper;

    public AccountTccServiceImpl(AccountMapper accountMapper,
                                  AccountFreezeMapper freezeMapper) {
        this.accountMapper = accountMapper;
        this.freezeMapper = freezeMapper;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean prepare(BusinessActionContext context, Long userId, BigDecimal amount) {
        // 检查余额
        Account account = accountMapper.selectById(userId);
        if (account.getBalance().compareTo(amount) < 0) {
            throw new RuntimeException("余额不足");
        }
        // 扣减余额(可冻结金额)
        accountMapper.updateFrozenBalance(userId, amount);
        // 记录冻结记录
        freezeMapper.insert(new AccountFreeze(userId, amount, AccountFreeze.STATUS_TRY));
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean confirm(BusinessActionContext context) {
        // 正式扣减:将冻结金额从冻结字段清除
        Long userId = Long.valueOf(context.getActionContext("userId").toString());
        freezeMapper.updateStatus(userId, AccountFreeze.STATUS_TRY, AccountFreeze.STATUS_CONFIRM);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean cancel(BusinessActionContext context) {
        // 撤销:归还冻结金额
        Long userId = Long.valueOf(context.getActionContext("userId").toString());
        BigDecimal amount = new BigDecimal(context.getActionContext("amount").toString());
        accountMapper.unfreezeBalance(userId, amount);
        freezeMapper.updateStatus(userId, AccountFreeze.STATUS_TRY, AccountFreeze.STATUS_CANCEL);
        return true;
    }
}
import io.seata.rm.tcc.api.BusinessActionContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class AccountTccServiceImpl implements AccountTccService {

    private final AccountMapper accountMapper;
    private final AccountFreezeMapper freezeMapper;

    public AccountTccServiceImpl(AccountMapper accountMapper,
                                  AccountFreezeMapper freezeMapper) {
        this.accountMapper = accountMapper;
        this.freezeMapper = freezeMapper;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean prepare(BusinessActionContext context, Long userId, BigDecimal amount) {
        // 检查余额
        Account account = accountMapper.selectById(userId);
        if (account.getBalance().compareTo(amount) < 0) {
            throw new RuntimeException("余额不足");
        }
        // 扣减余额(可冻结金额)
        accountMapper.updateFrozenBalance(userId, amount);
        // 记录冻结记录
        freezeMapper.insert(new AccountFreeze(userId, amount, AccountFreeze.STATUS_TRY));
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean confirm(BusinessActionContext context) {
        // 正式扣减:将冻结金额从冻结字段清除
        Long userId = Long.valueOf(context.getActionContext("userId").toString());
        freezeMapper.updateStatus(userId, AccountFreeze.STATUS_TRY, AccountFreeze.STATUS_CONFIRM);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean cancel(BusinessActionContext context) {
        // 撤销:归还冻结金额
        Long userId = Long.valueOf(context.getActionContext("userId").toString());
        BigDecimal amount = new BigDecimal(context.getActionContext("amount").toString());
        accountMapper.unfreezeBalance(userId, amount);
        freezeMapper.updateStatus(userId, AccountFreeze.STATUS_TRY, AccountFreeze.STATUS_CANCEL);
        return true;
    }
}

6.6 Saga 模式 ​

Saga 模式适合长事务和异步编排场景,通过状态机驱动执行流程。

Saga 执行流程示例:
  开始
   ↓
  Step 1: 创建订单     → 成功 → Step 2
                        失败 → 全局回滚(结束)
   ↓
  Step 2: 扣减库存     → 成功 → Step 3
                        失败 → 执行 Step1 的补偿操作
   ↓
  Step 3: 扣减余额     → 成功 → 全局提交
                        失败 → 执行 Step2 补偿 → 执行 Step1 补偿
Saga 执行流程示例:
  开始
   ↓
  Step 1: 创建订单     → 成功 → Step 2
                        失败 → 全局回滚(结束)
   ↓
  Step 2: 扣减库存     → 成功 → Step 3
                        失败 → 执行 Step1 的补偿操作
   ↓
  Step 3: 扣减余额     → 成功 → 全局提交
                        失败 → 执行 Step2 补偿 → 执行 Step1 补偿

Saga 模式在 Seata 中通过状态机定义文件(JSON/YAML)来配置:

json
{
    "name": "createOrderSaga",
    "startState": "CreateOrder",
    "states": {
        "CreateOrder": {
            "type": "ServiceTask",
            "serviceName": "orderService",
            "serviceMethod": "create",
            "compensateState": "CompensateCreateOrder",
            "next": "DeductInventory"
        },
        "CompensateCreateOrder": {
            "type": "ServiceTask",
            "serviceName": "orderService",
            "serviceMethod": "cancel"
        },
        "DeductInventory": {
            "type": "ServiceTask",
            "serviceName": "inventoryService",
            "serviceMethod": "deduct",
            "compensateState": "CompensateDeductInventory",
            "next": "DeductAccount"
        },
        "CompensateDeductInventory": {
            "type": "ServiceTask",
            "serviceName": "inventoryService",
            "serviceMethod": "compensateDeduct"
        },
        "DeductAccount": {
            "type": "ServiceTask",
            "serviceName": "accountService",
            "serviceMethod": "deduct",
            "compensateState": "CompensateDeductAccount",
            "next": "Success"
        },
        "CompensateDeductAccount": {
            "type": "ServiceTask",
            "serviceName": "accountService",
            "serviceMethod": "compensateDeduct"
        },
        "Success": {
            "type": "Succeed"
        }
    }
}
{
    "name": "createOrderSaga",
    "startState": "CreateOrder",
    "states": {
        "CreateOrder": {
            "type": "ServiceTask",
            "serviceName": "orderService",
            "serviceMethod": "create",
            "compensateState": "CompensateCreateOrder",
            "next": "DeductInventory"
        },
        "CompensateCreateOrder": {
            "type": "ServiceTask",
            "serviceName": "orderService",
            "serviceMethod": "cancel"
        },
        "DeductInventory": {
            "type": "ServiceTask",
            "serviceName": "inventoryService",
            "serviceMethod": "deduct",
            "compensateState": "CompensateDeductInventory",
            "next": "DeductAccount"
        },
        "CompensateDeductInventory": {
            "type": "ServiceTask",
            "serviceName": "inventoryService",
            "serviceMethod": "compensateDeduct"
        },
        "DeductAccount": {
            "type": "ServiceTask",
            "serviceName": "accountService",
            "serviceMethod": "deduct",
            "compensateState": "CompensateDeductAccount",
            "next": "Success"
        },
        "CompensateDeductAccount": {
            "type": "ServiceTask",
            "serviceName": "accountService",
            "serviceMethod": "compensateDeduct"
        },
        "Success": {
            "type": "Succeed"
        }
    }
}

6.7 XA 模式 ​

XA 模式基于 X/Open DTP 标准,是数据库层面的分布式事务协议。Seata 对 XA 做了封装,用户无需关心底层 XA 协议细节。

java
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class XaOrderService {

    @GlobalTransactional
    @Transactional
    public void createOrderXa(Order order) {
        // XA 模式要求所有参与事务的连接必须是 XAConnection
        // 通过开启 spring.datasource.dynamic.seata-xa=true 来启用
        orderMapper.insert(order);
        accountFeignClient.deduct(order.getUserId(), order.getAmount());
        inventoryFeignClient.deduct(order.getProductId(), order.getQuantity());
    }
}
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class XaOrderService {

    @GlobalTransactional
    @Transactional
    public void createOrderXa(Order order) {
        // XA 模式要求所有参与事务的连接必须是 XAConnection
        // 通过开启 spring.datasource.dynamic.seata-xa=true 来启用
        orderMapper.insert(order);
        accountFeignClient.deduct(order.getUserId(), order.getAmount());
        inventoryFeignClient.deduct(order.getProductId(), order.getQuantity());
    }
}

6.8 四种模式对比与选型 ​

维度ATTCCSagaXA
侵入性低,自动生成补偿 SQL高,需实现三个方法中,需定义状态机低,数据库层面支持
性能中(全局锁影响并发)高高(异步吞吐大)低(锁时间长)
隔离性弱(读取未提交的数据)手工控制无隔离保证强(全局锁)
一致性最终一致最终一致最终一致强一致
代码量少多(3个方法)中(状态机配置)少
场景通用场景,对一致性要求不高高性能强隔离场景长事务、异步编排强一致、短事务
数据库支持MySQL、PostgreSQL、Oracle不限不限需 XA 驱动支持

选型建议:

  • AT 模式:首选,适合绝大多数业务场景,改造最小。
  • TCC 模式:对性能要求极高,或需要精细化控制资源锁定范围。
  • Saga 模式:长事务链,如订单全流程(下单->支付->发货->确认收货)。
  • XA 模式:金融场景,必须强一致性,数据零丢失。

七、分布式链路追踪 ​

在微服务架构中,一个请求可能跨越多个服务,如何快速定位问题出在哪个环节?链路追踪通过 Trace ID 将所有调用串联起来,形成完整的调用拓扑。

7.1 Micrometer Tracing 架构 ​

Spring Cloud 2022.x 之后,官方链路追踪方案从 Sleuth 迁移到 Micrometer Tracing,底层支持 Brave(Zipkin)、OpenTelemetry 等多种实现。

                   ┌──────────────────────────────┐
                   │    Micrometer Tracing API     │ ← 统一 API 抽象
                   ├──────────────┬───────────────┤
                   │   Brave      │  OpenTelemetry │ ← 可替换的 Tracer 实现
                   │  (Zipkin)    │  (Jaeger/OTLP) │
                   ├──────────────┴───────────────┤
                   │  收集 & 上报                   │
                   │  Zipkin Server / OTLP          │
                   └──────────────────────────────┘
                   ┌──────────────────────────────┐
                   │    Micrometer Tracing API     │ ← 统一 API 抽象
                   ├──────────────┬───────────────┤
                   │   Brave      │  OpenTelemetry │ ← 可替换的 Tracer 实现
                   │  (Zipkin)    │  (Jaeger/OTLP) │
                   ├──────────────┴───────────────┤
                   │  收集 & 上报                   │
                   │  Zipkin Server / OTLP          │
                   └──────────────────────────────┘

核心概念 ​

概念说明
Span一次调用的基础单元,包含服务名、耗时、标签、事件等
Trace由多个 Span 组成的调用链,同一次请求的 Trace ID 相同
Trace ID全局唯一标识,贯穿整个调用链
Span ID单个 Span 的唯一标识
Parent Span ID父 Span 的 ID,用于构建调用层级关系
Sampler采样策略,决定哪些请求需要追踪
一次完整的 Trace 结构:

Span A (Trace ID = X, Span ID = A)
  ├── Span B (Trace ID = X, Span ID = B, Parent = A)
  │     └── Span C (Trace ID = X, Span ID = C, Parent = B)
  └── Span D (Trace ID = X, Span ID = D, Parent = A)
        └── Span E (Trace ID = X, Span ID = E, Parent = D)
一次完整的 Trace 结构:

Span A (Trace ID = X, Span ID = A)
  ├── Span B (Trace ID = X, Span ID = B, Parent = A)
  │     └── Span C (Trace ID = X, Span ID = C, Parent = B)
  └── Span D (Trace ID = X, Span ID = D, Parent = A)
        └── Span E (Trace ID = X, Span ID = E, Parent = D)

7.2 Brave + Zipkin 集成 ​

引入依赖 ​

xml
<!-- Micrometer Tracing + Brave -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>

<!-- Zipkin 上报 -->
<dependency>
    <groupId>io.zipkin.reporter2</groupId>
    <artifactId>zipkin-reporter-brave</artifactId>
</dependency>

<!-- Feign 链路追踪(可选) -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-integration-test</artifactId>
    <scope>test</scope>
</dependency>

<!-- Gateway / WebFlux 支持 -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-observation</artifactId>
</dependency>
<!-- Micrometer Tracing + Brave -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>

<!-- Zipkin 上报 -->
<dependency>
    <groupId>io.zipkin.reporter2</groupId>
    <artifactId>zipkin-reporter-brave</artifactId>
</dependency>

<!-- Feign 链路追踪(可选) -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-integration-test</artifactId>
    <scope>test</scope>
</dependency>

<!-- Gateway / WebFlux 支持 -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-observation</artifactId>
</dependency>

配置 ​

yaml
# application.yml
spring:
  application:
    name: user-service

# Micrometer Tracing 配置
management:
  tracing:
    enabled: true
    sampling:
      probability: 0.1                  # 采样率:10% 的请求被采样
    propagation:
      type: W3C                         # 传播格式:W3C Trace Context 或 B3
  zipkin:
    tracing:
      endpoint: http://localhost:9411/api/v2/spans  # Zipkin 服务地址
# application.yml
spring:
  application:
    name: user-service

# Micrometer Tracing 配置
management:
  tracing:
    enabled: true
    sampling:
      probability: 0.1                  # 采样率:10% 的请求被采样
    propagation:
      type: W3C                         # 传播格式:W3C Trace Context 或 B3
  zipkin:
    tracing:
      endpoint: http://localhost:9411/api/v2/spans  # Zipkin 服务地址

采样策略说明:

采样率含义适用场景
1.0100% 采样开发测试环境
0.110% 采样生产环境低流量服务
0.011% 采样生产环境高流量服务
0.0不采样关闭追踪

部署 Zipkin ​

bash
docker run -d -p 9411:9411 openzipkin/zipkin:latest
# 访问 http://localhost:9411 查看追踪数据
docker run -d -p 9411:9411 openzipkin/zipkin:latest
# 访问 http://localhost:9411 查看追踪数据

自定义 Span ​

java
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.tracing.Tracer;
import org.springframework.stereotype.Service;

@Service
public class BusinessService {

    private final ObservationRegistry observationRegistry;
    private final Tracer tracer;

    public BusinessService(ObservationRegistry observationRegistry, Tracer tracer) {
        this.observationRegistry = observationRegistry;
        this.tracer = tracer;
    }

    /**
     * 方式一:使用 Observation API(推荐)
     */
    public void doBusinessWithObservation() {
        Observation.createNotStarted("business-operation", observationRegistry)
                .lowCardinalityKeyValue("operation", "createOrder")
                .highCardinalityKeyValue("orderId", "ORD123456")
                .observe(() -> {
                    // 执行业务逻辑,自动包裹 Span
                    // span.start -> 执行业务 -> span.end
                });
    }

    /**
     * 方式二:直接使用 Tracer API
     */
    public void doBusinessWithTracer() {
        var span = tracer.nextSpan().name("manual-span").start();
        try (var ws = tracer.withSpan(span)) {
            span.tag("key", "value");
            // 业务逻辑
        } finally {
            span.end();
        }
    }
}
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.tracing.Tracer;
import org.springframework.stereotype.Service;

@Service
public class BusinessService {

    private final ObservationRegistry observationRegistry;
    private final Tracer tracer;

    public BusinessService(ObservationRegistry observationRegistry, Tracer tracer) {
        this.observationRegistry = observationRegistry;
        this.tracer = tracer;
    }

    /**
     * 方式一:使用 Observation API(推荐)
     */
    public void doBusinessWithObservation() {
        Observation.createNotStarted("business-operation", observationRegistry)
                .lowCardinalityKeyValue("operation", "createOrder")
                .highCardinalityKeyValue("orderId", "ORD123456")
                .observe(() -> {
                    // 执行业务逻辑,自动包裹 Span
                    // span.start -> 执行业务 -> span.end
                });
    }

    /**
     * 方式二:直接使用 Tracer API
     */
    public void doBusinessWithTracer() {
        var span = tracer.nextSpan().name("manual-span").start();
        try (var ws = tracer.withSpan(span)) {
            span.tag("key", "value");
            // 业务逻辑
        } finally {
            span.end();
        }
    }
}

在异步线程中传递 Trace 上下文 ​

java
import io.micrometer.context.ContextSnapshot;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.util.concurrent.CompletableFuture;

@Component
public class AsyncService {

    /**
     * 异步方法中的链路追踪
     * 必须在异步线程中传递 Trace 上下文
     */
    @Async
    public CompletableFuture<String> asyncMethod() {
        // 方式一:通过 ContextSnapshot 传递(推荐)
        try (var snapshot = ContextSnapshot.capture()) {
            snapshot.setThreadLocals();
            // 异步业务逻辑
            return CompletableFuture.completedFuture("done");
        }
    }
}
import io.micrometer.context.ContextSnapshot;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.util.concurrent.CompletableFuture;

@Component
public class AsyncService {

    /**
     * 异步方法中的链路追踪
     * 必须在异步线程中传递 Trace 上下文
     */
    @Async
    public CompletableFuture<String> asyncMethod() {
        // 方式一:通过 ContextSnapshot 传递(推荐)
        try (var snapshot = ContextSnapshot.capture()) {
            snapshot.setThreadLocals();
            // 异步业务逻辑
            return CompletableFuture.completedFuture("done");
        }
    }
}

7.3 在 Gateway 中传递 Trace ID ​

java
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.UUID;

/**
 * Gateway 层生成 Trace ID 传递到下游
 */
@Component
public class TraceIdGatewayFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String traceId = exchange.getRequest().getHeaders()
                .getFirst("X-Trace-Id");
        if (traceId == null || traceId.isEmpty()) {
            traceId = UUID.randomUUID().toString().replace("-", "").substring(0, 16);
        }

        exchange.getRequest().mutate()
                .header("X-Trace-Id", traceId)
                .build();

        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.UUID;

/**
 * Gateway 层生成 Trace ID 传递到下游
 */
@Component
public class TraceIdGatewayFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String traceId = exchange.getRequest().getHeaders()
                .getFirst("X-Trace-Id");
        if (traceId == null || traceId.isEmpty()) {
            traceId = UUID.randomUUID().toString().replace("-", "").substring(0, 16);
        }

        exchange.getRequest().mutate()
                .header("X-Trace-Id", traceId)
                .build();

        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}

7.4 日志与 Trace ID 关联 ​

通过日志配置将 Trace ID 和 Span ID 输出到日志中,便于检索:

yaml
# logback-spring.xml
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>
                %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId:-},%X{spanId:-}] %-5level %logger{36} - %msg%n
            </pattern>
        </encoder>
    </appender>
</configuration>
# logback-spring.xml
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>
                %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId:-},%X{spanId:-}] %-5level %logger{36} - %msg%n
            </pattern>
        </encoder>
    </appender>
</configuration>

输出的日志格式为:

2024-06-15 10:30:00.123 [http-nio-8080-exec-1] [abc123def456,abc123def456] INFO  c.e.UserController - 查询用户 100
2024-06-15 10:30:00.456 [http-nio-8080-exec-2] [abc123def456,xyz789] INFO  c.e.UserFeignClient - Feign 调用 /api/user/100
2024-06-15 10:30:00.123 [http-nio-8080-exec-1] [abc123def456,abc123def456] INFO  c.e.UserController - 查询用户 100
2024-06-15 10:30:00.456 [http-nio-8080-exec-2] [abc123def456,xyz789] INFO  c.e.UserFeignClient - Feign 调用 /api/user/100

同一 Trace ID 的所有日志可以关联检索。


八、其他组件 ​

8.1 Spring Cloud Task + @EnableScheduling ​

Spring Cloud Task 提供了短生命周期的微服务任务框架,适用于批处理、数据同步等一次性或定时任务:

java
import org.springframework.boot.ApplicationRunner;
import org.springframework.cloud.task.configuration.EnableTask;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableTask
public class TaskConfiguration {

    @Bean
    public ApplicationRunner taskRunner() {
        return args -> {
            System.out.println("Task executed with args: " + args.getOptionValues("key"));
        };
    }
}
import org.springframework.boot.ApplicationRunner;
import org.springframework.cloud.task.configuration.EnableTask;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableTask
public class TaskConfiguration {

    @Bean
    public ApplicationRunner taskRunner() {
        return args -> {
            System.out.println("Task executed with args: " + args.getOptionValues("key"));
        };
    }
}

在微服务中使用定时任务调度:

java
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
@EnableScheduling
public class ScheduledTasks {

    /**
     * 定时数据同步任务
     * 每秒执行一次(仅用于演示,生产环境建议使用分布式调度框架如 XXL-Job)
     */
    @Scheduled(fixedRate = 5000)
    public void syncData() {
        System.out.println("同步数据中...");
    }

    /**
     * Cron 表达式调度
     * 每天凌晨 2 点执行
     */
    @Scheduled(cron = "0 0 2 * * ?")
    public void dailyCleanup() {
        System.out.println("每日清理任务...");
    }
}
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
@EnableScheduling
public class ScheduledTasks {

    /**
     * 定时数据同步任务
     * 每秒执行一次(仅用于演示,生产环境建议使用分布式调度框架如 XXL-Job)
     */
    @Scheduled(fixedRate = 5000)
    public void syncData() {
        System.out.println("同步数据中...");
    }

    /**
     * Cron 表达式调度
     * 每天凌晨 2 点执行
     */
    @Scheduled(cron = "0 0 2 * * ?")
    public void dailyCleanup() {
        System.out.println("每日清理任务...");
    }
}

注意:@Scheduled 注解在分布式环境中会存在重复执行问题,所有实例都会执行。生产环境应使用分布式调度框架(XXL-Job / Elastic-Job)或通过数据库锁控制单机执行。

8.2 Spring Cloud Bus ​

Spring Cloud Bus 用于在微服务之间传播状态变更,通常配合配置中心实现配置动态刷新的广播通知。

xml
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
yaml
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

management:
  endpoints:
    web:
      exposure:
        include: bus-refresh    # 暴露配置刷新端点
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

management:
  endpoints:
    web:
      exposure:
        include: bus-refresh    # 暴露配置刷新端点

工作原理:

1. 运维人员 POST 请求 /actuator/bus-refresh 到任意一个微服务实例
2. 该实例通过 RabbitMQ(或 Kafka)向总线发送刷新事件
3. 所有监听该总线的微服务收到事件后,重新拉取配置
4. 配置变更通过 @RefreshScope 生效
1. 运维人员 POST 请求 /actuator/bus-refresh 到任意一个微服务实例
2. 该实例通过 RabbitMQ(或 Kafka)向总线发送刷新事件
3. 所有监听该总线的微服务收到事件后,重新拉取配置
4. 配置变更通过 @RefreshScope 生效

定向刷新(只刷新指定服务):

bash
# 只刷新 user-service 的配置
curl -X POST http://gateway:8080/actuator/bus-refresh/user-service:**

# 只刷新 user-service 在 8081 端口的实例
curl -X POST http://gateway:8080/actuator/bus-refresh/user-service:8081
# 只刷新 user-service 的配置
curl -X POST http://gateway:8080/actuator/bus-refresh/user-service:**

# 只刷新 user-service 在 8081 端口的实例
curl -X POST http://gateway:8080/actuator/bus-refresh/user-service:8081

8.3 Resilience4j 断路器 ​

Resilience4j 是 Spring Cloud 官方推荐的断路器库(Hystrix 的替代品),提供了断路器、重试、限流器、隔离、缓存等模块。

xml
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
yaml
resilience4j:
  circuitbreaker:
    configs:
      default:                                    # 默认配置
        registerHealthIndicator: true
        slidingWindowSize: 10                     # 滑动窗口大小
        minimumNumberOfCalls: 5                   # 最小调用次数
        permittedNumberOfCallsInHalfOpenState: 3  # 半开状态允许的请求数
        automaticTransitionFromOpenToHalfOpen: true
        waitDurationInOpenState: 10s              # 熔断持续时间
        failureRateThreshold: 50                  # 失败率阈值(百分比)
        eventConsumerBufferSize: 10
    instances:
      userService:
        baseConfig: default
        slidingWindowSize: 20
        failureRateThreshold: 30

  retry:                                          # 重试配置
    configs:
      default:
        maxAttempts: 3
        waitDuration: 1s
        retryExceptions:
          - org.springframework.web.client.HttpServerErrorException
    instances:
      userService:
        baseConfig: default

  ratelimiter:                                    # 限流配置
    configs:
      default:
        limitForPeriod: 10
        limitRefreshPeriod: 1s
        timeoutDuration: 0
resilience4j:
  circuitbreaker:
    configs:
      default:                                    # 默认配置
        registerHealthIndicator: true
        slidingWindowSize: 10                     # 滑动窗口大小
        minimumNumberOfCalls: 5                   # 最小调用次数
        permittedNumberOfCallsInHalfOpenState: 3  # 半开状态允许的请求数
        automaticTransitionFromOpenToHalfOpen: true
        waitDurationInOpenState: 10s              # 熔断持续时间
        failureRateThreshold: 50                  # 失败率阈值(百分比)
        eventConsumerBufferSize: 10
    instances:
      userService:
        baseConfig: default
        slidingWindowSize: 20
        failureRateThreshold: 30

  retry:                                          # 重试配置
    configs:
      default:
        maxAttempts: 3
        waitDuration: 1s
        retryExceptions:
          - org.springframework.web.client.HttpServerErrorException
    instances:
      userService:
        baseConfig: default

  ratelimiter:                                    # 限流配置
    configs:
      default:
        limitForPeriod: 10
        limitRefreshPeriod: 1s
        timeoutDuration: 0

@CircuitBreaker 注解 ​

java
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.retry.annotation.Retry;
import org.springframework.stereotype.Service;

@Service
public class UserServiceClient {

    /**
     * 熔断 + 重试组合
     * name 对应配置文件中的实例名
     * fallbackMethod 为降级方法
     */
    @CircuitBreaker(name = "userService", fallbackMethod = "getUserFallback")
    @Retry(name = "userService")
    public UserDTO getUser(Long id) {
        // 调用远程服务
        return restTemplate.getForObject("http://user-service/api/user/" + id, UserDTO.class);
    }

    /**
     * 降级方法
     * 必须与原方法在同一个类中,且参数签名一致(加上 Throwable)
     */
    public UserDTO getUserFallback(Long id, Throwable t) {
        return new UserDTO(-1L, "fallback_user");
    }
}
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.retry.annotation.Retry;
import org.springframework.stereotype.Service;

@Service
public class UserServiceClient {

    /**
     * 熔断 + 重试组合
     * name 对应配置文件中的实例名
     * fallbackMethod 为降级方法
     */
    @CircuitBreaker(name = "userService", fallbackMethod = "getUserFallback")
    @Retry(name = "userService")
    public UserDTO getUser(Long id) {
        // 调用远程服务
        return restTemplate.getForObject("http://user-service/api/user/" + id, UserDTO.class);
    }

    /**
     * 降级方法
     * 必须与原方法在同一个类中,且参数签名一致(加上 Throwable)
     */
    public UserDTO getUserFallback(Long id, Throwable t) {
        return new UserDTO(-1L, "fallback_user");
    }
}

Resilience4j 与 Feign 集成 ​

yaml
spring:
  cloud:
    openfeign:
      circuitbreaker:
        enabled: true                        # 开启 Feign 的断路器支持
        alphanumeric-ids:
          enabled: true
spring:
  cloud:
    openfeign:
      circuitbreaker:
        enabled: true                        # 开启 Feign 的断路器支持
        alphanumeric-ids:
          enabled: true

Feign 客户端定义(使用 FallbackFactory):

java
@FeignClient(name = "user-service", path = "/api/user",
        fallbackFactory = UserFeignFallbackFactory.class)
public interface UserFeignClient {
    @GetMapping("/{id}")
    UserDTO getUserById(@PathVariable("id") Long id);
}
@FeignClient(name = "user-service", path = "/api/user",
        fallbackFactory = UserFeignFallbackFactory.class)
public interface UserFeignClient {
    @GetMapping("/{id}")
    UserDTO getUserById(@PathVariable("id") Long id);
}

Resilience4j vs Sentinel 对比:

维度Resilience4jSentinel
定位轻量级断路器库完整的流量治理框架
限流RateLimiter 模块丰富的限流模式和策略
熔断CircuitBreaker 模块熔断降级规则
隔离Bulkhead(线程池/信号量隔离)线程数限流
控制台无官方控制台Sentinel Dashboard
规则持久化需自行实现支持 Nacos/Push/Pull
Spring Cloud 集成度官方集成Alibaba 生态集成

九、项目结构实践 ​

9.1 父 POM 依赖管理 ​

多模块微服务项目通常使用一个父 POM 统一管理所有依赖的版本,避免版本冲突。

xml
<?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>

    <groupId>com.example</groupId>
    <artifactId>microservice-parent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <modules>
        <module>common</module>
        <module>gateway</module>
        <module>auth-service</module>
        <module>user-service</module>
        <module>order-service</module>
        <module>business-service</module>
    </modules>

    <!-- 统一版本管理 -->
    <properties>
        <java.version>17</java.version>
        <spring-boot.version>3.2.2</spring-boot.version>
        <spring-cloud.version>2023.0.0</spring-cloud.version>
        <spring-cloud-alibaba.version>2023.0.0.0-RC1</spring-cloud-alibaba.version>
        <mybatis-plus.version>3.5.5</mybatis-plus.version>
        <mysql-connector.version>8.0.33</mysql-connector.version>
        <hutool.version>5.8.25</hutool.version>
        <mapstruct.version>1.5.5.Final</mapstruct.version>
    </properties>

    <!-- dependencyManagement 只管理版本,不引入依赖 -->
    <dependencyManagement>
        <dependencies>
            <!-- Spring Boot BOM -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- Spring Cloud BOM -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- Spring Cloud Alibaba BOM -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- 内部模块 -->
            <dependency>
                <groupId>com.example</groupId>
                <artifactId>common</artifactId>
                <version>${project.version}</version>
            </dependency>

            <!-- 第三方依赖 -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
<?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>

    <groupId>com.example</groupId>
    <artifactId>microservice-parent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <modules>
        <module>common</module>
        <module>gateway</module>
        <module>auth-service</module>
        <module>user-service</module>
        <module>order-service</module>
        <module>business-service</module>
    </modules>

    <!-- 统一版本管理 -->
    <properties>
        <java.version>17</java.version>
        <spring-boot.version>3.2.2</spring-boot.version>
        <spring-cloud.version>2023.0.0</spring-cloud.version>
        <spring-cloud-alibaba.version>2023.0.0.0-RC1</spring-cloud-alibaba.version>
        <mybatis-plus.version>3.5.5</mybatis-plus.version>
        <mysql-connector.version>8.0.33</mysql-connector.version>
        <hutool.version>5.8.25</hutool.version>
        <mapstruct.version>1.5.5.Final</mapstruct.version>
    </properties>

    <!-- dependencyManagement 只管理版本,不引入依赖 -->
    <dependencyManagement>
        <dependencies>
            <!-- Spring Boot BOM -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- Spring Cloud BOM -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- Spring Cloud Alibaba BOM -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- 内部模块 -->
            <dependency>
                <groupId>com.example</groupId>
                <artifactId>common</artifactId>
                <version>${project.version}</version>
            </dependency>

            <!-- 第三方依赖 -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

9.2 模块划分 ​

推荐的项目模块结构:

microservice-parent/
├── pom.xml                              # 父 POM
├── common/                              # 公共模块(无启动类)
│   ├── pom.xml
│   └── src/main/java/com/example/common/
│       ├── dto/                         # 公共数据传输对象
│       │   ├── Result.java              # 统一响应封装
│       │   └── PageResult.java          # 分页结果封装
│       ├── entity/                      # 公共实体
│       ├── exception/                   # 全局异常定义
│       │   ├── BizException.java
│       │   └── GlobalExceptionHandler.java
│       ├── constant/                    # 常量定义
│       │   ├── ApiConstant.java
│       │   └── AuthConstant.java
│       └── util/                        # 工具类
│           ├── JsonUtils.java
│           └── SpringContextHolder.java
│
├── gateway/                             # API 网关(独立部署)
│   ├── pom.xml
│   └── src/main/java/com/example/gateway/
│       ├── GatewayApplication.java
│       ├── config/
│       │   ├── CorsConfig.java
│       │   └── RouteConfig.java
│       └── filter/
│           ├── AuthGlobalFilter.java
│           └── RateLimitFilter.java
│
├── auth-service/                        # 认证授权服务
│   ├── pom.xml
│   └── src/main/java/com/example/auth/
│       ├── AuthApplication.java
│       ├── controller/                  # 接口层
│       ├── service/                     # 业务层
│       ├── mapper/                      # 数据访问层
│       └── config/
│           └── SecurityConfig.java
│
├── user-service/                        # 用户服务
│   ├── pom.xml
│   ├── src/main/java/com/example/user/
│   │   ├── UserApplication.java
│   │   ├── controller/
│   │   ├── service/
│   │   │   ├── impl/
│   │   │   └── remote/                  # Feign 客户端远程调用
│   │   │       └── OrderFeignClient.java
│   │   ├── mapper/
│   │   ├── entity/
│   │   └── config/
│   └── src/main/resources/
│       ├── bootstrap.yml
│       ├── application.yml
│       └── mapper/                      # MyBatis XML
│
├── order-service/                       # 订单服务
│   └── ... (同 user-service 结构)
│
└── business-service/                    # 业务聚合服务
    └── ... (同 user-service 结构)
microservice-parent/
├── pom.xml                              # 父 POM
├── common/                              # 公共模块(无启动类)
│   ├── pom.xml
│   └── src/main/java/com/example/common/
│       ├── dto/                         # 公共数据传输对象
│       │   ├── Result.java              # 统一响应封装
│       │   └── PageResult.java          # 分页结果封装
│       ├── entity/                      # 公共实体
│       ├── exception/                   # 全局异常定义
│       │   ├── BizException.java
│       │   └── GlobalExceptionHandler.java
│       ├── constant/                    # 常量定义
│       │   ├── ApiConstant.java
│       │   └── AuthConstant.java
│       └── util/                        # 工具类
│           ├── JsonUtils.java
│           └── SpringContextHolder.java
│
├── gateway/                             # API 网关(独立部署)
│   ├── pom.xml
│   └── src/main/java/com/example/gateway/
│       ├── GatewayApplication.java
│       ├── config/
│       │   ├── CorsConfig.java
│       │   └── RouteConfig.java
│       └── filter/
│           ├── AuthGlobalFilter.java
│           └── RateLimitFilter.java
│
├── auth-service/                        # 认证授权服务
│   ├── pom.xml
│   └── src/main/java/com/example/auth/
│       ├── AuthApplication.java
│       ├── controller/                  # 接口层
│       ├── service/                     # 业务层
│       ├── mapper/                      # 数据访问层
│       └── config/
│           └── SecurityConfig.java
│
├── user-service/                        # 用户服务
│   ├── pom.xml
│   ├── src/main/java/com/example/user/
│   │   ├── UserApplication.java
│   │   ├── controller/
│   │   ├── service/
│   │   │   ├── impl/
│   │   │   └── remote/                  # Feign 客户端远程调用
│   │   │       └── OrderFeignClient.java
│   │   ├── mapper/
│   │   ├── entity/
│   │   └── config/
│   └── src/main/resources/
│       ├── bootstrap.yml
│       ├── application.yml
│       └── mapper/                      # MyBatis XML
│
├── order-service/                       # 订单服务
│   └── ... (同 user-service 结构)
│
└── business-service/                    # 业务聚合服务
    └── ... (同 user-service 结构)

9.3 API 模块与业务模块分离 ​

实践中的一个好习惯是将 Feign 接口定义提取到独立的 API 模块,由服务提供者维护,服务消费者引用:

api-module/                              # API 接口模块(纯接口 + DTO)
├── src/main/java/com/example/api/
│   ├── user/
│   │   ├── UserFeignClient.java         # Feign 接口
│   │   ├── UserDTO.java                 # 数据传输对象
│   │   └── UserQueryDTO.java
│   └── order/
│       ├── OrderFeignClient.java
│       ├── OrderDTO.java
│       └── OrderCreateDTO.java
└── pom.xml

user-service/                            # 提供者:实现 API + 业务逻辑
└── pom.xml
    └── <!-- 依赖 api-module -->

order-service/                           # 消费者:调用 API
└── pom.xml
    └── <!-- 依赖 api-module -->
api-module/                              # API 接口模块(纯接口 + DTO)
├── src/main/java/com/example/api/
│   ├── user/
│   │   ├── UserFeignClient.java         # Feign 接口
│   │   ├── UserDTO.java                 # 数据传输对象
│   │   └── UserQueryDTO.java
│   └── order/
│       ├── OrderFeignClient.java
│       ├── OrderDTO.java
│       └── OrderCreateDTO.java
└── pom.xml

user-service/                            # 提供者:实现 API + 业务逻辑
└── pom.xml
    └── <!-- 依赖 api-module -->

order-service/                           # 消费者:调用 API
└── pom.xml
    └── <!-- 依赖 api-module -->

优点:

  1. 接口契约统一:DTO 和 Feign 接口由服务提供者维护,消费者以 JAR 方式引入,保证参数和返回值一致。
  2. 减少重复代码:通用 DTO 不用在每个消费者项目中重复定义。
  3. 版本演进清晰:API 模块可以独立版本号,服务提供者和消费者的版本可以解耦升级。

9.4 统一异常处理 ​

java
import com.example.common.exception.BizException;
import com.example.common.dto.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(BizException.class)
    @ResponseStatus(HttpStatus.OK)
    public Result<Void> handleBizException(BizException e) {
        log.warn("业务异常: {}", e.getMessage());
        return Result.error(e.getCode(), e.getMessage());
    }

    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<Void> handleIllegalArgument(IllegalArgumentException e) {
        return Result.error(400, e.getMessage());
    }

    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Result<Void> handleException(Exception e) {
        log.error("系统异常", e);
        return Result.error(500, "系统繁忙,请稍后再试");
    }
}
import com.example.common.exception.BizException;
import com.example.common.dto.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(BizException.class)
    @ResponseStatus(HttpStatus.OK)
    public Result<Void> handleBizException(BizException e) {
        log.warn("业务异常: {}", e.getMessage());
        return Result.error(e.getCode(), e.getMessage());
    }

    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<Void> handleIllegalArgument(IllegalArgumentException e) {
        return Result.error(400, e.getMessage());
    }

    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Result<Void> handleException(Exception e) {
        log.error("系统异常", e);
        return Result.error(500, "系统繁忙,请稍后再试");
    }
}

9.5 统一响应体 ​

java
package com.example.common.dto;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Result<T> {

    private int code;
    private String message;
    private T data;

    private Result(int code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public static <T> Result<T> success(T data) {
        return new Result<>(200, "success", data);
    }

    public static <T> Result<T> success() {
        return new Result<>(200, "success", null);
    }

    public static <T> Result<T> error(int code, String message) {
        return new Result<>(code, message, null);
    }

    public static <T> Result<T> error(String message) {
        return new Result<>(500, message, null);
    }
}
package com.example.common.dto;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Result<T> {

    private int code;
    private String message;
    private T data;

    private Result(int code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public static <T> Result<T> success(T data) {
        return new Result<>(200, "success", data);
    }

    public static <T> Result<T> success() {
        return new Result<>(200, "success", null);
    }

    public static <T> Result<T> error(int code, String message) {
        return new Result<>(code, message, null);
    }

    public static <T> Result<T> error(String message) {
        return new Result<>(500, message, null);
    }
}

附录 ​

A. Spring Cloud Alibaba 版本兼容矩阵 ​

Spring Cloud Alibaba 官方发布版本与对应 Spring Cloud / Spring Boot 版本的映射关系如下(以 2022.x ~ 2023.x 线为例):

Spring Cloud AlibabaSpring CloudSpring BootJDK
2023.0.0.0-RC12023.0.03.2.x17+
2022.0.0.02022.0.x3.1.x17
2021.0.5.02021.0.x2.7.x11+
2.2.10-RC1Hoxton.SR122.3.x8+

具体版本兼容性请以 Spring Cloud Alibaba 官方文档 的最新公告为准。

B. 端口规划参考 ​

组件端口说明
Nacos Server8848客户端 HTTP 端口
Nacos Server9848客户端 gRPC 端口
Sentinel Dashboard8081控制台端口
Seata Server8091服务端口
Zipkin Server9411UI 与 API 端口
Gateway8080API 网关统一入口
微服务业务端口808x ~ 809x各业务服务端口

C. 常用 Docker 命令速查 ​

bash
# Nacos(单机模式 + MySQL)
docker run -d --name nacos -p 8848:8848 -p 9848:9848 \
  -e MODE=standalone \
  -e SPRING_DATASOURCE_PLATFORM=mysql \
  -e MYSQL_SERVICE_HOST=host.docker.internal \
  -e MYSQL_SERVICE_DB_NAME=nacos_config \
  -e MYSQL_SERVICE_USER=root \
  -e MYSQL_SERVICE_PASSWORD=root \
  nacos/nacos-server:v2.2.3

# Sentinel Dashboard
docker run -d --name sentinel -p 8081:8081 \
  bladex/sentinel-dashboard:1.8.6

# Seata Server
docker run -d --name seata -p 8091:8091 \
  -e SEATA_IP=your-host-ip \
  seataio/seata-server:1.7.0

# Zipkin
docker run -d --name zipkin -p 9411:9411 \
  openzipkin/zipkin:latest

# RabbitMQ(Spring Cloud Bus 使用)
docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 \
  rabbitmq:3.12-management

# Redis(网关限流使用)
docker run -d --name redis -p 6379:6379 \
  redis:7-alpine
# Nacos(单机模式 + MySQL)
docker run -d --name nacos -p 8848:8848 -p 9848:9848 \
  -e MODE=standalone \
  -e SPRING_DATASOURCE_PLATFORM=mysql \
  -e MYSQL_SERVICE_HOST=host.docker.internal \
  -e MYSQL_SERVICE_DB_NAME=nacos_config \
  -e MYSQL_SERVICE_USER=root \
  -e MYSQL_SERVICE_PASSWORD=root \
  nacos/nacos-server:v2.2.3

# Sentinel Dashboard
docker run -d --name sentinel -p 8081:8081 \
  bladex/sentinel-dashboard:1.8.6

# Seata Server
docker run -d --name seata -p 8091:8091 \
  -e SEATA_IP=your-host-ip \
  seataio/seata-server:1.7.0

# Zipkin
docker run -d --name zipkin -p 9411:9411 \
  openzipkin/zipkin:latest

# RabbitMQ(Spring Cloud Bus 使用)
docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 \
  rabbitmq:3.12-management

# Redis(网关限流使用)
docker run -d --name redis -p 6379:6379 \
  redis:7-alpine

D. 参考资源 ​


最后更新:2024 年 6 月 | 作者:Java 学习笔记整理