Spring Cloud 微服务架构:注册中心、配置中心与网关

摘要

当一个系统从单体应用拆分成多个服务后,服务之间不再共享同一个进程和固定的本地方法调用。订单服务需要找到库存服务,网关需要将请求转发到正确实例,多个环境还需要使用不同配置。服务数量增加后,硬编码地址和每个服务独立维护配置会迅速变得难以管理。

Spring Cloud 提供了一组面向分布式系统常见问题的工具和抽象,覆盖服务发现、配置管理、路由、服务间调用、负载均衡、熔断和消息通信等能力。注册中心、配置中心和网关是微服务入门阶段最常见的三个基础组件,但它们并不能自动解决所有分布式问题。

本文以用户服务、订单服务和库存服务为例,介绍服务注册与发现、Spring Cloud Config、Spring Cloud Gateway 的基本原理和落地方式,并说明如何处理版本兼容、配置安全、网关路由、服务不可用和链路观测。读完本文后,你应该能够:

  • 理解注册中心、配置中心和网关分别解决什么问题;
  • 搭建一个包含注册中心、配置中心和网关的示例架构;
  • 使用服务名而不是固定 IP 调用下游服务;
  • 设计配置加载、路由转发和健康检查;
  • 识别微服务基础设施中的常见故障和安全风险;
  • 判断哪些能力适合由 Spring Cloud 提供,哪些应交给云平台或 Kubernetes。

一、背景与问题

1. 服务拆分后,地址从哪里来

在单体应用中,订单代码可以直接调用库存方法:

java 复制代码
inventoryService.reserve(command);

拆成两个服务后,订单服务需要通过网络调用库存服务:

text 复制代码
订单服务
  -> http://某个地址/api/inventories/reservations
  -> 库存服务

生产环境中,库存服务可能有多个实例:

text 复制代码
inventory-service-1
inventory-service-2
inventory-service-3

实例可能因为发布、扩容、故障和迁移而动态变化。订单服务不应该把某个实例的 IP 写死在代码或配置中,而应该通过服务发现找到当前可用实例。

2. 配置为什么需要集中管理

每个服务通常都有多套环境:

text 复制代码
开发环境
测试环境
预发布环境
生产环境

同一个配置项在不同环境中可能不同:

  • 数据库地址;
  • Redis 地址;
  • 下游服务地址;
  • 日志级别;
  • 功能开关;
  • 超时时间;
  • 灰度比例。

如果每个服务都把配置打包在自己的镜像里,会出现:

  • 配置变更需要重新构建和发布;
  • 多个服务的公共配置不一致;
  • 敏感配置容易进入代码仓库;
  • 配置来源和版本难以追踪;
  • 环境切换容易出错。

配置中心可以将配置从应用构建产物中分离出来,并以统一方式提供给服务。

3. 为什么需要 API 网关

客户端直接访问几十个内部服务,会带来:

  • 服务地址暴露;
  • 客户端维护大量路由;
  • 认证和限流逻辑重复;
  • 跨域和协议适配分散;
  • 版本发布难以控制;
  • 多次调用导致客户端逻辑复杂。

网关可以作为统一入口:

text 复制代码
客户端
  -> API Gateway
      -> 用户服务
      -> 商品服务
      -> 订单服务
      -> 支付服务

网关适合处理路由、认证入口、限流、跨域、日志和基础流量治理,但不应承载复杂业务规则。

4. Spring Cloud 解决的是分布式模式问题

Spring Cloud 不是一个单独的服务器,而是一组围绕分布式系统常见模式的项目集合,覆盖配置管理、服务发现、路由、服务调用、负载均衡、熔断和消息等能力。

这些工具降低了基础设施接入成本,但不会消除分布式系统的本质问题:

  • 网络可能失败;
  • 服务可能超时;
  • 数据可能短暂不一致;
  • 配置可能错误;
  • 请求可能重复;
  • 依赖可能级联故障。

使用 Spring Cloud 的同时,仍然需要设计超时、重试、幂等、降级、权限和可观测性。

二、核心概念

1. 服务注册与发现

服务注册与发现可以分成两个动作:

服务注册:

