系统设计
1. 高并发系统设计
1.1 垂直扩展 (Scale Up)
| 方式 |
说明 |
| 增加硬件资源 |
CPU、内存、磁盘 |
| 数据库优化 |
索引、查询优化 |
| 应用优化 |
代码调优、算法优化 |
1.2 水平扩展 (Scale Out)
┌─────────────┐
│ Nginx │
│ 负载均衡 │
└──────┬──────┘
│
┌──────────────┼──────────────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ Server1 │ │ Server2 │ │ Server3 │
└─────────┘ └─────────┘ └─────────┘
1.3 核心策略
| 策略 |
手段 |
| 缓存 |
Redis、Memcached、CDN |
| 异步 |
消息队列、线程池 |
| 拆分 |
分库分表、微服务 |
| 负载均衡 |
Nginx、LVS、F5 |
2. 高可用设计
2.1 限流 (Rate Limiting)
// 令牌桶算法
public class TokenBucket {
private int capacity; // 桶容量
private int tokens; // 当前令牌数
private double refillRate; // refill rate
public boolean tryAcquire(int permits) {
if (tokens >= permits) {
tokens -= permits;
return true;
}
return false;
}
}
| 算法 |
特点 |
| 令牌桶 |
允许突发流量 |
| 漏桶 |
平滑输出 |
| 滑动窗口 |
精度高 |
2.2 熔断器 (Circuit Breaker)
CLOSED (正常) → 失败率超阈值 → OPEN (熔断) → 半开 → OPEN/CLOSED
// Resilience4j 熔断示例
@CircuitBreaker(name = "backend", fallbackMethod = "fallback")
public String callService() {
return restTemplate.getForObject("http://service/api", String.class);
}
public String fallback(Exception e) {
return "服务暂时不可用,请稍后重试";
}
2.3 降级 (Degradation)
- 读服务降级:返回缓存数据/默认值
- 写服务降级:延迟写入/本地队列
- 非核心功能关闭:推荐、统计等
2.4 多活架构
┌─────────────┐ ┌─────────────┐
│ 机房 A │ │ 机房 B │
│ (主站) │ │ (备站) │
│ 写+读 │ │ 读 │
└─────────────┘ └─────────────┘
3. 缓存设计
3.1 缓存策略
| 策略 |
说明 |
场景 |
| Cache-Aside |
先读缓存,miss后读DB并写缓存 |
读多写少 |
| Write-Through |
同步写缓存和DB |
数据一致性要求高 |
| Write-Behind |
异步写DB |
写并发高 |
| Read-From-Replica |
读从库 |
主库压力大的分库分表场景 |
3.2 缓存问题
缓存穿透
// 布隆过滤器或空值缓存
public String get(String key) {
String value = redis.get(key);
if (value == null) {
value = db.query(key);
if (value == null) {
redis.setex(key, 60, ""); // 空值缓存,避免穿透
} else {
redis.setex(key, 3600, value);
}
}
return value;
}
缓存击穿
// 互斥锁 + 永不过期
public String get(String key) {
String value = redis.get(key);
if (value == null) {
synchronized(this) {
value = redis.get(key);
if (value == null) {
value = db.query(key);
redis.set(key, value, 0); // 永不过期,手动控制
}
}
}
return value;
}
缓存雪崩
// 过期时间 + 随机偏移
public void set(String key, String value, long expire) {
// 添加随机偏移,避免同时过期
long randomOffset = ThreadLocalRandom.current().nextLong(0, expire / 10);
redis.setex(key, expire + randomOffset, value);
}
3.3 Redis 集群
- 主从复制:读写分离
- Sentinel:故障自动切换
- Cluster:数据分片
4. 消息队列
4.1 常用场景
| 场景 |
说明 |
| 异步解耦 |
订单完成后,通知库存、物流 |
| 削峰填谷 |
秒杀流量写入MQ,慢慢处理 |
| 系统间通信 |
跨服务消息传递 |
4.2 Kafka vs RabbitMQ
| 特性 |
Kafka |
RabbitMQ |
| 吞吐量 |
高 |
中 |
| 延迟 |
低 |
低 |
| 消息持久化 |
✅ |
✅ |
| 消息顺序 |
✅ 分区有序 |
✅ 单队列有序 |
| 复杂路由 |
❌ |
✅ |
| 适用场景 |
日志、大数据 |
业务消息 |
4.3 消息可靠性
// Kafka 可靠生产
properties.put("acks", "all"); // 等待所有副本确认
properties.put("retries", 3); // 重试3次
properties.put("enable.idempotence", true); // 幂等生产
// 消费端确认
consumer.commitSync(); // 手动提交offset
4.4 消息顺序
// 确保同一Key的消息发送到同一Partition
producer.send(new ProducerRecord<>("topic", "user_id", message));
// 消费端按Partition顺序处理
while (true) {
ConsumerRecords<String, String> records = consumer.poll();
for (ConsumerRecord<String, String> record : records) {
// 按顺序处理
}
}
5. 数据库设计
5.1 读写分离
写 → 主库
读 → 从库
| 优点 |
缺点 |
| 提升读性能 |
数据延迟 |
| 主库专注写入 |
写操作仍需主库 |
| 负载分担 |
复杂查询可能不一致 |
5.2 分库分表
垂直拆分
用户库:用户基本信息
订单库:订单相关表
商品库:商品相关表
水平拆分
| 拆分方式 |
说明 |
| 按ID范围 |
user_0: id 0-1000万, user_1: id 1000万-2000万 |
| 按时间 |
order_202401, order_202402 |
| 按Hash |
id % 4 = 0/1/2/3 |
5.3 分页问题
-- 深度分页优化:游标分页
SELECT * FROM orders
WHERE id > last_max_id
ORDER BY id
LIMIT 20;
-- 或者使用ElasticSearch
5.4 索引设计
| 索引类型 |
场景 |
| B+Tree |
范围查询、等值查询 |
| Hash |
精确匹配 |
| 复合索引 |
多条件查询,遵循最左前缀 |
| 全文索引 |
文本搜索 |
6. 微服务架构
6.1 服务拆分
| 方式 |
说明 |
| 业务拆分 |
按业务域拆分(用户、订单、商品) |
| 技术拆分 |
前台、后台、开放平台 |
| 团队拆分 |
匹配小团队,2Pizza团队 |
6.2 服务治理
注册中心:Nacos / Consul / Eureka
配置中心:Apollo / Nacos Config
服务网关:Kong / Spring Cloud Gateway
服务监控:Prometheus + Grafana
链路追踪:SkyWalking / Zipkin
6.3 服务间通信
| 方式 |
场景 |
| 同步 HTTP/gRPC |
实时性要求高 |
| 异步 MQ |
解耦、削峰 |
| Dubbo |
Java 高性能 RPC |
7. 分布式 ID 生成
7.1 方案对比
| 方案 |
优点 |
缺点 |
| UUID |
简单、全球唯一 |
无序、占用空间大 |
| 数据库自增 |
有序、简单 |
单点、无法横向扩展 |
| Snowflake |
高性能、有序 |
依赖时钟 |
| 雪花算法变种 |
可自行优化 |
实现复杂 |
7.2 Snowflake 算法
┌─────────┬────────┬────────┬────────┬────────┬────────┐
│ 符号位 │ 时间戳 │ 机器ID │ 序列号 │ │ │
│ 1 bit │ 41 bit │ 10 bit│ 12 bit│ │ │
└─────────┴────────┴────────┴────────┴────────┴────────┘
- 时间戳:可使用69年
- 机器ID:1024个节点
- 序列号:每毫秒4096个ID
public class SnowflakeIdGenerator {
private final long workerId;
private final long epoch = 1609459200000L; // 2021-01-01
private long sequence = 0L;
public synchronized long nextId() {
long timestamp = System.currentTimeMillis() - epoch;
sequence = (sequence + 1) & 4095; // 毫秒内序列溢出归零
return (timestamp << 22) | (workerId << 12) | sequence;
}
}
8. 接口设计原则
8.1 RESTful API
| 方法 |
用途 |
示例 |
| GET |
查询 |
GET /users/123 |
| POST |
创建 |
POST /users |
| PUT |
更新(全量) |
PUT /users/123 |
| PATCH |
更新(部分) |
PATCH /users/123 |
| DELETE |
删除 |
DELETE /users/123 |
8.2 统一响应格式
{
"code": 200,
"message": "success",
"data": {
"id": 1,
"name": "张三"
},
"timestamp": "2024-01-15T10:30:00Z"
}
8.3 幂等性设计
| 方法 |
幂等性 |
实现 |
| GET |
✅ |
天然幂等 |
| PUT |
✅ |
资源替换 |
| DELETE |
✅ |
删除操作幂等 |
| POST |
❌ |
需额外处理 |
// 幂等Token
public String createToken() {
return UUID.randomUUID().toString();
}
// 提交时验证
public boolean submitOrder(String idempotentKey, Order order) {
if (redis.exists("order:token:" + idempotentKey)) {
return false; // 已处理
}
// 处理订单
redis.setex("order:token:" + idempotentKey, 86400, "1");
return true;
}
9. 系统设计步骤
9.1 分析需求
- 功能需求:核心功能有哪些
- 非功能需求:QPS、延迟、可用性、数据量
- 约束条件:技术栈、团队能力
9.2 设计要点
1. 高并发 → 缓存 + 异步 + 负载均衡
2. 高可用 → 限流 + 熔断 + 降级 + 多活
3. 数据一致性 → 最终一致 vs 强一致
4. 扩展性 → 水平扩展优先于垂直扩展
9.3 常见系统
| 系统类型 |
关键设计 |
| 设计秒杀系统 |
库存预扣、MQ异步下单、限流防刷 |
| 设计短链系统 |
哈希算法、301重定向、容量规划 |
| 设计IM系统 |
长连接、WebSocket、消息可靠性 |
| 设计评论系统 |
漏斗模型、异步写入、显示策略 |
参考资料