Spring Cloud 全家桶教程
从入门到精通,覆盖 Spring Cloud 核心生态
第一章:微服务与 Spring Cloud 概述
1.1 什么是微服务架构
微服务架构是一种将单一应用程序划分为一组小服务的设计方法,每个服务运行在自己的进程中,通过轻量级通信机制(通常是 HTTP/REST)进行协作。
单体架构 vs 微服务架构
| 特性 | 单体架构 | 微服务架构 |
|---|---|---|
| 部署 | 整体部署 | 独立部署 |
| 扩展 | 整体扩展 | 按需扩展 |
| 技术栈 | 单一 | 多语言多技术 |
| 团队规模 | 大团队 | 小团队 |
| 容错性 | 单点故障 | 故障隔离 |
| 开发效率 | 低(项目越大越慢) | 高(独立开发部署) |
微服务带来的挑战
微服务优势 微服务挑战
✅ 独立部署 ❌ 分布式事务
✅ 技术多样性 ❌ 服务发现
✅ 弹性扩展 ❌ 配置管理
✅ 故障隔离 ❌ 链路追踪
✅ 团队自治 ❌ 熔断降级
❌ 网关路由
❌ 安全认证
1.2 Spring Cloud 是什么
Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的开发便利性,简化了分布式系统基础设施的开发,如服务发现注册、配置中心、负载均衡、断路器、网关等。
Spring Cloud 核心组件
Spring Cloud 生态
├── 服务注册与发现
│ ├── Netflix Eureka(已维护模式)
│ └── Nacos(推荐)
├── 配置中心
│ ├── Spring Cloud Config
│ └── Nacos Config(推荐)
├── 服务网关
│ ├── Netflix Zuul(已维护模式)
│ └── Spring Cloud Gateway(推荐)
├── 负载均衡
│ └── Spring Cloud LoadBalancer
├── 服务调用
│ ├── RestTemplate + LoadBalancer
│ └── OpenFeign(声明式调用,推荐)
├── 熔断降级
│ └── Spring Cloud Circuit Breaker
│ ├── Resilience4j(推荐)
│ └── Hystrix(已维护模式)
├── 分布式事务
│ └── Seata
├── 链路追踪
│ └── Micrometer Tracing + Zipkin
└── 安全
└── Spring Cloud Security / OAuth2
1.3 技术选型建议(2024+)
| 组件 | 推荐方案 | 淘汰方案 |
|---|---|---|
| 注册中心 | Nacos | Eureka |
| 配置中心 | Nacos Config | Spring Cloud Config |
| 网关 | Spring Cloud Gateway | Zuul |
| 负载均衡 | Spring Cloud LoadBalancer | Ribbon(已维护模式) |
| 声明式调用 | OpenFeign | - |
| 熔断降级 | Resilience4j | Hystrix(已维护模式) |
| 链路追踪 | Micrometer Tracing + Zipkin | Sleuth + Zipkin |
第二章:项目搭建
2.1 项目结构
建议使用 Maven 多模块项目:
spring-cloud-demo/
├── pom.xml # 父工程
├── common/ # 公共模块
│ └── pom.xml
├── gateway/ # 网关服务
│ └── pom.xml
├── auth/ # 认证服务
│ └── pom.xml
├── user-service/ # 用户服务
│ └── pom.xml
├── order-service/ # 订单服务
│ └── pom.xml
└── product-service/ # 商品服务
└── pom.xml
2.2 父工程 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
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>spring-cloud-demo</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2024.0.0</spring-cloud.version>
<nacos.version>2024.0.0</nacos.version>
</properties>
<modules>
<module>common</module>
<module>gateway</module>
<module>auth</module>
<module>user-service</module>
<module>order-service</module>
<module>product-service</module>
</modules>
<dependencyManagement>
<dependencies>
<!-- Spring Cloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${nacos.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.3 公共模块 common
pom.xml
<project>
<parent>
<groupId>com.example</groupId>
<artifactId>spring-cloud-demo</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>common</artifactId>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
</dependencies>
</project>
统一返回结果
package com.example.common.result;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
private int code;
private String message;
private T data;
private long timestamp = System.currentTimeMillis();
public static <T> Result<T> success() {
return new Result<>(200, "success", null, System.currentTimeMillis());
}
public static <T> Result<T> success(T data) {
return new Result<>(200, "success", data, System.currentTimeMillis());
}
public static <T> Result<T> error(int code, String message) {
return new Result<>(code, message, null, System.currentTimeMillis());
}
public static <T> Result<T> error(String message) {
return new Result<>(500, message, null, System.currentTimeMillis());
}
}
通用异常
package com.example.common.exception;
public class BusinessException extends RuntimeException {
private int code;
public BusinessException(String message) {
super(message);
this.code = 500;
}
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
public int getCode() {
return code;
}
}
第三章:Nacos 服务注册与发现
3.1 Nacos 简介
Nacos(Dynamic Naming and Configuration Service)是阿里巴巴开源的服务发现和配置管理平台。它同时提供了服务注册发现和配置管理两大功能。
核心特性
服务发现
├── 服务注册
├── 健康检查
├── 负载均衡
└── 服务路由
配置管理
├── 配置集中管理
├── 动态刷新
├── 配置版本管理
└── 命名空间隔离
3.2 安装 Nacos Server
方式一:Docker 安装(推荐)
# 拉取镜像
docker pull nacos/nacos-server:v2.4.0
# 单机模式启动
docker run -d \
--name nacos \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
-e MODE=standalone \
-e JVM_XMS=512m \
-e JVM_XMX=512m \
nacos/nacos-server:v2.4.0
方式二:直接下载运行
# 1. 下载 Nacos
wget https://github.com/alibaba/nacos/releases/download/2.4.0/nacos-server-2.4.0.zip
unzip nacos-server-2.4.0.zip
# 2. 启动(单机模式)
cd nacos/bin
startup.cmd -m standalone # Windows
sh startup.sh -m standalone # Linux/Mac
# 3. 访问控制台
# http://localhost:8848/nacos
# 默认账号密码:nacos / nacos
3.3 服务提供者注册到 Nacos
引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
配置文件
# application.yml
server:
port: 8081
spring:
application:
name: user-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: public
group: DEFAULT_GROUP
# 元数据(可选)
metadata:
version: 1.0.0
author: zhangsan
启动类
package com.example.userservice;
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);
}
}
服务接口
package com.example.userservice.controller;
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/{id}")
public Result<User> getById(@PathVariable Long id) {
return Result.success(userService.findById(id));
}
@GetMapping("/list")
public Result<List<User>> list() {
return Result.success(userService.findAll());
}
}
3.4 启动更多服务实例
# application.yml(副本1:8081)
server:
port: 8081
spring:
application:
name: user-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
# application-replica1.yml(副本2:8082)
server:
port: 8082
spring:
application:
name: user-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
# application-replica2.yml(副本3:8083)
server:
port: 8083
spring:
application:
name: user-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
启动命令:
java -jar user-service.jar
java -jar user-service.jar --spring.profiles.active=replica1
java -jar user-service.jar --spring.profiles.active=replica2
验证注册
打开 Nacos 控制台 http://localhost:8848/nacos → 服务管理 → 服务列表,应该能看到 user-service 有 3 个实例。
第四章:Nacos 配置中心
4.1 引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
4.2 配置中心配置
bootstrap.yml(Spring Cloud 专用)
# bootstrap.yml(Nacos Config 必须在引导阶段加载)
spring:
application:
name: user-service
cloud:
nacos:
config:
server-addr: localhost:8848
namespace: public
group: DEFAULT_GROUP
file-extension: yaml # 配置文件格式
refresh-enabled: true # 开启动态刷新
注意:Spring Cloud 2020+ 之后,可以使用
spring.config.import替代bootstrap.yml:
# application.yml
spring:
application:
name: user-service
config:
import:
- nacos:user-service.yaml?group=DEFAULT_GROUP&refreshEnabled=true
cloud:
nacos:
config:
server-addr: localhost:8848
discovery:
server-addr: localhost:8848
4.3 在 Nacos 中创建配置
通过 Nacos 控制台
登录 Nacos 控制台 → 配置管理 → 配置列表 → 点击 "+" 创建配置:
- Data ID:
user-service.yaml - Group:
DEFAULT_GROUP - 格式: YAML
- 配置内容:
# user-service.yaml
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost:3306/user_db
username: root
password: 123456
app:
config:
name: 用户服务
version: 1.0.0
max-allowed-orders: 10
blacklist:
- user1
- user2
4.4 动态刷新配置
@RestController
@RequestMapping("/api/config")
@RefreshScope // 关键注解:自动刷新配置
public class ConfigController {
@Value("${app.config.name}")
private String appName;
@Value("${app.config.version}")
private String version;
@Value("${app.config.max-allowed-orders:5}")
private int maxAllowedOrders;
@GetMapping("/info")
public Map<String, Object> getConfigInfo() {
return Map.of(
"appName", appName,
"version", version,
"maxAllowedOrders", maxAllowedOrders
);
}
}
使用 @ConfigurationProperties 实现动态刷新
@Component
@ConfigurationProperties(prefix = "app.config")
@RefreshScope // 必须加上才能动态刷新
@Data
public class AppConfigProperties {
private String name;
private String version;
private int maxAllowedOrders;
private List<String> blacklist;
}
4.5 配置优先级
Nacos 配置中心的配置优先级(高到低):
1. 本地配置(application.yml)中 spring.cloud.nacos.config 的扩展配置
2. 指定 profile 的配置:{data-id}.yaml + {profile}
3. 应用名配置:{spring.application.name}.yaml
4. 共享配置:share-configs
多 Data ID 配置
spring:
cloud:
nacos:
config:
server-addr: localhost:8848
# 应用主配置
prefix: user-service
file-extension: yaml
# 共享配置
shared-configs:
- data-id: common-datasource.yaml
group: DEFAULT_GROUP
refresh: true
- data-id: common-redis.yaml
group: SHARED_GROUP
refresh: false
# 扩展配置
extension-configs:
- data-id: user-service-ext.yaml
group: DEFAULT_GROUP
refresh: true
4.6 命名空间与环境隔离
Nacos 的命名空间(Namespace)用于实现多环境隔离:
Namespace: dev(开发环境)
├── user-service-dev.yaml
└── common-datasource-dev.yaml
Namespace: test(测试环境)
├── user-service-test.yaml
└── common-datasource-test.yaml
Namespace: prod(生产环境)
├── user-service-prod.yaml
└── common-datasource-prod.yaml
# 指定命名空间(使用命名空间 ID)
spring:
cloud:
nacos:
config:
namespace: 8a9b7c6d-xxxx-xxxx-xxxx-xxxxxxxxxxxx
discovery:
namespace: 8a9b7c6d-xxxx-xxxx-xxxx-xxxxxxxxxxxx
第五章:Spring Cloud Gateway 网关
5.1 为什么需要网关
客户端
│
▼
┌─────────────┐
│ 网关 │ ← 统一入口、路由、鉴权、限流、日志
└──────┬──────┘
│
├──→ user-service (用户服务)
├──→ order-service (订单服务)
└──→ product-service (商品服务)
网关的核心功能:
- 路由转发:将请求转发到对应的微服务
- 鉴权认证:统一验证 Token
- 限流熔断:防止流量冲击
- 日志监控:统一记录请求日志
- 跨域处理:统一处理 CORS
5.2 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
重要:Gateway 基于 WebFlux(响应式编程),不能和
spring-boot-starter-web同时使用!
5.3 配置文件方式路由
server:
port: 80
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
# ========== 路由配置 ==========
routes:
# 用户服务
- id: user-service-route # 路由 ID(唯一)
uri: lb://user-service # lb:// 表示从 Nacos 负载均衡
predicates:
- Path=/api/users/** # 匹配路径
filters:
- StripPrefix=1 # 去除前缀
- name: RequestRateLimiter # 限流过滤器
args:
key-resolver: "#{@userKeyResolver}"
redis-rate-limiter.replenishRate: 100
redis-rate-limiter.burstCapacity: 200
# 订单服务
- id: order-service-route
uri: lb://order-service
predicates:
- Path=/api/orders/**
filters:
- StripPrefix=1
# 商品服务
- id: product-service-route
uri: lb://product-service
predicates:
- Path=/api/products/**
filters:
- StripPrefix=1
# 认证服务(无需鉴权)
- id: auth-service-route
uri: lb://auth-service
predicates:
- Path=/api/auth/**
filters:
- StripPrefix=1
# ========== 全局配置 ==========
# 全局过滤器
default-filters:
- AddResponseHeader=X-Response-Source, Gateway
# 全局跨域
globalcors:
cors-configurations:
'[/**]':
allowed-origin-patterns: "*"
allowed-methods: "*"
allowed-headers: "*"
allow-credentials: true
5.4 Predicate 断言工厂
断言(Predicate)用于匹配请求条件,Spring Cloud Gateway 内置了丰富的断言工厂:
| 断言 | 示例 | 说明 |
|---|---|---|
Path |
Path=/api/users/** |
匹配路径 |
Method |
Method=GET,POST |
匹配 HTTP 方法 |
Header |
Header=X-Request-Id, \d+ |
匹配请求头 |
Query |
Query=page, \d+ |
匹配查询参数 |
Cookie |
Cookie=token, .+ |
匹配 Cookie |
Host |
Host=**.example.com |
匹配主机名 |
RemoteAddr |
RemoteAddr=192.168.1.1/24 |
匹配远程地址 |
Weight |
Weight=group1, 80 |
权重路由 |
After |
After=2024-01-01T00:00:00Z |
指定时间之后 |
Before |
Before=2024-12-31T23:59:59Z |
指定时间之前 |
Between |
Between=start, end |
指定时间之间 |
复杂断言示例
spring:
cloud:
gateway:
routes:
# 组合断言:GET 请求且 Header 包含特定值
- id: user-api-v1
uri: lb://user-service
predicates:
- Path=/api/v1/users/**
- Method=GET
- Header=X-Version, v1
- Query=page, \d+
# 权重路由:灰度发布
- id: user-service-v1
uri: lb://user-service
predicates:
- Path=/api/users/**
- Weight=user-group, 90
- id: user-service-v2
uri: lb://user-service-v2
predicates:
- Path=/api/users/**
- Weight=user-group, 10
5.5 自定义全局过滤器
认证过滤器
@Component
@RequiredArgsConstructor
@Slf4j
public class AuthGlobalFilter implements GlobalFilter, Ordered {
private final JwtUtils jwtUtils;
// 白名单路径(不需要认证)
private static final List<String> WHITE_LIST = Arrays.asList(
"/api/auth/login",
"/api/auth/register",
"/api/auth/refresh"
);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
// 白名单放行
if (WHITE_LIST.stream().anyMatch(path::startsWith)) {
return chain.filter(exchange);
}
// 提取 Token
String token = extractToken(request);
if (token == null) {
return unauthorized(exchange, "Token 不能为空");
}
try {
// 验证 Token
Claims claims = jwtUtils.parseToken(token);
// 将用户信息放入请求头,传递给下游服务
ServerHttpRequest mutatedRequest = request.mutate()
.header("X-User-Id", claims.getSubject())
.header("X-User-Name", claims.get("username", String.class))
.build();
ServerWebExchange mutatedExchange = exchange.mutate()
.request(mutatedRequest)
.build();
return chain.filter(mutatedExchange);
} catch (Exception e) {
log.warn("Token 验证失败: {}", e.getMessage());
return unauthorized(exchange, "Token 无效或已过期");
}
}
private String extractToken(ServerHttpRequest request) {
String bearerToken = request.getHeaders().getFirst("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
byte[] body = new byte[0];
try {
body = new ObjectMapper().writeValueAsBytes(Result.error(401, message));
} catch (Exception e) {
// ignore
}
DataBuffer buffer = response.bufferFactory().wrap(body);
return response.writeWith(Mono.just(buffer));
}
@Override
public int getOrder() {
return -100; // 优先级最高,最先执行
}
}
请求日志过滤器
@Component
@Slf4j
public class RequestLogGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
String method = request.getMethod().name();
String ip = Objects.requireNonNull(
request.getRemoteAddress()).getAddress().getHostAddress();
long startTime = System.currentTimeMillis();
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
long duration = System.currentTimeMillis() - startTime;
int status = Objects.requireNonNull(
exchange.getResponse().getStatusCode()).value();
log.info("[{}] {} {} - {} ({}ms)", ip, method, path, status, duration);
}));
}
@Override
public int getOrder() {
return -1;
}
}
5.6 限流过滤器
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
自定义限流 Key
@Bean
public KeyResolver userKeyResolver() {
return exchange -> {
// 基于用户 ID 限流
String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
if (userId != null) {
return Mono.just(userId);
}
// 基于 IP 限流
String ip = Objects.requireNonNull(
exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
return Mono.just(ip);
};
}
5.7 Java 代码方式路由
@Configuration
public class GatewayRouteConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("user-service", r -> r
.path("/api/users/**")
.filters(f -> f
.stripPrefix(1)
.addRequestHeader("X-Gateway", "Spring-Cloud-Gateway")
.retry(3)) // 重试 3 次
.uri("lb://user-service"))
.route("order-service", r -> r
.path("/api/orders/**")
.filters(f -> f
.stripPrefix(1)
.circuitBreaker(config -> config
.setName("orderCircuitBreaker")
.setFallbackUri("forward:/fallback/orders")))
.uri("lb://order-service"))
.build();
}
}
第六章:OpenFeign 声明式服务调用
6.1 为什么需要 OpenFeign
在微服务架构中,服务间调用通常使用 HTTP。OpenFeign 让远程调用像调用本地方法一样简单。
// ❌ 传统方式:RestTemplate
@Autowired
private RestTemplate restTemplate;
public User getUser(Long userId) {
String url = "http://user-service/api/users/" + userId;
return restTemplate.getForObject(url, User.class);
}
// ✅ OpenFeign 方式:声明式接口
@FeignClient("user-service")
public interface UserClient {
@GetMapping("/api/users/{id}")
User getUser(@PathVariable Long id);
}
@Autowired
private UserClient userClient;
public User getUser(Long userId) {
return userClient.getUser(userId); // 像调用本地方法一样
}
6.2 引入依赖
<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>
6.3 开启 Feign
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.example.order-service.client") // 扫描 Feign 接口
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
6.4 声明 Feign 接口
package com.example.orderservice.client;
import com.example.common.result.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
// 调用 user-service 服务
@FeignClient(
name = "user-service", // 目标服务名称(从 Nacos 获取)
path = "/api/users", // 公共路径前缀
fallbackFactory = UserClientFallbackFactory.class // 熔断降级工厂
)
public interface UserClient {
@GetMapping("/{id}")
Result<User> getById(@PathVariable("id") Long id);
@GetMapping("/batch")
Result<List<User>> getByIds(@RequestParam("ids") List<Long> ids);
@PostMapping
Result<User> create(@RequestBody UserCreateDTO dto);
@PutMapping("/{id}")
Result<User> update(@PathVariable("id") Long id, @RequestBody UserUpdateDTO dto);
@DeleteMapping("/{id}")
Result<Void> delete(@PathVariable("id") Long id);
}
package com.example.orderservice.client;
import com.example.common.result.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
// 调用 product-service 服务
@FeignClient(name = "product-service", path = "/api/products")
public interface ProductClient {
@GetMapping("/{id}")
Result<Product> getById(@PathVariable("id") Long id);
@PutMapping("/{id}/stock")
Result<Void> deductStock(@PathVariable("id") Long id,
@RequestParam("quantity") Integer quantity);
}
6.5 使用 Feign 客户端
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderMapper orderMapper;
private final UserClient userClient;
private final ProductClient productClient;
@Transactional
public Order createOrder(OrderCreateDTO dto) {
// 1. 远程调用:查询用户
Result<User> userResult = userClient.getById(dto.getUserId());
User user = userResult.getData();
if (user == null) {
throw new BusinessException("用户不存在");
}
// 2. 远程调用:扣减库存
Result<Void> stockResult = productClient.deductStock(
dto.getProductId(), dto.getQuantity());
// 3. 创建订单
Order order = new Order();
order.setUserId(dto.getUserId());
order.setProductId(dto.getProductId());
order.setQuantity(dto.getQuantity());
order.setStatus(OrderStatus.CREATED);
orderMapper.insert(order);
return order;
}
}
6.6 Feign 配置详解
application.yml 配置
spring:
cloud:
openfeign:
# ========== 通用配置 ==========
client:
config:
default: # 全局默认配置
connect-timeout: 5000 # 连接超时(ms)
read-timeout: 10000 # 读取超时(ms)
logger-level: BASIC # 日志级别
user-service: # 指定服务配置
connect-timeout: 3000
read-timeout: 5000
# ========== 压缩配置 ==========
compression:
request:
enabled: true
mime-types: application/json
min-request-size: 2048
response:
enabled: true
# ========== 熔断配置 ==========
circuitbreaker:
enabled: true
# ========== 超时配置 ==========
okhttp:
enabled: false
httpclient:
max-connections: 200
time-to-live: 900
connection-timeout: 5000
disable-ssl-validation: true
日志级别配置
// Feign 日志级别
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
| 日志级别 | 说明 |
|---|---|
NONE |
不记录(默认) |
BASIC |
仅记录请求方法、URL、响应状态码、执行时间 |
HEADERS |
记录 BASIC 级别 + 请求/响应头 |
FULL |
记录全部(请求/响应头、body、元数据) |
请求/响应拦截器
@Component
@Slf4j
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 传递请求头(如从网关过来的 Token)
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
String traceId = request.getHeader("X-Trace-Id");
if (traceId != null) {
template.header("X-Trace-Id", traceId);
}
}
}
}
Feign 错误解码器
@Component
@Slf4j
public class FeignErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
String body;
try {
body = new String(response.body().asInputStream().readAllBytes(), StandardCharsets.UTF_8);
} catch (Exception e) {
body = "{}";
}
log.error("Feign 调用失败: method={}, status={}, body={}",
methodKey, response.status(), body);
return new FeignException.FeignClientException(
response.status(),
"服务调用失败: " + response.reason(),
response.request(),
body.getBytes(StandardCharsets.UTF_8)
);
}
}
第七章:Spring Cloud Circuit Breaker (Resilience4j) 熔断降级
7.1 为什么需要熔断
正常情况:
Client → Service A → Service B → Service C(正常运行)
Service B 故障:
Client → Service A → Service B ❌ → 等待超时...
↓
线程阻塞,资源耗尽
↓
整个系统雪崩!
加入熔断后:
Client → Service A → Service B ❌
↓
熔断器打开
↓
直接返回降级结果(快速失败)
↓
保护整个系统
7.2 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
7.3 熔断配置
resilience4j:
# ========== 熔断器配置 ==========
circuitbreaker:
configs:
default:
register-health-indicator: true # 注册到健康检查
sliding-window-size: 10 # 滑动窗口大小(请求数)
minimum-number-of-calls: 5 # 最少调用次数(达到后才计算)
permitted-number-of-calls-in-half-open-state: 3 # 半开状态允许的请求数
automatic-transition-from-open-to-half-open-enabled: true # 自动从开→半开
wait-duration-in-open-state: 10s # 开→半开等待时间
failure-rate-threshold: 50 # 失败率阈值(%)
slow-call-duration-threshold: 5s # 慢调用阈值
slow-call-rate-threshold: 50 # 慢调用率阈值
max-wait-duration-in-half-open-state: 30s # 半开状态最大等待时间
writable-stack-trace-enabled: true
instances:
user-service:
base-config: default
failure-rate-threshold: 30 # 覆盖:30% 失败就熔断
# ========== 重试配置 ==========
retry:
configs:
default:
max-attempts: 3 # 最大重试次数
wait-duration: 500ms # 重试间隔
retry-exceptions:
- org.springframework.web.client.HttpServerErrorException
- java.net.ConnectException
- java.net.SocketTimeoutException
ignore-exceptions:
- com.example.common.exception.BusinessException
# ========== 限流配置 ==========
ratelimiter:
configs:
default:
limit-for-period: 100 # 周期内的请求数
limit-refresh-period: 1s # 周期
timeout-duration: 500ms # 等待超时
# ========== 舱壁隔离 ==========
bulkhead:
configs:
default:
max-concurrent-calls: 20 # 最大并发数
max-wait-duration: 500ms # 等待超时
7.4 使用熔断器
方式一:配合 Feign(推荐)
// Feign + 熔断降级(Fallback 类)
@Component
@Slf4j
public class UserClientFallback implements UserClient {
@Override
public Result<User> getById(Long id) {
log.warn("user-service 熔断降级: getUserById({})", id);
return Result.error(503, "用户服务暂时不可用,请稍后重试");
}
@Override
public Result<List<User>> getByIds(List<Long> ids) {
return Result.error(503, "用户服务暂时不可用");
}
@Override
public Result<User> create(UserCreateDTO dto) {
return Result.error(503, "用户服务暂时不可用");
}
@Override
public Result<User> update(Long id, UserUpdateDTO dto) {
return Result.error(503, "用户服务暂时不可用");
}
@Override
public Result<Void> delete(Long id) {
return Result.error(503, "用户服务暂时不可用");
}
}
// Feign 声明中指定 fallback
@FeignClient(name = "user-service",
path = "/api/users",
fallback = UserClientFallback.class)
public interface UserClient {
// ...
}
方式二:使用 FallbackFactory(可获取异常原因)
@Component
@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable cause) {
log.error("user-service 调用失败", cause);
return new UserClient() {
@Override
public Result<User> getById(Long id) {
return Result.error(503, "用户服务异常: " + cause.getMessage());
}
@Override
public Result<List<User>> getByIds(List<Long> ids) {
return Result.error(503, "用户服务异常: " + cause.getMessage());
}
@Override
public Result<User> create(UserCreateDTO dto) {
return Result.error(503, "用户服务异常: " + cause.getMessage());
}
@Override
public Result<User> update(Long id, UserUpdateDTO dto) {
return Result.error(503, "用户服务异常: " + cause.getMessage());
}
@Override
public Result<Void> delete(Long id) {
return Result.error(503, "用户服务异常: " + cause.getMessage());
}
};
}
}
方式三:使用 @CircuitBreaker 注解
@Service
@RequiredArgsConstructor
public class OrderService {
@CircuitBreaker(name = "user-service", fallbackMethod = "getUserFallback")
@Retry(name = "user-service") // 重试
public User getUser(Long userId) {
// 可能失败的远程调用
return userClient.getById(userId).getData();
}
// 降级方法:参数包含异常
public User getUserFallback(Long userId, Throwable t) {
log.warn("获取用户失败,使用降级: userId={}", userId, t);
return new User(userId, "默认用户", "default@email.com");
}
}
熔断器状态机
┌──────────────────────┐
│ │
▼ 失败率到达阈值 │
┌──────────┐ ┌──────────┐
│ CLOSED │ ────→ │ OPEN │
│ (关闭) │ │ (开启) │
└──────────┘ └──────────┘
▲ │
│ │ 等待超时
│ ▼
│ ┌──────────┐
└───────│ HALF_OPEN │ ← 尝试放行少量请求
│ (半开) │
└──────────┘
- CLOSED:正常状态,请求正常通过
- OPEN:熔断打开,请求直接失败(快速失败)
- HALF_OPEN:半开状态,放行部分请求测试服务是否恢复
第八章:Spring Cloud LoadBalancer 负载均衡
8.1 简介
Spring Cloud LoadBalancer 是 Spring Cloud 官方提供的客户端负载均衡器,替代了已进入维护模式的 Netflix Ribbon。
支持的负载均衡策略
| 策略 | 说明 |
|---|---|
RoundRobinLoadBalancer |
轮询(默认) |
RandomLoadBalancer |
随机 |
| 自定义策略 | 实现 ReactorServiceInstanceLoadBalancer 接口 |
8.2 自定义负载均衡策略
@Configuration
public class LoadBalancerConfig {
// ========== 随机策略 ==========
@Bean
@LoadBalancerClient(name = "user-service", configuration = RandomLoadBalancerConfig.class)
public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
name);
}
// ========== 权重策略(结合 Nacos 元数据)==========
@Bean
public ReactorLoadBalancer<ServiceInstance> weightedLoadBalancer(
Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new WeightedLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
name);
}
}
// 随机策略配置类
public class RandomLoadBalancerConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
name);
}
}
// 自定义权重负载均衡器
public class WeightedLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private final String serviceId;
private final ObjectProvider<ServiceInstanceListSupplier> supplierProvider;
public WeightedLoadBalancer(
ObjectProvider<ServiceInstanceListSupplier> supplierProvider,
String serviceId) {
this.supplierProvider = supplierProvider;
this.serviceId = serviceId;
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
return supplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new)
.get(request)
.next()
.map(instances -> {
// 按权重选择(从元数据中读取 weight)
List<ServiceInstance> instanceList = instances.getInstances();
if (instanceList.isEmpty()) {
return Response.empty();
}
// 计算总权重
int totalWeight = instanceList.stream()
.mapToInt(instance -> {
String weight = instance.getMetadata().get("weight");
return weight != null ? Integer.parseInt(weight) : 1;
})
.sum();
// 随机选择
int random = new Random().nextInt(totalWeight);
int sum = 0;
for (ServiceInstance instance : instanceList) {
String weight = instance.getMetadata().get("weight");
sum += weight != null ? Integer.parseInt(weight) : 1;
if (random < sum) {
return Response.of(instance);
}
}
return Response.of(instanceList.get(0));
});
}
}
8.3 配置负载均衡
spring:
cloud:
loadbalancer:
enabled: true
# 开启缓存
cache:
enabled: true
ttl: 5s
# 为指定服务配置负载均衡策略
user-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
第九章:分布式事务 (Seata)
9.1 分布式事务问题
传统单体应用:本地事务(ACID)
┌──────────────────┐
│ 订单服务 │
│ ┌──────────────┐ │
│ │ 1. 扣库存 │ │
│ │ 2. 创建订单 │ │
│ │ 3. 扣余额 │ │
│ │ ── 同一 DB ─│ │
│ │ 本地事务 │ │
│ └──────────────┘ │
└──────────────────┘
微服务架构:分布式事务(CAP)
┌──────┐ ┌───────┐ ┌──────┐
│ 订单 │ │ 库存 │ │ 账户 │
│ 服务 │ │ 服务 │ │ 服务 │
└──┬───┘ └───┬───┘ └──┬───┘
│ │ │
└─────── 不同数据库 ──────┘
无法使用本地事务!
9.2 Seata 简介
Seata(Simple Extensible Autonomous Transaction Architecture)是阿里巴巴开源的分布式事务解决方案。
Seata 三种角色
TC (Transaction Coordinator) - 事务协调器
↓ 维护全局事务状态
TM (Transaction Manager) - 事务管理器
↓ 定义全局事务范围
RM (Resource Manager) - 资源管理器
↓ 管理分支事务的资源
工作流程
1. TM 向 TC 申请开启全局事务
2. TM 调用各微服务的业务方法
3. 各 RM 向 TC 注册分支事务
4. 所有 RM 执行本地事务(准备阶段)
5. TM 根据执行结果向 TC 提交或回滚
├── 全部成功 → TC 通知各 RM 提交(异步)
└── 任一失败 → TC 通知各 RM 回滚(通过 undo_log 回滚)
9.3 部署 Seata Server
Docker 部署
# 拉取镜像
docker pull seataio/seata-server:2.0.0
# 启动
docker run -d \
--name seata-server \
-p 8091:8091 \
-p 7091:7091 \
-e SEATA_IP=localhost \
-e SEATA_PORT=8091 \
seataio/seata-server:2.0.0
配置文件(registry.conf)
registry:
type: nacos
nacos:
application: seata-server
server-addr: localhost:8848
namespace: public
group: SEATA_GROUP
config:
type: nacos
nacos:
server-addr: localhost:8848
namespace: public
group: SEATA_GROUP
data-id: seataServer.properties
9.4 引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
9.5 配置 Seata
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: default_tx_group
service:
vgroup-mapping:
default_tx_group: default
grouplist:
default: localhost:8091
registry:
type: nacos
nacos:
server-addr: localhost:8848
namespace: public
group: SEATA_GROUP
config:
type: nacos
nacos:
server-addr: localhost:8848
namespace: public
group: SEATA_GROUP
业务数据库创建 undo_log 表
-- 每个业务数据库都需要创建
CREATE TABLE `undo_log` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`context` VARCHAR(128) NOT NULL,
`rollback_info` LONGBLOB NOT NULL,
`log_status` INT NOT NULL,
`log_created` DATETIME NOT NULL,
`log_modified` DATETIME NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
9.6 使用 @GlobalTransactional
// 订单服务 - 全局事务发起者
@Service
@RequiredArgsConstructor
@Slf4j
public class OrderService {
private final OrderMapper orderMapper;
private final AccountClient accountClient;
private final InventoryClient inventoryClient;
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
@Transactional(rollbackFor = Exception.class)
public Order createOrder(OrderCreateDTO dto) {
log.info("========== 开始全局事务 ==========");
// 1. 扣减库存
inventoryClient.deduct(dto.getProductId(), dto.getQuantity());
log.info("扣减库存成功");
// 2. 扣减余额
accountClient.deduct(dto.getUserId(), dto.getAmount());
log.info("扣减余额成功");
// 3. 创建订单
Order order = new Order();
order.setUserId(dto.getUserId());
order.setProductId(dto.getProductId());
order.setQuantity(dto.getQuantity());
order.setAmount(dto.getAmount());
order.setStatus(OrderStatus.CREATED);
orderMapper.insert(order);
log.info("创建订单成功");
// 模拟异常(测试回滚)
if (dto.getAmount() > 10000) {
throw new BusinessException("订单金额超过上限");
}
log.info("========== 全局事务提交 ==========");
return order;
}
}
// 库存服务 - 分支事务
@RestController
@RequestMapping("/api/inventory")
@RequiredArgsConstructor
@Slf4j
public class InventoryController {
private final InventoryService inventoryService;
@PutMapping("/deduct")
public Result<Void> deduct(@RequestParam Long productId,
@RequestParam Integer quantity) {
inventoryService.deduct(productId, quantity);
return Result.success();
}
}
@Service
@RequiredArgsConstructor
public class InventoryService {
private final InventoryMapper inventoryMapper;
@Transactional(rollbackFor = Exception.class)
public void deduct(Long productId, Integer quantity) {
Inventory inventory = inventoryMapper.findByProductId(productId);
if (inventory == null || inventory.getStock() < quantity) {
throw new BusinessException("库存不足");
}
inventory.setStock(inventory.getStock() - quantity);
inventoryMapper.updateById(inventory);
}
}
9.7 Seata 事务模式
| 模式 | 说明 | 适用场景 |
|---|---|---|
| AT(默认) | 自动补偿,基于本地 ACID + undo_log | 大多数场景,对业务无侵入 |
| TCC | Try-Confirm-Cancel 手动补偿 | 对性能要求高,需要精细化控制 |
| SAGA | 长事务,通过状态机编排 | 业务流程复杂,涉及多个服务 |
| XA | 数据库原生 XA 协议 | 要求严格一致性 |
AT 模式原理
AT 模式执行流程:
1. 业务 SQL 执行前
┌─────────────────────┐
│ 解析 SQL → 生成镜像 │
└─────────────────────┘
2. 执行前镜像(Before Image)
┌─────────────────────┐
│ SELECT * FROM table │
│ WHERE condition │
│ → 记录原始数据 │
└─────────────────────┘
3. 执行业务 SQL
┌─────────────────────┐
│ UPDATE table SET ... │
└─────────────────────┘
4. 执行后镜像(After Image)
┌─────────────────────┐
│ SELECT * FROM table │
│ WHERE condition │
│ → 记录修改后数据 │
└─────────────────────┘
5. 生成 undo_log
┌─────────────────────┐
│ 将前/后镜像写入 │
│ undo_log 表 │
└─────────────────────┘
回滚时:通过 undo_log 中的 before image 还原数据
第十章:Micrometer Tracing + Zipkin 链路追踪
10.1 为什么需要链路追踪
分布式系统中,一个请求涉及多个服务:
用户请求 → API网关 → 订单服务 → 库存服务
↓
用户服务
↓
支付服务
问题:请求慢在哪里?哪个环节出错了?
└── 链路追踪可以完整记录请求路径和耗时
10.2 引入依赖
<!-- Micrometer Tracing(替代 Sleuth) -->
<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>
<!-- 可选:通过 RabbitMQ 或 Kafka 异步上报 -->
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-sender-amqp-client</artifactId>
</dependency>
10.3 配置
# 每个微服务都需要配置
spring:
application:
name: order-service
# 链路追踪采样率
sleuth:
enabled: true
# Micrometer Tracing 配置
management:
tracing:
sampling:
probability: 1.0 # 采样率 100%(生产环境可调低,如 0.1)
propagation:
type: w3c, b3 # 传播格式
zipkin:
tracing:
endpoint: http://localhost:9411/api/v2/spans # Zipkin 地址
10.4 部署 Zipkin
Docker 部署
# 使用内存存储(开发环境)
docker run -d \
--name zipkin \
-p 9411:9411 \
openzipkin/zipkin:latest
# 使用 Elasticsearch 存储(生产环境)
docker run -d \
--name zipkin \
-p 9411:9411 \
-e STORAGE_TYPE=elasticsearch \
-e ES_HOSTS=http://elasticsearch:9200 \
openzipkin/zipkin:latest
访问 Zipkin UI
打开 http://localhost:9411 即可查看链路追踪数据。
10.5 在代码中使用
// 链路追踪自动生效,只需在 Feign 或 RestTemplate 中调用
// Micrometer Tracing 会自动传播 Trace ID
// 手动埋点(可选)
@Service
@RequiredArgsConstructor
@Slf4j
public class OrderService {
private final Tracer tracer;
public Order createOrder(OrderCreateDTO dto) {
// 创建自定义 Span
Tracer.SpanInScope scope = null;
try {
Span customSpan = tracer.nextSpan()
.name("custom-business-log")
.tag("order.amount", String.valueOf(dto.getAmount()))
.tag("order.userId", String.valueOf(dto.getUserId()))
.start();
scope = tracer.withSpan(customSpan);
// 业务逻辑
// ...
customSpan.end();
} finally {
if (scope != null) {
scope.close();
}
}
return order;
}
}
在日志中查看 Trace ID
配置日志格式加入 Trace ID:
logging:
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId:-},%X{spanId:-}] %-5level %logger{36} - %msg%n"
日志输出示例:
2024-06-15 10:30:45.123 [http-nio-8080-exec-1] [abc123def456,ghi789] INFO c.e.o.service.OrderService - 创建订单开始
2024-06-15 10:30:45.234 [http-nio-8080-exec-1] [abc123def456,ghi789] INFO c.e.o.client.UserClient - 调用用户服务
2024-06-15 10:30:45.345 [http-nio-8080-exec-1] [abc123def456,jkl012] INFO c.e.u.service.UserService - 查询用户
第十一章:Spring Cloud 安全(OAuth2 + Spring Authorization Server)
11.1 微服务认证架构
┌──────────────┐
│ 认证服务器 │
│ (Authorization│
│ Server) │
└──────┬───────┘
│
1. 登录获取 Token │ 2. 验证 Token
│
┌──────────┐ ┌────┴───────┐
│ 客户端 │ ──────→ │ 网关 │
└──────────┘ └────┬───────┘
│
3. 携带 Token 访问
│
┌─────┴──────┐
│ 微服务 │
│ (Resource │
│ Server) │
└────────────┘
11.2 认证与授权流程
OAuth2 授权码模式(推荐用于第三方应用):
1. 用户访问客户端
2. 客户端引导用户跳转到认证服务器
3. 用户登录并授权
4. 认证服务器返回授权码
5. 客户端用授权码换取 Token
6. 客户端用 Token 访问资源服务器
简化模式(适用于前后端分离):
1. 前端发送用户名密码到认证服务器
2. 认证服务器返回 JWT Token
3. 前端每次请求携带 Token
4. 网关验证 Token 并转发到后端服务
11.3 Spring Authorization Server
Spring Authorization Server 是 Spring Security 官方推出的 OAuth2 授权服务器实现。
引入依赖
<!-- 认证服务器(auth-service) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-authorization-server</artifactId>
</dependency>
认证服务器配置
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class AuthorizationServerConfig {
@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(
HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults()); // 启用 OpenID Connect
http.oauth2ResourceServer(oauth2 ->
oauth2.jwt(Customizer.withDefaults()));
return http.build();
}
// 客户端注册信息(实际应从数据库读取)
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient gatewayClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("gateway-client")
.clientSecret("{noop}gateway-secret") // {noop} 表示明文
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri("http://localhost:8080/login/oauth2/code/gateway")
.postLogoutRedirectUri("http://localhost:8080/logout")
.scope(OidcScopes.OPENID)
.scope("read")
.scope("write")
.tokenSettings(TokenSettings.builder()
.accessTokenTimeToLive(Duration.ofHours(2))
.refreshTokenTimeToLive(Duration.ofDays(7))
.reuseRefreshTokens(false)
.build())
.clientSettings(ClientSettings.builder()
.requireAuthorizationConsent(false)
.build())
.build();
return new InMemoryRegisteredClientRepository(gatewayClient);
}
// 授权码模式
@Bean
public OAuth2AuthorizationService authorizationService() {
return new InMemoryOAuth2AuthorizationService();
}
// 授权确认
@Bean
public OAuth2AuthorizationConsentService authorizationConsentService() {
return new InMemoryOAuth2AuthorizationConsentService();
}
// JWT 编码器
@Bean
public OAuth2TokenGenerator<?> tokenGenerator() {
NimbusJwtEncoder jwtEncoder = new NimbusJwtEncoder(
JWKSourceBuilder.<RSAKey>create()
.jwsKeySelector(RSAPrivateJwtDecoder::new)
.build());
JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);
jwtGenerator.setJwtCustomizer(jwtCustomizer());
return new DelegatingOAuth2TokenGenerator(jwtGenerator);
}
private OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
return context -> {
if (context.getTokenType().equals(OAuth2TokenType.ACCESS_TOKEN)) {
context.getClaims().claim("custom-claim", "custom-value");
}
};
}
}
// 安全登录配置
@Configuration
@Order(2)
public class DefaultSecurityConfig {
@Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http.authorizeHttpRequests(authorize ->
authorize.anyRequest().authenticated())
.formLogin(Customizer.withDefaults());
return http.build();
}
// 用户存储(实际应从数据库读取)
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("admin")
.password("admin123")
.roles("ADMIN", "USER")
.build();
UserDetails normalUser = User.withDefaultPasswordEncoder()
.username("user")
.password("user123")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user, normalUser);
}
}
网关集成 JWT 验证
@Component
@RequiredArgsConstructor
public class JwtAuthGlobalFilter implements GlobalFilter, Ordered {
@Value("${auth.jwk-set-uri}")
private String jwkSetUri;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 从请求头中提取 Token
String token = extractToken(exchange.getRequest());
if (token == null) {
return unauthorized(exchange, "缺少 Token");
}
// 验证 JWT 令牌(使用 Spring Security 的 JWT 解码器)
NimbusJwtDecoder decoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();
try {
Jwt jwt = decoder.decode(token);
// 将用户信息注入请求头
ServerHttpRequest mutatedRequest = exchange.getRequest().mutate()
.header("X-User-Id", jwt.getSubject())
.header("X-User-Name",
jwt.getClaimAsString("preferred_username"))
.header("X-User-Authorities",
String.join(",",
jwt.getClaimAsStringList("authorities")))
.build();
return chain.filter(exchange.mutate().request(mutatedRequest).build());
} catch (Exception e) {
return unauthorized(exchange, "Token 无效或已过期: " + e.getMessage());
}
}
// ...
}
第十二章:消息驱动(Spring Cloud Stream)
12.1 简介
Spring Cloud Stream 是一个消息中间件框架,通过**绑定器(Binder)**抽象,让开发者可以用统一的方式操作不同的消息中间件(RabbitMQ、Kafka、RocketMQ 等)。
应用程序
│
├── @EnableBinding
│ │
│ ├── Source(生产者)
│ └── Sink(消费者)
│
├── Binder(绑定器)
│ │
│ ├── RabbitMQ Binder
│ ├── Kafka Binder
│ └── RocketMQ Binder
│
└── 消息中间件
├── RabbitMQ
├── Kafka
└── RocketMQ
12.2 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<!-- 或 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-kafka</artifactId>
</dependency>
12.3 生产消息
// 1. 定义消息通道
public interface OrderEventChannel {
String OUTPUT = "order-event-output";
@Output(OUTPUT)
MessageChannel output();
}
// 2. 发送消息
@Service
@EnableBinding(OrderEventChannel.class)
@RequiredArgsConstructor
@Slf4j
public class OrderEventPublisher {
private final OrderEventChannel orderEventChannel;
public void sendOrderCreatedEvent(Order order) {
OrderCreatedEvent event = new OrderCreatedEvent(
order.getId(),
order.getUserId(),
order.getAmount(),
LocalDateTime.now()
);
Message<OrderCreatedEvent> message = MessageBuilder
.withPayload(event)
.setHeader("event-type", "order-created")
.setHeader("version", "1.0")
.build();
boolean sent = orderEventChannel.output().send(message, 30000);
if (sent) {
log.info("订单事件发送成功: orderId={}", order.getId());
} else {
log.error("订单事件发送失败: orderId={}", order.getId());
}
}
}
12.4 消费消息
// 库存服务:监听订单创建事件
@Component
@EnableBinding(Sink.class)
@RequiredArgsConstructor
@Slf4j
public class OrderCreatedReceiver {
@StreamListener(Sink.INPUT)
public void handleOrderCreated(OrderCreatedEvent event) {
log.info("接收到订单创建事件: {}", event);
try {
// 处理业务
inventoryService.lockStock(event.getOrderId());
log.info("库存锁定成功: orderId={}", event.getOrderId());
} catch (Exception e) {
log.error("库存锁定失败: orderId={}", event.getOrderId(), e);
// 可发送到死信队列或重试
}
}
}
12.5 配置
spring:
cloud:
stream:
bindings:
# ========== 生产者 ==========
order-event-output:
destination: order.events # 目标(交换机/主题)
content-type: application/json # 消息格式
producer:
required-groups: order-group # 消费者组
# ========== 消费者 ==========
input:
destination: order.events
group: inventory-group # 消费者组(同一个组内竞争消费)
consumer:
max-attempts: 3 # 最大重试次数
back-off-initial-interval: 1000 # 初始退避间隔
back-off-multiplier: 2.0 # 退避乘数
# 绑定器配置
binders:
defaultRabbit:
type: rabbit
environment:
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
virtual-host: /
12.6 Spring Cloud Stream 函数式编程(Spring Cloud Stream 4.x 推荐方式)
// Spring Cloud Stream 4.x 不再使用 @EnableBinding、@StreamListener,
// 改为函数式编程模型
@Configuration
public class StreamFunctionConfig {
// 生产者(Supplier)
@Bean
public Supplier<OrderCreatedEvent> orderEventSupplier() {
return () -> {
// 从数据库或其他地方获取待发送的事件
return new OrderCreatedEvent(...);
};
}
// 消费者(Consumer)
@Bean
public Consumer<OrderCreatedEvent> inventoryEventConsumer() {
return event -> {
log.info("接收到订单事件: {}", event);
inventoryService.lockStock(event.getOrderId());
};
}
// 处理函数(Function:接收并返回)
@Bean
public Function<OrderCreatedEvent, PaymentEvent> orderToPayment() {
return orderEvent -> {
// 处理并返回新的事件
return new PaymentEvent(orderEvent.getOrderId());
};
}
}
# Spring Cloud Stream 4.x 配置
spring:
cloud:
function:
definition: orderEventSupplier;inventoryEventConsumer;orderToPayment
stream:
bindings:
orderEventSupplier-out-0:
destination: order.events
inventoryEventConsumer-in-0:
destination: order.events
group: inventory-group
orderToPayment-in-0:
destination: order.events
orderToPayment-out-0:
destination: payment.events
第十三章:项目实战 — 电商微服务系统
13.1 系统架构
┌────────────────────────────────────────────────────┐
│ 客户端 │
└──────────────────────┬─────────────────────────────┘
│
┌──────────────────────▼─────────────────────────────┐
│ API 网关(Gateway) │
│ ┌── 鉴权 ── 限流 ── 路由 ── 日志 ──┐ │
└──┬──────┬──────┬──────┬──────┬──────┬──────┬───┘
│ │ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼ ▼
┌─────┐┌─────┐┌─────┐┌─────┐┌─────┐┌─────┐
│用户 ││商品 ││订单 ││库存 ││支付 ││通知 │
│服务 ││服务 ││服务 ││服务 ││服务 ││服务 │
└──┬──┘└──┬──┘└──┬──┘└──┬──┘└──┬──┘└──┬──┘
│ │ │ │ │ │
└──────┴──────┴──────┴──────┴──────┘
│
┌───────▼───────┐
│ Nacos 注册中心 │
│ Nacos 配置中心 │
└───────────────┘
│
┌───────▼───────┐
│ Seata + DB │
└───────────────┘
│
┌───────▼───────┐
│ RabbitMQ/Kafka │
│ + Zipkin │
└───────────────┘
13.2 订单创建完整流程
// 订单服务 - 主流程
@Service
@RequiredArgsConstructor
@Slf4j
public class OrderService {
private final OrderMapper orderMapper;
private final ProductClient productClient;
private final UserClient userClient;
private final PaymentClient paymentClient;
private final OrderEventPublisher eventPublisher;
@GlobalTransactional(name = "create-order-tx", rollbackFor = Exception.class)
@Transactional(rollbackFor = Exception.class)
public Order createOrder(OrderCreateDTO dto) {
// 1. 校验用户
Result<User> userResult = userClient.getById(dto.getUserId());
if (userResult.getCode() != 200 || userResult.getData() == null) {
throw new BusinessException("用户不存在");
}
// 2. 查询商品
Result<Product> productResult = productClient.getById(dto.getProductId());
if (productResult.getCode() != 200 || productResult.getData() == null) {
throw new BusinessException("商品不存在");
}
Product product = productResult.getData();
BigDecimal totalAmount = product.getPrice()
.multiply(BigDecimal.valueOf(dto.getQuantity()));
// 3. 扣减库存
productClient.deductStock(dto.getProductId(), dto.getQuantity());
// 4. 创建订单
Order order = new Order();
order.setUserId(dto.getUserId());
order.setProductId(dto.getProductId());
order.setProductName(product.getName());
order.setPrice(product.getPrice());
order.setQuantity(dto.getQuantity());
order.setTotalAmount(totalAmount);
order.setStatus(OrderStatus.PENDING_PAYMENT);
orderMapper.insert(order);
// 5. 发送订单创建事件
eventPublisher.sendOrderCreatedEvent(order);
return order;
}
}
13.3 支付流程
@Service
@RequiredArgsConstructor
@Slf4j
public class PaymentService {
private final PaymentMapper paymentMapper;
private final OrderClient orderClient;
private final EventPublisher eventPublisher;
@GlobalTransactional(name = "payment-tx", rollbackFor = Exception.class)
@Transactional(rollbackFor = Exception.class)
public Payment pay(PaymentDTO dto) {
// 1. 查询订单
Result<Order> orderResult = orderClient.getById(dto.getOrderId());
Order order = orderResult.getData();
if (order == null) {
throw new BusinessException("订单不存在");
}
if (order.getStatus() != OrderStatus.PENDING_PAYMENT) {
throw new BusinessException("订单状态不正确");
}
// 2. 创建支付记录
Payment payment = new Payment();
payment.setOrderId(dto.getOrderId());
payment.setUserId(order.getUserId());
payment.setAmount(order.getTotalAmount());
payment.setPayMethod(dto.getPayMethod());
payment.setStatus(PaymentStatus.SUCCESS);
paymentMapper.insert(payment);
// 3. 更新订单状态
orderClient.updateStatus(dto.getOrderId(), OrderStatus.PAID);
// 4. 发送支付成功事件
eventPublisher.sendPaymentSuccessEvent(payment);
return payment;
}
}
13.4 完整配置示例
网关 application.yml
server:
port: 80
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
filters:
- StripPrefix=1
- id: product-service
uri: lb://product-service
predicates:
- Path=/api/products/**
filters:
- StripPrefix=1
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/orders/**
filters:
- StripPrefix=1
- id: auth-service
uri: lb://auth-service
predicates:
- Path=/api/auth/**
filters:
- StripPrefix=1
default-filters:
- name: RequestRateLimiter
args:
key-resolver: "#{@ipKeyResolver}"
redis-rate-limiter.replenishRate: 100
redis-rate-limiter.burstCapacity: 200
management:
endpoints:
web:
exposure:
include: health,info,gateway
业务服务通用配置
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848
file-extension: yaml
datasource:
url: jdbc:mysql://localhost:3306/order_db
username: root
password: 123456
data:
redis:
host: localhost
port: 6379
seata:
enabled: true
tx-service-group: default_tx_group
service:
vgroup-mapping:
default_tx_group: default
grouplist:
default: localhost:8091
registry:
type: nacos
nacos:
server-addr: localhost:8848
management:
tracing:
sampling:
probability: 1.0
zipkin:
tracing:
endpoint: http://localhost:9411/api/v2/spans
resilience4j:
circuitbreaker:
instances:
user-service:
sliding-window-size: 10
failure-rate-threshold: 50
wait-duration-in-open-state: 10s
第十四章:常见问题与最佳实践
14.1 常见问题
1. 服务注册不上 Nacos
# 检查点
1. Nacos 是否启动:http://localhost:8848/nacos
2. 网络是否通:ping localhost 8848
3. 依赖是否正确:spring-cloud-starter-alibaba-nacos-discovery
4. 配置是否正确:
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
2. Feign 调用失败
// 1. 检查 Feign 接口注解是否正确
@FeignClient(name = "user-service") // name 必须与 Nacos 服务名一致
// 2. 检查超时配置
spring:
cloud:
openfeign:
client:
config:
default:
connect-timeout: 5000
read-timeout: 10000
// 3. 检查熔断降级
@FeignClient(name = "user-service", fallbackFactory = UserClientFallbackFactory.class)
3. 网关路由不生效
# 1. 确认路由 ID 唯一
# 2. 确认 lb:// 后的服务名正确
# 3. 确认 Path 断言匹配
# 4. 查看网关日志
logging:
level:
org.springframework.cloud.gateway: DEBUG
4. 分布式事务回滚失败
-- 1. 检查 undo_log 表是否存在
SELECT * FROM undo_log;
-- 2. 检查 Seata Server 日志
-- 3. 确认 @GlobalTransactional 注解在事务入口
-- 4. 检查分支事务是否都向 TC 注册成功
5. 链路追踪数据看不到
# 1. 确认 Zipkin 已启动
# 2. 确认采样率
management:
tracing:
sampling:
probability: 1.0 # 开发环境用 1.0
# 3. 确认 Zipkin 地址正确
management:
zipkin:
tracing:
endpoint: http://localhost:9411/api/v2/spans
14.2 最佳实践
1. 服务划分原则
✅ 正确:按业务边界划分
├── 用户服务(User Service)
├── 订单服务(Order Service)
├── 商品服务(Product Service)
└── 支付服务(Payment Service)
❌ 错误:按技术层划分
├── DAO 服务
├── Service 服务
└── Controller 服务
2. 接口设计
// ✅ 正确:Feign 接口使用 Result 包装
@FeignClient("user-service")
public interface UserClient {
@GetMapping("/{id}")
Result<User> getById(@PathVariable Long id);
}
// ❌ 错误:直接返回实体(异常时无法反序列化)
@FeignClient("user-service")
public interface UserClient {
@GetMapping("/{id}")
User getById(@PathVariable Long id);
}
3. 配置管理
# ✅ 正确:敏感配置放在 Nacos 配置中心
# 本地只保留非敏感配置
spring:
application:
name: user-service
# ❌ 错误:敏感配置写死在本地
spring:
datasource:
password: 123456 # 应放在配置中心
4. 熔断降级
// ✅ 正确:重要接口配置熔断
@FeignClient(name = "user-service", fallbackFactory = UserClientFallbackFactory.class)
// ✅ 正确:降级方法返回默认值
public Result<User> getUserFallback(Long id, Throwable t) {
log.warn("用户服务熔断", t);
return Result.error(503, "服务繁忙,请稍后重试");
}
5. 日志规范
// ✅ 正确:打印 Trace ID 便于排查
log.info("[{}] 创建订单: orderId={}, userId={}",
MDC.get("traceId"), orderId, userId);
// ✅ 正确:Feign 调用时记录日志
@Slf4j
public class FeignLogInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
log.debug("Feign 请求: {} {}", template.method(), template.url());
}
}
6. 版本管理
<!-- ✅ 正确:统一管理版本 -->
<properties>
<spring-cloud.version>2024.0.0</spring-cloud.version>
<spring-cloud-alibaba.version>2024.0.0</spring-cloud-alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
7. 版本兼容性参考
| Spring Boot | Spring Cloud | Spring Cloud Alibaba | Nacos |
|---|---|---|---|
| 3.4.x | 2024.0.x | 2024.0.x | 2.4.x |
| 3.3.x | 2023.0.x | 2023.0.x | 2.3.x |
| 3.2.x | 2023.0.x | 2022.0.x | 2.2.x |
| 3.1.x | 2022.0.x | 2021.0.x | 2.1.x |
| 3.0.x | 2022.0.x | 2021.0.x | 2.1.x |
附录:启动顺序与命令
启动顺序
1. Nacos Server
2. Seata Server(如果需要分布式事务)
3. Zipkin(如果需要链路追踪)
4. RabbitMQ / Kafka(如果需要消息队列)
5. 基础服务(user-service, product-service)
6. 业务服务(order-service, payment-service)
7. 网关(gateway)
常用 Docker 命令
# Nacos
docker run -d --name nacos -p 8848:8848 -p 9848:9848 -e MODE=standalone nacos/nacos-server:v2.4.0
# MySQL
docker run -d --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0
# Redis
docker run -d --name redis -p 6379:6379 redis:7-alpine
# RabbitMQ
docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management
# Zipkin
docker run -d --name zipkin -p 9411:9411 openzipkin/zipkin:latest
# Seata
docker run -d --name seata -p 8091:8091 -p 7091:7091 seataio/seata-server:2.0.0
Maven 命令
# 编译
mvn clean compile
# 打包
mvn clean package -DskipTests
# 启动服务(各开一个终端)
java -jar user-service/target/user-service.jar
java -jar order-service/target/order-service.jar
java -jar product-service/target/product-service.jar
java -jar gateway/target/gateway.jar
💡 本文档覆盖 Spring Cloud 核心生态,包括服务注册发现(Nacos)、配置中心(Nacos Config)、网关(Gateway)、声明式调用(OpenFeign)、熔断降级(Resilience4j)、负载均衡(LoadBalancer)、分布式事务(Seata)、链路追踪(Micrometer Tracing + Zipkin)、安全认证(Spring Authorization Server)、消息驱动(Spring Cloud Stream),并提供一个电商微服务实战案例。
建议结合官方文档和实际项目练习以获得最佳学习效果。
Comments | 0条评论