Spring Cloud 微服务
基于 Spring Cloud Alibaba 的微服务架构全面指南,涵盖服务注册发现、配置管理、网关、熔断降级、分布式事务及链路追踪等核心组件。
目录
- 微服务概述
- Nacos 服务发现与配置中心
- Spring Cloud Gateway 网关
- Sentinel 流量防卫兵
- OpenFeign 声明式调用
- Seata 分布式事务
- 分布式链路追踪
- 其他组件
- 项目结构实践
一、微服务概述
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.x | 3.2.x | 2023.0.x | 最新主线,JDK 17+ 推荐 |
| 2022.0.x | 3.1.x / 3.0.x | 2022.0.x | 较成熟的版本线 |
| 2021.0.x | 2.7.x / 2.6.x | 2021.0.x | LTS 版本,JDK 11 推荐 |
版本命名说明:Spring Cloud 从 2020.0.x 开始改用日历版本命名(之前是伦敦地铁站名如 Hoxton / Greenwich)。
Alibaba 组件版本对照(以 2022.x 为例)
<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 方式(推荐开发环境使用)
# 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启动命令:
docker-compose up -d
# 访问控制台 http://localhost:8848/nacos 默认用户名/密码: nacos/nacosdocker-compose up -d
# 访问控制台 http://localhost:8848/nacos 默认用户名/密码: nacos/nacos使用外部 MySQL(生产环境推荐)
Nacos 默认使用内嵌 Derby 数据库,仅适用于单机演示。生产环境必须切换为 MySQL:
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=falseservices:
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 服务注册与发现
引入依赖
<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
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: beijingspring:
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启用服务发现
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 获取服务实例
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 秒 未收到则删除实例。
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 配置中心
引入依赖
<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 方式(传统)
# 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: trueSpring Boot 3.x 推荐方式(不使用 bootstrap)
在 application.yml 中通过 spring.config.import 导入 Nacos 配置:
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: devspring:
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
# 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动态刷新配置
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.yamlNamespace: 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 指定所属环境:
# 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启动命令:
# 开发环境
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 基础依赖与配置
<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>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-Gatewayserver:
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/1003.3 内置断言工厂(Predicate Factories)
Gateway 提供了丰富的内置断言工厂,支持按请求的不同维度进行路由匹配。
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, 80spring:
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)
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)
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:/fallbackspring:
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:/fallback3.5 自定义过滤器
全局过滤器(GlobalFilter)— 自动应用于所有路由
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)— 需在路由配置中显式引用
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;
}
}
}在配置中使用:
filters:
- RemoveRequestHeaderSuffix=-sensitivefilters:
- RemoveRequestHeaderSuffix=-sensitive3.6 熔断降级集成 Sentinel
<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>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 分组管理:
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)
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: 3600spring:
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: 3600Java 配置方式:
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 实现令牌桶限流:
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 解析器(支持多种维度):
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 引入依赖
<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 基础配置
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-servicespring:
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 控制台:
# 下载最新 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.jar4.4 流量控制规则
QPS 流量控制
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 内存中生效。
方式二:代码硬编码(适合测试)
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);
}
}流控模式详解
| 流控模式 | 说明 | 适用场景 |
|---|---|---|
| 直接 | 资源达到阈值直接限流 | 接口级别的直接保护 |
| 关联 | 当关联资源达到阈值时,对当前资源限流 | 读写分离场景:写操作过高时限制读操作 |
| 链路 | 根据调用链入口限流 | 控制某个调用来源对资源的访问频率 |
# 控制台配置 - 关联模式示例
资源名: 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 |
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 单独限制。
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 使用率 五个维度对整体系统进行保护,当系统指标超过阈值时触发限流。
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 -> 客户端内存。
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 的内容示例:
// 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 集成
<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 支持 -->feign:
sentinel:
enabled: true # 开启 Feign 对 Sentinel 的支持feign:
sentinel:
enabled: true # 开启 Feign 对 Sentinel 的支持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 引入依赖
<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
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 客户端
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 自定义配置
日志级别配置
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;
}
}logging:
level:
com.example.feign.UserFeignClient: DEBUG # 开启 Feign 客户端日志logging:
level:
com.example.feign.UserFeignClient: DEBUG # 开启 Feign 客户端日志编码器与解码器
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 等。
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 超时与重试配置
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: truespring:
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自定义重试器
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 属性更加强大。
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 支持两种负载均衡策略:
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
# 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 需要以下表来存储全局事务和分支事务):
-- 全局事务表
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 模式使用):
-- 业务库中创建 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 引入依赖与配置
<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>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_GROUPseata:
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_GROUP6.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 恢复数据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 阶段预留的资源)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);
}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)来配置:
{
"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 协议细节。
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 四种模式对比与选型
| 维度 | AT | TCC | Saga | XA |
|---|---|---|---|---|
| 侵入性 | 低,自动生成补偿 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 集成
引入依赖
<!-- 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>配置
# 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.0 | 100% 采样 | 开发测试环境 |
| 0.1 | 10% 采样 | 生产环境低流量服务 |
| 0.01 | 1% 采样 | 生产环境高流量服务 |
| 0.0 | 不采样 | 关闭追踪 |
部署 Zipkin
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
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 上下文
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
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 输出到日志中,便于检索:
# 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/1002024-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 提供了短生命周期的微服务任务框架,适用于批处理、数据同步等一次性或定时任务:
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"));
};
}
}在微服务中使用定时任务调度:
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 用于在微服务之间传播状态变更,通常配合配置中心实现配置动态刷新的广播通知。
<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>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 生效定向刷新(只刷新指定服务):
# 只刷新 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:80818.3 Resilience4j 断路器
Resilience4j 是 Spring Cloud 官方推荐的断路器库(Hystrix 的替代品),提供了断路器、重试、限流器、隔离、缓存等模块。
<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>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: 0resilience4j:
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 注解
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 集成
spring:
cloud:
openfeign:
circuitbreaker:
enabled: true # 开启 Feign 的断路器支持
alphanumeric-ids:
enabled: truespring:
cloud:
openfeign:
circuitbreaker:
enabled: true # 开启 Feign 的断路器支持
alphanumeric-ids:
enabled: trueFeign 客户端定义(使用 FallbackFactory):
@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 对比:
| 维度 | Resilience4j | Sentinel |
|---|---|---|
| 定位 | 轻量级断路器库 | 完整的流量治理框架 |
| 限流 | RateLimiter 模块 | 丰富的限流模式和策略 |
| 熔断 | CircuitBreaker 模块 | 熔断降级规则 |
| 隔离 | Bulkhead(线程池/信号量隔离) | 线程数限流 |
| 控制台 | 无官方控制台 | Sentinel Dashboard |
| 规则持久化 | 需自行实现 | 支持 Nacos/Push/Pull |
| Spring Cloud 集成度 | 官方集成 | Alibaba 生态集成 |
九、项目结构实践
9.1 父 POM 依赖管理
多模块微服务项目通常使用一个父 POM 统一管理所有依赖的版本,避免版本冲突。
<?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 -->优点:
- 接口契约统一:DTO 和 Feign 接口由服务提供者维护,消费者以 JAR 方式引入,保证参数和返回值一致。
- 减少重复代码:通用 DTO 不用在每个消费者项目中重复定义。
- 版本演进清晰:API 模块可以独立版本号,服务提供者和消费者的版本可以解耦升级。
9.4 统一异常处理
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 统一响应体
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 Alibaba | Spring Cloud | Spring Boot | JDK |
|---|---|---|---|
| 2023.0.0.0-RC1 | 2023.0.0 | 3.2.x | 17+ |
| 2022.0.0.0 | 2022.0.x | 3.1.x | 17 |
| 2021.0.5.0 | 2021.0.x | 2.7.x | 11+ |
| 2.2.10-RC1 | Hoxton.SR12 | 2.3.x | 8+ |
具体版本兼容性请以 Spring Cloud Alibaba 官方文档 的最新公告为准。
B. 端口规划参考
| 组件 | 端口 | 说明 |
|---|---|---|
| Nacos Server | 8848 | 客户端 HTTP 端口 |
| Nacos Server | 9848 | 客户端 gRPC 端口 |
| Sentinel Dashboard | 8081 | 控制台端口 |
| Seata Server | 8091 | 服务端口 |
| Zipkin Server | 9411 | UI 与 API 端口 |
| Gateway | 8080 | API 网关统一入口 |
| 微服务业务端口 | 808x ~ 809x | 各业务服务端口 |
C. 常用 Docker 命令速查
# 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-alpineD. 参考资源
- Spring Cloud 官方文档:https://spring.io/projects/spring-cloud
- Spring Cloud Alibaba Wiki:https://github.com/alibaba/spring-cloud-alibaba/wiki
- Nacos 官方文档:https://nacos.io/zh-cn/docs/
- Sentinel 官方文档:https://sentinelguard.io/zh-cn/docs/
- Seata 官方文档:https://seata.io/zh-cn/docs/
- Micrometer Tracing:https://micrometer.io/docs/tracing
- Resilience4j:https://resilience4j.readme.io/docs
最后更新:2024 年 6 月 | 作者:Java 学习笔记整理
Cao Note