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

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

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

学习

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

更多

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

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
计算机科学入门/课时/系统设计入门
RawBlame
课时目录12 节

计算机科学入门

  1. 01计算机如何工作
  2. 02数据结构
  3. 03算法与搜索排序
  4. 04复杂度分析
  5. 05离散数学基础
  6. 06操作系统概念
  7. 07数据库原理
  8. 08计算机网络
  9. 09系统设计入门
  10. 10安全基础
  11. 11设计模式与 UML
  12. 12综合练习

系统设计入门

负载均衡、缓存、CDN、REST。

35 分钟

系统设计入门

这一课你将能做到

  1. 用「用户量 → QPS → 存储/带宽」的思路对一个业务做容量估算(数字在正确数量级内)
  2. 画出无状态服务 + 负载均衡 + 缓存 + 数据库的典型可扩展架构,并解释每一层解决的瓶颈
  3. 识别缓存穿透、击穿、雪崩,设计出符合 REST 风格的资源型 API

为什么要学

会写功能和能设计系统是两级台阶。当判题平台从课程作业走向真实运营,问题会变:

  • 一天 10 万注册用户、晚上 8 点集中刷题,需要多少台应用服务器?
  • 单库扛不住提交记录写入怎么办?判题结果查询慢怎么办?
  • 一道热门题被几十万用户同时查看,如何不把数据库打穿?

系统设计面试考的不是背诵架构图,而是在约束下做权衡的过程:先估算量级,再识别瓶颈,然后逐层加组件,每个选择讲出取舍。本课给你这套入门框架,并始终围绕判题平台推演。

先建立图像

可扩展架构的「标准分层」
                    用户
                     │
              ┌──────▼──────┐
              │ CDN         │  静态资源就近分发(题目图片、前端打包文件)
              └──────┬──────┘
              ┌──────▼──────┐
              │ 负载均衡 LB  │  Nginx/LVS:健康检查,按策略分发
              └──┬───┬───┬──┘
          ┌──────▼┐ ┌▼────┐ ┌▼────────┐
          │应用实例1│ │实例2│ │实例3     │  无状态:会话放 Redis,随意增删
          └──┬──┬──┘ └┬────┘ └┬────────┘
             │  │     │       │
      ┌──────▼┐ ┌▼────────────▼─────┐
      │ Redis │ │ 消息队列 MQ         │  削峰填谷:提交高峰先入队
      │ 缓存  │ └──────────┬──────────┘
      └───────┘     ┌──────▼──────┐
              ┌─────▼────┐  ┌─────▼─────┐
              │ MySQL主库 │  │ 判题机集群  │  主从复制、读写分离
              │ +从库     │  │ (隔离容器)  │
              └──────────┘  └───────────┘

核心思路:能水平扩展的层就不要垂直硬扛;状态集中到少数组件(Redis/DB),应用层保持无状态;峰值用队列削平。

跟着做

步骤 1:给判题平台做容量估算

容量估算靠「假设 + 乘除」,不需要精确,数量级对就有价值。常用备忘数字:

量级速查:1万=10⁴  100万=10⁶  1亿=10⁸
一天 = 86400 秒 ≈ 10⁵ 秒
QPS = 日请求量 / 86400;峰值 QPS 常按日均的 3-10 倍估(二八效应)

推演过程(写在纸上的标准四步):

  1. 用户规模:10 万注册用户,日活 20% = 2 万人;每人每天刷 20 道题
  2. 请求量:页面浏览 + 提交 + 轮询,约每人每天 200 次请求 → 日请求 400 万 → 平均 QPS ≈ 46;晚高峰按 6 倍 → 峰值约 300 QPS
  3. 应用层:单台 4 核 Spring Boot 实例按经验支撑 100-300 QPS(取决于接口轻重),取 2 台 + 冗余 → 3 台
  4. 存储量:每份提交代码平均 2KB,日新增 40 万份 × 2KB ≈ 800MB/天,一年约 290GB;题目与用户数据小;Redis 存热数据 5GB 足够
  5. 带宽:单次响应平均 5KB,峰值 300 QPS → 1.5MB/s ≈ 12Mbps,很小;代码提交上传另算

做完估算你会发现瓶颈往往不在「算」而在「写」:提交高峰写入 MySQL 和判题机产能才是约束,这直接引出后面的队列与判题机集群设计。