text 复制代码
库存服务启动
  -> 向注册中心登记服务名、地址、端口和健康状态

服务发现:

text 复制代码
订单服务需要调用库存服务
  -> 根据服务名查询可用实例
  -> 选择一个实例发起请求

服务名比固定地址更稳定:

text 复制代码
inventory-service

而不是:

text 复制代码
10.0.0.12:8080

注册中心通常还会维护实例的心跳、状态、元数据和注销信息。

2. 注册中心的职责边界

注册中心主要负责:

  • 保存服务实例信息;
  • 提供服务查询;
  • 维护实例健康状态;
  • 通知实例变化;
  • 支持服务元数据;
  • 协助客户端选择实例。

它通常不负责:

  • 业务鉴权;
  • 订单状态;
  • 数据一致性;
  • 复杂流量编排;
  • 业务级重试;
  • 业务降级结果。

注册中心不可用时,客户端是否可以使用本地缓存的实例列表、是否允许继续调用,需要根据客户端和部署环境设计。

3. 配置中心

配置中心的核心是:

text 复制代码
配置文件集中存储
  -> 服务启动时读取
  -> 注入 Spring Environment
  -> 应用通过配置绑定使用

配置可以按以下维度组织:

text 复制代码
应用名
  + 环境
      + Profile
          + 配置版本

例如:

text 复制代码
order-service
  -> application.yml
  -> order-service-dev.yml
  -> order-service-prod.yml

配置中心可以基于 Git、数据库、对象存储或其他后端保存配置。重点不在于配置存在哪里,而在于版本、权限、审计、发布和回滚是否可控。

4. API 网关

网关通常由三部分组成:

部分 作用
Route 定义请求应该转发到哪里
Predicate 判断当前请求是否匹配路由
Filter 在转发前后修改请求、响应或执行横切逻辑

一个路由可以抽象为:

text 复制代码
请求路径或请求头
  -> Predicate 判断匹配
  -> Filter 执行前置处理
  -> 转发到目标服务
  -> Filter 执行后置处理
  -> 返回客户端

Spring Cloud Gateway 官方文档将路由、断言和过滤器作为核心构件,并支持在网关层处理路由、监控、安全和韧性等横切需求。

5. 网关和反向代理的区别

Nginx、云负载均衡和 API 网关都可以转发请求,但关注点不同:

组件 主要关注点
负载均衡 四层或七层流量转发、健康检查
反向代理 TLS、静态资源、基础转发和连接管理
API 网关 业务 API 路由、认证入口、限流和协议治理
服务网格 服务间通信、流量治理和可观测性

实际架构可以同时使用它们:

text 复制代码
客户端
  -> 云负载均衡
  -> API 网关
  -> 微服务
  -> 服务网格或服务客户端

不要把所有功能都堆到网关。网关故障会影响大量业务,功能越多,越需要隔离和限流。

6. 客户端负载均衡

服务发现返回多个实例后,还需要选择其中一个:

text 复制代码
实例列表
  -> 轮询
  -> 随机
  -> 权重
  -> 区域优先
  -> 健康状态过滤
  -> 选择目标实例

客户端负载均衡和网关负载均衡可以同时存在,但要避免重复和冲突。实例选择还需要结合超时、重试、熔断和区域流量策略。

7. Spring Cloud 版本对齐

Spring Cloud 的依赖通常需要与 Spring Boot 版本匹配。不同 Release Train 支持的 Boot 代际不同,不能随意组合最新的 Boot 和任意旧版 Cloud。

建议:

  • 通过 Spring Initializr 生成兼容的项目骨架;
  • 使用 Spring Cloud BOM 管理依赖;
  • 在升级前查看官方兼容关系;
  • 不要手动给每个 Cloud 组件指定互相冲突的版本;
  • 通过集成测试验证网关、配置和发现功能。

版本号只是构建配置的一部分,真正需要验证的是整套依赖、JDK、容器和部署平台的兼容性。

8. 健康检查和实例状态

服务注册成功不等于服务可用。至少要区分:

  • 应用进程是否存活;
  • 应用是否已经准备好接收流量;
  • 数据库是否可连接;
  • 关键下游是否可用;
  • 当前实例是否正在优雅停机。

