· 王思腾 · 系统架构 · 10 分钟阅读
从 0 到 100 万用户:架构演进与系统扩展性设计
没有一步到位的架构,只有随规模不断演化的架构。本文按用户量级(单机 → 垂直扩展 → 水平扩展 → 服务化 → 多机房)梳理系统在不同阶段遇到的瓶颈与应对,讲清扩展性的两个方向(垂直/水平)、无状态化、分库分表、缓存与读写分离,以及“什么时候该做什么”的取舍。

很多团队一上来就想设计“能撑百万用户”的架构,结果是技术复杂度远超业务需要,几个人的团队被微服务、中间件拖得寸步难行。架构不是设计出来的,是演化出来的——正确的做法是在每个阶段用“刚好够用”的方案解决“当前最痛的瓶颈”,同时为下一步留好演化空间。
一、扩展性的两个方向
先厘清概念,扩展(Scale)无非两条路:
- 垂直扩展(Scale Up):升级单机配置——更强的 CPU、更多内存、更快的 SSD。简单、零改造,但有硬件上限,且成本非线性增长(顶配机器贵得离谱);
- 水平扩展(Scale Out):加更多机器,把负载分摊到多个实例。理论上无上限、成本线性,但要求系统能被“拆开”,引入了分布式复杂度。
早期几乎总是先垂直扩展(花钱省时间),等单机到顶或成本不划算,再转向水平扩展(花复杂度换规模)。水平扩展的前提是无状态化。
二、阶段一:单体 + 单库(0 ~ 1 万用户)
刚起步,一台应用服务器 + 一个数据库就够了:
flowchart LR
U["用户"] --> App["应用服务器<br/>单体应用"]
App --> DB[("MySQL 单库")]
App --> FS["本地文件存储"]- 所有功能在一个代码库、一个进程,本地事务、本地日志,开发部署都简单;
- 这个阶段不要过度设计:不上微服务、不上消息队列、不搞分库分表。
- 唯一要提前留的习惯:把应用做成无状态的(会话存 Redis/Token 而不是进程内存、文件别只存在本机),这是将来水平扩展最便宜的准备。
瓶颈信号:应用 CPU/内存打满,或数据库查询变慢。
三、阶段二:垂直扩展 + 缓存 + 读写分离(1 万 ~ 10 万用户)
流量上来后,最先扛不住的通常是数据库。按性价比依次优化:
- 加缓存:热点数据(商品、用户资料、配置)放 Redis,挡掉大部分读请求——这是收益最大的一步;
- 数据库读写分离:主库写、从库读,读负载摊到多个副本;
- 应用服务器垂直扩容,并开始部署多个实例 + 负载均衡。
flowchart TB
U["用户"] --> LB["负载均衡 Nginx"]
LB --> A1["应用实例 1"]
LB --> A2["应用实例 2"]
A1 --> Cache[("Redis 缓存")]
A2 --> Cache
A1 --> M[("MySQL 主库 · 写")]
A2 --> M
M -. 复制 .-> S1[("从库 · 读")]
M -. 复制 .-> S2[("从库 · 读")]
A1 -. 读 .-> S1
A2 -. 读 .-> S2注意两个坑:
- 无状态化是加多实例的前提:会话、本地缓存、定时任务都不能绑死在某台机器上;
- 读写分离引入主从延迟:刚写完立刻读可能读到旧数据,核心链路(如支付后查订单)要走主库或做延迟兜底。
四、阶段三:水平扩展 + 服务化(10 万 ~ 50 万用户)
单库写入和单表数据量到达极限(经验上单表数千万行、单库 QPS 数千就要警惕),单体也臃肿到团队难以并行开发。这一步做两件大事:
1. 数据库水平拆分
- 分库分表:按用户 ID 等维度哈希取模,把数据分散到多个库、多张表。拆分后单表数据量和写入压力都下降;
- 带来的代价要提前想清楚:跨片查询、分布式事务、全局唯一 ID、分页排序都变复杂,通常靠中间件(ShardingSphere)或在应用层路由解决;
- 能不拆就不拆,先靠“垂直拆库”(按业务把订单、用户、商品拆到不同库)缓解,它比分表简单得多。
2. 应用服务化
把单体按业务边界拆成若干服务(订单、用户、商品、支付……),前面加 API 网关,服务间用消息队列异步解耦。详见微服务架构篇。
flowchart TB
U["用户"] --> GW["API 网关"]
GW --> Svc
subgraph Svc["服务集群(各自多实例 + 独立扩容)"]
S1["订单服务"] --- S2["用户服务"] --- S3["商品服务"] --- S4["支付服务"]
end
Svc --> MQ["消息队列 Kafka"]
Svc --> C[("Redis 集群")]
S1 --> D1[("订单库 分库分表")]
S2 --> D2[("用户库")]
S3 --> D3[("商品库 + ES")]关键原则:不同服务按各自压力独立扩容——搜索服务吃内存就给它加内存型实例,订单服务吃 IO 就单独扩,不必整体一起扩。
五、阶段四:多机房与弹性化(50 万 ~ 100 万+ 用户)
到这个量级,单点机房故障、活动大促脉冲流量、跨地域访问延迟成为新问题:
- CDN + 静态化:图片、静态资源、甚至热点页面全部边缘加速,源站压力骤降;
- 弹性伸缩:跑在 Kubernetes 上,按 CPU/QPS 自动扩缩容,大促前预置、过后回收;
- 限流降级:网关层和服务层都要有限流,非核心功能(推荐、评论)可降级,保住下单、支付主链路;
- 多可用区 / 多机房部署:单机房故障能切流,数据跨机房备份;
- 异步化削峰:秒杀、下单等写洪峰用消息队列缓冲,后端按能力消费,避免直接压垮数据库。
flowchart LR
U["全国用户"] --> CDN["CDN 边缘节点"]
CDN --> GW["网关集群 · 限流"]
GW --> K8s["K8s 服务集群<br/>自动扩缩容"]
K8s --> MQ["MQ 削峰"]
MQ --> W["后端消费者(按能力处理)"]
W --> DB[("分库分表 + 多级缓存")]六、贯穿始终的扩展性原则
无论哪个阶段,这几条都成立:
- 无状态化:应用实例不存会话、不存本地关键状态,随时可加可减可重启;
- 让瓶颈可观测:没有监控(QPS、延迟 P99、错误率、资源水位)就不知道该扩哪里,扩容全靠猜;
- 优先垂直扩展、优先缓存、优先异步——这三件事性价比最高,往往能把“必须分布式化”的时间点往后拖很久;
- 容量规划:压测摸清单机/单库上限,按“峰值 × 冗余系数(通常 2~3 倍)”预留容量,别等打挂了才扩;
- 为演化留接口,不为假想需求做实现:代码里把数据访问、会话、存储抽象好,将来分库、换中间件时改动可控。
七、小结
- 架构是演化出来的:每个阶段解决当前最痛的瓶颈,用刚好够用的方案,别提前背上分布式复杂度;
- 扩展两条路:早期垂直扩展省时间,后期水平扩展换规模,水平扩展的前提是无状态化;
- 典型演进主线:单体单库 → 缓存 + 读写分离 + 多实例 → 分库分表 + 服务化 → CDN/弹性伸缩/多机房;
- 缓存、异步、垂直扩容是性价比最高的三板斧,能拖就拖“重型分布式改造”的到来;
- 可观测性和容量规划要先行——看得见瓶颈,才谈得上精准扩展。
从 0 到百万用户,拼的不是一开始用了多炫的技术,而是每一步有没有把钱和复杂度花在刀刃上。
本文由 AI 辅助创作,所有内容仅供交流与参考。