步骤 2:无状态服务与负载均衡

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SessionDemo {

    private final StringRedisTemplate redis;

    public SessionDemo(StringRedisTemplate redis) {
        this.redis = redis;
    }

    // ❌ 反模式:把登录态/计数器存在实例内存里 → 用户下次落到另一台机器就「掉线」
    // ✅ 正解:状态全部放 Redis,任何实例都能处理任何请求(无状态)
    @GetMapping("/me")
    public String me(@RequestHeader("X-Token") String token) {
        return redis.opsForValue().get("login:" + token);
    }
}

负载均衡策略知道三种即可:轮询(均匀)、加权(机器配置不同)、最少连接(长连接场景);健康检查自动摘除故障实例。因为应用无状态,扩容只需启动新实例并注册到 LB,滚动发布也零停机——这是云原生弹性的基础。

步骤 3:缓存三大故障及对策

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;

@Service
public class ProblemCacheService {

    private final StringRedisTemplate redis;

    public ProblemCacheService(StringRedisTemplate redis) {
        this.redis = redis;
    }

    public String getProblem(Long id) {
        String key = "problem:" + id;
        String value = redis.opsForValue().get(key);
        if (value == null) {
            value = loadFromDb(id);                        // 回源
            if (value != null) {
                redis.opsForValue().set(key, value, Duration.ofMinutes(10));
            } else {
                // 防穿透:不存在的 key 也缓存空值(短 TTL),或用布隆过滤器前置拦截
                redis.opsForValue().set(key, "", Duration.ofMinutes(1));
            }
        }
        return value;
    }

    private String loadFromDb(Long id) { return null; }
}

三个概念务必分清,面试高频:

  • 缓存穿透:查的是根本不存在的数据,每次都打到数据库。对策:空值缓存、布隆过滤器
  • 缓存击穿:某个热点 key 恰好过期,大量请求同时回源。对策:互斥锁只放一个请求回源、热点 key 逻辑不过期
  • 缓存雪崩:大批 key 同时过期或 Redis 宕机,回源洪峰压垮数据库。对策:TTL 加随机抖动、Redis 高可用集群、多级缓存与限流降级

步骤 4:设计 REST 风格 API

资源用名词,动作交给 HTTP 方法(以判题平台为例)
GET    /api/problems            题目列表(分页 ?page=1&size=20)
GET    /api/problems/1024       题目详情
POST   /api/problems            管理员新建题目
PUT    /api/problems/1024       全量更新
DELETE /api/problems/1024       删除
POST   /api/problems/1024/submissions   用户提交代码(提交是「题下的资源」)
GET    /api/users/me/submissions        我的提交记录

配套约定
  状态码语义化:200 成功 / 201 已创建 / 400 参数错 / 401 未登录 /
               403 无权限 / 404 不存在 / 429 触发限流 / 500 服务端错
  版本与兼容:/api/v1/...;新增字段不破坏旧客户端
  幂等性:GET/PUT/DELETE 天然幂等;POST 用唯一请求 ID 防重复提交

讲清楚要点

  1. 垂直扩展(加 CPU/内存)有上限,水平扩展(加机器)靠无状态:数据库这类有状态组件扩展难,所以它前面要加缓存、读写分离、分库分表;把「贵的有状态部分」做小,是所有架构演进的主线。
  2. 读写分离与分片是两回事:主从复制解决「读多写少」(从库承担查询),分库分表解决「单库容量/写入瓶颈」(数据按 key 拆散),两者经常组合使用。
  3. MQ 解决三个问题:削峰(提交高峰排队慢慢判)、解耦(提交后触发统计、通知等后续动作互不阻塞)、异步(用户不必在线等结果,配合回调/推送);代价是引入消息可靠性与顺序性问题。
  4. 缓存是性能组件也是事故源:要管理好 TTL、一致性(先更新库再删缓存的 Cache-Aside 最常用)、热点与大 key;缓存不能替代数据库,它丢了要能从库重建。
  5. CDN 和多级缓存本质相同:让数据离用户/计算更近;判题平台的题目图片、富文本、前端静态资源上 CDN,回源压力和延迟同时下降。

