7微服务架构设计模式

使用 Saga 管理事务

分布式事务解决方案

Saga编排式协调式补偿事务

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第7章:使用Spring Boot开发微服务(Developing Microservices with Spring Boot)

一、导读

1.1 本章学习目标

第7章以 Java/Spring 生态为例,讲解如何使用 Spring Boot 和 Spring Cloud 构建微服务。从项目结构、自动配置开始,逐步介绍服务注册与发现、配置中心、API 网关、声明式客户端等核心组件。

本章的核心学习目标包括:

  • 掌握 Spring Boot 的核心机制:自动配置、起步依赖、Actuator 监控。
  • 学会使用 Spring Cloud 核心组件:Eureka/Consul、Config、Gateway、Feign。
  • 理解服务注册与发现的实现:@EnableDiscoveryClient 的工作原理。
  • 掌握配置中心的使用:Git 仓库存储配置、动态刷新。
  • 学会使用 Feign 声明式客户端:简化服务间调用。
  • 理解 Spring Cloud Gateway 的路由和过滤机制。

1.2 为什么本章如此重要

Spring Boot + Spring Cloud 是 Java 生态中最成熟的微服务框架。Richardson 选择 Spring 作为示例框架,不仅因为它的广泛使用,更因为它提供了微服务所需的全套基础设施——从服务注册到配置管理,从 API 网关到熔断器。

本章的重要性在于它将前面章节的理论模式落地为具体的代码实现。读者将看到一个完整的微服务从创建到部署的全过程。

1.3 核心问题引导

Spring Boot 的自动配置是如何工作的?

服务注册与发现在 Spring Cloud 中如何实现?

配置中心如何实现配置的动态刷新?

Feign 客户端如何与熔断器集成?

Spring Cloud Gateway 的路由规则如何配置?

Spring Boot Actuator 提供了哪些监控端点?


二、核心概念详解

2.1 Spring Boot 核心机制

2.1.1 自动配置

Spring Boot 的自动配置通过 @EnableAutoConfiguration 注解实现。它根据 classpath 中的依赖自动配置 Bean。

java
// 自动配置示例:
// 当 classpath 中有 DataSource 和 JdbcTemplate 时
// Spring Boot 自动配置:
// 1. 创建 HikariCP 连接池
// 2. 创建 JdbcTemplate Bean
// 3. 配置事务管理器

// 自动配置的条件注解:
@ConditionalOnClass(DataSource.class)        // classpath 中有这个类
@ConditionalOnMissingBean(DataSource.class)  // 容器中没有这个 Bean
@ConditionalOnProperty("spring.datasource.url") // 有这个配置属性

// 自定义自动配置:
@Configuration
@ConditionalOnClass(KafkaTemplate.class)
@EnableConfigurationProperties(KafkaProperties.class)
public class KafkaAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    public KafkaTemplate<String, String> kafkaTemplate(
            ProducerFactory<String, String> producerFactory) {
        return new KafkaTemplate<>(producerFactory);
    }
}

2.1.2 Actuator 监控

yaml
# application.yml
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: always
  metrics:
    tags:
      application: ${spring.application.name}

# 常用端点:
# GET /actuator/health     → 健康检查
# GET /actuator/info       → 应用信息
# GET /actuator/metrics    → 指标列表
# GET /actuator/metrics/jvm.memory.used → JVM 内存使用
# GET /actuator/prometheus → Prometheus 格式指标

2.2 服务注册与发现

2.2.1 Eureka 实现

yaml
# Eureka Server 配置(application.yml)
server:
  port: 8761

eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
  server:
    enable-self-preservation: false

# Eureka Client 配置(order-service/application.yml)
eureka:
  client:
    service-url:
      defaultZone: http://eureka-server:8761/eureka/
    registry-fetch-interval-seconds: 5
  instance:
    prefer-ip-address: true
    lease-renewal-interval-in-seconds: 10
    lease-expiration-duration-in-seconds: 30
java
// 服务注册
@SpringBootApplication
@EnableDiscoveryClient
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

// 使用 DiscoveryClient 获取服务实例
@Autowired
private DiscoveryClient discoveryClient;

