Spring Cloud 微服务

 什么是微服务?

微服务是一种将应用划分为多个独立服务的架构风格,服务通过轻量级通信协议互相协作,每个服务负责单一功能,可独立开发、部署和运行。

微服务架构的优缺点

  • 优点
    • 独立部署,技术选型灵活;
    • 容错性强,支持快速迭代;
    • 高扩展性,更易于维护。
  • 缺点
    • 系统复杂度高;
    • 数据一致性和分布式事务管理难;
    • 运维成本和性能开销增加。

单体架构向微服务架构的演进

  • 单体架构缺点: 扩展性差、维护困难、更新风险高,无法满足复杂业务需求。
  • 演进过程: 拆分单体模块为独立服务,定义接口,采用API网关统一入口,通过分布式通信和独立数据库实现服务化。
  • 迁移策略: 分步迁移,优先处理高频模块,确保新旧系统平稳过渡。

深入解析 bootstrap.yml 文件

(1)什么是 bootstrap.yml

  • Spring Cloud 的引导配置文件bootstrap.yml(或 bootstrap.properties)是 Spring Cloud 项目的特殊配置文件,它在应用启动时优先于 application.yml 加载

  • 核心作用:用于配置与分布式系统基础设施相关的参数,例如:

    • 配置中心(Nacos、Consul、Spring Cloud Config Server)

    • 服务注册与发现(Nacos、Eureka)

    • 加密配置(如 Vault)

  • 优先级规则bootstrap.yml > application.yml,因此它能确保关键基础设施配置在应用启动初期就被加载。


(2)是否需要自己创建?放在哪里?

  • 必须手动创建:Spring Boot 默认不会自动生成该文件,需要开发者自行在以下位置创建:

    src/main/resources/bootstrap.yml
  • 文件命名规范:支持 YAML(.yml)和 Properties(.properties)两种格式,建议使用 YAML 格式以获得更好的可读性。


(3)使用 Nacos 时需要配置哪些内容?

典型配置项(YAML 格式):

# ===== 基础配置 =====
spring:
  application:
    name: order-service  # 应用名称(决定Data ID)
  profiles:
    active: dev
  cloud:
    nacos:
      # ===== 配置中心 =====
      config:
        server-addr: 192.168.1.100:8848      # Nacos服务器地址
        namespace: dev-7e3d                  # 命名空间ID(环境隔离)
        group: PAYMENT_GROUP                 # 配置分组
        file-extension: yaml                 # 配置文件格式
        refresh-enabled: true                # 启用自动刷新
        shared-configs:                      # 共享配置
          - data-id: common-db.yaml
            group: COMMON_GROUP
            refresh: true
      # ===== 服务注册与发现 =====
      discovery:
        server-addr: ${spring.cloud.nacos.config.server-addr}  # 复用配置中心地址
        namespace: ${spring.cloud.nacos.config.namespace}      # 复用命名空间
        ephemeral: true  

关键配置说明

  • spring.application.name:决定 Nacos 配置中心的 Data ID(如 order-service.yaml

  • spring.cloud.nacos.config:所有与配置中心相关的参数

  • spring.cloud.nacos.discovery:所有与服务注册发现相关的参数

  • 共享配置:多个微服务共用的配置(如数据库连接信息),通过 shared-configs 指定

  • ephemeral 是 Nacos 中注册实例的生命周期设置:true(默认):临时实例,心跳断开后自动删除;false:持久实例,即使服务宕机,也不会从注册中心自动移除,必须手动下线
  • 如果你使用的是 Kubernetes、Docker 等容器环境,推荐用临时实例(ephemeral: true),自动上下线更方便;

  • 如果你希望服务下线必须人为控制,或者用于 Dubbo RPC 注册中心这类对实例稳定性要求高的场景,可使用 ephemeral: false


(4)为什么必须用 bootstrap.yml 配置?

  • 生命周期差异

    阶段 bootstrap.yml application.yml
    加载时机 应用启动的最早期 主上下文初始化时
    典型用途 连接配置中心、注册中心等基础设施 业务相关配置
    能否访问远程配置 ✅ 是 ❌ 否(此时远程配置尚未加载)
  • 依赖关系:Spring Cloud 应用启动时,需要先获取配置中心的地址,才能拉取远程配置。如果将这些配置放在 application.yml 中,会导致循环依赖问题(先有鸡还是先有蛋)。


(5)注意事项

  • 版本兼容性:若使用 Spring Cloud 2020.0.0(含)以上版本,需额外添加依赖以启用引导上下文:

    
        org.springframework.cloud
        spring-cloud-starter-bootstrap
    
  • 配置覆盖规则bootstrap.yml → bootstrap-{profile}.yml → application.yml → application-{profile}.yml

  • 每个微服务都需要独立的 bootstrap.yml 文件,但配置内容可以部分共享。
    关键点

  • 独立性:每个微服务通过 spring.application.name 定义自己的唯一标识(决定 Nacos 中的 Data ID)。

  • 共享配置:公共配置(如 Nacos 服务器地址、命名空间等)可以通过 环境变量Maven/Gradle 多环境配置 或 外部化配置 统一管理,避免重复。

1. 服务注册与发现

服务注册与发现是微服务架构的基础组件,用于管理和定位服务实例,帮助实现服务之间的动态通信和解耦。通过注册中心,服务提供者可以注册自身信息,服务消费者可以根据服务名动态发现目标服务,从而避免硬编码服务地址的问题。


Nacos 是由阿里巴巴开源的一个易用的动态服务发现、配置管理和服务治理平台。相较于其他注册中心(Eureka、Consul),具有明显的配置管理与动态扩展方面的优势。

它是一个专门用于服务注册和发现的程序,同时具备配置管理功能。在微服务架构中,它类似于一个“电话簿”,服务提供者将自己的信息注册到 Nacos,服务消费者通过 Nacos 动态发现目标服务,而无需硬编码服务的地址。

  • 服务提供者注册
    服务提供者启动后,会通过 Nacos 客户端向 Nacos 注册自己的服务实例信息,包括:

    • 服务名称:如 user-service
    • 实例信息:包括实例的 IP 和端口。
    • 元数据:如服务版本、环境信息。

    注册的信息存储在 Nacos 的注册表中。

  • 服务消费者发现
    服务消费者调用其他服务时,会通过 Nacos 客户端向注册中心查询目标服务的信息,动态获取服务实例的地址列表,进行负载均衡调用。

  • 动态维护

    • 心跳机制:服务实例定期向 Nacos 发送心跳,证明自己还在运行。
    • 服务下线:如果服务实例长时间未发送心跳,Nacos 会将其标记为不可用并从注册表中移除。

1.1 服务注册

1.1.1 什么是服务注册

服务注册是指微服务实例启动时,将自己的地址(IP 和端口)、服务名及元数据注册到注册中心,供其他服务发现和调用。服务注册是服务通信的基础,确保微服务可以通过服务名动态定位。

1.1.2 服务注册的工作原理

1.服务启动时注册: 服务实例在启动时,通过注册客户端(如 nacos-clienteureka-client)向注册中心发送注册请求,包含以下信息:

  • 服务名称:唯一标识服务的逻辑名称(如 user-service)。
  • 实例信息:实例的 IP 地址、端口号、元数据(如版本号)。
  • 健康信息:服务是否健康的标记。

2.注册中心保存信息: 注册中心将这些信息存储在注册表中,以供其他服务发现。

3.动态更新和维护:

  • 心跳机制:服务实例定期向注册中心发送心跳包,表明服务正常运行。
  • 服务下线:服务实例关闭或异常时,注册中心会移除该实例。

1.1.3 示例:使用 Nacos 实现服务注册

(1)安装 Nacos 注册中心

下载 Nacos:发布历史 | Nacos 官网。

启动 Nacos

  • 解压后,在bin目录下运行以下命令:
    .\startup.cmd -m standalone
  • 默认控制台地址:http://localhost:8848/nacos

(2)Spring Boot 项目集成 Nacos

引入依赖: 在 pom.xml 文件中添加以下依赖:

SpringCloud 2021.* 版本把bootstrap禁用了,导致在读取文件的时候读取不到而报空指针
解决:我们只要把spring-cloud-starter-bootstrap导入进来就会生效了。 (一定要导入)


   com.alibaba.cloud
   spring-cloud-starter-alibaba-nacos-discovery
   2022.0.0.0-RC1


   org.springframework.cloud
   spring-cloud-starter-bootstrap

配置服务注册信息: 在 application.yml 文件中,添加如下配置:

server:
  port: 8082


spring:
  application:
    name: order-server
  profiles:
    active: dev #测试环境
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        namespace: 你自定义的命名空间  # 这里需要单独为discovery配置命名空间
      config:
        server-addr: localhost:8848
        namespace: 你自定义的命名空间
        file-extension: yaml

启用服务注册: 在主类中添加 @EnableDiscoveryClient 注解,启用服务注册功能:

@SpringBootApplication
@EnableDiscoveryClient  // 启用服务注册功能
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}

启动服务并验证

  • 启动 user-service 项目。
  • 打开 Nacos 控制台(http://localhost:8848/nacos),在 服务管理 -> 服务列表 中,可以看到 user-service 已成功注册。

1.2 服务发现

1.2.1 什么是服务发现

服务发现是指服务消费者通过注册中心动态获取目标服务的实例信息(IP 地址、端口等),并发起调用。
通过服务发现,服务之间无需提前配置地址,解决了服务动态扩缩容和部署复杂性问题。

1.2.2 服务发现的工作原理

  1. 查询服务列表:服务消费者通过注册客户端向注册中心发送查询请求,指定服务名(如 product-service)。
  2. 返回实例列表:注册中心返回目标服务的所有实例信息(包括 IP、端口和元数据)。
  3. 负载均衡选择实例:消费者根据负载均衡策略(如轮询、随机)选择一个实例并发起调用。

1.2.3 示例:服务调用

(1)创建服务提供者

新建服务 product-service: 提供一个简单的商品查询接口:

@RestController
@RequestMapping("/product")
public class ProductController {
    @GetMapping("/{id}")
    public String getProduct(@PathVariable String id) {
        return "Product ID: " + id;
    }
}

配置注册信息: 在 application.properties 中:

spring.application.name=product-service       # 服务名
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848  # 注册中心地址
server.port=8081                              # 服务端口

(2)创建服务消费者

新建服务 user-service: 消费者服务调用 product-service

引入 Feign 依赖: 在 pom.xml 文件中添加:


    org.springframework.cloud
    spring-cloud-starter-openfeign

定义 Feign 客户端: 使用 @FeignClient 调用远程服务:

@FeignClient("product-service")  // 指定调用的目标服务
public interface ProductClient {
    @GetMapping("/product/{id}")
    String getProduct(@PathVariable("id") String id);
}

消费者 Controller: 调用 product-service 的接口:

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

    @Autowired
    private ProductClient productClient;

    @GetMapping("/product/{id}")
    public String getProduct(@PathVariable String id) {
        return productClient.getProduct(id);
    }
}

配置注册信息: 在 application.properties 中:

spring.application.name=user-service       # 服务名
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848  # 注册中心地址
server.port=8082                            # 服务端口

