CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Spring Cloud 微服务入门/课时/综合练习与部署直觉
RawBlame
课时目录10 节

Spring Cloud 微服务入门

  1. 01微服务与 Spring Cloud
  2. 02架构模式与术语
  3. 03服务注册与 Eureka
  4. 04Spring Cloud Gateway
  5. 05Cloud Config 配置中心
  6. 06OpenFeign 服务调用
  7. 07熔断与容错
  8. 08Spring Data 扩展
  9. 09Micrometer 与可观测性
  10. 10综合练习与部署直觉

综合练习与部署直觉

两服务 + 网关 Demo;K8s/Docker 下一步。

45 分钟

综合练习与部署直觉

这一课你将能做到

  1. 按正确顺序把 5 个进程完整跑起来,并用端到端验收清单验证注册、路由、Feign、熔断、配置、指标全链路
  2. 为每个服务编写多阶段 Dockerfile,用 docker-compose 一键拉起整套微服务
  3. 建立 K8s 部署的正确直觉:Deployment/Service/Ingress 各管什么、K8s DNS 和 Eureka 什么关系

为什么要学

前面八课的知识点都是「单件」:注册中心一个、网关一个、Feign 一套。但面试官问「你的微服务怎么部署的」「某个服务挂了流量怎么办」,考的是把单件串成系统的能力。本课做三件事:

  1. 串——5 个进程按依赖顺序拉起,走一遍完整验收,查漏补缺;
  2. 容器化——java -jar 手动起 5 个窗口的方式在同事电脑、测试环境都复制不了,Docker 镜像 + compose 让「一键起全套」成为可能;
  3. 给方向——真实生产的容器编排叫 Kubernetes,本课不深入 K8s,但会把核心概念与已学知识对上号,让你后面学 K8s 时不是从零开始。

贯穿项目仍是 CodeForge 拆分:eureka-server(8761)、config-server(8888)、api-gateway(8080)、user-service(8081)、problem-service(8082)、judge-service(8083),父工程 com.example:cloud-demo。

先建立图像

启动顺序拓扑

① eureka-server :8761  ←── 一切的前提,最先起
        ▲ 注册
② config-server :8888  ←── 业务服务要从它拉配置
        ▲ 拉配置
③ user-service :8081   ④ problem-service :8082   ⑤ judge-service :8083
        │  client 们起完成后注册进 Eureka
        ▼
⑥ api-gateway :8080    ←── 最后起(路由虽然不依赖服务先在,但人验收要看全链路)

记忆口诀:先注册中心,再配置中心,然后业务服务,最后网关。 业务服务启动时会连 Eureka、连 Config,两者不在就报错重试;网关路由到不了服务只会 503,不会启动失败,所以顺序上最宽容。

生产部署视图(本课终点)

本地开发:  5 个 IDEA 窗口 / mvn spring-boot:run
     ↓ 容器化
测试环境:  每服务一个镜像 + docker-compose.yml 一键编排
     ↓ 编排升级
生产环境:  K8s:Deployment(多副本) + Service(稳定地址) + Ingress(外部入口)

跟着做

步骤 1:按顺序启动 5 个进程

# 终端 1:注册中心
mvn -pl eureka-server spring-boot:run

# 终端 2:配置中心(等 eureka 起完,约 15 秒)
mvn -pl config-server spring-boot:run

# 终端 3~5:业务服务(等 config 起完)
mvn -pl user-service spring-boot:run
mvn -pl problem-service spring-boot:run
mvn -pl judge-service spring-boot:run

# 终端 6:网关
mvn -pl api-gateway spring-boot:run

打开 http://localhost:8761,Eureka 控制台应看到 4 个 client(user、problem、judge、gateway)全部 UP。

步骤 2:端到端验收清单

逐条执行,每条对应前面某一课的知识点——任何一条失败,回到对应课程排查:

#验收项命令/操作验证的课程
1注册中心 4 个 client 全 UP打开
http://localhost:8761
Eureka
2经网关访问题目接口通curl http://localhost:8080/api/problems/1网关路由
3业务服务从配置中心拉到了值curl http://localhost:8082/features配置中心
4跨服务 Feign 调用通curl http://localhost:8081/dashboard/1001/problem/1OpenFeign
5停掉 problem-service 后学习面板仍可用(返回兜底文案且毫秒级响应)停服务后重复第 4 条熔断降级
6重启 problem-service,熔断器回到 CLOSEDcurl http://localhost:8081/actuator/circuitbreakers熔断状态机
7配置热更新生效改 config-repo 值后 curl -X POST .../actuator/refresh配置刷新
8指标端点有数据、日志带 traceIdcurl http://localhost:8083/actuator/prometheus;grep traceId可观测性