public List<ServiceInstance> getOrderServiceInstances() {
    return discoveryClient.getInstances("order-service");
}

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

// 使用服务名代替具体地址
restTemplate.getForObject("http://payment-service/api/payments/123", Payment.class);

2.2.2 Consul 实现

yaml
# Consul Client 配置
spring:
  cloud:
    consul:
      host: consul-server
      port: 8500
      discovery:
        service-name: order-service
        health-check-interval: 10s
        health-check-path: /actuator/health
        prefer-ip-address: true

2.3 配置中心

2.3.1 Spring Cloud Config

yaml
# Config Server 配置
spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/ftgo/config-repo
          search-paths: '{application}'
          default-label: main
  profiles:
    active: native

# Config Client 配置(bootstrap.yml)
spring:
  cloud:
    config:
      uri: http://config-server:8888
      name: order-service
      profile: ${spring.profiles.active}
      fail-fast: true
      retry:
        initial-interval: 1000
        max-interval: 5000
        max-attempts: 5
// Git 配置仓库结构:
// config-repo/
// ├── order-service/
// │   ├── order-service.yml          (默认配置)
// │   ├── order-service-dev.yml      (开发环境)
// │   └── order-service-prod.yml     (生产环境)
// ├── payment-service/
// │   └── payment-service.yml
// └── application.yml                (全局默认配置)

// 动态刷新配置:
// 1. 修改 Git 仓库中的配置文件
// 2. 调用 /actuator/refresh 端点(或通过 Bus 广播)
// 3. @RefreshScope 标记的 Bean 会重新加载配置

@RefreshScope
@RestController
public class OrderController {
    @Value("${order.max-items:10}")
    private int maxItems;
}

2.4 Feign 声明式客户端

java
// Feign 客户端定义
@FeignClient(name = "payment-service",
    fallbackFactory = PaymentClientFallback.class,
    configuration = FeignClientConfig.class)
public interface PaymentClient {
    
    @PostMapping("/api/payments")
    Payment createPayment(@RequestBody PaymentRequest request);
    
    @GetMapping("/api/payments/{id}")
    Payment getPayment(@PathVariable("id") String id);
    
    @PostMapping("/api/payments/{id}/refund")
    Payment refundPayment(@PathVariable("id") String id);
}

// 降级处理
@Component
public class PaymentClientFallback implements FallbackFactory<PaymentClient> {
    @Override
    public PaymentClient create(Throwable cause) {
        return new PaymentClient() {
            @Override
            public Payment createPayment(PaymentRequest request) {
                throw new ServiceUnavailableException("Payment service unavailable", cause);
            }
            @Override
            public Payment getPayment(String id) {
                return Payment.defaultPayment(id);
            }
            @Override
            public Payment refundPayment(String id) {
                throw new ServiceUnavailableException("Payment service unavailable", cause);
            }
        };
    }
}

// Feign 配置(超时、拦截器)
public class FeignClientConfig {
    @Bean
    public Request.Options requestOptions() {
        return new Request.Options(
            Duration.ofSeconds(3),   // connect timeout
            Duration.ofSeconds(5),   // read timeout
            true                      // follow redirects
        );
    }
    
    @Bean
    public RequestInterceptor authInterceptor() {
        return template -> {
            String token = SecurityContextHolder.getContext()
                .getAuthentication().getCredentials().toString();
            template.header("Authorization", "Bearer " + token);
        };
    }
}

2.5 Spring Cloud Gateway

