已完成服务注册发现与 API 网关

约 38 分钟 · 阶段:Java 微服务栈实战 · 能力:搭建注册中心、注册 order/inventory 并通过 Gateway 统一入口访问

为什么要学本章

上一章 能单点启动 order-service;多实例、多服务后客户端不能再记 IP:端口。 服务发现 解决「找谁」;API 网关 解决「从哪进、横切怎么做」。

学完你能
  • 对比 Eureka 与 Nacos 选型,并在 ShopFlow 落地 Eureka
  • 让 order-service、inventory-service 注册、续约与下线
  • 用 Spring Cloud Gateway 配置路由、谓词与过滤器
  • 透传鉴权头、重写路径,并用 Docker Compose 起本地最小栈
本章先记住 3 件事
  • 先注册中心、再业务服务、最后网关:实例列表为空时 Gateway 无法路由。
  • 网关只做横切:路由、鉴权头、限流;业务编排仍在 order-service(见 通信模式)。
  • 心跳 ≠ 强一致:注册表是近似视图,调用仍可能命中刚下线的实例(下一章 LoadBalancer + 超时重试)。

Eureka / Nacos 注册中心选型对比

服务注册发现:服务启动时向注册中心登记自己的 host、port、元数据;消费者从注册中心查询可用实例,而不是写死 URL。ShopFlow 本课选用 Netflix Eureka(Spring Cloud 一等公民);Nacos 在阿里生态与配置中心一体化场景更常见。

Eureka vs Nacos(ShopFlow 视角)

维度EurekaNacos
Spring Cloud 集成 eureka-server/client 成熟,本课 BOM 直接管理版本 spring-cloud-alibaba 另需对齐 BOM
配置中心 否(Config Server 另章) 注册 + 配置一体
健康模型 客户端心跳 + 服务端剔除 心跳 + 可选 CP 模式
本课选择 ✓ 教程默认,Compose 一键起 延伸阅读;生产可评估
硬编码地址
  • http://192.168.1.8:8081 写进 Feign
  • 扩容 / 换端口要改代码
  • 无法感知实例宕机
注册发现
  • 按 spring.application.name 查实例
  • LoadBalancer 轮询 / 随机
  • 心跳过期后剔除(最终一致)

order-service、inventory-service 注册与心跳

在 脚手架 基础上,为各 service 模块添加 Eureka Client(版本由 Spring Cloud BOM 管理),并新增独立模块 shopflow-discovery-server(或 Docker 镜像)作为注册中心。

Discovery Server(Eureka Server)

java
@SpringBootApplication
@EnableEurekaServer
public class DiscoveryServerApplication {
  public static void main(String[] args) {
    SpringApplication.run(DiscoveryServerApplication.class, args);
  }
}
yaml
# discovery-server / application-local.yml
server:
  port: 8761
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false

业务服务注册(Client)

xml
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
yaml
# order-service / application-local.yml
spring:
  application:
    name: order-service
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
  instance:
    prefer-ip-address: true
    lease-renewal-interval-in-seconds: 10

