跳到主要内容

设计读密集型系统

问题

如何设计一个读多写少的高性能数据库架构?

答案

一、场景分析

典型的读密集场景:

场景读写比特点
商品详情页100:1高并发读,数据相对静态
新闻资讯500:1大量读取,偶尔更新
配置中心1000:1读极多,写极少
用户个人资料50:1频繁查看,偶尔修改

二、多级缓存架构

各层职责:

层级技术延迟命中率目标
CDNCloudFlare、阿里云 CDN~10ms60~80%
L1 本地缓存Caffeine、HashMap~1ms30~50%
L2 分布式缓存Redis / Memcached~5ms90~95%
数据库MySQL 从库~50ms100%(兜底)

三、读写分离

// 读写分离数据源路由
class DataSourceRouter {
getDataSource(operation: 'read' | 'write') {
if (operation === 'write') {
return this.masterDataSource;
}
// 读请求轮询从库
return this.slaveDataSources[this.nextSlaveIndex()];
}
}
主从延迟问题

写入主库后立即从从库读取,可能读到旧数据。解决方案:

  1. 写后读走主库:关键读操作强制走主库
  2. 延迟感知:检测从库复制延迟,超阈值则路由到主库
  3. 半同步复制:至少一个从库确认收到 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: 读写分离后如何处理「写后立即读」的一致性?

答案

  1. Session 粘滞:同一 Session 的读写都走主库(最简单但失去读写分离优势)
  2. 延迟标记:写入后设置一个短TTL标记,标记期间读走主库
  3. GTID 同步:携带写入的 GTID,从库追上后再读
  4. 业务层兜底:前端乐观更新,不依赖后端立即返回最新数据

相关链接