健康检查应避免把所有下游故障都简单地映射为实例死亡,否则可能导致实例频繁上下线和流量震荡。

三、工作原理

1. 服务启动和注册流程

如果配置中心在启动阶段不可用,应用是否允许使用本地配置启动,要根据业务要求决定。核心交易服务通常不应静默使用不完整配置。

2. 网关请求路由流程

路由匹配和实例选择属于基础设施层,订单业务规则仍然由订单服务负责。

3. 配置加载和刷新

配置变化可能采用两种方式:

启动时加载:

text 复制代码
服务启动
  -> 读取配置中心
  -> 创建应用上下文
  -> 使用配置运行

运行时刷新:

text 复制代码
配置仓库变更
  -> 配置中心感知变化
  -> 发布刷新事件
  -> 服务重新绑定配置

运行时刷新并不是所有 Bean 都能安全支持。线程池、数据源、客户端连接和缓存等组件刷新时可能影响正在执行的请求,需要明确哪些配置允许动态变更。

4. 服务调用失败传播

一个请求可能经过:

text 复制代码
网关
  -> 订单服务
      -> 库存服务
          -> 商品服务
              -> 数据库

如果每一层都设置相同的超时和重试,可能导致总耗时和请求数量被放大:

text 复制代码
上游重试
  -> 中间服务重试
      -> 下游再次重试
          -> 大量重复请求

重试应尽量靠近能够判断业务幂等性的边界,并设置总预算,而不是每一层都无限重试。

5. 配置中心的版本和回滚

配置发布应具有版本概念:

text 复制代码
配置 v10
  -> 灰度服务加载
  -> 验证指标
  -> 扩大范围
  -> 全量发布

发生问题时:

text 复制代码
发现异常
  -> 停止继续发布
  -> 回滚到上一个稳定版本
  -> 检查已有实例是否已刷新
  -> 观察恢复结果

配置变更与代码变更一样需要审计、评审和回滚。不能因为配置不是代码,就绕过发布流程。

6. 网关过滤器的执行位置

网关过滤器可以在请求转发前后执行:

text 复制代码
请求进入
  -> 认证信息解析
  -> 请求头补充
  -> 限流
  -> 路由转发
  -> 响应状态记录
  -> 敏感头清理
  -> 返回客户端

认证和权限不能只放在网关。网关可以做统一入口检查,但服务内部仍应根据业务资源执行授权,避免内部调用绕过网关后失去保护。

四、实战示例

下面搭建一个简化的 Spring Cloud 架构:

text 复制代码
config-server       配置中心
discovery-server    注册中心
api-gateway         API 网关
user-service        用户服务
order-service       订单服务

示例中的版本号使用占位符,实际项目应根据 Spring Boot 版本选择兼容的 Spring Cloud Release Train。

1. 使用 BOM 管理依赖版本

xml 复制代码
<properties>
    <java.version>17</java.version>
    <spring-cloud.version>SPRING_CLOUD_VERSION</spring-cloud.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>

不要直接复制其他项目的 Cloud 版本。先确认当前 Boot、JDK 和目标 Cloud Release Train 的兼容关系。

2. 创建配置中心服务

添加配置中心服务依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
</dependency>

启动类:

java 复制代码
package com.example.config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@EnableConfigServer
@SpringBootApplication
public class ConfigServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(
                ConfigServerApplication.class,
                args
        );
    }
}

配置中心自身的 application.yml:

yaml 复制代码
server:
  port: 8888

spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          uri: https://git.example.com/platform/config-repository
          default-label: main

开发环境也可以使用本地目录作为后端:

yaml 复制代码
spring:
  cloud:
    config:
      server:
        native:
          search-locations: file:./config-repository

生产环境需要保护配置仓库凭据,并限制配置中心接口的访问权限。

3. 编写远程配置文件

配置仓库中可以放置:

yaml 复制代码
# order-service-dev.yml
server:
  port: 8082

spring:
  application:
    name: order-service

clients:
  inventory:
    timeout-ms: 800

配置文件名称通常与应用名和 profile 有关。配置加载规则受具体版本和客户端方式影响,项目中应使用启动日志确认最终生效的 PropertySource。