步骤 3:给每个服务写多阶段 Dockerfile

在每个服务的模块目录下建 Dockerfile(以 problem-service 为例):

# ============ 第一阶段:构建 ============
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml .
COPY src ./src
RUN mvn -B package -DskipTests

# ============ 第二阶段:运行 ============
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=builder /build/target/problem-service-1.0.0.jar app.jar
EXPOSE 8082
ENTRYPOINT ["java", "-jar", "app.jar"]

要点:

  • 多阶段构建:构建层带着 Maven 和全部依赖(1GB+),运行层只有 JRE + jar(约 200MB),镜像又小又干净;
  • 分层缓存:先 COPY pom.xml 再 COPY src,改业务代码时 Maven 依赖层命中缓存,构建提速明显;
  • 运行镜像用 17-jre 而不是 JDK(运行不需要编译器)。

在父工程目录构建(-f 指向各模块):

docker build -t cloud-demo/problem-service:1.0.0 -f problem-service/Dockerfile problem-service/
docker build -t cloud-demo/user-service:1.0.0   -f user-service/Dockerfile user-service/
# 其余同理

步骤 4:docker-compose 编排整套系统

父工程根目录 docker-compose.yml:

services:
  eureka-server:
    image: cloud-demo/eureka-server:1.0.0
    ports: [ "8761:8761" ]

  config-server:
    image: cloud-demo/config-server:1.0.0
    ports: [ "8888:8888" ]
    depends_on:
      - eureka-server
    environment:
      EUREKA_CLIENT_SERVICEURL_DEFAULTZONE: http://eureka-server:8761/eureka/

  user-service:
    image: cloud-demo/user-service:1.0.0
    depends_on:
      - eureka-server
      - config-server
    environment:
      EUREKA_CLIENT_SERVICEURL_DEFAULTZONE: http://eureka-server:8761/eureka/
      SPRING_CONFIG_IMPORT: optional:configserver:http://config-server:8888

  problem-service:
    image: cloud-demo/problem-service:1.0.0
    depends_on:
      - eureka-server
      - config-server
    environment:
      EUREKA_CLIENT_SERVICEURL_DEFAULTZONE: http://eureka-server:8761/eureka/
      SPRING_CONFIG_IMPORT: optional:configserver:http://config-server:8888

  judge-service:
    image: cloud-demo/judge-service:1.0.0
    depends_on:
      - eureka-server
      - config-server
    environment:
      EUREKA_CLIENT_SERVICEURL_DEFAULTZONE: http://eureka-server:8761/eureka/

  api-gateway:
    image: cloud-demo/api-gateway:1.0.0
    ports: [ "8080:8080" ]
    depends_on:
      - user-service
      - problem-service
    environment:
      EUREKA_CLIENT_SERVICEURL_DEFAULTZONE: http://eureka-server:8761/eureka/
docker compose up -d      # 一键起全套
docker compose ps         # 看状态
docker compose down       # 一键停

三个关键机制:

  1. 服务名即主机名:compose 会建内部网络,容器之间直接用 http://eureka-server:8761、http://config-server:8888 互访——服务名就是 DNS 名;
  2. 环境变量覆盖 yml:EUREKA_CLIENT_SERVICEURL_DEFAULTZONE 覆盖 application.yml 里的 eureka.client.service-url.defaultZone(Spring Boot 宽松绑定把下划线映射为点),镜像不变、配置随环境变,这正是十二因子的做法;
  3. depends_on 只管启动顺序不管就绪:config-server 进程起了不代表 8888 能响应,业务服务可能要靠 Spring 的重试机制连上——所以配置里用了 optional: 前缀 + 良好的重试,别指望 depends_on 解决一切。

步骤 5:踩一遍「容器内 localhost」陷阱(必踩教育坑)

如果 user-service 的 yml 里写死 configserver:http://localhost:8888,容器化后必然连不上——容器里的 localhost 是容器自己,不是宿主机也不是别的容器。修改方法只有两种:

  1. 环境变量覆盖(上面 compose 的做法)——推荐;
  2. 配置文件里用服务名:http://config-server:8888(compose 网络内有效)。

把 localhost 从所有服务间调用的配置里清除干净,是容器化最大的思维转换。

