Chapter 02

扩展性设计
水平与垂直扩展

从单台服务器到分布式集群,理解系统扩展的本质。
掌握无状态设计、Session 管理与数据库扩展策略。

从单机到集群的演进

每个大型系统都是从单台服务器开始的。理解扩展的过程,才能在面试中给出合理的演进路径。

阶段一:单机部署(初创期,<1000 DAU) ┌─────────────────────────────┐ │ 单台服务器 │ │ ┌────────┐ ┌──────────┐ │ │ │ Web │ │ MySQL │ │ │ │ App │ │ 单实例 │ │ │ └────────┘ └──────────┘ │ └─────────────────────────────┘ 优点:简单,开发部署快 问题:单点故障,垂直扩展有上限 阶段二:分离 Web 与 DB(成长期,~10K DAU) ┌──────────────┐ ┌──────────────┐ │ Web Server │ │ DB Server │ │ 4核8GB │───▶│ 16核64GB │ └──────────────┘ └──────────────┘ 优点:可以独立扩展 Web 和 DB 问题:Web 还是单点 阶段三:多 Web Server + LB(成熟期,~100K DAU) ┌─────────────────┐ Client ────▶│ Load Balancer │ └────────┬────────┘ ┌────┴────┐ ┌────▼──┐ ┌───▼───┐ │ Web │ │ Web │ │ S1 │ │ S2 │ └───────┘ └───────┘ └────┬────┘ ┌────▼────┐ │ DB │ │Primary │ └─────────┘ 问题:Session 在哪里?DB 成为新瓶颈

垂直扩展(Scale Up)

垂直扩展是最直觉的做法:买更好的机器。给服务器增加 CPU、内存、更快的磁盘。

垂直扩展的优点

  • 简单,不需要修改应用代码
  • 没有分布式一致性问题
  • 网络延迟低(同机器内通信)
  • 运维成本低,单机管理

垂直扩展的限制

  • 硬件有物理上限(256核/数TB内存)
  • 成本非线性增长(2倍性能花4倍钱)
  • 单点故障风险依然存在
  • 升级往往需要停机维护
ℹ 真实案例:Stack Overflow

Stack Overflow 用垂直扩展撑住了惊人的流量——他们的主 SQL Server 是一台 384GB 内存、56 核的超级机器,处理数百万 QPS 的读请求。证明对于某些系统,垂直扩展配合精心的缓存策略,比微服务更经济高效。

水平扩展(Scale Out)

水平扩展是增加更多同等规格的机器,通过负载均衡分发请求。这是互联网大厂应对海量流量的核心策略。

水平扩展架构示意 ┌──────────────────────────────┐ │ Load Balancer │ │ (nginx / HAProxy / ALB) │ └──┬─────────┬─────────┬───────┘ │ │ │ ┌────▼──┐ ┌───▼───┐ ┌──▼────┐ │ App │ │ App │ │ App │ │ #1 │ │ #2 │ │ #3 │ └───┬───┘ └───┬───┘ └───┬───┘ └──────────┼──────────┘ │ ┌────────────▼─────────────┐ │ 共享状态层 │ │ Redis (Session / Cache) │ └────────────┬─────────────┘ │ ┌────────────▼─────────────┐ │ 数据库 │ │ Primary ──▶ Replica │ └──────────────────────────┘ 关键点: - App Server 本身必须是「无状态」的 - 状态(Session、缓存)集中到 Redis 等共享存储 - 任何一台 App Server 宕机,LB 自动切走流量

无状态服务设计(Stateless Architecture)

无状态意味着服务器不保存任何请求间的状态信息。每个请求必须携带完成该请求所需的全部信息。

有状态 vs 无状态对比

有状态服务(反模式): 用户张三登录 → 命中 Server #1 → Session 存在 Server #1 内存 下次请求 → 负载均衡 → 必须路由到 Server #1(粘性会话) Server #1 宕机 → 张三的 Session 丢失 → 被迫重新登录 ❌ 问题: - 负载均衡被迫粘性路由,失去均衡效果 - 单台 Server 宕机影响用户 ───────────────────────────────────────────────── 无状态服务(推荐): 用户张三登录 → 任意 Server 处理 → Session 存入 Redis 下次请求 → 携带 Token → 任意 Server 验证 Token → 查 Redis Server #1 宕机 → 自动切到 Server #2 → 体验无中断 ✓ 优点: - 任意 Server 都能处理任意请求 - 水平扩展无障碍 - 故障恢复透明

Session 管理方案

状态不能消失,只是被移到了不同的地方。水平扩展后,Session 有三种主要存储方案:

方案 原理 优点 缺点 适用场景
粘性会话
Sticky Session
LB 将同一用户永远路由到同一服务器 无需外部存储,简单 节点故障会话丢失,负载不均 遗留系统改造期
集中式 Session
Redis/Memcached
Session 存入共享缓存,任意服务器可读 真正无状态,弹性扩展 引入 Redis 依赖,网络开销 推荐方案
JWT Token 状态编码在 Token 中,服务端不存储 完全无状态,无外部依赖 Token 无法立即失效,大小有限制 无状态 API、微服务间认证
会话复制
Session Replication
各服务器间同步 Session 无外部依赖 广播风暴,内存浪费,延迟高 不推荐

JWT 无状态认证流程

用户登录流程: Client App Server Redis / DB │ │ │ │──POST /login──▶│ │ │ {user, pwd} │──验证用户────────▶│ │ │◀─验证通过─────────│ │◀──200 OK────────│ │ │ {jwt_token} │ │ │ │ │ 已登录用户请求流程: Client App Server Redis / DB │ │ │ │──GET /profile──▶│ │ │ Authorization: │ │ │ Bearer │ │ │ │──验证签名(本地) │ │ │──无需查 DB! │ │◀──200 OK────────│ │ │ {user_data} │ │ JWT 结构: Header.Payload.Signature eyJhbGci... . eyJ1c2VyX... . SflKxwRJ...

数据库扩展瓶颈

扩展了 App Server 之后,数据库很快成为新瓶颈。应对数据库压力有以下递进策略:

策略一:读写分离

读写分离架构: ┌──────────────┐ 写请求 ─────────▶│ DB Primary │─────┐ └──────────────┘ │ 主从复制 │ (Replication) 读请求 ─────┬──▶ ┌──────────────┐◀───┘ │ │ DB Replica 1 │ │ └──────────────┘ │ ┌──────────────┐ └──▶ │ DB Replica 2 │ └──────────────┘ 适用:读多写少场景(如新闻、博客、商品详情) 读 QPS 翻倍靠增加 Replica 写 QPS 仍受 Primary 限制

策略二:连接池

每次查询建立数据库连接开销巨大(约 10ms)。使用连接池(如 PgBouncer、HikariCP)复用连接,减少握手开销,是不改架构情况下最简单的性能提升手段。

策略三:数据库分片(Sharding)

当单机 DB 写入达到瓶颈,需要水平分片。详见第5章。

扩展策略 解决什么问题 复杂度 推荐顺序
加索引 + 优化查询 慢查询 第1步
连接池(PgBouncer) 连接数瓶颈 第2步
缓存(Redis) 热数据重复读 第3步
读写分离 + Replica 读 QPS 瓶颈 第4步
垂直分库(按业务) 单库过大 第5步
水平分片(Sharding) 写 QPS 瓶颈 / 数据量过大 非常高 最后手段
▶ 面试要点

被问到「如何扩展这个系统」时,永远从最简单的方案开始,按需逐步演进。说「先加缓存,再读写分离,最后才考虑分片」比直接说「用微服务+分布式数据库」要专业得多。展示你理解每一步的代价和收益。