4. 创建配置客户端

添加 Config Client 依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>

客户端 application.yml:

yaml 复制代码
spring:
  application:
    name: order-service
  config:
    import: optional:configserver:http://localhost:8888

  profiles:
    active: dev

optional 表示配置中心不可用时允许继续尝试本地启动。对必须依赖远程配置的生产服务,不应无条件使用 optional,而应明确启动失败和降级策略。

5. 绑定配置属性

java 复制代码
package com.example.order.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "clients.inventory")
public record InventoryClientProperties(
        int timeoutMs
) {
}

在启动类或配置类中启用属性扫描:

java 复制代码
@Configuration
@EnableConfigurationProperties(
        InventoryClientProperties.class
)
public class ClientConfig {
}

相比在代码中到处使用字符串读取配置,类型安全的配置绑定更容易检查、测试和维护。

6. 创建注册中心服务

这里以 Eureka 作为示例注册中心。添加依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

启动类:

java 复制代码
package com.example.discovery;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class DiscoveryServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(
                DiscoveryServerApplication.class,
                args
        );
    }
}

注册中心的开发配置:

yaml 复制代码
server:
  port: 8761

spring:
  application:
    name: discovery-server

eureka:
  client:
    register-with-eureka: false
    fetch-registry: false

生产环境通常需要多个注册中心节点、访问认证、网络隔离、持久化和故障演练。开发环境的单节点配置不能直接用于生产。

7. 创建业务服务客户端

业务服务添加发现客户端依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

服务 application.yml:

yaml 复制代码
spring:
  application:
    name: order-service

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
    prefer-ip-address: true

服务启动后会使用 order-service 作为服务名注册。其他服务可以通过服务名进行调用,而不需要知道具体实例地址。

8. 使用 OpenFeign 调用服务

添加 OpenFeign 依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

启动类启用 Feign:

java 复制代码
@SpringBootApplication
@EnableFeignClients
public class OrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(
                OrderApplication.class,
                args
        );
    }
}

定义库存服务客户端:

java 复制代码
@FeignClient(name = "inventory-service")
public interface InventoryClient {

    @PostMapping("/api/inventories/reservations")
    ReservationResponse reserve(
            @RequestHeader("Idempotency-Key")
            String idempotencyKey,
            @RequestBody ReserveRequest request
    );
}

使用服务名后,客户端需要结合负载均衡组件从注册中心实例列表中选择目标。超时、错误码、重试和幂等仍需要在业务层明确设计。

9. 创建 Spring Cloud Gateway

添加网关依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

网关配置:

yaml 复制代码
server:
  port: 8080

spring:
  application:
    name: api-gateway

  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**

lb://user-service 表示通过服务发现和负载均衡选择 user-service 实例。路由语法和网关实现方式可能随 Spring Cloud 版本变化,升级时应重新核对官方文档。

10. 添加网关过滤器

可以编写一个全局过滤器记录请求:

java 复制代码
@Component
public class GatewayTraceFilter
        implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(
            ServerWebExchange exchange,
            GatewayFilterChain chain) {

        String traceId = exchange.getRequest()
                .getHeaders()
                .getFirst("X-Trace-Id");

        if (traceId == null || traceId.isBlank()) {
            traceId = UUID.randomUUID().toString();
        }

        ServerHttpRequest request = exchange
                .getRequest()
                .mutate()
                .header("X-Trace-Id", traceId)
                .build();

        ServerWebExchange updated = exchange
                .mutate()
                .request(request)
                .build();

        return chain.filter(updated)
                .then(Mono.fromRunnable(() ->
                        exchange.getResponse()
                                .getHeaders()
                                .add("X-Trace-Id", traceId)
                ));
    }

    @Override
    public int getOrder() {
        return -100;
    }
}

网关使用响应式运行模型时,过滤器中应避免执行阻塞式数据库查询和文件操作。需要阻塞调用时,应明确隔离线程池和超时策略。

11. 为服务添加健康检查

添加 Actuator 依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

配置健康端点:

yaml 复制代码
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics
  endpoint:
    health:
      probes:
        enabled: true