yaml
# Gateway 路由配置
spring:
  cloud:
    gateway:
      routes:
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**
            - Method=GET,POST,PUT,DELETE
          filters:
            - StripPrefix=0
            - name: CircuitBreaker
              args:
                name: orderCB
                fallbackUri: forward:/fallback/orders
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 100
                redis-rate-limiter.burstCapacity: 200
        
        - id: payment-service
          uri: lb://payment-service
          predicates:
            - Path=/api/payments/**
          filters:
            - name: CircuitBreaker
              args:
                name: paymentCB
                fallbackUri: forward:/fallback/payments

      default-filters:
        - name: AddResponseHeader
          args:
            name: X-Gateway-Version
            value: "1.0"
java
// 自定义过滤器
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getURI().getPath();
        
        if (path.startsWith("/api/admin")) {
            String token = exchange.getRequest().getHeaders().getFirst("Authorization");
            if (token == null || !jwtUtil.validate(token)) {
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
            }
        }
        
        return chain.filter(exchange);
    }
    
    @Override
    public int getOrder() {
        return -1;
    }
}

三、实战案例

3.1 案例一:完整的订单服务

java
@SpringBootApplication
@EnableDiscoveryClient
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
    
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

@RestController
@RequestMapping("/api/orders")
public class OrderController {
    
    @Autowired private OrderService orderService;
    @Autowired private PaymentClient paymentClient;
    
    @PostMapping
    public ResponseEntity<Order> createOrder(@RequestBody CreateOrderRequest request) {
        Order order = orderService.createOrder(request);
        
        try {
            Payment payment = paymentClient.createPayment(
                new PaymentRequest(order.getId(), order.getTotalAmount()));
            order.setStatus(OrderStatus.PAID);
        } catch (Exception e) {
            order.setStatus(OrderStatus.PAYMENT_PENDING);
        }
        
        orderService.updateOrder(order);
        return ResponseEntity.status(201).body(order);
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<OrderDetail> getOrderDetail(@PathVariable String id) {
        Order order = orderService.getOrder(id);
        if (order == null) return ResponseEntity.notFound().build();
        
        Restaurant restaurant = null;
        try {
            restaurant = restTemplate.getForObject(
                "http://restaurant-service/api/restaurants/" + order.getRestaurantId(),
                Restaurant.class);
        } catch (Exception e) {
            // 降级处理
        }
        
        OrderDetail detail = new OrderDetail(order, restaurant);
        return ResponseEntity.ok(detail);
    }
}

3.2 案例二:完整的项目结构

ftgo-order-service/
├── build.gradle
├── src/main/java/com/ftgo/order/
│   ├── OrderServiceApplication.java
│   ├── config/
│   │   ├── WebConfig.java
│   │   ├── SecurityConfig.java
│   │   └── KafkaConfig.java
│   ├── controller/
│   │   └── OrderController.java
│   ├── service/
│   │   ├── OrderService.java
│   │   └── OrderServiceImpl.java
│   ├── repository/
│   │   └── OrderRepository.java
│   ├── model/
│   │   ├── Order.java
│   │   ├── OrderItem.java
│   │   └── OrderStatus.java
│   ├── dto/
│   │   ├── CreateOrderRequest.java
│   │   └── OrderDetail.java
│   ├── client/
│   │   ├── PaymentClient.java
│   │   └── PaymentClientFallback.java
│   └── event/
│       ├── OrderEventPublisher.java
│       └── OrderEventListener.java
├── src/main/resources/
│   ├── application.yml
│   └── bootstrap.yml
└── src/test/java/
    └── com/ftgo/order/
        ├── controller/OrderControllerTest.java
        └── service/OrderServiceTest.java

四、本章小结

本章以 Spring Boot + Spring Cloud 为例讲解了微服务的实现:

核心知识点:

  • Spring Boot 自动配置根据 classpath 依赖自动配置 Bean。
  • 服务注册与发现通过 Eureka/Consul 实现,客户端使用 @LoadBalanced 负载均衡。
  • 配置中心通过 Git 仓库存储配置,支持动态刷新。
  • Feign 提供声明式 REST 客户端,集成熔断器实现降级。
  • Spring Cloud Gateway 提供路由、过滤、限流等网关功能。

五、思考与练习

思考题

Spring Boot 自动配置的原理是什么?如何自定义自动配置?

Eureka 和 Consul 的一致性模型有何不同?

Feign 的降级策略应该如何设计?

练习题

使用 Spring Initializr 创建两个微服务项目,配置 Eureka 服务注册。

实现 Feign 客户端调用另一个服务,集成 Resilience4j 熔断器。

配置 Spring Cloud Gateway 路由规则,实现 API 路由和限流。

7/12使用 S
0%