Skip to content

系统设计基础:从单机到分布式

为什么需要系统设计?

很多开发者工作几年后都会遇到瓶颈:代码写得熟练,但面对"设计一个秒杀系统"或"设计一个短链接服务"这类问题时却无从下手。

系统设计的本质不是炫技,而是在约束条件下做权衡

核心概念:CAP 定理

CAP 定理是分布式系统的基石,它指出一个分布式系统最多同时满足以下三点中的两点:

一致性(Consistency)

所有节点在同一时间看到的数据是相同的。

可用性(Availability)

每个请求都能在合理时间内收到响应(不保证是最新数据)。

分区容错性(Partition Tolerance)

网络分区发生时,系统仍能继续运行。

现实中的选择

重要认知:在分布式系统中,P(分区容错)是必须接受的。网络不可靠是常态,不是例外。

所以实际选择是 CP 还是 AP

场景选择理由
银行转账CP数据一致性优先,宁可暂时不可用
社交网络点赞AP可用性优先,短暂不一致可接受
电商库存CP超卖比暂时不可用更严重
新闻评论AP评论延迟几秒显示没关系

从单机到分布式的演进

第一阶段:单体应用

用户 → Nginx → 应用服务器 → 数据库

特点

  • 简单,开发效率高
  • 所有代码在一个进程
  • 数据库和应用在一起

瓶颈

  • 单点故障
  • 性能受限于单机
  • 无法水平扩展

第二阶段:应用与数据库分离

用户 → Nginx → 应用服务器集群 → 数据库主从

                                读从库

改进

  • 数据库主从复制
  • 应用可以水平扩展
  • 读写分离

新问题

  • 主从延迟
  • 连接池管理复杂

第三阶段:引入缓存

用户 → Nginx → 应用服务器 → Redis 缓存 → 数据库

缓存策略

  1. Cache-Aside(旁路缓存)

    • 读:先查缓存,没有再查数据库并写入缓存
    • 写:先写数据库,再删除缓存
    • 最常用,推荐
  2. Read-Through

    • 应用只和缓存交互
    • 缓存负责和数据库同步
    • 需要缓存支持
  3. 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. 识别瓶颈

系统瓶颈通常在:

  1. 数据库:最常见的瓶颈
  2. 网络带宽:大文件传输场景
  3. CPU:计算密集型任务
  4. 磁盘 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/3D7

2. 数据库如何设计?

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 法则)

总结

系统设计的核心不是记住多少架构模式,而是:

  1. 理解业务需求:不要为了技术而技术
  2. 做好容量估算:心中有数,不盲目堆机器
  3. 识别关键瓶颈:好钢用在刀刃上
  4. 接受权衡:没有完美的设计,只有合适的选择

下一步学习

  • 分布式事务(2PC、TCC、Saga)
  • 消息队列(Kafka、RabbitMQ)
  • 负载均衡(LVS、Nginx、Consul)
  • 服务发现与注册

系统设计是实践出来的,不是看书看出来的。遇到问题时,先思考再查资料,逐渐积累经验。