inventory-service 同理,spring.application.name 必须为 inventory-service(与 Gateway 路由 lb://inventory-service 一致)。

ShopFlow · 注册与心跳
sequenceDiagram
  participant O as order-service
  participant E as Eureka :8761
  participant I as inventory-service
  O->>E: REGISTER (8081)
  I->>E: REGISTER (8082)
  loop 每 10s
    O->>E: RENEW 心跳
    I->>E: RENEW 心跳
  end
  Note over E: 超时未续约则剔除实例

注册表是软状态;调用方仍可能遇到 部分失败,需客户端超时与重试。

Spring Cloud Gateway 路由与谓词过滤器

新增 shopflow-gateway 模块,依赖 spring-cloud-starter-gateway-server-webflux(Spring Cloud 2025 命名;WebFlux 网关为 ShopFlow 默认)。Gateway 从 Eureka 拉取实例,按路由(Route) = 谓词(Predicate)+ 过滤器(Filter)+ 目标 URI 转发流量。

最小路由:按路径前缀

yaml
# gateway / application-local.yml(Spring Cloud 2025 属性前缀)
server:
  port: 8080
spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      server:
        webflux:
          routes:
            - id: order-route
              uri: lb://order-service
              predicates:
                - Path=/api/orders/**
            - id: inventory-route
              uri: lb://inventory-service
              predicates:
                - Path=/api/inventory/**

lb:// 表示通过 Spring Cloud LoadBalancer 从 Eureka 选实例。若从旧版迁移,可用 spring-boot-properties-migrator 对照 spring.cloud.gateway.* → spring.cloud.gateway.server.webflux.*。

  • Path:路径匹配,最常用
  • Header、Method:按头或 HTTP 方法
  • After / Before:时间窗口(金丝雀可扩展)
  • AddRequestHeader:注入 traceId、租户 ID
  • StripPrefix:去掉路径前缀再转发
  • RequestRateLimiter:限流(advanced 章展开)

统一入口:鉴权头透传与路径重写

外部客户端只访问 https://api.shopflow.example(本地 :8080),Gateway 负责 TLS 终止(生产)、JWT 校验(安全与部署 章)与把用户身份传给下游。业务服务不应再对外暴露端口。

StripPrefix + 鉴权头透传

yaml
spring:
  cloud:
    gateway:
      server:
        webflux:
          routes:
            - id: order-api
              uri: lb://order-service
              predicates:
                - Path=/api/orders/**
              filters:
                - StripPrefix=1
                - AddRequestHeader=X-ShopFlow-User-Id, #{headers['X-ShopFlow-User-Id']}
# 外部: GET /api/orders/123  →  order-service: GET /orders/123

本章仅演示头透传占位;完整 OAuth2 Resource Server 在 Gateway 统一验签后,将 sub 写入 X-ShopFlow-User-Id 即可。勿在 Gateway 写「下单编排」——那是 order-service 职责。

先想 10 秒:为何 StripPrefix=1 后,下游 Controller 仍映射 /orders/{id}?

对外 API 带 /api 命名空间便于版本与 BFF 区分;对内 微服务 保持领域路径简洁。StripPrefix 去掉第一段 api,避免每个 Controller 重复写前缀。

本地 Docker Compose 最小依赖栈

本地联调推荐 Compose 起 Eureka;业务服务仍可用 IDE / Maven 启动便于断点。Gateway 与 order / inventory 需能访问 8761。

compose 片段

yaml
# demos/practice-02-discovery-gateway-lab/docker-compose.yml
services:
  discovery-server:
    image: eclipse-temurin:21-jre
    ports:
      - "8761:8761"
    # 实际项目: 挂载 discovery-server.jar 或 build 镜像
    environment:
      SPRING_PROFILES_ACTIVE: docker

推荐启动顺序

  1. docker compose up -d discovery-server(或本地跑 DiscoveryServerApplication)
  2. 打开 http://localhost:8761,确认 Eureka 控制台可用
  3. 启动 inventory-service(8082)、order-service(8081),在控制台看到 UP 实例
  4. 启动 api-gateway(8080),curl http://localhost:8080/api/orders/actuator/health(经路由转发)
验收要点
  • Eureka 上至少 2 个应用:ORDER-SERVICE、INVENTORY-SERVICE(大写为默认展示)
  • 经 Gateway 访问成功,直连业务端口仅用于调试
  • 停掉 inventory 后,Gateway 路由应在一段时间后不再转发到该实例

本章结论

每条对应上一节 h3。

  • 注册中心选型:ShopFlow 用 Eureka + Spring Cloud BOM;Nacos 适合需配置一体的阿里栈,另评估 BOM。
  • 注册与心跳:eureka-client + defaultZone;spring.application.name 即服务 ID;心跳续约后注册表才可靠。
  • Gateway 路由:Path 谓词 + lb://service-id;2025 属性前缀 gateway.server.webflux。
  • 统一入口:StripPrefix 与鉴权头透传;网关不做业务编排。
  • 本地栈:Compose 起 Eureka → 业务服务 → Gateway;按顺序验收控制台与 curl。

复习与自检

过关清单(勾选会保存在本浏览器)

0 / 5 已勾选

下一步:完成 Demo 与测验;继续 OpenFeign、负载均衡与服务间调用,实现 order 同步调用 inventory 预留库存。

动手练习

按「推荐启动顺序」搭 Eureka + order/inventory + Gateway,完成 8080 转发与实例剔除观察。

操作步骤

  1. 启动 discovery-server(8761),对照 register-with-eureka: false 解释为何 Server 不注册自己。
  2. 为 order/inventory 添加「业务服务注册」yaml,确认控制台 ORDER-SERVICE、INVENTORY-SERVICE 为 UP。
  3. 写入 Gateway 路由 yaml(Path=/api/orders/** → lb://order-service),curl http://localhost:8080/api/orders/actuator/health 经网关可达。
  4. 增加 StripPrefix=1,说明 /api/orders/... 到下游 /orders/... 的映射。

判断练习

  1. Gateway 先于 Eureka 启动最可能导致长时间 503。请写出正确启动顺序(① Eureka ② inventory ③ order ④ Gateway)。

    参考答案

    正确顺序:① → ③ → ② → ④(或 ① → ② → ③ → ④,业务服务顺序可互换,但必须在 Gateway 前)。Gateway 依赖注册表中有可用实例,否则 lb:// 无法解析。

  2. 路由写 uri: http://localhost:8081 而非 lb://order-service,且已有 2 实例。会有什么问题?改哪一行?

    参考答案

    无法负载均衡与故障转移,扩缩容需改 Gateway 配置。应改路由 uri 为 lb://order-service,由 LoadBalancer 从 Eureka 选实例。

Demo:Eureka + Gateway 本地栈

目录:demos/practice-02-discovery-gateway-lab/。验收:Eureka 2 应用 UP + 8080 curl health 成功 + 停 inventory 后观察剔除。

延伸学习