生产环境不要无条件暴露所有管理端点。应通过网络隔离、认证和最小暴露原则保护管理接口。

12. 启动顺序与调用验证

开发环境可以按以下顺序启动:

text 复制代码
1. 配置中心
2. 注册中心
3. 用户服务和订单服务
4. API 网关

验证注册中心中是否出现业务实例,再通过网关调用:

bash 复制代码
curl -i "http://localhost:8080/api/orders/10001"

排查时可以分别绕过网关调用业务服务,以区分:

text 复制代码
网关路由问题
服务发现问题
业务服务问题
数据库或下游问题

五、常见问题与实践建议

1. Spring Boot 和 Spring Cloud 版本不兼容

表现可能包括:

  • 启动时报条件配置异常;
  • 类找不到或方法找不到;
  • 网关路由配置不生效;
  • 配置客户端启动失败;
  • 负载均衡组件行为异常。

解决方法:

  • 查官方兼容关系;
  • 使用 BOM 统一管理版本;
  • 删除手动指定的冲突传递依赖;
  • 清理本地缓存后重新构建;
  • 使用最小项目验证组件组合;
  • 升级时一次只改变一个主要版本维度。

2. Gateway 和传统 Servlet 依赖冲突

Spring Cloud Gateway 的 Server 实现通常基于 WebFlux 和 Reactor。若项目同时引入不匹配的 Web MVC 依赖,可能出现运行模型、自动配置和服务器选择冲突。

建议:

  • 网关项目保持依赖单一;
  • 不要把业务服务的全部依赖复制到网关;
  • 明确使用 WebFlux 还是 MVC 兼容实现;
  • 避免在响应式链路中执行阻塞代码;
  • 使用压测验证内存和线程行为。

3. 服务注册成功但调用不到

排查:

  • 服务名大小写和命名是否一致;
  • 注册中心地址是否正确;
  • 实例健康状态是否正常;
  • 服务端口是否能从调用方访问;
  • 容器网络和安全组是否放行;
  • 是否注册了错误的 IP;
  • 网关或客户端是否使用了缓存的旧实例列表。

"注册中心看得到服务"只说明注册动作成功,不代表调用链路一定可达。

4. 网关返回 404

常见原因:

  • Path Predicate 没有匹配;
  • 路径前缀与服务端 Controller 不一致;
  • 网关没有加载最新配置;
  • 路由顺序被更宽泛的规则抢先匹配;
  • 服务名或 URI 写错;
  • 网关上下文路径配置不一致。

建议先查看网关实际加载的路由,再分别验证网关和下游服务。

5. 配置中心修改后没有生效

可能原因:

  • 只修改了配置仓库,但没有触发刷新;
  • Bean 没有使用可刷新范围;
  • 配置键名称不一致;
  • 环境 profile 错误;
  • 远程配置被本地配置覆盖;
  • 配置客户端仍使用旧连接;
  • 刷新操作没有经过认证。

动态刷新不是默认适用于所有配置。对连接池、线程池和客户端实例,应设计安全的重新初始化策略。

6. 配置中心保存密码安全吗

配置中心不是天然的密钥管理系统。敏感配置应考虑:

  • 不直接提交明文密码;
  • 使用密钥管理服务;
  • 对配置仓库做访问控制;
  • 配置中心接口启用认证;
  • 传输过程使用 TLS;
  • 日志中脱敏;
  • 轮换密钥并保留审计记录。

加密后的配置也需要保护解密密钥,否则只是把问题转移了位置。

7. 网关是否应该做所有权限校验

网关可以做:

  • Token 格式检查;
  • 基础认证;
  • 全局黑名单;
  • 接口级别的入口权限;
  • 限流和审计。

业务服务仍然需要做:

  • 资源归属判断;
  • 细粒度业务授权;
  • 租户隔离;
  • 状态和操作条件校验。

如果只依赖网关,内部服务之间的调用、定时任务和旁路接口可能绕过关键授权。

8. 服务调用应该自动重试吗

不要默认重试所有失败:

  • 查询通常比较容易重试;
  • 创建操作必须有幂等键;
  • 支付和扣库存需要严格控制;
  • 超时不代表服务端没有执行;
  • 重试会增加下游压力;
  • 非幂等操作重复执行可能造成业务损失。

