高并发高可用架构怎么设计

接入与开发

给后端架构师的判断框架:高并发高可用系统该在哪些层面做冗余、限流与降级,以及什么情况下不要过度设计。

流量一涨就超时、一挂全挂,很多团队上来就加机器、上消息队列,结果复杂度涨了稳定性没涨。这篇不推销具体技术栈,而是给一套从上到下的分层判断顺序:先量基线、再拆瓶颈、最后才谈冗余和降级,并说清哪些做法在小体量下反而不适用。

流量一涨就超时、一挂全挂,很多团队第一反应是加机器、上消息队列、拆微服务,结果复杂度上去了,稳定性却没跟着上去。问题通常不在技术栈选得对不对,而在于顺序错了:还没有基线就开始扩容,还没找到瓶颈就开始冗余,还没定义目标就开始降级。

这篇不讲具体中间件选型,只给一套从上到下的判断顺序:先量基线、再拆瓶颈、然后才谈冗余和降级,最后说清楚什么阶段不该这么做。每个环节都给可执行的动作和判断标准,以及什么情况下这条经验不适用。模型和接口的实时价格与可用性,可以到 站内价格追踪页 核对当时的数字。

先量基线,再谈架构

没有延迟和吞吐基线,任何扩容都是猜。加机器之前先问自己:现在的 P50 和 P99 各是多少,峰值出现在什么时段,超过某个延迟的请求占比多少。这些数字用压测拿,也可以从真实流量的监控里捞,但一定要有,否则你无法判断改动是变好还是变坏。

  1. 记录链路耗时占比把一次请求拆成入口网关、应用逻辑、缓存、数据库、外部依赖几段,分别记各自耗时占总延迟的比例。找最粗的那一段,而不是均匀优化。
  2. 统一并发口径同时在线数、同一秒请求数、排队长度,这三个数说的完全不是一回事。跟产品和运维对齐口径,写进文档,别在会议上各说各话。
  3. 定义够用的标准写清业务能接受的可用性目标和延迟上限。没有终点线的优化会无限做下去,最后变成成本黑洞。
  • 基线要区分常态和峰值,用峰值做容量规划,用常态做成本核算
  • 延迟看分位不看均值,均值会掩盖长尾问题
  • 压测环境和生产的差异要写在报告里,别把压测结论直接当生产结论
  • 监控口径要和压测口径一致,否则数据对不上,排查会更慢

读写分离与热点识别

读写分离和多级缓存不是默认选项,而是针对特定形态的手段。先看读写比例,读多写少的系统加只读副本和缓存收益明显;写重的系统加副本反而增加同步开销和一致性负担,可能比不加还慢。所以动手之前,先把这个系统的读写特征说清楚。

特征含义适合的做法
读多写少读远多于写,读放大明显只读副本、多级缓存
写多读少写是主要压力来源分片、批量合并、异步落盘
单键高频读同一条数据被反复访问本地缓存加集中缓存
单键高频写同一个键被高频更新串行化、分片、合并写

缓存一定要定失效策略和击穿兜底。缓存集体失效时,所有请求会同时打到数据库,比没有缓存更糟。常见手段是过期时间加随机抖动、热点键永不过期加主动更新、以及缓存未命中时的单飞控制。这些机制要在设计阶段就写清楚,而不是出事之后补。

  • 不适用场景:强一致要求高的账务类数据,缓存只能做展示层加速,不能作为读源
  • 内部管理系统的读放大通常不明显,上多级缓存收益有限,维护成本反而高
  • 热点识别要靠采样和统计,不要凭直觉猜哪个键热,猜错的概率不低
  • 缓存和数据库的一致性方案要提前选好,是失效还是更新,选了就别中途换

入口层的限流、排队与背压

限流不是一道闸门,而是分三层做的:网关按来源限,防止单个调用方打满;服务按自身容量限,保护自己不被上游压垮;下游依赖处也限,保护别人。只做一层的话,压力只是从一处转移到另一处,整体稳定性不会变好。

  1. 定拒绝策略限流触发后是排队等待、快速失败还是降级返回,要按接口重要性分别设定,不能全局一刀切。
  2. 设最大等待时长排队不是失败,但没有上限的排队等于雪崩。给每个队列设明确的等待上限,超时就拒绝,让上游尽快感知。
  3. 打通背压信号下游变慢时要能把信号往上传,而不是每一层都自己超时重试。重试次数和超时时间要一起算总账,避免放大下游压力。
  4. 做资源隔离核心接口和后台任务共用同一套线程池或连接池,一方出问题会拖死另一方。按业务重要性分池,是最省事的隔离手段。

判断限流做得好不好,看一个指标就够了:当流量超过容量时,系统是整体变慢还是只拒绝一部分请求。前者说明没有保护,后者说明限流在起作用。要观察这个指标,可以借助 站内工具目录 里的压测与监控类工具,具体能用哪些到 站内工具目录 页面看当时的收录情况。

