参考架构:大规模 / 高流量
随着流量增长,保护数据库有一个正确的顺序:先用简单、便宜的手段,再用复杂的手段。这棵决策树逐步介绍 垂直扩展 → 只读副本 → 分片 的顺序、缓存模式(cache-aside、write-back)、用队列 + 背压(backpressure)进行缓冲、限流、用 bulkhead/circuit breaker 隔离数据库,以及防止热点。核心原则:将分片这类难以逆转的决策留到最后,先用尽便宜的收益。
瓶颈在哪里:单实例已饱和,读取或写入繁重,还是流量呈突发性?
要在正确的地方解决扩展问题,首先要找出瓶颈。是单实例的 CPU/内存已饱和,读取占主导,写入触及上限,还是突发的流量峰值正在压垮数据库?每种情况都指向不同的组件。
- 单实例已饱和;我还没有尝试简单的手段
- 读取非常繁重且在增长
- 突发的流量峰值 / 突发负载正在压垮数据库
你处于哪个扩展阶段:垂直扩展、只读副本还是分片?
扩展有正确的顺序。垂直扩展(更强大的硬件)是最简单的第一步;然后将读取分散到副本上;只有当单个主库再也无法承载写入时,才转向水平分区(分片)。你处于哪个阶段?
- 我还没有升级硬件 / 还没有用尽基础调优
- 写入可由一个实例承载,但我需要分散读负载
- 单个主库再也无法承载写入负载
如何保护数据库免受负载冲击:缓冲、限流还是隔离?
扩展并不总是让数据库变得更大;很多时候是要保护数据库免于过载。你是要用队列平滑突发的写入峰值,限制传入的请求速率(限流),还是防止单个依赖拖垮整个系统(bulkhead/circuit breaker)?
- 我想平滑突发的写入峰值
- 我想限制/公平分配传入的请求速率
- 我不希望一个故障的依赖拖垮整个系统
建议:先进行垂直扩展和基础调优
最便宜、风险最低的一步是垂直扩展:增加更多 CPU/RAM/更快的磁盘,并修复基础问题(索引、连接池、内存参数)。在做出分片等复杂决策之前,先用尽这些收益。
建议:用只读副本分散读取
写入保留在单个主库上,只读流量分散到一个或多个副本。它比分片简单得多,能解决大多数读密集型负载;主库只负责写入和对一致性要求严格的读取。
建议:分片(水平分区)作为最后手段
当单个主库无法承载写入负载时,按某个键将数据拆分为多个独立的部分(分片);每个分片承担一部分写入。它很强大但难以逆转;会使跨分片查询和事务复杂化,因此留到最后。
建议:缓存模式(cache-aside + write-back)
在读密集的高流量下,在数据库前面部署内存缓存(Redis),使大多数读取根本无需到达数据库。对于大量写入,write-back 会缓冲写入并批量刷新到数据库,减轻主库压力。
建议:用队列进行缓冲(背压)
为防止突发的写入峰值直接冲击数据库,在中间放置一个持久化队列(例如 Kafka/消息队列),并以数据库能够承受的速率消费写入。队列吸收峰值;系统不会同步失败,而是以可控的延迟运行(背压)。
建议:限流(限制请求速率)
通过按来源限制传入请求(限流),既能防止滥用,又能防止单个客户端耗尽所有资源。限制到达数据库的负载,可以使系统保持可预测和公平。
建议:bulkhead + circuit breaker(隔离)
为防止某个依赖(例如变慢的数据库或外部服务)拖垮整个系统,将资源划分为多个隔舱(bulkhead),并暂时切断对持续失败的依赖的调用(circuit breaker)。这样局部故障就不会蔓延到整个系统。