重试应设置最大次数、退避、随机抖动和总时间预算,并结合熔断和降级。

9. 注册中心不可用怎么办

需要提前决定:

  • 已注册实例缓存是否继续使用;
  • 新实例是否允许启动;
  • 是否允许已有请求继续;
  • 服务发现失败如何告警;
  • 是否切换到平台原生服务发现;
  • 注册中心恢复后如何重新同步。

注册中心是控制面还是请求链路的一部分,取决于客户端实现。无论如何,都要进行故障演练。

10. 配置中心不可用怎么办

不同服务可以采用不同策略:

  • 核心服务:配置读取失败则不启动;
  • 非核心服务:使用经过审计的本地只读配置;
  • 临时任务:失败后延迟重试;
  • 网关:保留最近一次合法路由配置;
  • 敏感配置:禁止使用不完整默认值。

关键是避免"使用错误默认值但服务看起来正常"。

11. 网关会不会成为单点瓶颈

网关承载所有外部请求,可能出现:

  • CPU 和内存成为瓶颈;
  • 过滤器执行时间过长;
  • 日志量过大;
  • 单个租户占满资源;
  • 下游慢请求长期占用连接;
  • 网关升级影响所有入口。

应部署多个网关实例,并通过负载均衡分流。过滤器要轻量,慢业务应下沉到服务层或异步处理。

12. 如何传递 traceId

建议在入口生成或接收 traceId,然后在:

text 复制代码
网关
  -> 服务 A
      -> 服务 B
          -> 消息消费者
              -> 数据库和外部系统

之间持续传播。日志中记录 traceId 和 spanId,指标中避免将每个 traceId 作为高基数标签,否则会增加监控系统压力。

六、进阶思考

1. 注册中心并不是唯一的服务发现方案

在 Kubernetes 环境中,Service、DNS 和平台控制面已经提供了服务发现能力。此时是否继续使用独立注册中心,需要评估:

  • 部署环境是否固定;
  • 是否需要跨平台发现;
  • 团队是否已有 Spring Cloud 生态;
  • 服务元数据和客户端负载均衡要求;
  • 运维复杂度和故障面。

架构选型应优先利用平台已有能力,避免重复建设控制面。

2. 配置中心和密钥中心应该分离

普通配置和敏感密钥的生命周期不同:

text 复制代码
普通配置:版本管理、灰度和回滚
密钥:访问控制、轮换、审计和最小暴露

可以让 Config Server 管理非敏感配置,将数据库密码、签名密钥和第三方凭证交给专用密钥管理服务,再通过运行环境注入。

3. 网关路由的动态发布

路由配置也需要版本管理:

text 复制代码
路由草稿
  -> 校验目标服务和权限
  -> 测试环境验证
  -> 小流量发布
  -> 监控错误率和延迟
  -> 全量发布
  -> 支持快速回滚

动态路由可以提高发布效率,但也增加了配置错误风险。每条路由都应有负责人、目标服务、权限要求和回滚记录。

4. 负载均衡和重试要一起设计

如果客户端在每个实例上都重试两次,一个包含三层调用的链路,实际请求数可能快速放大:

text 复制代码
上游 1 次请求
  -> 中间层 3 次尝试
      -> 下游每次再尝试 3 次

需要设置:

  • 整体请求截止时间;
  • 每层最大尝试次数;
  • 重试对象;
  • 重试退避;
  • 熔断阈值;
  • 可用实例过滤;
  • 区域和版本权重。

5. 灰度发布和版本路由

网关和服务发现可以支持按以下条件分流:

  • 用户;
  • 租户;
  • 请求头;
  • 流量比例;
  • 地域;
  • 服务版本。

但灰度发布涉及数据库结构、消息格式和缓存兼容。服务版本切换前,应保证新旧版本在兼容窗口内能够共同工作。

6. 服务间契约测试

服务调用方和提供方需要验证契约:

text 复制代码
请求字段是否兼容
响应字段是否兼容
状态码是否稳定
错误码是否可识别
幂等行为是否符合约定
超时和重试是否可接受