启动服务并验证

  • 启动 product-serviceuser-service
  • 访问 http://localhost:8082/user/product/123
  • 结果应返回:
    Product ID: 123
    

1.3 服务健康检查

1.3.1 什么是服务健康检查

服务健康检查用于检测服务实例的运行状态,确保注册表中的服务是健康可用的。如果某服务实例不可用,注册中心会将其从注册表中移除,避免请求失败。

1.3.2 Nacos 健康检查机制

主动心跳检测

  • 服务实例定期向注册中心发送心跳包(默认每 5 秒),通知注册中心自己在线。

被动健康检查

  • 注册中心主动向服务实例发送探测请求(如 HTTP),检查其运行状态。

1.3.3 示例:模拟服务宕机

  1. 启动服务后,手动关闭 product-service
  2. 在 Nacos 控制台的 服务列表 中,可以看到 product-service 的实例状态变为不健康,并被移除。

2. 服务间通信

服务间通信是微服务架构中的核心环节,用于实现服务之间的协作。在微服务中,服务之间通常通过 HTTP 通信来实现数据交互。Spring Cloud 提供了多种方式来实现服务间通信,包括 FeignRestTemplate,同时结合 Spring Cloud LoadBalancer 实现负载均衡。


2.1 Feign(声明式服务调用)

2.1.1 什么是 Feign

Feign 是 Spring Cloud 提供的声明式服务调用工具,它将 HTTP 请求抽象成 Java 接口,开发者只需定义接口方法和注解,就可以调用远程服务,无需手动构造 HTTP 请求。

2.1.2 Feign 的特点

  • 声明式调用:通过接口方法调用远程服务,代码清晰易读。
  • 集成负载均衡:与 Spring Cloud LoadBalancer 集成,可实现多实例负载均衡。
  • 自动解析返回值:支持将远程服务返回的 JSON 自动映射为 Java 对象。

2.1.3 Feign 的工作原理

  1. 接口定义:开发者定义一个接口,使用 @FeignClient 注解标注目标服务名称。
  2. 方法映射:通过注解(如 @GetMapping@PostMapping)指定 HTTP 请求的路径和参数。
  3. 代理实现:Spring Cloud 自动生成接口的代理类,将接口方法调用转换为 HTTP 请求。
  4. 负载均衡:Feign 与 Spring Cloud LoadBalancer 集成,通过服务名自动选择实例。

2.1.4 Feign 的使用

(1)引入依赖

pom.xml 中添加 Feign 依赖:


    org.springframework.cloud
    spring-cloud-starter-openfeign

(2)配置 Feign

application.properties 中:

spring.application.name=user-service
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848  # Nacos 地址
server.port=8080

(3)定义 Feign 客户端

创建一个接口,用于调用远程服务:

@FeignClient("product-service")  // 指定目标服务名称
public interface ProductClient {
    @GetMapping("/product/{id}")
    String getProduct(@PathVariable("id") String id);
}
  • 这里的URL链接要与另一个服务中Controller中的请求一致。 

(4)使用 Feign 客户端

在 Controller 中注入并使用 Feign 客户端:

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

    @Autowired
    private ProductClient productClient;

    @GetMapping("/product/{id}")
    public String getProduct(@PathVariable String id) {
        return productClient.getProduct(id);  // 通过 Feign 调用远程服务
    }
}

(5)启用 Feign

在主类上添加 @EnableFeignClients 注解:

@SpringBootApplication
@EnableFeignClients  // 启用 Feign 功能
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}

(6)测试服务调用

启动 product-serviceuser-service 后,访问:

http://localhost:8080/user/product/123

返回结果:

Product ID: 123

2.1.5 Feign 的超时与重试机制配置

在远程调用中,为了防止请求阻塞,可以设置超时时间和重试策略。

(1)超时配置:application.properties 中:

feign.client.config.default.connectTimeout=5000  # 连接超时(毫秒)
feign.client.config.default.readTimeout=10000    # 读取超时(毫秒)

(2)重试配置: 通过 Feign 的配置启用重试:

feign.client.config.default.retryer=Retryer.Default
feign.client.config.default.maxAttempts=3  # 最大重试次数
feign.client.config.default.backoff=2000   # 重试间隔(毫秒)

2.1.6 Feign 的日志配置

Feign 提供了日志功能,可以记录请求和响应的详细信息,便于调试。

(1)启用 Feign 日志:application.properties 中:

logging.level.feign=DEBUG  # 将 Feign 日志级别设置为 DEBUG

(2)自定义日志级别: 通过配置类设置日志级别:

@Configuration
public class FeignConfig {
    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;  // FULL 表示记录完整的请求和响应数据
    }
}

2.1.7 Feign 请求拦截器

通过拦截器从当前请求的上下文中获取 X-User-Id,并添加到 Feign 请求头中。

@Component
public class FeignUserInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        // 从当前请求的上下文(如ThreadLocal)获取用户ID
        String userId = UserContext.getUserId(); 
        if (userId != null) {
            template.header("X-User-Id", userId);
        }
    }
}

2.2 RestTemplate(传统方式调用服务)

2.2.1 什么是 RestTemplate

RestTemplate 是 Spring 提供的同步 HTTP 客户端工具,用于构造和发送 HTTP 请求。虽然 RestTemplate 功能强大,但需要手动处理服务地址,代码复杂度较高。


2.2.2 RestTemplate 的基本使用

(1)引入依赖

Spring Boot 默认包含 RestTemplate 的依赖,无需额外引入。

(2)定义 RestTemplate Bean

在配置类中定义 RestTemplate Bean:

@Configuration
public class RestTemplateConfig {
    @Bean
    @LoadBalanced  // 启用负载均衡
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

(3)使用 RestTemplate

在代码中调用远程服务:

@Autowired
private RestTemplate restTemplate;

@GetMapping("/user/product/{id}")
public String getProduct(@PathVariable String id) {
    String url = "http://product-service/product/" + id;  // 服务名替代硬编码地址
    return restTemplate.getForObject(url, String.class);
}

2.3 Spring Cloud LoadBalancer

2.3.1 什么是 Spring Cloud LoadBalancer

Spring Cloud LoadBalancer 是 Spring Cloud 提供的客户端负载均衡器,支持根据服务名动态解析服务实例列表,并在请求时自动选择一个实例。


2.3.2 负载均衡的实现原理