冗余、故障域与切换

冗余的关键在故障域,不在实例数量。同一个机房里挂十个实例,挡不住交换机或机房级故障。冗余要跨机架、跨可用区,甚至跨地域,具体跨到哪一层,取决于业务能接受多大的中断和多少成本。

无状态化是水平扩展的前提。会话、上传文件、定时任务这三类是常见的有状态残留,有它们在,加机器解决不了问题。把状态挪到外部存储,实例才能随时被替换和扩缩。

故障域
一处故障会同时影响的范围,冗余要跨域才有意义
无状态化
实例本身不保存请求之间的状态,可随时替换
自动摘除
健康检查失败时把实例从负载均衡里移出
多活
多个地域同时承载流量,代价是数据一致性变复杂

降级、熔断与兜底

降级的前提是先给依赖排优先级。核心链路依赖必须保,非核心依赖比如推荐、统计、附加信息,坏了可以直接砍掉,让核心流程继续走。很多系统出事不是因为核心挂了,而是被一个非核心依赖拖死。

降级还有一个容易被忽略的点:降级期间的返回结果要有明确语义,是旧数据、空值还是默认值,前端和调用方要能区分。否则用户会以为看到的是实时结果,产生误判,事后排查也会更混乱。

数据层才是真瓶颈

大部分所谓的并发问题,最后都落在数据层。连接池打满、慢查询、锁竞争、大事务,这四个是最常见的隐形杀手。遇到性能下降,先查这四个,再考虑分库分表这种重型手段。

排查项典型表现先做的动作
连接池等待连接超时增多看池大小和平均持有时间
慢查询数据库 CPU 高但 QPS 不高抓慢日志,看执行计划
锁竞争吞吐上不去、延迟抖动看锁等待和事务粒度
大事务主从延迟、回滚变慢拆小事务,缩短持锁时间

分库分表是最后手段。一旦拆了,跨片查询、分布式事务和运维成本都会上一个台阶。拆之前先确认单表数据量和写入量是不是真的到了瓶颈,很多情况加索引、归档历史数据就能缓解。

容量要留水位线,磁盘、连接数、内存都定好告警线,别等用满了才发现。备份这件事,恢复演练比备份本身更重要,恢复时间决定了你的实际可用性,而不是备份是否成功。

什么时候不该这么设计

前面讲的都是手段,不是默认配置。用户量和请求量都小的系统,加上消息队列、多活、服务网格,只会拖慢迭代速度,出了问题还更难排查。阶段性简单,是一种主动选择,不是能力不足。

  • 单体加水平复制能扛住的阶段,不要提前拆微服务
  • 没有明确容量压力时,多活和分库分表都先放一放
  • 判断标准是瓶颈在不在架构上,而不是代码里一个慢查询或一次同步调用
  • 把复杂度预算花在真正会出问题的地方,其余保持简单
架构不是越复杂越稳,而是复杂度刚好覆盖你真实会遇到的那几类故障。
下一步把你的链路按入口、应用、缓存、存储、外部依赖拆一遍,记下各段耗时占比和峰值压力;然后到站内降价与可用性追踪页面核对依赖服务的实时状态,再决定哪些环节值得上冗余和降级。

常见问题

高并发是不是一定要用消息队列?

不是。消息队列解决的是削峰、解耦和异步,如果你的瓶颈是同步调用拖长响应,先优化调用链和缓存可能更划算。只有当写入峰值远高于下游处理能力、或者需要解耦上下游发布节奏时,引入队列才值得,否则只是多了一个要运维的组件。

限流设多少合适?

没有一个通用数字。正确做法是先压测出单实例在可接受延迟下的吞吐上限,再按实例数和安全系数(通常留一部分余量)反推整体阈值,并让阈值可动态调整。可以到 站内 /tools/speed-test 先量出你的延迟基线,再决定留多少余量。

多机房多活是不是高可用的标配?

不是标配,是成本很高的选项。单机房多可用区已经能挡掉大部分故障。多活要额外解决数据同步、流量调度和冲突合并,运维复杂度成倍上升。只有当业务对中断时间的要求高到单机房切换也来不及,并且团队有相应运维能力时才考虑。

降级和熔断有什么区别?

熔断是被动保护:下游错误率或延迟超阈值时自动断开,避免雪崩,恢复靠半开探测。降级是主动取舍:提前决定哪些功能可以被牺牲,返回简化结果或缓存值。两者通常配合用——熔断触发后走哪条路,就是降级策略。

怎么判断架构该不该重构?

看瓶颈位置和改动成本。如果每次扩容都要改代码、故障都出在同一处、且已经影响到业务指标,就值得动。如果只是担心未来量会涨,先把监控和水位告警做起来,等比数据出来再决定,避免为想象中的流量买单。

本文依据各平台公开文档与官方公告整理,只做中文归纳;具体做法、参数与限制以官方文档为准。