契约测试可以在服务独立发布的情况下提前发现接口破坏,避免只能依赖联调环境排查。

7. 最小化网关业务逻辑

网关适合做:

text 复制代码
路由
认证入口
限流
跨域
协议转换
基础审计
请求头处理

网关不适合做:

text 复制代码
订单状态机
库存扣减
复杂价格计算
跨服务事务
大量数据库查询
长时间文件处理

网关逻辑越复杂,发布风险和故障影响范围越大。

8. 观测基础设施本身

注册中心、配置中心和网关也需要被监控:

  • 注册实例数量;
  • 实例上下线频率;
  • 服务发现失败;
  • 配置读取失败;
  • 配置刷新失败;
  • 网关路由命中率;
  • 网关 4xx 和 5xx;
  • 下游响应延迟;
  • 熔断和限流次数;
  • 网关连接池和线程资源;
  • 配置版本和路由版本。

如果只监控业务服务,基础设施故障可能表现为大量业务接口同时失败,却找不到共同原因。

9. 优雅停机和流量摘除

服务发布或扩容时,应先停止接收新流量,再等待正在执行的请求完成:

text 复制代码
实例进入下线状态
  -> 注册中心不再分配新请求
  -> 网关和客户端刷新实例列表
  -> 等待存量请求结束
  -> 关闭连接池和线程池
  -> 进程退出

没有流量摘除的强制停止,可能导致请求中断、消息重复和事务未完成。

10. Spring Cloud 只是实现手段

一个成熟的微服务架构还需要:

  • 清晰的服务边界;
  • 稳定的 API 契约;
  • 数据所有权;
  • 事务和补偿设计;
  • 幂等和状态机;
  • 安全和权限;
  • 监控、日志和追踪;
  • 发布、灰度和回滚;
  • 故障演练。

框架可以减少样板代码,但不能替代架构决策和工程治理。

结论

Spring Cloud 中的注册中心、配置中心和网关,分别解决了服务地址动态变化、配置集中管理和统一 API 入口的问题。它们是微服务基础设施的重要组成部分,但并不能自动解决网络故障、数据一致性、权限、安全、重试和可观测性问题。

本文的重点可以归纳为:

  • 服务发现让调用方通过服务名找到动态实例;
  • 注册中心负责实例信息和健康状态,不负责业务一致性;
  • 配置中心可以统一管理多环境配置,但敏感密钥应交给专用密钥系统;
  • API 网关负责路由和横切治理,不应承载复杂业务逻辑;
  • Gateway、Config 和 Discovery 的依赖版本必须与 Spring Boot 对齐;
  • 服务调用需要明确超时、重试、幂等和降级;
  • 网关和服务内部都需要权限校验;
  • 配置和路由发布同样需要版本、审计、灰度和回滚;
  • Kubernetes 等平台已经提供部分服务发现能力,应避免重复建设;
  • 可观测性、优雅停机和故障演练是微服务基础设施的必要组成。

本文内容可进一步参考 Spring 官方资料:

下一篇可以继续学习分布式事务到底该怎么选,比较 Seata、消息最终一致性和本地消息表的适用边界。

相关推荐
xuxigifxfh1 小时前
HJ5 进制转换
java·开发语言·华为机考
denlaku1 小时前
JDK 12 新特性详解
java·开发语言
用户094248568031 小时前
第28章:JDK容器感知、cgroup与云原生JVM参数治理
java·jvm
鬼手点金1 小时前
opencode-全能开发者配置
java·linux·服务器·前端·javascript·学习·前向传播
Nebula_g1 小时前
JavaSE拓展:可变参数
java·开发语言·算法·安全·javase·可变参数
weixin_750330231 小时前
AI获客技术选型:基于OPC架构的智能营销方案实践
人工智能·架构·ai获客
念越1 小时前
初中语数英答题与竞赛平台
java·数据库·spring boot
知守观2 小时前
一个半天需求干了三天:代码腐化的五个信号与自查命令
java·后端·代码规范
新鲜势力呀2 小时前
PHP 日志系统实战:从排查线上故障困难到 ELK日志分析 + 链路追踪 + 实时监控完整架构方案
elk·架构·php