高并发读写方案

高并发不是一个技术名词,而是一组取舍:流量从哪里来、瓶颈在读取还是写入、能否容忍延迟或短暂不一致。本章先按读写比例把场景分成三类,再给出水平扩展的推荐顺序、写扩散与读扩散的对比、削峰的常用手段,以及一张从 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、数据量、延迟目标

小结:先量化读写比例再定方案,按缓存、读扩展、写分片、异步化的顺序做水平扩展;写扩散与读扩散按粉丝规模和读写比取舍,用队列与批量把尖峰摊平,最后用限流降级守住底线。

笔记加载中…