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综合练习与部署直觉

架构模式与术语

服务边界、API 网关、配置中心、注册发现。

30 分钟

架构模式与术语

这一课你将能做到

  1. 看懂一张典型微服务拓扑图,准确说出注册中心、网关、配置中心、熔断器各自的职责
  2. 用标准术语完整描述「用户提交一道题」从浏览器到判题完成的全链路
  3. 对任意一个业务交互,判断它该用同步调用还是异步消息,并给出理由

为什么要学

微服务的文档和面试里满屏 Gateway、Registry、Config Server、Circuit Breaker——术语对不齐,就没法和同事对齐需求,也没法答好架构题。更重要的是:拆微服务的第一步不是写代码,而是画架构图、定边界,这一课就是给你画图和说话的词汇表。

结合本站场景:假设把 CodeForge 拆成用户服务(user-service)、题目服务(problem-service)、判题服务(judge-service)。「提交一道题」会横跨三个服务,每种基础组件在哪个环节出场,学完本课你就能对着图讲清楚。

先建立图像

                          ┌────────────────────┐
   浏览器 / App ─────────► │  API Gateway:8080  │  统一入口:路由/鉴权/限流
                          └─────────┬──────────┘
              ┌─────────────────────┼─────────────────────┐
              ▼                     ▼                     ▼
       user-service:8081    problem-service:8082   judge-service:8083
              │                     │                     │
              ▼                     ▼                     ▼
           user_db              problem_db           judge_db + Redis

   【后台基础设施】
   Eureka:8761 注册中心 —— 各服务启动上报地址,调用方按名字查
   Config Server:8888 —— 配置集中存放,各服务启动拉取
   MQ(RabbitMQ/Kafka)—— 异步事件通道,如「判题结果已出」

一次「提交代码」的完整链路:

1. 浏览器 POST /api/submissions ──► Gateway 校验 JWT,路由到 judge-service
2. judge-service 落库「提交记录:PENDING」
3. judge-service 发事件「需要评测 submission#1024」到 MQ,立刻返回「已接收」
4. 评测 Worker 消费事件 → 同步调 problem-service 拿测试用例
5. 编译运行 → 写回结果 → 前端轮询/WebSocket 拿到判题结果

注意第 4 步是同步(必须拿到用例才能跑),第 3 步是异步(用户不必等判完)。

跟着做

步骤 1:先定服务间契约(DTO)

微服务第一原则:接口契约先行。在 judge-service 里定义它需要的数据结构:

package com.example.judge.dto;

/** 判题所需的题目信息——从 problem-service 获取 */
public record ProblemBrief(
        Long id,
        String title,
        int timeLimitMs,
        int memoryLimitMb,
        int caseCount
) {}

用 Java 17 的 record 做不可变 DTO,Jackson 2.15+ 原生支持,不用手写 getter/setter。

步骤 2:同步调用的直觉代码

第 4 步「拿测试用例」是同步调用。Spring 6 自带 RestClient,先手写一次理解本质(后面课程会换成 Feign):

package com.example.judge.remote;

import com.example.judge.dto.ProblemBrief;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;

@Component
public class ProblemClient {

    private final RestClient rest = RestClient.builder()
            .baseUrl("http://localhost:8082")   // 问题:地址写死了!
            .build();

    public ProblemBrief getProblem(Long id) {
        return rest.get()
                .uri("/problems/{id}/brief", id)
                .retrieve()
                .body(ProblemBrief.class);
    }
}

能跑,但暴露两个大问题:地址硬编码(扩容即失效)、没有超时与容错。这正是后面「注册中心 + Feign + 熔断」要逐个解决的。

步骤 3:异步消息的直觉代码

第 3 步「通知评测」先用 Spring 事件模拟解耦(生产换 MQ,代码结构不变):

package com.example.judge.event;

/** 领域事件:一道提交等待评测 */
public record SubmissionSubmittedEvent(Long submissionId, Long problemId, String language) {}
package com.example.judge.service;

import com.example.judge.event.SubmissionSubmittedEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;

@Service
public class SubmissionService {

    private final ApplicationEventPublisher publisher;

    public SubmissionService(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public Long submit(Long problemId, String language, String code) {
        Long submissionId = saveAsPending(problemId, language, code);
        // 发布事件:谁关心谁监听,本服务不再关心后续流程
        publisher.publishEvent(new SubmissionSubmittedEvent(submissionId, problemId, language));
        return submissionId;
    }

    private Long saveAsPending(Long problemId, String language, String code) {
        // 省略落库:INSERT INTO submissions(status='PENDING', ...) RETURNING id
        return 1024L;
    }
}

关键收益:提交接口只负责「受理」,判题快慢不影响用户拿到响应。换成 RabbitMQ 后,把 publisher.publishEvent(...) 换成 rabbitTemplate.convertAndSend(...),监听方挪到独立 Worker 进程,其余代码不动。

步骤 4:画出这条链路的时序图

拿纸画出:浏览器 → Gateway → judge-service → MQ → Worker → problem-service,并标注每条箭头是「同步 HTTP」「异步消息」还是「本地调用」。面试白板题常要求现场画架构图,画得出来才算真懂。

讲清楚要点

