系统设计基础:从单机到分布式
为什么需要系统设计?
很多开发者工作几年后都会遇到瓶颈:代码写得熟练,但面对"设计一个秒杀系统"或"设计一个短链接服务"这类问题时却无从下手。
系统设计的本质不是炫技,而是在约束条件下做权衡。
核心概念:CAP 定理
CAP 定理是分布式系统的基石,它指出一个分布式系统最多同时满足以下三点中的两点:
一致性(Consistency)
所有节点在同一时间看到的数据是相同的。
可用性(Availability)
每个请求都能在合理时间内收到响应(不保证是最新数据)。
分区容错性(Partition Tolerance)
网络分区发生时,系统仍能继续运行。
现实中的选择
重要认知:在分布式系统中,P(分区容错)是必须接受的。网络不可靠是常态,不是例外。
所以实际选择是 CP 还是 AP:
| 场景 | 选择 | 理由 |
|---|---|---|
| 银行转账 | CP | 数据一致性优先,宁可暂时不可用 |
| 社交网络点赞 | AP | 可用性优先,短暂不一致可接受 |
| 电商库存 | CP | 超卖比暂时不可用更严重 |
| 新闻评论 | AP | 评论延迟几秒显示没关系 |
从单机到分布式的演进
第一阶段:单体应用
用户 → Nginx → 应用服务器 → 数据库特点:
- 简单,开发效率高
- 所有代码在一个进程
- 数据库和应用在一起
瓶颈:
- 单点故障
- 性能受限于单机
- 无法水平扩展
第二阶段:应用与数据库分离
用户 → Nginx → 应用服务器集群 → 数据库主从
↓
读从库改进:
- 数据库主从复制
- 应用可以水平扩展
- 读写分离
新问题:
- 主从延迟
- 连接池管理复杂
第三阶段:引入缓存
用户 → Nginx → 应用服务器 → Redis 缓存 → 数据库缓存策略:
Cache-Aside(旁路缓存)
- 读:先查缓存,没有再查数据库并写入缓存
- 写:先写数据库,再删除缓存
- 最常用,推荐
Read-Through
- 应用只和缓存交互
- 缓存负责和数据库同步
- 需要缓存支持
Write-Behind
- 写操作只写缓存
- 缓存异步批量写入数据库
- 性能最高,但有数据丢失风险
第四阶段:服务拆分
当单体应用过于庞大时,需要按业务拆分成独立服务:
用户 → API 网关 → 用户服务
→ 订单服务
→ 支付服务
→ 库存服务微服务的好处:
- 独立部署
- 技术栈灵活
- 故障隔离
微服务的代价:
- 分布式事务复杂
- 运维成本增加
- 网络调用延迟
设计系统的思维方式
1. 从需求出发
不要一上来就画架构图,先问清楚:
- 用户规模:DAU 多少?峰值 QPS 多少?
- 数据规模:总数据量?日增量?
- 一致性要求:能接受多久的数据延迟?
- 可用性要求:允许宕机多久?
2. 估算先行
在深入设计前,先做粗略估算:
假设 DAU = 100 万
假设峰值 QPS = DAU × 10% / 3600 ≈ 28 QPS
假设每请求 10KB 数据
带宽需求 = 28 × 10KB ≈ 280KB/s ≈ 2.2Mbps经验法则:
- 单台服务器处理 1000-5000 QPS(简单请求)
- 单台 MySQL 处理 1000-3000 QPS(读写混合)
- 单台 Redis 处理 5 万 -10 万 QPS
3. 识别瓶颈
系统瓶颈通常在:
- 数据库:最常见的瓶颈
- 网络带宽:大文件传输场景
- CPU:计算密集型任务
- 磁盘 I/O:大量读写场景
4. 设计原则
KISS 原则(Keep It Simple, Stupid)
- 简单的设计更容易维护
- 不要过度设计
80/20 法则
- 80% 的流量集中在 20% 的功能
- 优先优化热点
故障是常态
- 假设任何组件都会挂
- 设计降级和熔断机制
实战:设计一个短链接服务
需求分析
- 将长 URL 缩短为 6-8 位短链
- 日活 100 万
- 短链永久有效
- 支持自定义短链
核心问题
1. 短链如何生成?
方案对比:
| 方案 | 优点 | 缺点 |
|---|---|---|
| MD5 哈希 | 简单 | 可能冲突 |
| 自增 ID+Base62 | 不冲突 | 暴露业务信息 |
| 雪花算法 | 分布式友好 | 需要时间同步 |
| 随机字符串 + 查重 | 简单 | 需要查重 |
推荐:自增 ID + Base62 编码
ID: 12345
Base62(12345) = "3D7"
短链:https://s.url/3D72. 数据库如何设计?
sql
CREATE TABLE url_mapping (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
short_code VARCHAR(10) UNIQUE NOT NULL,
long_url VARCHAR(2048) NOT NULL,
user_id BIGINT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_short_code (short_code)
);3. 缓存策略?
短链接是典型的读多写少场景:
- 99% 的请求是访问(读)
- 1% 的请求是创建(写)
使用 Cache-Aside 策略:
- 访问短链:先查 Redis,没有再查 MySQL 并回写
- 创建短链:写 MySQL 后删除 Redis 缓存
架构设计
用户 → Nginx → 应用服务器 → Redis 缓存
↓ (miss)
MySQL 主从容量估算:
- 日活 100 万 → 峰值 QPS ≈ 300
- 单台服务器足够
- Redis 缓存热点短链(80/20 法则)
总结
系统设计的核心不是记住多少架构模式,而是:
- 理解业务需求:不要为了技术而技术
- 做好容量估算:心中有数,不盲目堆机器
- 识别关键瓶颈:好钢用在刀刃上
- 接受权衡:没有完美的设计,只有合适的选择
下一步学习:
- 分布式事务(2PC、TCC、Saga)
- 消息队列(Kafka、RabbitMQ)
- 负载均衡(LVS、Nginx、Consul)
- 服务发现与注册
系统设计是实践出来的,不是看书看出来的。遇到问题时,先思考再查资料,逐渐积累经验。