  • 服务名解析
    • 消费者通过服务名(如 product-service)向 Spring Cloud LoadBalancer 发起请求。
    • LoadBalancer 从服务注册中心(如 Eureka 或 Nacos)获取服务名对应的实例列表(IP 和端口)。
  • 负载均衡策略
    • 根据配置的负载均衡算法,从实例列表中选择一个可用的服务实例。
  • 调用目标服务
    • 将请求转发到选定的服务实例,并返回响应结果。

2.3.3 配置与使用

application.properties 中无需额外配置,默认启用。

Spring Cloud LoadBalancer 默认使用 轮询策略(Round Robin

修改为随机策略

  • 创建配置类: 自定义负载均衡策略为随机(Random):

@Configuration
public class LoadBalancerConfig {

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

3. API 网关

API 网关是微服务架构中的入口组件,用于实现路由转发、统一认证、流量控制等功能。Spring Cloud Gateway 是目前推荐的高性能网关解决方案,基于非阻塞的 WebFlux,提供灵活的路由和过滤器机制。


3.1 什么是 API 网关

API 网关的核心功能:

  1. 路由转发:将客户端的请求根据规则转发到后端服务。
  2. 统一认证:实现集中式认证和权限校验,避免重复开发。
  3. 流量控制:通过限流和熔断保护后端服务。
  4. 日志监控:记录请求日志,便于分析和排查问题。

3.2 Spring Cloud Gateway 使用

3.2.1 引入依赖

pom.xml 文件中添加以下依赖:

        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-discovery
            2022.0.0.0-RC1
        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-config
            2022.0.0.0-RC1
        
        
            org.springframework.cloud
            spring-cloud-starter-bootstrap
        
        
            org.springframework.cloud
            spring-cloud-starter-gateway
        
        
            org.springframework.cloud
            spring-cloud-starter-loadbalancer
        
  • spring-cloud-starter-gateway:提供网关的核心功能。
  • spring-boot-starter-data-redis:为限流功能提供支持。

3.2.2 静态路由配置

application.yml 文件中配置简单的路由规则:

server:
  port: 9000

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true                # 启用注册中心路由功能
          lower-case-service-id: true # 将服务名转小写(URL美观)

      globalcors:                      # 跨域配置
        corsConfigurations:
          '[/**]':
            allowedOriginPatterns: "*"  
            allowedMethods: "*"
            allowedHeaders: "*"
            allowCredentials: true

      routes:                         # 这一行必须和 discovery/globalcors 同级!
        - id: order-server            #  路由ID,没有固定要求,但要求保证唯一,建议配合服务名
          uri: lb://order-server      # 使用注册中心中的服务名,lb:// 表示使用负载均衡
          predicates:
            - Path=/order/**        #当路径匹配/order/**  时,将请求转发到order-server服务

        - id: resource-server
          uri: lb://resource-server
          predicates:
            - Path=/resource/**
配置解析
  • id:路由规则的唯一标识。
  • uri:目标服务的地址,可以是具体的 http://localhost:8081 或服务名(结合服务注册中心使用)。
  • predicates:路由的条件,Path 表示按路径规则转发请求。
测试静态路由

启动 user-service(8081)和 product-service(8082),访问:

  • http://localhost:8080/user/123 转发到 http://localhost:8081/user/123
  • http://localhost:8080/product/456 转发到 http://localhost:8082/product/456

3.2.3 动态路由配置

Spring Cloud Gateway 可以结合服务注册中心(如 Nacos、Eureka)实现动态路由。动态路由会根据注册的服务名生成路由规则,避免手动配置。

application.yml 中启用服务发现:

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true                       # 启用服务发现
          lower-case-service-id: true        # 服务名自动转换为小写
动态路由规则
  • 访问 http://localhost:8080/user-service/** 会自动转发到 user-service 的实例。
  • 访问 http://localhost:8080/product-service/** 会自动转发到 product-service 的实例。
测试动态路由
  1. 启动注册中心(如 Nacos)和各微服务。
  2. 访问网关地址:
    • http://localhost:8080/user-service/user/123
    • http://localhost:8080/product-service/product/456

3.2.4 自定义过滤器

过滤器是 Gateway 的核心组件,可用于拦截和修改请求或响应,实现日志记录、鉴权等功能。

全局过滤器示例

@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 日志记录示例
        String path = exchange.getRequest().getPath().toString();
        System.out.println("Request Path: " + path);

        // 添加自定义请求头
        exchange.getRequest().mutate().header("X-Custom-Header", "CustomValue");

        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0; // 优先级,值越小优先级越高
    }
}

功能解析

  • 日志记录:记录每个请求的路径。
  • 添加请求头:为所有请求添加自定义请求头。

测试过滤器 启动网关服务,访问任意路由地址,例如 /user/123,在控制台可以看到日志输出。


3.2.5 限流功能

限流功能通过 Redis 实现,使用 RequestRateLimiter 过滤器限制请求速率。

限流配置示例

application.yml 文件中:

spring:
  cloud:
    gateway:
      routes:
        - id: user-service-route
          uri: http://localhost:8081
          predicates:
            - Path=/user/**
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10     # 每秒生成的令牌数
                redis-rate-limiter.burstCapacity: 20     # 最大令牌数
                key-resolver: "#{@remoteAddrKeyResolver}" # 限流的唯一标识(按 IP)
配置解析
  • redis-rate-limiter.replenishRate:每秒生成的令牌数(允许的请求数)。
  • redis-rate-limiter.burstCapacity:令牌桶的最大容量(允许的瞬时请求量)。
  • key-resolver:限流依据,这里使用客户端 IP 作为唯一标识。
定义 KeyResolver

在配置类中定义限流的 KeyResolver:

@Configuration
public class RateLimiterConfig {

    @Bean
    public KeyResolver remoteAddrKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }
}
测试限流功能
  • 使用工具(如 Postman 或 JMeter)模拟并发请求。
  • 超过限流阈值时,网关会返回 HTTP 429 状态码(Too Many Requests)。

4. 配置管理

在微服务架构中,随着服务数量的增加,不同服务需要管理大量的配置信息,例如数据库连接信息、服务端口、日志级别等。这些配置可能因环境(开发、测试、生产)不同而有所变化。配置管理的目标是集中化、动态化地管理这些配置文件,提升维护效率和系统灵活性。


推荐方案:Nacos 配置中心

  • 集成方便、功能丰富,推荐在 Spring Cloud 项目中作为配置管理工具。

  • 配合动态刷新和命名空间,能够满足多环境配置的需求。

备选方案:Spring Cloud Config

  • 配置存储依赖 Git 或 SVN,更适合需要版本控制的场景。

  • 配置刷新需要结合消息队列(如 RabbitMQ、Kafka)才能实现动态更新。

4.1 什么是配置中心

配置中心是一个专门用来集中管理微服务配置的服务,能够提供以下功能:

  • 集中管理:统一管理所有微服务的配置文件。
  • 环境隔离:支持不同环境(如开发、测试、生产)下的配置分离。
  • 动态刷新:配置变更后,无需重启服务即可应用最新配置。
  • 版本控制:对配置文件进行版本管理,便于回溯历史记录。

4.2 Nacos 配置中心(推荐)

Nacos 是阿里巴巴开源的微服务解决方案,既能充当服务注册中心,又可以作为配置中心使用,推荐在 Spring Cloud 微服务中使用。

4.2.1 Nacos 配置管理功能

  • 集中管理配置:支持将多个微服务的配置文件统一存储到 Nacos 中。
  • 多环境支持:按命名空间、分组、DataId 管理不同环境的配置。
  • 动态刷新:通过 Spring Cloud 与 Nacos 集成,配置变更后能实时刷新。

4.2.2 Nacos 配置中心的基本使用

(1)安装和启动 Nacos

下载 Nacos:下载链接。

启动 Nacos:

sh startup.sh -m standalone

访问 Nacos 控制台:http://localhost:8848/nacos


(2)Spring Boot 集成 Nacos 配置

引入依赖pom.xml 中添加 Nacos 的配置依赖:

 
   com.alibaba.cloud
   spring-cloud-starter-alibaba-nacos-config
   2022.0.0.0-RC1

配置 bootstrap.yml 配置与 Nacos 的连接信息:

server:
  port: 8082


spring:
  application:
    name: order-server
  profiles:
    active: dev #测试环境
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        namespace: 你自定义的命名空间  # 这里需要单独为discovery配置命名空间
      config:
        server-addr: localhost:8848
        namespace: 你自定义的命名空间
        file-extension: yaml

创建 Nacos 配置文件 在 Nacos 控制台添加配置:

  • DataIdconfig-demo.yaml(对应服务名称加后缀)
  • GroupDEFAULT_GROUP(默认分组,可自定义)
  • 内容
    custom:
      message: Hello from Nacos!      # 自定义配置
    

在代码中读取配置 使用 @Value@ConfigurationProperties 注解读取配置:

@RestController
@RequestMapping("/config")
public class ConfigController {

    @Value("${custom.message}")
    private String message;

    @GetMapping("/message")
    public String getMessage() {
        return message;
    }
}

测试配置 启动服务后,访问 http://localhost:8080/config/message,输出:

Hello from Nacos!

4.2.3 配置动态刷新

编写配置类并使用 @RefreshScope 注解

@Component
@RefreshScope  // 允许配置刷新
public class MyConfig {

    @Value("${my.name}")
    private String name;

    @Value("${my.age}")
    private int age;

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

测试动态刷新

  • 修改 Nacos 配置中心中的 custom.message 内容。
  • 刷新页面访问 http://localhost:8080/config/message,即可看到实时更新的内容。

4.3 Spring Cloud Config(备选)

Spring Cloud Config 是另一种配置管理方案,支持将配置文件存储在 Git、SVN 等版本管理工具中,并通过 Config Server 提供统一的访问接口。

4.3.1 Spring Cloud Config 的基本使用

架构图

  • Config Server:配置中心服务,负责从 Git 拉取配置并提供给客户端。
  • Config Client:微服务客户端,从 Config Server 拉取配置。

(1)创建 Config Server

引入依赖pom.xml 中添加 Config Server 依赖:


    org.springframework.cloud
    spring-cloud-config-server

配置 application.yml 配置 Config Server 的 Git 仓库地址:

spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          uri: https://github.com/your-repo/config-repo.git # Git 仓库地址
          search-paths: config                            # 配置文件所在目录
server:
  port: 8888

启动类 启用 Config Server 功能:

@SpringBootApplication
@EnableConfigServer  // 启用 Config Server
public class ConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}

(2)创建 Config Client

引入依赖pom.xml 中添加 Config Client 依赖:


    org.springframework.cloud
    spring-cloud-starter-config

配置 bootstrap.yml 配置客户端与 Config Server 的连接:

spring:
  application:
    name: client-demo
  cloud:
    config:
      uri: http://localhost:8888        # Config Server 地址

读取配置 在代码中读取配置内容:

@RestController
@RequestMapping("/config")
public class ConfigController {

    @Value("${custom.message}")
    private String message;

    @GetMapping("/message")
    public String getMessage() {
        return message;
    }
}

4.3.2 配置刷新与 Spring Cloud Bus 集成

引入依赖pom.xml 中添加 Spring Cloud Bus 的依赖:


    org.springframework.cloud
    spring-cloud-starter-bus-amqp


    org.springframework.boot
    spring-boot-starter-actuator

配置 application.yml 配置消息队列(如 RabbitMQ):

spring:
  cloud:
    bus:
      enabled: true
  rabbitmq:
    host: localhost
    username: guest
    password: guest

刷新配置

  • 修改 Git 仓库中的配置文件。
  • 提交变更后,向 /actuator/bus-refresh 发送 POST 请求:
    curl -X POST http://localhost:8080/actuator/bus-refresh
    
  • 客户端会实时刷新配置。

4.4 Nacos 与 Spring Cloud Config 对比

功能 Nacos 配置中心 Spring Cloud Config
配置存储 内置存储,支持多种格式(YAML、JSON 等) 基于 Git 或其他版本管理工具
动态刷新 原生支持动态刷新 需结合 Spring Cloud Bus 实现
多环境支持 支持命名空间、分组管理环境 支持环境的配置文件分层(如 devprod
操作界面 提供友好的 Web 管理界面 无界面,仅支持 Git/SVN 管理

5. 服务容错与熔断

服务容错与熔断的目的是在微服务系统中保证部分功能可用,避免服务雪崩效应。当某些服务不可用时,系统通过熔断、降级、限流等方式保障服务的整体稳定性。

  • 熔断、降级、重试:推荐使用 Resilience4j,简单轻量,功能满足大部分需求。
  • 限流与监控:推荐使用 Sentinel,支持复杂规则,且提供完善的监控界面。

5.1 什么是服务容错与熔断

服务容错

服务容错是指在服务调用失败或超时时,提供备用处理逻辑,保证系统的部分功能仍然可用。

熔断

熔断是一种保护机制,当某服务的失败率达到一定阈值时,短路请求,不再调用该服务。熔断器一段时间后会进入“半开”状态,尝试恢复调用。

服务降级

服务降级是在目标服务不可用或超时时,为调用方提供备用处理逻辑,避免用户体验过度受影响。

限流

限流是通过限制接口的访问频率或并发量,防止服务因流量过大而崩溃。


5.2 Resilience4j

Resilience4j 是一款轻量级的服务容错库,支持熔断器、限流、重试等功能。

5.2.1 熔断器

熔断器用于检测服务调用的健康状况,并在服务连续失败时短路请求。Resilience4j 的熔断器支持三种状态:

  • CLOSED(关闭):正常状态,允许所有请求通过。
  • OPEN(打开):熔断状态,所有请求直接失败。
  • HALF_OPEN(半开):测试状态,部分请求尝试调用目标服务。

配置熔断器

引入依赖pom.xml 文件中添加 Resilience4j 的依赖:


    io.github.resilience4j
    resilience4j-spring-boot2

application.yml 文件中配置熔断规则

resilience4j:
  circuitbreaker:
    instances:
      demoCircuitBreaker:                 # 熔断器名称
        sliding-window-size: 10           # 滑动窗口的大小
        failure-rate-threshold: 50        # 失败率阈值(%)
        wait-duration-in-open-state: 5000ms  # 熔断后等待时间(毫秒)
        permitted-number-of-calls-in-half-open-state: 3  # 半开状态时允许的调用数量

解释:

  • sliding-window-size:统计最近多少次请求(窗口大小)。
  • failure-rate-threshold:当请求失败率超过 50% 时触发熔断。
  • wait-duration-in-open-state:熔断器打开后的等待时间,时间到后进入半开状态。
  • permitted-number-of-calls-in-half-open-state:半开状态允许的调用次数,用于测试服务恢复情况。

在代码中使用熔断器

@RestController
public class DemoController {

    @Autowired
    private RestTemplate restTemplate;

    // 通过注解使用熔断器
    @CircuitBreaker(name = "demoCircuitBreaker", fallbackMethod = "fallback")
    @GetMapping("/api/demo")
    public String callExternalService() {
        // 模拟调用外部服务
        return restTemplate.getForObject("http://external-service/api", String.class);
    }

    // 熔断后触发的降级逻辑
    public String fallback(Throwable t) {
        return "Service is unavailable. Please try again later.";
    }
}

代码解释:

  • @CircuitBreaker(name = "demoCircuitBreaker")
    • 指定熔断器的名称为 demoCircuitBreaker
    • 熔断器的配置从 application.yml 中读取。
  • fallbackMethod
    • 指定熔断触发后调用的降级方法。
    • fallback 方法的参数类型为 Throwable,用于捕获异常信息。
  • RestTemplate.getForObject()
    • 模拟调用外部服务 http://external-service/api
    • 如果服务不可用或失败,触发熔断逻辑。

测试熔断器

  • 模拟 external-service 服务连续失败。
  • 调用 /api/demo,观察是否返回降级响应 Service is unavailable.
  • 等待熔断器进入半开状态后,观察是否尝试恢复调用。

5.2.2 服务降级

降级逻辑是在目标服务不可用时返回备用响应,保证系统部分功能可用。

降级代码实现

降级逻辑已通过 fallbackMethod 实现,下面补充其工作机制:

// 原服务调用方法
@CircuitBreaker(name = "demoCircuitBreaker", fallbackMethod = "fallback")
public String callExternalService() {
    return restTemplate.getForObject("http://external-service/api", String.class);
}

// 降级方法
public String fallback(Throwable t) {
    return "Fallback response: external service is down.";
}

详细解析:

  • 当调用 http://external-service/api 时发生以下情况之一时,触发 fallback 方法:
    • 超时:外部服务响应时间超过阈值。
    • 异常:调用发生异常(如连接失败)。
    • 失败率超过设定值。

5.2.3 重试机制

重试机制用于对失败的调用按照设定的规则重新尝试。

配置重试规则

application.yml 文件中:

resilience4j:
  retry:
    instances:
      demoRetry:                       # 重试策略的名称
        max-attempts: 3                # 最大重试次数
        wait-duration: 1000ms          # 每次重试间隔

代码实现重试

@RestController
public class RetryController {

    @Retry(name = "demoRetry", fallbackMethod = "retryFallback")
    @GetMapping("/api/retry")
    public String callWithRetry() {
        // 模拟调用外部服务
        return restTemplate.getForObject("http://external-service/api", String.class);
    }

    // 重试失败后的降级逻辑
    public String retryFallback(Throwable t) {
        return "Retry failed after multiple attempts.";
    }
}

详细解析:

  • @Retry(name = "demoRetry")
    • 指定重试策略名称为 demoRetry
    • 配置中的 max-attemptswait-duration 控制重试行为。
  • retryFallback
    • 在所有重试失败后执行降级逻辑。

5.3 Sentinel

5.3.1 限流规则配置

Sentinel 支持多种限流方式,如 QPS 限制、并发线程限制。

配置 Sentinel

引入依赖pom.xml 中添加:


    com.alibaba.cloud
    spring-cloud-starter-alibaba-sentinel

application.yml 中配置

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080         # Sentinel Dashboard 地址

定义限流规则 使用 @SentinelResource 注解:

@RestController
public class SentinelDemoController {

    @GetMapping("/api/sentinel")
    @SentinelResource(value = "sentinelDemo", blockHandler = "blockHandler")
    public String sentinelDemo() {
        return "Request processed.";
    }

    // 限流触发时的逻辑
    public String blockHandler(BlockException ex) {
        return "Request has been limited. Please try again later.";
    }
}

代码解释:

  • @SentinelResource(value = "sentinelDemo")
    • 定义资源名称为 sentinelDemo
  • blockHandler
    • 当限流规则触发时调用的逻辑。
    • 参数类型为 BlockException,表示限流异常。

测试限流

  • 启动 Sentinel Dashboard(java -jar sentinel-dashboard.jar)。
  • 在 Dashboard 中为 sentinelDemo 配置 QPS 限制为 5。
  • 使用压力测试工具(如 JMeter)发送超过 5 个并发请求,观察是否触发限流逻辑。

5.3.2 Sentinel 实时监控

启动 Sentinel Dashboard 下载并启动 Sentinel Dashboard:

java -jar sentinel-dashboard.jar

连接到 Dashboard 服务启动后,Sentinel 会自动将实例信息注册到 Dashboard,可以在界面上查看限流规则和流量统计。


5.4 总结

功能 Resilience4j Sentinel
熔断降级 通过 @CircuitBreaker 实现,支持多种配置规则 提供丰富的熔断策略,结合 Dashboard 实时监控
限流 支持基本限流功能 提供多种限流方式(QPS、线程数),支持动态规则调整
重试 使用 @Retry 实现,配置灵活 不提供原生重试功能
监控界面 无原生监控界面,需结合 Prometheus 或 Micrometer 实现 提供完整的 Dashboard,可实时查看规则和流量数据

6. 分布式追踪

在微服务架构中,服务之间存在复杂的调用关系。排查问题时,单个服务的日志通常无法反映全局问题。分布式追踪通过记录请求在各服务中的流转过程,让开发者可以全面了解请求的执行路径和性能瓶颈。

  • 使用 Spring Cloud Sleuth 配合 Zipkin,实现分布式追踪的基础功能。
  • 在需要复杂性能分析和扩展性的场景下,可选择 Jaeger

6.1 什么是分布式追踪

分布式追踪是一种记录请求跨服务调用行为的方法。通过为每个请求生成唯一标识,记录请求经过的服务、操作和耗时,从而提供一条完整的调用链。

核心概念
  • Trace:一个请求的完整调用链路,由多个 Span 组成。
  • Span:调用链中的一个片段,代表一次操作(如调用某个服务或执行某个方法)。
  • TraceId:标识整个请求的唯一 ID,贯穿所有 Span。
  • SpanId:标识某个具体操作的唯一 ID,是 Trace 的子集。

6.2 Spring Cloud Sleuth

Spring Cloud Sleuth 是 Spring 提供的分布式追踪工具,与 Zipkin、Jaeger 等工具无缝集成,用于生成和传递 TraceId 和 SpanId。

6.2.1 基本功能

  • 为每个请求生成 TraceId,并在调用链中传递。
  • 为每个操作生成 SpanId,记录操作的开始和结束时间。
  • 集成日志系统,自动将 TraceId 和 SpanId 添加到日志中。

6.2.2 Sleuth 的使用

(1)引入依赖

pom.xml 中添加 Sleuth 和日志依赖:


    org.springframework.cloud
    spring-cloud-starter-sleuth


    org.springframework.cloud
    spring-cloud-starter-zipkin

(2)配置 Sleuth

application.yml 中添加以下配置:

spring:
  application:
    name: order-service           # 服务名称
  sleuth:
    sampler:
      probability: 1.0            # 采样率(1.0 表示记录所有请求)
  zipkin:
    base-url: http://localhost:9411  # Zipkin 服务地址
    enabled: true                 # 启用 Zipkin

解释:

  • spring.application.name:服务的名称,用于区分调用链中的服务。

  • sleuth.sampler.probability:采样率,1.0 表示记录所有请求,0.5 表示记录 50% 的请求。

  • zipkin.base-url:Zipkin 的服务地址,Sleuth 会自动将追踪数据发送到 Zipkin。

(3)日志集成

Sleuth 会自动将 TraceId 和 SpanId 添加到日志中,无需额外配置。示例日志输出格式:

2024-11-15 12:00:00.123  INFO [order-service,abc123,xyz456] 12 --- [nio-8080-exec-1] c.e.OrderController : Processing order
  • [order-service,abc123,xyz456]

    • order-service:当前服务名。

    • abc123:TraceId,标识整个请求。

    • xyz456:SpanId,标识当前操作。

(4)模拟服务间调用

假设有两个服务:order-servicepayment-service

order-service 中通过 RestTemplate 调用 payment-service

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/{id}")
    public String createOrder(@PathVariable String id) {
        // 调用 payment-service
        String paymentStatus = restTemplate.getForObject("http://payment-service/payment/" + id, String.class);
        return "Order created with Payment Status: " + paymentStatus;
    }
}

配置 RestTemplate

@Configuration
public class RestTemplateConfig {

    @Bean
    @LoadBalanced  // 支持服务发现
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

在日志中可以看到完整的调用链信息,包括 TraceId 和 SpanId。


6.3 Zipkin 或 Jaeger

6.3.1 什么是 Zipkin

Zipkin 是一个分布式追踪系统,用于收集、存储和可视化追踪数据。它与 Spring Cloud Sleuth 集成,可以展示请求的调用链路和每个操作的耗时。


6.3.2 使用 Zipkin

(1)安装 Zipkin

下载 Zipkin 的可执行 Jar 包:下载地址。

启动 Zipkin:

java -jar zipkin-server-2.23.2-exec.jar

访问 Zipkin 控制台:http://localhost:9411

(2)配置 Sleuth 发送数据到 Zipkin

application.yml 中添加:

spring:
  zipkin:
    base-url: http://localhost:9411  # Zipkin 地址
    enabled: true                   # 启用 Zipkin

(3)调用链展示

启动 order-servicepayment-service

调用 http://localhost:8080/order/123

打开 Zipkin 控制台,查询 TraceId,可以看到完整的调用链:

Order-Service --> Payment-Service

每个服务的耗时会以图形化的形式展示。


6.3.3 Jaeger

Jaeger 是另一种分布式追踪系统,与 Zipkin 类似,提供分布式调用链的可视化功能。它更注重性能监控和扩展性,适合大规模系统。

安装 Jaeger

使用 Docker 启动 Jaeger:

docker run -d --name jaeger \
    -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
    -p 5775:5775/udp \
    -p 6831:6831/udp \
    -p 6832:6832/udp \
    -p 5778:5778 \
    -p 16686:16686 \
    -p 14268:14268 \
    -p 14250:14250 \
    -p 9411:9411 \
    jaegertracing/all-in-one:1.33

访问 Jaeger 控制台:http://localhost:16686

集成 Jaeger

与 Zipkin 集成类似,将 Zipkin 的 base-url 替换为 Jaeger 的地址。


6.4 调用链性能分析

分布式追踪的一个重要功能是性能分析,通过展示每个服务的耗时帮助开发者优化系统。

示例分析

在 Zipkin 中查看某个 Trace。

点击某个服务(如 payment-service),查看其详细耗时:

  • 请求时间:10:00:00.123
  • 响应时间:10:00:00.456
  • 耗时:333 毫秒

如果某个服务的耗时明显较长,可以深入分析具体原因(如数据库查询慢、依赖服务超时)。


6.5 总结

功能 Spring Cloud Sleuth Zipkin Jaeger
核心功能 生成并传递 TraceId 和 SpanId 可视化调用链和性能数据 更强的扩展性和性能分析功能
日志集成 自动将 TraceId 和 SpanId 注入日志 无日志功能 无日志功能
界面 无可视化界面,需要结合 Zipkin 或 Jaeger 提供简单直观的调用链可视化界面 提供调用链和性能分析界面,支持大规模分布式系统
适用场景 微服务系统的基础追踪 中小型系统,便于快速定位调用链问题 大型分布式系统,适合深入性能优化

7. 消息驱动

在微服务架构中,服务之间的异步通信是解耦服务、提高系统弹性的重要手段。通过消息队列,可以在不同服务之间传递数据,实现松耦合,同时支持削峰填谷、流量控制等功能。

7.1 什么是消息驱动

消息驱动是一种微服务之间的异步通信模型,通过消息中间件(如 RabbitMQ、Kafka 等)实现服务之间的解耦和消息传递。消息驱动使得微服务系统在高并发和高可靠性场景下更加灵活和高效。


7.1.1 为什么需要消息驱动

  • 解耦服务:消息队列充当生产者和消费者之间的中间层,生产者只负责发送消息,而消费者根据需要消费消息,二者互不依赖。
  • 异步处理:通过消息队列,生产者不需要等待消费者完成处理,提升系统的响应速度。
  • 流量削峰:在高并发场景下,消息队列将请求暂存,消费者根据自身能力按需处理,避免系统崩溃。
  • 可靠性:通过消息确认机制和重试机制,确保消息不会因网络故障或系统故障而丢失。

7.1.2 核心概念

  • 消息队列:存储消息的缓冲区,生产者将消息放入队列,消费者从队列中取出消息进行处理。
  • 生产者:发送消息到队列的服务。
  • 消费者:从队列中拉取消息并处理的服务。
  • 消息确认
    • 生产者确认:消息是否成功发送到队列。
    • 消费者确认:消息是否成功消费。
  • 消息模型
    • 点对点模型:每条消息只能被一个消费者消费(RabbitMQ)。
    • 发布订阅模型:消息可以被多个消费者订阅(RabbitMQ、Kafka)。

7.1.3 消息驱动的典型应用场景

  • 事件通知:用户下单后,通过消息驱动通知库存系统减库存、物流系统生成订单。
  • 日志收集:将分布式系统的日志通过消息驱动集中存储和分析(如 Kafka)。
  • 订单处理:处理订单支付和发货,异步通知第三方支付系统。
  • 流式处理:对实时流数据进行分析和处理(如 Kafka)。

7.2 Spring Cloud Stream

Spring Cloud Stream 是 Spring 提供的消息驱动框架,用于统一处理消息的生产与消费。它支持多种消息中间件(如 RabbitMQ、Kafka),通过抽象化的 API 隐藏了底层消息队列的具体实现,降低了开发复杂度。


7.2.1 Spring Cloud Stream 的核心概念

  • Binding(绑定)

    • 消息通道与消息中间件的桥梁。
    • 定义消息生产者(Source)和消费者(Sink)。
  • Binder(绑定器)

    • 连接消息通道与具体消息中间件的组件。
    • 支持 RabbitMQ、Kafka 等主流消息中间件。
  • 消息通道

    • 输出通道(output:生产者通过输出通道发送消息。
    • 输入通道(input:消费者通过输入通道接收消息。
  • Stream API

    • 提供统一的消息处理 API,屏蔽不同消息中间件的实现差异。

7.2.2 Spring Cloud Stream 的优点
  • 中间件无关:支持多种消息中间件,开发者无需关注底层实现。
  • 配置灵活:通过简单的配置即可实现消息生产和消费。
  • 自动重试:内置消息重试机制,提升消息传递的可靠性。
  • 集成简单:与 Spring Boot 无缝集成。

7.2.3 配置 Spring Cloud Stream

(1)引入依赖

以 RabbitMQ 为例,在 pom.xml 中添加以下依赖:


    org.springframework.cloud
    spring-cloud-starter-stream-rabbit

(2)配置 application.yml

spring:
  application:
    name: message-service                 # 服务名称
  cloud:
    stream:
      bindings:
        output:                           # 定义生产者通道
          destination: my-topic           # 指定目标队列/主题
          content-type: application/json  # 消息内容类型
        input:                            # 定义消费者通道
          destination: my-topic           # 指定监听的队列/主题
          group: message-group            # 消费者组(Kafka 使用)
      rabbit:
        bindings:
          input:
            consumer:
              acknowledge-mode: MANUAL    # 手动确认消息
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

配置解析:

  • destination:绑定的消息队列或主题。
  • group:消费者组(Kafka 场景)。
  • acknowledge-mode:消费者手动确认消息的模式,避免消息丢失。

7.2.4 消息生产与消费

(1)消息生产

使用 MessageChannel 发送消息。

@EnableBinding(Source.class)  // 启用消息绑定
@RestController
@RequestMapping("/message")
public class MessageProducer {

    @Autowired
    private MessageChannel output;  // 自动绑定到配置中的 output 通道

    @PostMapping("/send")
    public String sendMessage(@RequestBody String payload) {
        // 使用 MessageBuilder 构建消息并发送
        output.send(MessageBuilder.withPayload(payload).build());
        return "Message sent: " + payload;
    }
}

代码解析:

  • @EnableBinding(Source.class)

    • 启用生产者绑定,将 output 通道与 my-topic 队列绑定。

  • MessageChannel

    • Spring Cloud Stream 提供的消息通道接口,用于发送消息。

  • MessageBuilder

    • 构建消息对象,包含消息内容和元数据。

(2)消息消费

使用 @StreamListener 监听消息。

@EnableBinding(Sink.class)  // 启用消费者绑定
public class MessageConsumer {

    @StreamListener(Sink.INPUT)  // 监听 input 通道
    public void handleMessage(String message) {
        System.out.println("Received message: " + message);
    }
}

代码解析:

  • @EnableBinding(Sink.class)
    • 启用消费者绑定,将 input 通道与 my-topic 队列绑定。
  • @StreamListener(Sink.INPUT)
    • 监听 input 通道,处理接收到的消息。

7.2.5 测试消息驱动

启动 RabbitMQ 服务。

启动 message-service

通过 Postman 或其他工具发送请求:

POST http://localhost:8080/message/send
Body: "Hello, RabbitMQ!"

消费端打印:

Received message: Hello, RabbitMQ!

7.3 RabbitMQ

RabbitMQ 是基于 AMQP(Advanced Message Queuing Protocol)协议的消息中间件,支持多种消息模型(如点对点、发布订阅)。它以可靠性和灵活性著称,适合中小型系统的消息驱动场景。


7.3.1 RabbitMQ 的核心概念

  • Producer(生产者)

    • 负责发送消息到 RabbitMQ 队列。
    • 生产者与队列之间通过交换机(Exchange)通信。
  • Consumer(消费者)

    • 负责从 RabbitMQ 队列中获取消息并处理。
    • 一个队列可以有多个消费者。
  • Queue(队列)

    • 用于存储消息,生产者将消息发送到队列,消费者从队列获取消息。
  • Exchange(交换机)

    • 接收生产者的消息并将其路由到绑定的队列。
    • 常见交换机类型:
      • Direct:按消息的路由键精确匹配队列。
      • Fanout:将消息广播到所有绑定的队列。
      • Topic:按路由键模式匹配队列(支持模糊匹配)。
      • Headers:按消息头属性匹配队列。
  • Binding(绑定)

    • 定义队列与交换机之间的关系,决定消息如何从交换机路由到队列。
  • Message(消息)

    • 包含消息内容(Payload)和消息属性(Headers、TTL 等)。

7.3.2 RabbitMQ 的消息模型

(1)点对点模型

  • 消息被发送到一个队列,且每条消息只能被一个消费者处理。

  • 场景:订单服务发送订单创建消息到队列,库存服务从队列消费消息。

(2)发布订阅模型

  • 消息通过 Fanout 类型的交换机广播到所有绑定的队列,每个队列的消费者都能收到消息。

  • 场景:用户注册后,发送消息通知多个服务(如发送邮件、推送消息)。

(3)路由模型

  • 消息通过 Direct 类型的交换机根据路由键分发到匹配的队列。

  • 场景:日志系统按日志级别(如 INFO、ERROR)将日志消息分发到不同的队列。

(4)主题模型

  • 消息通过 Topic 类型的交换机按路由键模式匹配分发到队列,支持模糊匹配。

  • 场景:电商系统按订单类型(如 order.payment.success)分发订单状态更新消息。

7.3.3 RabbitMQ 的集成

(1)安装 RabbitMQ

下载并安装 RabbitMQ:RabbitMQ 下载。

启动 RabbitMQ:

rabbitmq-server

启用管理插件:

rabbitmq-plugins enable rabbitmq_management

访问管理界面:http://localhost:15672
默认用户名和密码:guest/guest


(2)Spring Boot 集成 RabbitMQ

引入依赖

pom.xml 中添加 RabbitMQ 的依赖:


    org.springframework.boot
    spring-boot-starter-amqp

配置 application.yml

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

(3)实现消息生产与消费

消息生产者

生产者将消息发送到队列。

@RestController
@RequestMapping("/producer")
public class ProducerController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostMapping("/send")
    public String sendMessage(@RequestBody String message) {
        // 发送消息到默认交换机和队列
        rabbitTemplate.convertAndSend("my-exchange", "my-routing-key", message);
        return "Message sent: " + message;
    }
}

代码解释:

  • RabbitTemplate:Spring 提供的 RabbitMQ 操作模板。

  • convertAndSend

    • 第一个参数是交换机名称。

    • 第二个参数是路由键。

    • 第三个参数是消息内容。


消息消费者

消费者从队列中接收消息。

@Component
public class Consumer {

    @RabbitListener(queues = "my-queue")
    public void receiveMessage(String message) {
        System.out.println("Received message: " + message);
    }
}

代码解释:

  • @RabbitListener:声明消费者监听的队列名称。

  • receiveMessage:当队列中有新消息时自动触发处理逻辑。


(4)完整配置队列与交换机

RabbitMQ 需要提前声明队列、交换机和绑定关系。Spring 提供了 @Bean 注解的方式进行配置。

@Configuration
public class RabbitConfig {

    @Bean
    public Queue queue() {
        return new Queue("my-queue", true);  // 队列名称,是否持久化
    }

    @Bean
    public DirectExchange exchange() {
        return new DirectExchange("my-exchange");  // 交换机名称
    }

    @Bean
    public Binding binding(Queue queue, DirectExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("my-routing-key");
    }
}

代码解释:

  1. Queue:声明队列 my-queue,并设置持久化。

  2. DirectExchange:声明 Direct 类型的交换机 my-exchange

  3. Binding:将队列和交换机通过路由键 my-routing-key 绑定。


7.3.4 RabbitMQ 的消息确认机制

RabbitMQ 提供三种消息确认机制,确保消息可靠传递:

(1)生产者确认

生产者发送消息到交换机后,交换机会返回确认。

配置生产者确认:

spring:
  rabbitmq:
    publisher-confirm-type: correlated  # 启用发布确认

示例代码:

rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
    if (ack) {
        System.out.println("Message delivered successfully.");
    } else {
        System.out.println("Message delivery failed: " + cause);
    }
});

(2)消费者确认

消费者接收到消息后,需要手动确认消息已处理。

配置消费者手动确认:

spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: manual  # 手动确认

示例代码:

@RabbitListener(queues = "my-queue")
public void receiveMessage(String message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
    try {
        System.out.println("Processing message: " + message);
        channel.basicAck(deliveryTag, false);  // 手动确认
    } catch (Exception e) {
        channel.basicNack(deliveryTag, false, true);  // 拒绝并重新投递
    }
}

7.3.5 RabbitMQ 的重试与幂等处理

(1)消息重试

在消息消费失败时,RabbitMQ 可以自动重试。

配置重试策略:

spring:
  rabbitmq:
    listener:
      simple:
        retry:
          enabled: true
          initial-interval: 1000  # 初始重试间隔
          max-attempts: 3         # 最大重试次数
          multiplier: 2.0         # 间隔倍增

(2)幂等处理

为避免消息重复消费,可引入唯一标识(如消息 ID),并记录到数据库进行去重。

示例代码:

@RabbitListener(queues = "my-queue")
public void handleMessage(String message) {
    String messageId = extractMessageId(message);
    if (!isProcessed(messageId)) {
        processMessage(message);
        markAsProcessed(messageId);  // 标记消息已处理
    } else {
        System.out.println("Duplicate message ignored: " + messageId);
    }
}

7.4 Kafka

Kafka 是一个分布式流处理平台,最初由 LinkedIn 开发,后开源成为 Apache 项目。它以高吞吐量、高可用性和持久化特性著称,广泛用于日志收集、实时数据流处理等场景。


7.4.1 Kafka 的核心概念

1. Producer(生产者)

生产者负责将消息发送到 Kafka 的指定主题(Topic)。生产者可以自定义分区策略,将消息分发到不同的分区。

2. Consumer(消费者)

消费者从 Kafka 的主题中拉取消息。一个主题可以有多个消费者,消费者可以组成消费者组(Consumer Group)。

3. Topic(主题)

主题是 Kafka 中消息的逻辑分类,每条消息必须归属于一个主题。主题可以有多个分区。

4. Partition(分区)

每个主题可以有多个分区,消息会分布到不同的分区中。

  • 分区的好处

    • 提高并发处理能力。

    • 实现消息的有序性(分区内有序)。

    • 支持数据分布在不同的节点上。

5. Offset(偏移量)

Kafka 为每条消息分配一个唯一的 Offset,消费者通过 Offset 记录读取位置。

6. Broker

Kafka 集群中的节点称为 Broker,负责存储和管理消息。

7. ZooKeeper

Kafka 使用 ZooKeeper 管理集群的元数据(如分区、Broker 信息)。新版 Kafka 提供了 ZooKeeper-less 模式,支持无需 ZooKeeper 的运行。


7.4.2 Kafka 的消息模型

Kafka 的消息模型支持以下场景:

1. 点对点模式

  • 消息被发送到一个主题,由一个消费者组中的某个消费者接收。

  • 应用场景:订单服务发送消息,物流服务处理消息。

2. 发布/订阅模式

  • 消息被发送到一个主题,多个消费者组可以同时接收消息。

  • 应用场景:用户注册后发送消息,多个服务(如邮件、通知服务)订阅同一主题。

3. 分区消费

  • 一个主题分成多个分区,消费者组中的消费者会分摊处理这些分区。

  • 应用场景:高并发日志收集,每个消费者处理一部分日志。


7.4.3 Kafka 的安装与配置

1. 安装 Kafka

下载 Kafka:Kafka 官网。

解压后,启动 ZooKeeper:

bin/zookeeper-server-start.sh config/zookeeper.properties

启动 Kafka:

bin/kafka-server-start.sh config/server.properties

2. 创建主题

bin/kafka-topics.sh --create --topic my-topic --bootstrap-server localhost:9092 --partitions 3 --replication-factor 1

参数解析:

  • --topic:主题名称。

  • --partitions:分区数。

  • --replication-factor:副本因子(推荐 >=2)。

3. 验证 Kafka

生产消息

bin/kafka-console-producer.sh --topic my-topic --bootstrap-server localhost:9092

输入消息内容后按 Enter 发送。

消费消息

bin/kafka-console-consumer.sh --topic my-topic --from-beginning --bootstrap-server localhost:9092

查看从主题 my-topic 中读取的消息。


7.4.4 Spring Boot 集成 Kafka

1. 引入依赖

pom.xml 中添加 Kafka 相关依赖:


    org.springframework.boot
    spring-boot-starter


    org.springframework.kafka
    spring-kafka


2. 配置 Kafka

application.yml 文件中添加 Kafka 的基本配置:

spring:
  kafka:
    bootstrap-servers: localhost:9092  # Kafka 集群地址
    consumer:
      group-id: my-group               # 消费者组 ID
      auto-offset-reset: earliest      # 从最早的消息开始消费
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
    producer:
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer

3. 消息生产者

创建 Kafka 消息生产者:

@RestController
@RequestMapping("/producer")
public class KafkaProducerController {

    @Autowired
    private KafkaTemplate kafkaTemplate;

    @PostMapping("/send")
    public String sendMessage(@RequestParam String topic, @RequestBody String message) {
        kafkaTemplate.send(topic, message);  // 发送消息到指定主题
        return "Message sent to topic: " + topic;
    }
}

代码解释:

  • KafkaTemplate:Spring 提供的 Kafka 消息发送模板。

  • send():向指定的主题发送消息。


4. 消息消费者

创建 Kafka 消息消费者:

@Component
public class KafkaConsumer {

    @KafkaListener(topics = "my-topic", groupId = "my-group")
    public void consumeMessage(String message) {
        System.out.println("Received message: " + message);
    }
}

代码解释:

  • @KafkaListener:监听指定主题的消息。

  • topics:指定要监听的主题。

  • groupId:消费者组 ID,确保分区内消息只被同组中的一个消费者消费。


5. 测试 Kafka 集成

启动 Kafka 服务。

启动 Spring Boot 应用。

使用 Postman 发送 POST 请求:

POST http://localhost:8080/producer/send?topic=my-topic
Body: "Hello, Kafka!"

在控制台查看消费者输出:

Received message: Hello, Kafka!

7.4.5 Kafka 的消息确认机制

Kafka 提供两种确认机制:

生产者确认

  • 确保消息成功发送到 Kafka 的分区。
  • 配置生产者的 acks 参数:
    spring:
      kafka:
        producer:
          properties:
            acks: all  # 等待所有副本确认
    

消费者确认

  • 默认自动提交 Offset,可以设置为手动提交 Offset。
  • 手动提交 Offset 示例:
    @KafkaListener(topics = "my-topic", groupId = "my-group")
    public void consumeMessage(ConsumerRecord record, Acknowledgment acknowledgment) {
        System.out.println("Processing message: " + record.value());
        acknowledgment.acknowledge();  // 手动提交 Offset
    }
    

7.4.6 Kafka 的重试与幂等性

1. 消息重试

在消费消息失败时,Kafka 可以自动重新投递消息。

配置重试策略:

spring:
  kafka:
    listener:
      retry:
        max-attempts: 3       # 最大重试次数
        backoff: 1000         # 重试间隔(毫秒)

2. 幂等处理

为避免消息重复消费,可使用消息 ID 进行去重。

示例代码:

@KafkaListener(topics = "my-topic", groupId = "my-group")
public void handleMessage(ConsumerRecord record) {
    String messageId = record.key();  // 消息唯一 ID
    if (!isProcessed(messageId)) {
        processMessage(record.value());
        markAsProcessed(messageId);  // 记录消息已处理
    } else {
        System.out.println("Duplicate message ignored: " + messageId);
    }
}

7.5 总结

功能 Spring Cloud Stream RabbitMQ Kafka
消息模型 抽象化消息生产和消费,适配多种中间件 支持点对点、发布订阅等模式 高吞吐量,支持日志存储和流式处理
消息确认机制 支持手动确认 提供自动和手动确认模式 提供 offset 手动提交机制
消息重试与幂等处理 自动支持重试逻辑,需手动处理幂等 配置重试策略,需额外实现幂等逻辑 支持 offset 去重逻辑
推荐场景 需要适配多种消息中间件场景 中小规模消息传递,推荐作为入门学习中间件 大数据、高吞吐场景,推荐用于日志处理与实时分析

8. 分布式事务

分布式事务是解决微服务架构中跨多个服务或数据库操作时数据一致性问题的重要手段。Seata 是阿里巴巴开源的分布式事务框架,其核心特性包括分布式事务协调、异常传播、事务日志记录等。


8.1 Seata 的事务模式

1. AT 模式

  • 自动化程度高,适合大多数场景。
  • 推荐用于操作集中在数据库的事务。

 Seata AT 模式的工作原理

  • 第一阶段:业务操作阶段
    • 执行数据库操作,生成 Undo Log(记录前镜像和后镜像),但事务未提交。
  • 第二阶段:提交或回滚阶段
    • 提交:如果所有操作成功,TC 通知各分支事务提交本地事务。
    • 回滚:如果某一分支事务失败,TC 通知所有分支事务通过 Undo Log 恢复数据库。

2. TCC 模式

  • 更灵活,但实现复杂。
  • 推荐用于需要高性能和精确控制的场景(如金融支付)。

Seata TCC 模式工作原理

TCC(Try-Confirm-Cancel)是一种分布式事务实现方式。开发者需要显式实现三个阶段:

  • Try:预留资源(如冻结账户余额)。
  • Confirm:确认资源操作(如扣减冻结的余额)。
  • Cancel:取消资源操作(如释放冻结的余额)。

8.2 分布式事务的核心概念

8.2.1 什么是分布式事务

分布式事务保证跨多个服务的操作要么全部成功,要么全部回滚。它依赖于以下三大关键机制:

  • 本地事务:每个服务的数据库操作由本地事务管理。
  • 全局事务:由分布式事务框架协调各服务的事务状态。
  • 异常传播:当某一服务的本地事务失败时,异常被捕获并触发全局事务的回滚。

8.2.2 本地事务与分布式事务的结合

出现异常必须用throw显式地抛出异常。 

涉及数据库操作的子方法

  • 必须使用 @Transactional,以确保数据库操作的原子性。
  • Seata 会捕获本地事务并生成 Undo Log,支持全局回滚。

不涉及数据库操作的子方法

  • 可以省略 @Transactional,但需要确保异常能够正确传播到全局事务。

示例:

public void validateOrder(String userId, int count) {
    if (count <= 0) {
        throw new IllegalArgumentException("Invalid count");
    }
}
  • 子方法抛出的异常通过调用栈传播到 @GlobalTransactional 方法中。
  • 全局事务捕获异常后,通知事务协调器(TC)回滚所有子事务。

8.2.3 Seata 的核心组件

  • TM(Transaction Manager)事务管理器
    • 通过 @GlobalTransactional 启动全局事务。
    • 捕获异常并通知 TC 触发全局回滚。
  • TC(Transaction Coordinator)事务协调器
    • 管理全局事务状态,协调所有分支事务的提交或回滚。
  • RM(Resource Manager)资源管理器
    • 负责记录本地事务的 Undo Log,并根据 TC 指令进行提交或回滚。

AT 模式和 TCC 模式对比与选择建议

特性 AT 模式 TCC 模式
侵入性 无侵入 需要实现三个接口
适用场景 基于关系型数据库的简单操作 复杂业务逻辑/非关系型数据库
性能 较高(自动提交) 较低(需要多次交互)
锁机制 全局锁 无锁(业务自己控制)
实现复杂度 简单 复杂
补偿机制 自动生成回滚SQL 需要手动实现补偿逻辑
  • AT模式优先用于简单CRUD场景

  • TCC模式适用于需要:

    • 与非关系型数据库交互(Redis/MongoDB)

    • 调用第三方支付接口

    • 需要人工审核的长时间事务

8.3 AT 模式(Auto Transaction Mode)

8.3.1. 核心原理

一阶段(Phase 1)

  • 在本地事务中执行业务SQL

  • 生成反向回滚SQL(undo_log快照)

  • 提交本地事务时同时提交undo_log

二阶段(Phase 2)

  • 全局事务成功:异步删除所有undo_log记录(无补偿操作)

  • 全局事务失败:通过undo_log生成反向SQL执行回滚

8.3.2 引入依赖 



    io.seata
    seata-spring-boot-starter
    1.5.2


    com.alibaba.cloud
    spring-cloud-starter-alibaba-seata
    2021.0.5.0

8.3.3 配置中心(以 Nacos 为例)

# application.yml
seata:
  enabled: true
  application-id: ${spring.application.name}
  tx-service-group: my_tx_group
  service:
    vgroup-mapping:
      my_tx_group: default
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: 
      group: SEATA_GROUP
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      namespace:

8.3.4 undo_log表的作用与配置

每个业务数据库需要创建 undo_log 表:

关键点

  • 每个业务数据库实例需要创建一个undo_log表(不是每个业务表)

  • 与AT模式的关系

    • 数据修改前会记录beforeImage快照

    • 数据修改后会记录afterImage快照

    • 事务回滚时自动用beforeImage恢复数据

表结构设计原理

字段 作用
xid 全局事务ID
branch_id 分支事务ID
rollback_info 序列化的回滚信息(包含前后镜像)
log_status 日志状态(0-正常,1-已回滚)
CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) 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 AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

8.3.5 数据源代理配置

@Configuration
public class DataSourceConfig {
    
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DruidDataSource druidDataSource() {
        return new DruidDataSource();
    }

    @Bean
    public DataSource dataSource(DataSource druidDataSource) {
        return new DataSourceProxy(druidDataSource);
    }
}

DataSourceProxy的作用

  • SQL解析:解析业务SQL生成undo日志

  • 全局锁管理:检查数据修改是否与其他全局事务冲突

  • 连接管理:绑定XID到数据库连接

  • 必要性:必须配置才能让Seata正常工作

8.3.6 事务使用示例

@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderMapper orderMapper;
    
    @GlobalTransactional  // 开启全局事务
    @Override
    public void createOrder(Order order) {
        // 本地事务操作
        orderMapper.insert(order);
        
        // 远程调用(通过Feign)
        storageFeignService.deduct(order.getProductId(), order.getCount());
        
        // 模拟异常
        // int i = 1/0;
    }
}

8.3.7 Feign远程调用的事务管理

实现原理

  • 事务发起者生成全局事务ID(XID)

  • 通过Feign调用时,XID会通过请求头传递:

    // Seata的XID传播拦截器
    public class SeataFeignInterceptor implements RequestInterceptor {
        @Override
        public void apply(RequestTemplate template) {
            String xid = RootContext.getXID();
            if (StringUtils.isNotBlank(xid)) {
                template.header(RootContext.KEY_XID, xid);
            }
        }
    }
  • 被调用服务通过GlobalTransactionalInterceptor检测XID,加入全局事务

XID 传播拦截器的自动配置

  • 自动生效条件:当项目中同时存在以下依赖时,Seata 会自动配置 SeataFeignInterceptor

    
        com.alibaba.cloud
        spring-cloud-starter-alibaba-seata
    
    
        org.springframework.cloud
        spring-cloud-starter-openfeign
    
  • 验证方式:在 Feign 调用时,检查请求头中是否包含 TX_XID 字段(可通过抓包或日志查看)。

  • 步骤 1:在服务调用链的入口方法添加日志:

    @GlobalTransactional
    public void businessMethod() {
        log.info("XID in caller: {}", RootContext.getXID());
        feignClient.callRemote();
    }
  • 步骤 2:在被调用服务中添加日志:
  • @RestController
    public class RemoteController {
        @PostMapping("/api")
        public void handleRequest() {
            log.info("XID in callee: {}", RootContext.getXID());
        }
    }

XID 未传递的可能原因

问题原因 解决方案
Feign 版本冲突 确保使用 Spring Cloud 2020.0.x 以上版本
自定义拦截器覆盖了 Seata 的 检查是否有其他 RequestInterceptor 清除了请求头
跨线程调用未传递上下文 使用 RootContext.bind(xid) 手动传递

GlobalTransactionalInterceptor 的自动管理

  • 自动生效机制:通过 seata-spring-boot-starter 自动注册该拦截器。

  • 拦截范围:所有被 @GlobalTransactional 或 @GlobalLock 注解的方法。

  • 核心逻辑

    public class GlobalTransactionalInterceptor implements MethodInterceptor {
        public Object invoke(MethodInvocation methodInvocation) {
            // 1. 检查是否已有全局事务
            // 2. 开启新事务(若无)或加入已有事务
            // 3. 处理事务传播
        }
    }

回滚能力

  • 只要所有服务都接入Seata

  • 所有数据源都配置了DataSourceProxy

  • 整个调用链的数据库操作都会回滚

8.3.8 注意事项

  • 必须使用代理数据源(DataSourceProxy)

  • 需要支持本地事务的关系型数据库

  • 业务SQL必须为单表操作(不支持多表关联更新)无法生成准确的回滚SQL


8.2 TCC 模式(Try-Confirm-Cancel)

8.2.1 核心原理

为什么要用 TCC 模式?

因为很多系统不能像传统数据库一样支持本地事务回滚

  • ✅ Redis:没有事务回滚机制

  • ✅ MongoDB:事务支持有限,且分布式事务很难实现

  • ✅ 第三方支付接口:一旦调用成功,没法撤回(如微信/支付宝支付)

  • ✅ 外部接口/服务:失败了也不能自动撤销

➡️ 所以必须“提前做准备”,等确定无误后再“真正提交”,否则就“取消操作”。

  • Try(资源预留)

    • MySQL:插入预创建订单(status=预创建),本地事务提交。

    • Redis:冻结库存(如 DECRBY stock_frozen 1),记录预留资源。

    • 支付接口:调用预授权接口,冻结用户账户资金。

      目标:所有操作均为资源预留,确保后续阶段可提交或回滚。

  • Confirm(提交确认)

    • MySQL:更新订单状态为 已确认

    • Redis:扣减真实库存(DECRBY stock_real 1),释放冻结资源。

    • 支付接口:执行实际扣款,完成支付。

      目标:将预留资源转为真实数据,保证业务最终生效。

  • Cancel(回滚补偿)

    • MySQL:删除预创建订单。

    • Redis:解冻库存(INCRBY stock_frozen 1)。

    • 支付接口:调用取消预授权接口,释放冻结资金。

      目标:释放所有预留资源,确保数据回滚一致。

8.2.2 引入依赖并配置



    io.seata
    seata-spring-boot-starter
    1.6.1




    com.alibaba.cloud
    spring-cloud-starter-alibaba-seata
    2021.0.5.0
# 开启TCC模式
seata.tcc.mode=api
seata.tcc.fence.enable=true  # 开启防悬挂

8.2.3 接口定义

// 订单服务接口
@LocalTCC
public interface OrderService {
    @TwoPhaseBusinessAction(
        name = "createOrder",
        commitMethod = "confirm",
        rollbackMethod = "cancel"
    )
    boolean tryCreate(Order order);
    
    boolean confirm(BusinessActionContext context);
    
    boolean cancel(BusinessActionContext context);
}

// 库存服务接口
@LocalTCC
public interface StockService {
    @TwoPhaseBusinessAction(
        name = "deductStock",
        commitMethod = "confirmDeduct",
        rollbackMethod = "cancelDeduct"
    )
    boolean tryDeduct(Long productId, Integer quantity);
    
    boolean confirmDeduct(BusinessActionContext context);
    
    boolean cancelDeduct(BusinessActionContext context);
}

@TwoPhaseBusinessAction 注解必要性详解

注解的核心作用

@TwoPhaseBusinessAction(
    name = "actionName",          // 事务动作全局唯一标识
    commitMethod = "commit",      // 二阶段提交方法名(必须存在)
    rollbackMethod = "rollback",  // 二阶段回滚方法名(必须存在)
    useTCCFence = true            // 是否启用防悬挂(1.5+版本)
)

必要性体现

场景 无注解 有注解
事务识别 Seata 无法识别 TCC 服务 Seata 自动注册事务动作
参数传递 无法自动传递事务上下文参数 通过 @BusinessActionContextParameter 自动序列化参数
方法映射 需手动维护 Try-Confirm-Cancel 映射 自动关联三阶段方法
悬挂防护 需自行实现防悬挂逻辑 开启 useTCCFence 后自动处理

注解工作原理

  • 服务启动时

    // Seata 自动扫描带 @TwoPhaseBusinessAction 的接口
    TCCBeanParserUtils.processTCCAnnotation(TccBeanParserUtils.java:112)
  • 调用阶段

    // 代理拦截(TccActionInterceptor)
    public Object invoke(MethodInvocation invocation) {
        // 1. 创建 ActionContext
        // 2. 注册分支事务
        // 3. 调用 try 方法
    }
  • 事务协调

    // Seata Server 根据注解配置找到对应方法
    DefaultCore.commit() -> TCCResourceManager.commit()

8.2.4 实现接口

@Service
public class OrderTccServiceImpl implements OrderTccService {

    @Autowired
    private OrderMapper orderMapper;

    @Override
    @Transactional
    public boolean prepare(BusinessActionContext actionContext, Order order) {
        // Try阶段:资源预留
        order.setStatus(0); // 0-处理中
        orderMapper.insert(order);
        return true;
    }

    @Override
    @Transactional
    public boolean commit(BusinessActionContext actionContext) {
        // Confirm阶段:确认提交
        Order order = (Order) actionContext.getActionContext("order");
        order.setStatus(1); // 1-已完成
        orderMapper.updateById(order);
        return true;
    }

    @Override
    @Transactional
    public boolean rollback(BusinessActionContext actionContext) {
        // Cancel阶段:回滚补偿
        Order order = (Order) actionContext.getActionContext("order");
        orderMapper.deleteById(order.getId());
        return true;
    }
}

8.2.5 事务调用

@Service
public class BusinessService {

    @Autowired
    private OrderTccService orderTccService;

    @GlobalTransactional
    public void createOrder(Order order) {
        // TCC第一阶段
        orderTccService.prepare(null, order);
        
        // 调用其他服务的TCC接口
        storageTccService.prepare(...);
        
        // 当所有Try成功时,自动执行Confirm
        // 出现异常时自动执行Cancel
    }
}

8.2.6 注意事项

① 幂等处理

// 使用防重表
@Transactional
public boolean commit(BusinessActionContext context) {
    if(select count(*) from tcc_record where xid=context.getXid()) > 0){
        return true; // 已处理过
    }
    // 业务逻辑
    insert into tcc_record(xid) values(context.getXid());
}

② 空回滚处理

@Transactional
public boolean rollback(BusinessActionContext context) {
    // 检查Try是否执行
    if(!checkTryExecuted(context.getXid())){
        log.warn("空回滚: {}", context.getXid());
        return true;
    }
    // 正常回滚逻辑
}

③ 防悬挂方案

// 在Try阶段先插入状态记录
@Transactional
public boolean prepare(...) {
    insert into tcc_status(xid, status) values(xid, 'TRYING');
    // 业务操作
}

// Cancel阶段检查状态
public boolean rollback(...) {
    if(select status from tcc_status where xid=... == 'CANCELED'){
        return true; // 已经处理过
    }
}

④ 业务数据设计示例

public class Order {
    private Long id;
    private Integer status; // 0-预创建,1-已确认,2-已取消
    private LocalDateTime freezeTime; // 预留字段
}


10. 服务监控与健康检查

10.1 三者综合使用

在生产环境中,可以将 ActuatorPrometheus + GrafanaELK Stack 结合使用,以实现从性能监控到日志分析的全方位监控体系。

1. Actuator 的角色

  • 用于服务内部健康检查(如 /health)和性能指标暴露(如 /metrics)。
  • 是 Prometheus 的主要数据来源。

2. Prometheus + Grafana 的角色

  • 通过 Actuator 的 /metrics 接口,采集服务性能数据。
  • 用于全局监控和趋势分析。
  • 提供实时告警功能,帮助快速响应问题。

3. ELK Stack 的角色

  • 收集和存储分布式服务的日志。
  • 提供日志的全文检索和堆栈分析功能。
  • 作为 Prometheus + Grafana 的补充工具,用于定位具体问题。

10.2 Spring Boot Actuator

10.2.1. 什么是 Actuator?

Spring Boot Actuator 是 Spring 提供的一套开箱即用的工具,用于监控和管理服务。它的主要功能是:

  • 健康检查:检测服务是否正常运行,比如数据库连接是否可用、磁盘空间是否充足等。
  • 暴露核心指标:提供运行时的性能数据,比如内存使用率、CPU 使用率、HTTP 请求次数等。

10.2.2. Actuator 的核心功能

1. 健康检查

  • 地址/actuator/health
  • 返回服务的健康状态信息。

示例:

{
  "status": "UP",
  "components": {
    "db": { "status": "UP", "details": { "database": "MySQL", "result": "ok" } },
    "diskSpace": { "status": "UP", "details": { "total": 50000000000, "free": 20000000000 } }
  }
}

解读

  • status: UP 表示服务正常。
  • components 显示健康检查的各项子系统状态,比如数据库连接和磁盘空间。

2. 核心指标

  • 地址/actuator/metrics
  • 返回服务的性能指标。

示例:

{
  "names": [
    "jvm.memory.used",
    "system.cpu.usage",
    "http.server.requests"
  ]
}

解读

  • 可以查询 JVM 内存使用量(jvm.memory.used)、CPU 使用率(system.cpu.usage)等指标。

3. 自定义健康检查

开发者可以根据业务需求定义自定义的健康检查逻辑。

示例代码

@Component
public class CustomHealthIndicator implements HealthIndicator {

    @Override
    public Health health() {
        boolean dbConnectionOk = checkDatabase();
        if (dbConnectionOk) {
            return Health.up().withDetail("Database", "Connected").build();
        }
        return Health.down().withDetail("Database", "Disconnected").build();
    }

    private boolean checkDatabase() {
        // 自定义检查逻辑,比如尝试连接数据库
        return true;
    }
}

功能

  • 如果自定义服务(比如数据库)不可用,Actuator 的 /health 会返回异常状态,方便及时发现问题。

10.3 Prometheus + Grafana

10.3.1. 什么是 Prometheus 和 Grafana?

  • Prometheus
    • 开源的监控工具,专门用于收集和存储时间序列数据(比如每秒请求数、CPU 使用率)。
    • 支持自定义告警规则,当指标超过阈值时发送通知。
  • Grafana
    • 数据可视化工具,可以将 Prometheus 收集的数据通过图表、仪表盘实时展示。

10.3.2. Prometheus + Grafana 的核心功能

1. 数据采集与存储(Prometheus)

Prometheus 定期访问 Actuator 暴露的 /metrics 接口,采集服务性能数据并存储。

2. 数据可视化(Grafana)

Grafana 从 Prometheus 获取数据,并以图表、仪表盘形式展示服务性能。例如:

  • 实时查看 CPU 使用率、内存占用等。
  • 查看过去一小时内的请求数变化趋势。

3. 实时告警

Prometheus 支持基于指标配置告警规则,例如:

  • 当 CPU 使用率超过 90% 持续 5 分钟时,发送邮件通知。
  • 当 HTTP 请求失败率超过某个阈值时,触发警报。

10.3.3. Prometheus + Grafana 的整合流程

1. Prometheus 配置

Prometheus 需要配置一个 prometheus.yml 文件,用于指定要监控的服务:

scrape_configs:
  - job_name: 'spring-boot'
    static_configs:
      - targets: ['localhost:8080'] # 采集 Actuator 暴露的指标

2. Spring Boot 集成 Micrometer

添加依赖:


    io.micrometer
    micrometer-registry-prometheus

application.properties 中启用 Prometheus 端点:

management.endpoints.web.exposure.include=prometheus

Actuator 的 /actuator/prometheus 接口会返回 Prometheus 格式的数据。

3. Grafana 配置

安装并启动 Grafana。

添加 Prometheus 数据源:

  • 数据源 URL:http://localhost:9090(Prometheus 地址)。

创建仪表盘:

  • 使用 PromQL 查询数据,例如:
rate(http_server_requests_seconds_sum[1m])

展示服务请求速率、错误率等数据。


10.4 ELK Stack

10.4.1. 什么是 ELK Stack?

ELK 是 Elasticsearch、Logstash 和 Kibana 的组合,主要用于 日志收集、存储和分析

  • Elasticsearch
    • 用于存储和检索日志数据。
  • Logstash
    • 日志收集工具,将日志从各种来源(如文件、数据库)发送到 Elasticsearch。
  • Kibana
    • 日志的图形化展示工具,支持全文检索和日志分析。

10.4.2. ELK Stack 的核心功能

  • 日志聚合:集中收集所有微服务的日志,避免分散存储导致分析困难。
  • 快速查询:支持复杂的日志搜索,例如根据时间范围检索错误日志。
  • 问题定位:分析堆栈错误、慢查询、网络延迟等问题。

10.4.3. ELK Stack 的整合流程

1. 配置 Logstash

创建 logstash.conf

input {
    file {
        path => "/var/log/my-service.log" # 日志文件路径
        start_position => "beginning"
    }
}
output {
    elasticsearch {
        hosts => ["localhost:9200"] # Elasticsearch 地址
    }
}

启动 Logstash:

./logstash -f logstash.conf
2. 配置 Elasticsearch

启动 Elasticsearch:

./elasticsearch

默认运行在 http://localhost:9200

3. 配置 Kibana

启动 Kibana:

./kibana

通过浏览器访问 http://localhost:5601

配置日志索引(如 logstash-*),并创建仪表盘。


11. 容器化与部署

微服务架构强调快速部署与扩展,而容器化技术(如 Docker)是实现这一目标的关键工具。Docker 提供了轻量级、可移植的环境,可以轻松将应用打包成镜像,运行在任何支持 Docker 的平台上。同时,通过 Docker Compose 可以实现多个微服务的协调部署。


11.1 Docker:容器化 Spring Boot 项目

11.1.1. 什么是 Docker?

Docker 是一种容器化技术,可以将应用及其依赖打包到一个标准化的单元中,便于跨环境运行。Docker 的主要特点:

  • 轻量级:一个容器仅包含运行所需的最小资源。
  • 快速部署:启动容器的速度比传统虚拟机快得多。
  • 隔离性:每个容器相互独立,互不干扰。

11.1.2. 容器化 Spring Boot 项目的基本流程

创建 Dockerfile

Dockerfile 是一个脚本文件,定义了如何构建 Docker 镜像的步骤。以下是容器化 Spring Boot 项目的 Dockerfile 示例:

# 使用官方的 Java 运行环境镜像
FROM openjdk:17-jdk-alpine

# 设置工作目录
WORKDIR /app

# 将 Spring Boot 项目的 JAR 文件复制到容器中
COPY target/my-app-1.0.jar app.jar

# 暴露服务端口
EXPOSE 8080

# 定义容器启动时的命令
ENTRYPOINT ["java", "-jar", "app.jar"]

代码解析

  • FROM:指定基础镜像,openjdk:17-jdk-alpine 是一个轻量级的 Java 运行环境。
  • WORKDIR:设置容器的工作目录为 /app
  • COPY:将编译好的 JAR 文件复制到容器中。
  • EXPOSE:声明容器对外暴露的端口(与 Spring Boot 应用的端口一致)。
  • ENTRYPOINT:定义容器启动时的命令,运行 Spring Boot 项目的 JAR 文件。

构建 Docker 镜像

打包 Spring Boot 项目:

mvn clean package

输出的 JAR 文件通常位于 target 目录下。

构建 Docker 镜像:

docker build -t my-app:1.0 .

命令解析

  • -t my-app:1.0:为镜像指定名称和版本。
  • .:指向 Dockerfile 所在的目录。

查看生成的镜像:

docker images

示例输出:

REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
my-app       1.0       abc12345       10 seconds ago   500MB

运行 Docker 容器

启动容器:

docker run -d -p 8080:8080 --name my-app-container my-app:1.0

命令解析

  • -d:后台运行容器。
  • -p 8080:8080:将宿主机的 8080 端口映射到容器的 8080 端口。
  • --name my-app-container:为容器指定名称。

查看容器状态:

docker ps

示例输出:

CONTAINER ID   IMAGE         COMMAND               STATUS          PORTS                   NAMES
12345abcde     my-app:1.0    "java -jar app.jar"   Up 2 minutes    0.0.0.0:8080->8080/tcp  my-app-container

访问服务: 在浏览器或 Postman 中访问 http://localhost:8080,验证服务是否正常运行。


11.2 Docker Compose:多服务部署

11.2.1. 什么是 Docker Compose?

Docker Compose 是一个工具,用于定义和运行多容器 Docker 应用。通过一个 YAML 文件,可以配置多个服务的部署参数,实现微服务的快速部署。


11.2.2. Docker Compose 的基本结构

以下是一个示例 docker-compose.yml 文件,定义了两个服务(appdb)的部署。

version: '3.8'

services:
  app:
    image: my-app:1.0
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    depends_on:
      - db

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: mydb
      MYSQL_USER: user
      MYSQL_PASSWORD: password
    ports:
      - "3306:3306"

文件解析

  • version
    • 定义 Compose 文件的版本,这里使用 3.8
  • services
    • 定义多个服务:
      • app:表示 Spring Boot 服务。
      • db:表示 MySQL 数据库服务。
  • build
    • 指定 app 服务的镜像来源,可以通过 Dockerfile 构建镜像。
  • ports
    • 声明容器与宿主机的端口映射,例如将 MySQL 的 3306 端口映射到宿主机的 3306 端口。
  • depends_on
    • 定义服务间的依赖关系,app 服务依赖于 db 服务。
  • environment
    • 定义环境变量,如 MySQL 的数据库名称、用户名和密码。

11.2.3. 使用 Docker Compose 部署

创建 docker-compose.yml 文件,并放置在项目根目录。

启动服务:

docker-compose up -d

命令解析

  • up:启动 Compose 定义的服务。
  • -d:后台运行。

查看服务状态:

docker-compose ps

停止服务:

docker-compose down

11.3 综合使用:生产环境部署建议

1. 基本流程

  • 开发阶段:
    • 使用 Dockerfile 将每个微服务容器化。
    • 使用 Docker Compose 测试多服务的交互。
  • 测试和生产阶段:
    • 使用容器编排工具(如 Kubernetes 或 Docker Swarm)管理多个容器的部署、扩展和监控。

2. 容器化的好处

  • 快速部署:一个命令即可启动整个微服务集群。
  • 环境一致性:开发、测试和生产环境完全一致。
  • 轻量级:相比虚拟机,容器的资源占用更少,启动更快。
  • 易于扩展:通过配置即可轻松扩展服务的实例数量。

你可能感兴趣的:(Spring,Cloud,微服务,spring,cloud,微服务,spring)