WSの小屋

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条评论