  1. 服务实例:一个运行中的进程,如 problem-service:8082。同一服务可跑多个实例,这就是「水平扩容」。
  2. 服务注册 / 发现:实例启动时向注册中心上报「我叫 problem-service,地址 10.0.0.5:8082」;调用方按名字查列表——本质是电话簿。
  3. API 网关:对外唯一入口。路由、鉴权、限流、跨域都在这层做,业务服务不重复造轮子。
  4. 配置中心:配置从代码剥离、集中管理,改配置不用重新打包,还能动态刷新。
  5. 同步调用(HTTP/RPC):发起方阻塞等结果,适合「下一步必须依赖返回值」的场景(拿测试用例)。
  6. 异步消息(MQ):发完就走,适合「结果晚点无所谓、需要削峰」的场景(判题、发通知)。
  7. 数据私有:每个服务用自己的库,别的服务想看数据只能调 API——这是服务自治的底线。
  8. 熔断降级:下游故障时快速失败或返回兜底数据,防止故障沿调用链扩散成雪崩。

你可能会懵的地方

  • API 网关和 Nginx 不是一回事吗? Nginx 是 L7 反向代理,擅长静态资源、TLS 终止、负载均衡;Gateway 是 Java 进程内的应用层网关,能用代码写鉴权、限流、灰度路由,并与注册中心/Spring Security 无缝集成。生产常见「Nginx 在前收 TLS,Gateway 在后做路由」。

  • 注册中心和 DNS 有什么区别? DNS 也是「名字→地址」,但记录静态、变更传播慢(TTL 缓存);注册中心是动态的:实例秒级上下线、带健康检查、可挂元数据(版本号、机房)。K8s 的 Service DNS 相当于内建了动态注册。

  • 什么时候该用 MQ,什么时候该同步? 判断标准:调用方是否需要立刻拿到结果。判题要几分钟 → 异步;渲染页面要题目详情 → 同步。乱用同步会把长任务挂死 HTTP 线程;乱用异步会让简单查询变成事件溯源噩梦。

  • BFF(Backend for Frontend)是什么? 面向特定前端聚合后端数据的中间层。例如给 Web 端一个 web-bff,一次请求聚合「用户信息 + 今日推荐题目」,避免前端连发 5 个请求。小项目可先由网关聚合,别急着加层。

  • 微服务还要不要 Redis? 要,而且更分散:判题结果缓存放 judge-service 侧,登录态放 user-service 侧。原则是缓存归属服务私有,禁止跨服务直连别人的 Redis。

错法对照

错法问题正解
把「用户」拆成用户读服务 + 用户写服务再加 CRUD 服务粒度过细,网络开销爆炸先按业务域粗拆,出现真实瓶颈再细化
服务 A 直连服务 B 的数据库查数据数据耦合,B 改表 A 崩B 提供 API,A 通过调用/事件获取
判题这类长任务用同步 HTTP 等结果线程占满,超时大面积失败受理后立即返回,MQ 通知 Worker
所有服务共用一个 Redis/一台 MySQL基础设施耦合,故障连带缓存/存储随服务走,故障域隔离
把「注册中心」说成「负载均衡器」沟通歧义注册中心管地址列表,负载均衡管选哪个实例

卡点急救

  1. Spring 事件两个 Bean 互相收不到:@EventListener 的监听者必须和发布者在同一个 Spring 容器(同进程);跨进程直接上 MQ,不要硬绕。
  2. RestClient 调用报 404:占位符按顺序填充,uri("/problems/{id}/brief", id);确认目标服务真实暴露的路径与端口。
  3. 以为发布事件就是异步,结果接口还是慢:ApplicationEventPublisher 默认同步发布(同线程执行监听器)。要真异步需在监听方法加 @Async 并开启 @EnableAsync,或直接换 MQ。

面试常问

  • 同步调用和异步消息怎么选型? 看三点:实时性(要结果→同步)、削峰需求(大流量→异步)、最终一致性容忍度(可容忍→异步)。举「判题异步、查题目同步」并说明线程资源角度的原因,就是满分答案。

  • 微服务里一次请求变慢,你怎么定位? 先看网关日志拿 trace-id → 链路追踪(Zipkin/Jaeger)看每一跳耗时 → 定位是哪次跨服务调用或哪条 SQL 慢 → 结合该服务的指标(P99、连接池、GC)确认根因。这正是可观测性一课的内容。

  • 什么是服务雪崩?怎么防? 下游变慢 → 调用方线程被占满 → 线程池耗尽 → 上游也超时 → 层层传导全站瘫痪。防线组合:调用方设超时 → 熔断快速失败 → 降级兜底 → 必要时限流保护自身。

自测三问

  1. 注册中心、网关、配置中心各解决什么问题?各用一个比喻。
  2. 「判题」为什么该走异步而不是同步 HTTP?
  3. 服务 B 想读服务 A 的用户表,正确做法是什么?

小结一句话

微服务架构 = 按业务拆服务 + 注册中心寻址 + 网关统一入口 + 配置中心管配置 + 同步/异步混合通信 + 熔断兜底;术语对齐后,后面每课认领一个组件。

延伸

  • Spring Cloud 官方参考文档(总览)
  • 下一课:服务注册与 Eureka——让服务按名字互相找到。
加载本节练习…
← 上一课微服务与 Spring Cloud下一课 →服务注册与 Eureka