设计读密集型系统
问题
如何设计一个读多写少的高性能数据库架构?
答案
一、场景分析
典型的读密集场景:
| 场景 | 读写比 | 特点 |
|---|---|---|
| 商品详情页 | 100:1 | 高并发读,数据相对静态 |
| 新闻资讯 | 500:1 | 大量读取,偶尔更新 |
| 配置中心 | 1000:1 | 读极多,写极少 |
| 用户个人资料 | 50:1 | 频繁查看,偶尔修改 |
二、多级缓存架构
各层职责:
| 层级 | 技术 | 延迟 | 命中率目标 |
|---|---|---|---|
| CDN | CloudFlare、阿里云 CDN | ~10ms | 60~80% |
| L1 本地缓存 | Caffeine、HashMap | ~1ms | 30~50% |
| L2 分布式缓存 | Redis / Memcached | ~5ms | 90~95% |
| 数据库 | MySQL 从库 | ~50ms | 100%(兜底) |
三、读写分离
// 读写分离数据源路由
class DataSourceRouter {
getDataSource(operation: 'read' | 'write') {
if (operation === 'write') {
return this.masterDataSource;
}
// 读请求轮询从库
return this.slaveDataSources[this.nextSlaveIndex()];
}
}
主从延迟问题
写入主库后立即从从库读取,可能读到旧数据。解决方案:
- 写后读走主库:关键读操作强制走主库
- 延迟感知:检测从库复制延迟,超阈值则路由到主库
- 半同步复制:至少一个从库确认收到 binlog 后才返回
四、CQRS(命令查询职责分离)
将读模型和写模型完全分离:
写模型(MySQL): 读模型(ES / 宽表):
users + orders + items order_details_view
├── 范式化,避免冗余 ├── 反范式化,预聚合
├── 保证写入一致性 ├── 查询性能极佳
└── 适合事务操作 └── 允许短暂不一致
五、查询优化策略
| 策略 | 说明 |
|---|---|
| 覆盖索引 | 让查询只读索引,不回表 |
| 物化视图 | 预计算复杂聚合查询的结果 |
| 数据预热 | 应用启动时预加载热点数据到缓存 |
| 静态化 | 将数据渲染为静态页面(CDN 分发) |
常见面试问题
Q1: 设计一个商品详情页的数据读取方案
答案:
1. CDN 缓存静态资源(图片、CSS、JS)
2. 页面模板 SSR + 边缘缓存
3. 商品数据:
L1: 进程内 Caffeine 缓存(TTL 30s)
L2: Redis 缓存(TTL 5min)
L3: MySQL 从库查询
4. 库存/价格:实时从 Redis 读取(不走长 TTL 缓存)
5. 评论:ES 查询(支持全文搜索和排序)
Q2: 读写分离后如何处理「写后立即读」的一致性?
答案:
- Session 粘滞:同一 Session 的读写都走主库(最简单但失去读写分离优势)
- 延迟标记:写入后设置一个短TTL标记,标记期间读走主库
- GTID 同步:携带写入的 GTID,从库追上后再读
- 业务层兜底:前端乐观更新,不依赖后端立即返回最新数据