高并发读写方案
高并发不是一个技术名词,而是一组取舍:流量从哪里来、瓶颈在读取还是写入、能否容忍延迟或短暂不一致。本章先按读写比例把场景分成三类,再给出水平扩展的推荐顺序、写扩散与读扩散的对比、削峰的常用手段,以及一张从 1k 到 100 万 QPS 的演进路线表。
三类场景
| 场景 | 读写比 | 典型业务 | 主要瓶颈 | 首选手法 |
|---|---|---|---|---|
| 读多写少 | 100:1 以上 | 商品详情、文章、配置 | 读流量压垮数据库 | 多级缓存、读写分离、本地缓存 |
| 写多读少 | 写远多于读 | 埋点、日志、消息、计数 | 写入吞吐与磁盘 | 异步化、批量写、分片、专用存储 |
| 读写均衡 | 约 1:1 | 订单、支付、库存 | 事务与锁竞争 | 分库分表、削峰、热点拆分 |
先用监控把读写比例、QPS、响应时间、连接数四条曲线画出来再谈方案。没有数据的优化都是猜测。
水平扩展的推荐顺序
扩展不是一步到位,而是按“成本最低、收益最高”排序:
1. 加缓存 成本最低,挡住重复读,通常可砍掉 80% 以上的读流量
2. 读扩展 从库或只读副本分担读,注意主从延迟带来的一致性代价
3. 写分片 单库写入到顶后再分库分表,改动最大、最难回退
4. 异步化 通知、积分、统计等非核心链路改为消息异步处理
5. 限流降级 兜底手段,保证过载时核心链路依然可用
顺序反了会付出双倍代价:还没加缓存就分库分表,系统复杂了主要矛盾却依然存在。
写扩散 vs 读扩散
同一个功能,选择在写入时算好还是在读取时现算,性能特征完全不同:
| 维度 | 写扩散(写时扇出) | 读扩散(读时聚合) |
|---|---|---|
| 写入成本 | 高,一次写入要扇出到 N 个收件箱 | 低,只写自己的数据 |
| 读取成本 | 低,直接读自己的收件箱 | 高,每次读取都要聚合多份数据 |
| 存储占用 | 大,同一内容存 N 份 | 小,内容只存一份 |
| 适合场景 | 读多写少、粉丝量可控(普通用户) | 写多读少、超大规模(大 V、热点) |
| 典型问题 | 大 V 一条动态写几百万次 | 关注人数多时读取延迟高 |
工程上常采用推拉结合:普通用户走写扩散,超过粉丝阈值的大 V 只写发件箱,读取时合并两路结果。
削峰:把瞬时压力摊平
- 队列削峰:请求先入消息队列,消费端按数据库能承受的速率匀速处理,队列长度就是水位计。
- 批量聚合:把 N 次小写合并为一次批量写,例如点赞数每秒累加一次,而不是每次 +1 都写库。
- 请求合并:同一资源的并发查询合并成一次回源,结果广播给所有等待者。
- 预约式排队:秒杀场景先发号,前端轮询结果,把“抢”变成“等”。
- 背压:下游(数据库、消息队列)已经过载时主动降低上游速率,宁可快速失败也不要堆积。
瞬时 5 万 QPS → 队列缓冲 → 消费端 2000 TPS 匀速写库 → 数据库压力恒定
代价是链路变成异步,因此必须处理消息堆积告警、重复消费与最终一致性。
从 1k 到 100 万 QPS 的演进路线
| 阶段 | 量级 | 主要瓶颈 | 落地手段 |
|---|---|---|---|
| 单机起步 | < 1k | 基本没有 | 单应用加单库,先把慢查询与索引治理干净 |
| 缓存引入 | 1k ~ 1万 | 数据库读 | Redis 缓存热点、本地缓存兜底、读写分离 |
| 服务化 | 1万 ~ 10万 | 应用与数据库连接 | 应用无状态多实例、连接池调优、限流熔断 |
| 分片 | 10万 ~ 100万 | 单库写入与存储 | 分库分表、消息异步化、热点 key 拆分 |
| 多级与就近 | > 100万 | 网络与单点 | CDN、多级缓存、单元化部署、异地多活 |
每跨一档都要问一句:当前瓶颈到底在哪一层?盲目把上一档的手段继续堆叠,往往是钱花了而 QPS 没涨。
读扩展的前提
- 主从延迟:写入后立刻读从库可能读不到,对一致性敏感的场景(下单后跳订单页)要强制走主库。
- 资源隔离:读库与写库使用不同的连接池与线程池,避免慢查询互相影响。
- 缓存一致性:更新遵循“先更新数据库、再失效缓存”,并接受短暂不一致。
写请求 → 主库 → 复制(毫秒到秒级延迟)→ 从库 ← 读请求
读己之写:写后一小段时间内的读走主库,其余走从库
常见误区
| 误区 | 后果 | 正确做法 |
|---|---|---|
| 先分库分表,后加缓存 | 复杂度上去了,读压力依旧 | 先做缓存,再评估是否真要分片 |
| 缓存一切数据 | 内存成本高、一致性难维护 | 只缓存热点与读多写少的稳定数据 |
| 用异步解决一切 | 链路变长、排错难、一致性问题变多 | 只有非核心、可重试的逻辑才异步 |
| 只扩读不扩写 | 写路径瓶颈导致整体不可用 | 分别评估读路径与写路径的容量 |
| 讲“高并发”却给不出量级 | 方案无法验证与验收 | 先写明 QPS、数据量、延迟目标 |
小结:先量化读写比例再定方案,按缓存、读扩展、写分片、异步化的顺序做水平扩展;写扩散与读扩散按粉丝规模和读写比取舍,用队列与批量把尖峰摊平,最后用限流降级守住底线。