步骤 6:建立 K8s 直觉(只建立直觉,不展开)

你在 Spring Cloud 学的K8s 里对应物关系说明
Eureka 注册中心K8s Service + 集群 DNS(problem-service.default.svc.cluster.local)K8s 自带服务发现,同功能二选一
api-gateway 路由Ingress / Service外部流量入口可交给 Ingress
手动 docker run 多副本Deployment 副本数replicas: 3 声明 3 个实例,K8s 自动维持
Eureka 心跳摘除故障实例K8s liveness/readiness 探针探针失败自动重启/摘流
Config ServerConfigMap / Secret配置进 K8s 原生对象,可挂载或注入环境变量
手动改 yml 重启发布滚动更新(Rolling Update)新版本逐个替换旧 Pod,全程不掉线

结论分两种场景:部署在 K8s 里,服务发现可直接用 K8s DNS,Eureka 可以退役(配置也多交给 ConfigMap);混合部署或需要应用层健康状态感知时,保留 Eureka。两种都成立,面试说清「为什么选」比「选哪个」重要。

步骤 7:优雅停机(上线质量的细节分)

进程被 kill 时,正在处理的请求不该被腰斩:

server:
  shutdown: graceful          # 收到停机信号后停止接新请求,等在处理请求完成
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

配合 compose/K8s 的 SIGTERM(而不是 kill -9),发布时用户无感。K8s 滚动更新 + 就绪探针 + 优雅停机,就是「发布中零请求失败」的三件套。

讲清楚要点

  1. 启动顺序的因果:业务服务启动要做两件依赖别人的事——连 Config 拉配置、连 Eureka 注册;所以注册中心和配置中心必须先活。网关不注册到别人的下游,最后起只是习惯,不是硬约束。
  2. 多阶段 Dockerfile 的价值:构建环境(Maven、源码、依赖缓存)与运行环境(JRE、jar)彻底分离,镜像小、攻击面小、拉取快;「先拷 pom 后拷 src」利用 Docker 分层缓存加速重复构建。
  3. compose 的三件事:建内部网络(服务名互访)、编排依赖(depends_on 控制启动顺序)、注入环境变量(同一镜像适配不同环境)。它管不了「就绪」和「自愈」,那是 K8s 的活。
  4. 容器网络是思维分水岭:进了容器,localhost 就是你自己。服务间地址一律用「服务名」或环境变量注入,「IP + localhost 写死在 yml」是反模式。
  5. K8s 与 Spring Cloud 是部分重叠关系:K8s 在平台层解决了服务发现、配置、负载均衡、自愈;Spring Cloud 在应用层提供了跨平台的等价物。上了 K8s 后 Eureka/Config 可以简化,但 Feign 的声明式调用、Resilience4j 的熔断降级、Micrometer 的可观测依然有价值——平台管不了业务降级。
  6. 发布质量三件套:优雅停机(server.shutdown=graceful)+ 就绪探针(没准备好不接流量)+ 滚动更新(逐个替换副本)。三者合起来才能做到发布期间用户零失败。

你可能会懵的地方

  • docker compose 起来后服务互相连不上? 按顺序查:①容器都在 docker compose ps 且没有重启循环(docker compose logs 服务名);②地址用的是服务名不是 localhost;③环境变量名拼对没有(EUREKA_CLIENT_SERVICEURL_DEFAULTZONE 是全大写下划线);④docker compose down 后旧网络残留,重新 up 一次。

  • depends_on 了为什么服务还是启动失败? depends_on 只保证「容器的启动顺序」,不保证「应用已就绪」。config-server 容器起 5 秒,Tomcat 可能还没监听。解法:业务服务的 Config 连接配重试(spring.cloud.config.retry)或 optional: 前缀;K8s 用 readiness 探针才是正解。

  • 上了 K8s,前面学的 Eureka、Config、Gateway 白学了吗? 没有。①面试和存量项目里 Eureka 依然是主流方案之一;②K8s 的 Service 只做「服务发现 + 负载均衡」,业务熔断降级(Resilience4j)、声明式调用(Feign)、可观测(Micrometer)K8s 都不管,那些能力照样要;③很多公司 Eureka 和 K8s 长期共存(跨集群、IDC 混部)。

  • 本地开发也需要 Docker 吗? 分层看:中间件(MySQL/Redis/Mongo)用 Docker 一行起最方便;业务服务用 IDEA 直接跑——改代码热重启快;整套联调或给别人演示时才 docker compose up。全容器化开发在 Spring Boot 的重构循环里太慢。

  • 微服务数量多了,compose 管不动怎么办? compose 的定位是单机编排,超过 10 来个服务就该上 K8s(或多机 compose swarm)。判断标准不是服务数量,而是「是否需要多机、自愈、弹性伸缩」——需要,就直接上 K8s,别在 compose 上过度投资。