你可能会懵的地方

  • 容量估算老算错怎么办? 初学阶段先保证「流程正确」:列假设、拆环节、一步步乘,最后做合理性检查(结果是否离谱)。数字偏差 2-3 倍完全可以接受,面试官考的是思路;多练 5-10 个案例就有手感。

  • 到底什么时候才需要微服务? 单体 Spring Boot 对多数团队是更优选(部署简单、调试直接、没有分布式复杂度)。当团队规模、模块边界、独立扩缩容诉求都明确后,再按业务域拆分(如把判题执行独立成服务),拆早了是负担。

  • 缓存和数据库一致性怎么保证? 常用 Cache Aside:更新数据库后删除缓存(而不是更新缓存),下次读取自然回源重建。强一致成本高且通常不必要;延迟双删、消息通知删缓存是加强手段。

  • 判题机产能不足,加机器就够了吗? 先看瓶颈:任务分发是否均衡、容器启动开销是否摊薄(预热池)、单任务平均耗时(编译是否可复用、测试用例并行)。调度策略优化常常比加机器收益大。

  • 系统设计面试要画代码吗? 不写业务代码,但要能写接口定义、SQL 表结构草稿、估算算式,并在白板上边画架构边讲权衡。沟通条理性和「为什么不用另一个方案」比图本身重要。

错法对照

错法问题正解
不估算直接谈技术选型方案与规模脱节,可能过度设计先算 QPS/存储/带宽量级,再选组件
应用内存里放会话与本地状态扩容/负载均衡后行为错乱状态外移 Redis,实例无状态
所有 key 设同一个固定 TTL同时过期引发雪崩TTL = 基础值 + 随机抖动
同步调用所有下游且不降级一个慢服务拖垮整条链路超时、熔断、限流、降级四件套
URL 里放动词 /getProblem?id=1不符合资源模型,路由膨胀名词资源 + HTTP 方法语义

卡点急救

  1. 高峰期限提交失败:先确认入口 QPS 与后端实际产能,入口侧排队(MQ 削峰)+ 客户端排队提示,比让请求直接打满数据库好。
  2. 数据库慢查询突然增多:检查是否缓存集体失效或 Redis 故障;先限流保护数据库,再逐步恢复缓存,避免雪崩二次击垮系统。
  3. 扩容后性能没提升:检查瓶颈是否在有状态组件(数据库连接、行锁、单线程消费者);无状态层才能线性扩展,有状态层要单独优化。

面试常问

  • 设计一个支持万人同时使用的在线判题系统,说说你的思路。 先估算(日活、峰值 QPS、存储量),再给分层架构:CDN + LB + 无状态应用集群 + Redis 缓存(题目/会话/排行榜)+ MySQL 主从(提交记录分片)+ MQ 削峰 + 判题机容器集群(资源隔离、任务拉取、结果回调)。主动指出瓶颈在写入与判题产能,并讲一致性与限流降级。

  • 缓存穿透、击穿、雪崩的区别与应对? 穿透=数据不存在(空值缓存/布隆过滤器);击穿=单热点过期(互斥回源/逻辑过期);雪崩=大面积失效或缓存宕机(随机 TTL、集群高可用、限流降级)。

  • 为什么应用层要无状态?怎么做? 无状态才能随意水平扩展、故障转移和滚动发布。把会话、计数器、锁等状态统一放 Redis/数据库,实例只保留可随时丢弃的局部状态。

自测三问

  1. 给你「日活 50 万、每人每天提交 5 次」,能否 10 分钟内算出 QPS、年存储量和应用实例数?
  2. 判题平台晚高峰写入突增 20 倍,链路上哪几个组件依次会成为瓶颈?
  3. Cache Aside 为什么是「删缓存」而不是「更新缓存」?并发下还有什么残留问题?

小结一句话

系统设计 = 先估算量级、再沿着「无状态化、加缓存、队列削峰、拆分有状态瓶颈」的路径逐层扩展,每一步都讲得出代价与取舍。

延伸

  • 《System Design Interview》(Alex Xu)官方站点——容量估算与组件权衡的实战入门,Vol1/Vol2
  • GitHub:System Design Primer(英文高星指南)——结构化的系统设计知识库
  • Google SRE Book(在线免费)——可用性、容量规划与应急工程的权威实践
  • 下一课:安全基础——系统能扩展之后还要保证它不被攻破:CIA 三原则、密码学体系与常见 Web 漏洞防御。
加载本节练习…
← 上一课计算机网络下一课 →安全基础