错法对照

错法问题正解
yml 里写死 localhost:8888 直接做镜像容器内 localhost 是容器自身,必然连不上环境变量注入地址,或用 compose 服务名
全部依赖 depends_on 保证可用性只保证启动顺序,不保证就绪应用侧重试/optional + 生产用 K8s 探针
运行镜像用完整 JDK + 全量构建产物镜像 1GB+,拉取慢攻击面大多阶段构建,运行层只用 JRE
发布直接 kill -9在途请求腰斩,用户收到 502graceful shutdown + SIGTERM
Eureka 和 K8s DNS 同时启用且都注册双份发现路径,摘流行为不一致难以排查明确主发现机制,另一侧降级或退役

卡点急救

  1. docker compose up 后某服务一直重启:docker compose logs --tail=100 服务名 看启动日志——九成是连不上 Eureka/Config(地址写错或对应服务没起),修正环境变量后 docker compose up -d --force-recreate 服务名。
  2. 宿主机 curl 不通容器端口:确认 compose 里写了 ports: ["8080:8080"](映射了才通);没映射的服务只能进容器网络访问(docker compose exec user-service curl ...)——这也正是「网关要映射、业务服务可不暴露」的安全实践。
  3. 镜像构建时 jar 找不到:第一阶段产物路径必须与 pom 的 finalName/版本一致;确认模块版本号是 1.0.0,或者干脆在 Dockerfile 里用通配再 COPY 重命名。

面试常问

  • 描述一个微服务从开发到上线的完整流程。 ①代码与配置分离(配置进 Config/Git);②CI 流水线:编译、测试、构建 Docker 镜像(多阶段构建)并推送镜像仓库;③部署:K8s Deployment 声明副本数,滚动更新逐个替换 Pod;④就绪探针通过后才接入流量,liveness 探针故障自愈;⑤发布期间优雅停机保证在途请求完成;⑥可观测兜底:Prometheus 指标告警 + 日志 traceId 排查。能按「构建 → 部署 → 流量 → 回滚」四段讲清楚即可。

  • K8s 的服务发现和 Eureka 什么关系?上 K8s 后还需要注册中心吗? K8s Service + 集群 DNS 提供平台层服务发现:Pod 伸缩/重建时 Service 自动更新 Endpoint,天然免维护。上 K8s 后自建系统一般退役 Eureka 用 K8s DNS;但跨集群、虚机混合部署、或需要应用层心跳感知时保留 Eureka 也有价值。Spring Cloud Kubernetes 项目能把 K8s API 桥接成 DiscoveryClient,让既有代码平滑迁移。答案要点:不是替代关系而是层次关系,按部署形态选。

  • 滚动发布怎么做到用户无感? 三件套:①优雅停机——旧实例收到 SIGTERM 后停止接新请求、等在途请求处理完再退出(server.shutdown=graceful);②就绪探针——新实例真正可服务后才被 Service 挂上流量;③滚动更新——Deployment 控制新旧副本平滑交替,任何时刻都有健康实例承接。再叠加发布前的镜像可回滚(tag 化),出问题一键回到上一版本。

自测三问

  1. 五个进程的正确启动顺序是什么?为什么业务服务必须在 Config 之后?
  2. compose 里服务之间怎么互相访问?什么机制让服务名变成可解析的地址?
  3. K8s 的哪两个资源分别对应「多副本管理」和「稳定访问地址」?

小结一句话

把注册、配置、网关、Feign、熔断、可观测串成一条能跑通、能验收、能容器化的完整链路——之后进 K8s 的世界时,你会发现每个新概念都有一个你已经学过的原型。

延伸

  • Spring Boot Docker 官方指南(多阶段构建)
  • Kubernetes 官方文档:Deployment 概念
  • 本 track 到此完结。实际下一步方向:①把 compose 工程推上 GitHub 作为面试作品;②学 Docker Essentials 补容器细节;③进入消息中间件(RabbitMQ/Kafka),给判题队列换成真正的异步削峰。
加载本节练习…
← 上一课Micrometer 与可观测性