
简介一份聚焦大型网站高性能、高并发与高可用架构设计的系统化文档面向具备一定IT基础、关注系统架构与性能优化的工程师及架构师帮助梳理从架构目标、模式选型到分层优化的完整思路。内容围绕大型网站特性展开覆盖分层、分割、分布式部署、集群、缓存与异步等架构模式并延伸至前端、浏览器、应用层、代码层和存储层的性能优化以及高可用保障、可伸缩与可扩展设计、安全管理和电商网站架构演进案例。压缩包共1个docx文件约3.19MB便于集中阅读与检索。已有1124人学习。读者可从中获得架构设计原则、各层级优化策略、冗余备份与失效转移思路以及结合案例理解系统成长轨迹的参考适合用于大规模扩展前的方案梳理与高并发场景下的架构复盘。1. 从一台服务器到七层逻辑架构这份指南解决的是「没人算过」的问题某次大促前 40 分钟监控面板上的 QPS 从 3000 直接顶到 9000应用服务器 CPU 打到 95%数据库连接池在 30 秒内被占满前端开始大面积超时。事后复盘崩掉的不是某段代码逻辑而是没人算过单机到底能扛多少、缓存整批失效时兜底路径是什么、主库挂了谁接管。这份高性能、高并发、高可用的架构设计指南讲的就是把这些「没人算过」的东西提前算清楚十种架构模式的适用边界前端到存储五层的性能优化落点容量预估的换算公式多级缓存与读写分离的参数口径以及可用性指标的拆解方式。它面向的是正在把单体系统往分布式推进的团队——架构师拿它做选型对照后端负责人拿它对部署图运维拿它写演练清单。下面按我实际落地的顺序拆。2. 架构模式落地十种模式里真正决定成败的是分层、集群和缓存指南列了十种架构模式分层、分割、分布式、集群、缓存、异步、冗余、安全、自动化、敏捷。刚接触的人容易把它当成十个并列选项实际用起来不是这么回事——分层和分割决定系统骨架分布式和集群决定能不能扛住量缓存和异步决定响应时间冗余决定故障时能不能活下来剩下的安全、自动化、敏捷是长期维护成本。这一章按「骨架 → 扩容 → 提速」的顺序拆每个环节给出可对照的部署参数。2.1 七层逻辑架构的职责边界与一次请求的完整路径指南给的七层逻辑架构是客户层、前端层、应用层、服务层、数据层、大数据存储层、大数据处理层。很多人看完记不住是因为没把一次真实请求串进去。一个用户点开商品详情页路径大致是客户端发起请求 → DNS 解析到最近接入点 → CDN 命中静态资源直接返回 → 未命中的动态请求落到反向代理 → 反向代理按路径分发到应用集群 → 应用层调服务层的商品服务、库存服务、评价服务 → 服务层查缓存缓存未命中再落数据库 → 埋点日志异步写入大数据存储层离线任务在夜里跑批。把这条路径画出来每一层的职责边界就清楚了层级主要职责典型承载常见单点风险客户层请求发起、本地缓存浏览器、移动端客户端版本碎片化导致接口兼容问题前端层静态资源、接入调度CDN、DNS 轮询、反向代理DNS 缓存未过期切流不生效应用层业务编排、页面渲染应用集群 负载均衡Session 未外置扩容后登录态丢失服务层公共能力复用RPC 服务集群无超时、无降级一个慢服务拖垮全链路数据层持久化、读写分离关系库主备、分布式缓存、分布式文件系统主从延迟导致写后读不到大数据存储层日志与行为数据归集消息队列 批量落地采集端阻塞业务线程大数据处理层离线与实时计算批处理、流式计算与在线库争抢 IO这张表我一般贴在部署文档第一页。它的价值不在分类而在于「每一层都要能回答这一层挂了上一层的表现是什么」。答不上来的那一行就是你的隐藏单点。比如应用层如果还留着本地 Session那负载均衡就等于随时可能把用户踢下线——这类问题在压测时完全看不出来只有真实扩容时才翻车。还有一个容易忽略的点层数不是越多越好。七层是给千万级用户规模准备的十万级用户硬套七层运维成本会先把你压垮。判断标准很简单如果某一层存在的唯一理由是「未来可能会用到」那它现在就不该存在。2.2 分层与分割先切业务还是先切读写指南里分层是按「应用层、服务层、数据层、管理层、分析层」横切分割是按「业务/模块/功能」纵切。实际动手时第一个问题永远是先按业务拆还是先按读写拆。我的经验是先业务后读写理由是可逆性不一样。业务拆分一旦上线团队边界、发布流程、数据库归属都跟着变回退成本极高读写分离只是在数据访问层加一层路由改错了改回来就行。所以顺序应该是先用垂直切分把系统按业务域拆成产品、购物、支付、评论、客服、接口六个子系统再对其中读多写少的子系统做读写分离和分表。业务拆分的同时必须做等级定义这是指南里很实用但常被跳过的一步。按对交易的影响划分核心系统是商品、购物、支付非核心系统是评论、客服、对接外部系统的接口子系统。这个分级不是给领导看的是给降级开关用的——大促流量打满时先关评论、关推荐、关客服入口把线程池和数据库连接全部让给下单链路。没有分级出事时就会变成「全都降级等于都没降级」。拆完之后还有个坑公共代码。六个子系统各复制一份用户校验、鉴权、工具类三个月后就是六个版本。指南给的方向是把用户、订单、支付、安全这些被反复引用的能力抽成服务层的公共模块通过 RPC 调用而不是复制代码。抽取的判据我一般用「被三个以上子系统引用且变更频率低于每月一次」低于这个门槛就先忍一忍抽早了会变成分布式单体。2.3 集群与负载均衡四层和七层的选型参数应用层的高可用靠集群集群的前提是负载均衡。这里有个很实际的选型分歧四层负载均衡按目标地址和端口转发路径短、性能高但看不懂报文内容七层负载均衡能解析 HTTP 头可以按 URL 做动静分离、按 Header 做灰度路由代价是多一次完整的连接建立与解析。什么时候用哪个我的判断是这样的流量入口的第一跳南北向流量用四层因为它只做转发不做解析能扛住突发系统内部服务之间的调用东西向流量用七层因为要做路径分发和灰度标记。原文提到七层代理可以做动静分离这个能力在带宽紧张时非常值钱——静态资源和动态接口走不同后端静态资源直接由代理返回或者转向对象存储应用服务器只处理业务请求。下面这段配置是我在项目里最常用的七层反向代理模板upstream app_pool { least_conn; # 按当前连接数最少的节点分发比轮询更适合长请求 server 10.0.0.11:8080 max_fails3 fail_timeout10s weight1; server 10.0.0.12:8080 max_fails3 fail_timeout10s weight1; server 10.0.0.13:8080 max_fails3 fail_timeout10s weight1 backup; # 备用节点主节点全挂才接流量 keepalive 128; # 到后端的空闲长连接池大小 } server { listen 80; location /api/ { proxy_pass http://app_pool; proxy_http_version 1.1; # 必须显式声明 1.1否则 keepalive 不生效 proxy_set_header Connection ; # 清空短连接头长连接才能复用 proxy_connect_timeout 1s; # 建连超时压到 1s避免卡在半开连接上 proxy_read_timeout 3s; # 读超时 3s超过直接换下一台 proxy_next_upstream error timeout http_502 http_504; proxy_next_upstream_tries 2; # 最多换 2 台防止重试放大流量 } location /static/ { root /data/www; expires 7d; # 静态资源交给代理直接返回不打到应用服务器 } }几个参数值得单独说。keepalive 128如果不配proxy_http_version 1.1和空的Connection头是不会生效的这是最常见的配置失效原因配置看着没问题但后端 TIME_WAIT 依然堆到几万。max_fails3 fail_timeout10s表示 10 秒内失败 3 次就摘除节点10 秒后重新试探这个时间窗太短会导致抖动节点反复进出太长会导致故障恢复慢10 秒是折中值慢接口多的系统要调到 30 秒。proxy_next_upstream要特别注意它只对幂等请求开重试。GET 重试没问题POST 订单接口重试会直接造成重复下单所以订单路径要么单独配一个不重试的 location要么在应用层用幂等 token 兜住。这个点我在第 5 章还会展开。3. 容量预估换算从 1000 万注册用户倒推出每秒 8340 次并发架构设计里最容易被跳过又最贵的一步是容量预估。跳过它的后果是要么机器买少了大促现场扩容要么买多了一年几十万闲置。指南给了一套从注册用户数倒推服务器的算法我把它整理成可以反复跑的脚本。3.1 PV、UV 到 QPS 的四步换算预估链条是这样的注册用户数 → 日均 UV → 每日 PV → 集中访问时段的每分钟请求数 → 每秒 QPS → 峰值 QPS → 服务器台数。原文的例子是 1000 万注册用户按二八原则日均 UV 约 200 万每人每天点击 30 次PV 就是 6000 万80% 的访问集中在 20% 的时间里即 4.8 小时承载 4800 万次访问换算到每分钟约 16.7 万次每秒约 2780 次再乘以 3 倍的峰值系数得到每秒 8340 次。这套算法有一个隐含假设访问在 4.8 小时内是均匀分布的。真实业务不是这样活动开场那一分钟可能就吃掉半小时的流量。所以我一般会在这个基础上再加一层「瞬时倍数」把开场前 60 秒单独算一遍。下面是完整脚本def estimate_capacity(reg_users, uv_ratio, pv_per_user, busy_hours_ratio, busy_pv_ratio, peak_factor, burst_factor, qps_per_node): 容量预估从注册用户倒推服务器台数 reg_users 注册用户数 uv_ratio 日活占注册用户比例成熟业务 0.15~0.25 pv_per_user 人均日点击次数 busy_hours_ratio 集中访问时间占比二八原则取 0.2 busy_pv_ratio 集中时段承载的 PV 占比取 0.8 peak_factor 日常峰值系数活动期取 2~3 burst_factor 开场瞬时倍数秒杀场景取 5~10 qps_per_node 单机可承载 QPS需压测校准不能拍脑袋 uv reg_users * uv_ratio pv uv * pv_per_user busy_seconds 24 * busy_hours_ratio * 3600 avg_qps pv * busy_pv_ratio / busy_seconds peak_qps avg_qps * peak_factor burst_qps peak_qps * burst_factor return { 日均UV: int(uv), 日PV: int(pv), 平常QPS: round(avg_qps, 1), 峰值QPS: round(peak_qps, 1), 瞬时QPS: round(burst_qps, 1), 平常所需台数: -(-int(peak_qps * 0.6) // qps_per_node), # 60% 流量走弹性扩容 峰值所需台数: -(-int(peak_qps) // qps_per_node), 瞬时所需台数: -(-int(burst_qps) // qps_per_node), } print(estimate_capacity(10_000_000, 0.2, 30, 0.2, 0.8, 3, 3, 300))跑出来的峰值 QPS 是 8340跟原文一致瞬时 QPS 按 3 倍算是 25020这个数字决定了秒杀要不要单独做队列削峰——因为靠加机器扛住 25000 QPS 是极其不划算的按单机 300 QPS 要 84 台而这 84 台只在开场那一分钟有用。参数里最需要谨慎的是qps_per_node。它不能写成 300 就完事必须来自真实压测。压测方法是用生产同等配置的机器跑核心接口的混合流量逐步加压到 P99 响应时间超过 200ms 或者 CPU 到 70%此时的 QPS 才是可用的单机容量。3.2 「单机 300 并发」的含义并发数、QPS 与线程数不是一回事指南里写「按一台 Web 服务器支持每秒 300 个并发计算」这句话是整篇文档里最容易被误读的一句。并发连接数、并发线程数、QPS 是三个不同的量混用会导致机器数量差出好几倍。用利特尔法则换算并发数 QPS × 平均响应时间。假设单机 QPS 300、平均响应时间 100ms那么同时在处理的请求数是 300 × 0.1 30 个线程。如果平均响应时间涨到 200ms同样 300 QPS 就需要 60 个线程如果响应时间涨到 1 秒就需要 300 个线程而默认线程池只有 150 个这时候 QPS 不但上不去还会因为排队把响应时间进一步拉长形成正反馈崩溃。所以压测时必须同时记录三个数字QPS、P99 响应时间、活跃线程数。三者对不上说明压测模型有问题。常见的错误是用固定并发数压测却不看响应时间变化——并发从 50 加到 100QPS 只从 280 涨到 300响应时间从 80ms 涨到 260ms这说明系统已经在拐点之后了继续加压只会更慢。线程池配置也要跟着响应时间走。maxThreads设成 150 还是 500取决于你的接口平均耗时短接口 50ms150 足够撑 3000 QPS长接口 500ms设到 500 也只是把排队从队列挪到线程里根治方法是把长耗时操作异步化而不是加线程。这也是指南里「异步」模式的真正用处——把同步等待的链路砍掉比堆机器便宜得多。3.3 70/90 原则与资源水位红线指南给的容量红线是CPU 平常维持 70% 左右、高峰到 90%内存和 IO 同理。这条原则的意义在于它定义了「什么时候该扩容」而不是「什么时候该报警」。70% 是安全边界。因为一旦超过 80%单机响应时间开始非线性上升队列积压会让重试请求叠加进来很快从 85% 冲到 100%。90% 是硬红线触线就必须有自动扩容或者限流兜底不能靠人盯着面板手动加机器——人从看到报警到机器上线最快也要 5 分钟而流量洪峰 30 秒就能把系统打死。落到监控指标上我会给每个关键资源配三条线资源观察线扩容线熔断线应用 CPU60%75%90%应用内存老年代70%80%92%数据库连接数 / 上限50%70%85%缓存命中率92%88%80%接口 P99 响应时间150ms250ms500ms缓存命中率跌破 92% 就要查原因因为命中率的下降通常是渐进的等掉到 80% 时数据库早就被打穿了。这个指标比 CPU 更早发出信号是我最看重的一个。4. 多级缓存与读写分离把 28 原则翻译成 TTL、锁和路由键性能优化里投入产出比最高的是缓存最容易出事故的也是缓存。指南给的原则是「80% 的访问落在 20% 的数据上」所以只缓存热点数据就能覆盖绝大部分请求。问题在于「热点」是会漂移的缓存失效的方式、失效瞬间的并发、以及失效之后打到哪一层才是决定系统稳不稳的关键。4.1 两级缓存的结构与读写比门槛指南建议两级一级本地缓存放数据字典和基本不变的热点数据二级分布式缓存放全量可缓存数据。查询顺序是本地 → 分布式 → 数据库任一层命中就返回。为什么要有本地这一层因为分布式缓存再快也有一次网络往返通常 0.5~1ms而本地内存读是几十纳秒。对于每秒几万次访问的数据字典地区、类目、配置项这一层省下的网络开销非常可观。代价是一致性问题本地缓存各节点独立更新后不同节点看到的值可能不同所以要严格限制本地缓存的适用范围——不可变或者变化频率低于分钟级的数据且变更后能接受短暂的不一致。分布式缓存则承担热点业务数据比如商品详情、用户概要。这里有个门槛问题指南说读写比达到 4:1 就可以考虑上缓存理论门槛是 2:1。这个 4:1 指的是一个数据「被读次数」和「被写次数」的比值。写多读少的数据上缓存会出现缓存刚写好就被下一次写操作失效掉命中率极低反而多了一次缓存往返和一次失效广播。我一般的判断标准是三条同时满足才上缓存读请求占比超过 80%、单条数据被重复读取的次数在 TTL 内超过 10 次、数据一致性要求不是强一致。第三条最容易被忽略比如账户余额就不能走普通缓存只能走带版本号的短 TTL 缓存或者直接读主库。4.2 穿透、击穿、雪崩三个问题对应三组参数这三个词经常被混着说但它们的原因和对策完全不同。穿透是查一个数据库里也不存在的 key缓存永远不命中请求全部打库。原因通常是参数被构造比如用不存在的 ID 反复请求或者业务上真的查了空记录。对策是缓存空值并给一个短 TTL60 秒左右或者在前面加布隆过滤器拦截明显不存在的 key。击穿是单个热点 key 过期瞬间大量请求同时发现缓存没了一起去查库。对策是重建加锁只放一个请求去查库其余请求短暂等待后重试本地缓存。雪崩是大量 key 在同一时刻集体过期或者缓存集群整体不可用。对策是 TTL 加随机抖动、分批预热、以及缓存的降级开关。下面这段是带互斥重建和抖动 TTL 的读取逻辑import random, time def get_product(pid): key fp:detail:{pid} # 一级本地缓存只给超高热点数据TTL 短30 秒强制回源一次 local local_cache.get(key) if local is not None: return local # 二级分布式缓存 data redis.get(key) if data is not None: local_cache.set(key, data, ttl30) return data # 空值缓存防穿透TTL 必须短否则商品上架后要等很久才能被看到 if redis.get(f{key}:null): return None lock_key flock:{key} # nxTrue 保证只有一个请求拿到锁ex3 秒防止持锁进程挂掉导致死锁 if redis.set(lock_key, 1, nxTrue, ex3): try: data db.query(select * from product where id%s, pid) if data is None: redis.set(f{key}:null, 1, ex60) return None # 基础 TTL 1800 秒抖动 ±10%把集体过期打散成 3 分钟窗口 ttl 1800 random.randint(-180, 180) redis.set(key, data, exttl) local_cache.set(key, data, ttl30) return data finally: redis.delete(lock_key) # 无论成功失败都要释放避免锁泄漏 else: time.sleep(0.05) # 没抢到锁的请求等 50ms 后重试 data redis.get(key) if data is None: return None # 兜底返回降级值绝不直接打库 return data几个参数的取法ex3的锁超时要大于一次数据库查询的 P99否则会出现锁过期了查询还没回来第二个请求又进来查库random.randint(-180, 180)的抖动幅度取 TTL 的 ±10%太小起不到打散作用太大会影响命中率空值 TTL 的 60 秒是经验值取决于业务对「新数据可见性」的容忍度商品上架类业务可以压到 30 秒。最后那段return None是关键。很多实现里没抢到锁的请求会直接去查库结果就是锁形同虚设热点 key 失效瞬间照样一堆请求打到数据库。宁可返回降级内容也不要让请求穿透到最后一层。4.3 读写分离与分库分表路由键选错后面全白干单库撑不住时第一刀是读写分离主库承接写和强一致读从库承接普通读通过主备同步。第二刀是分库分表水平切分把一张大表按某个维度拆开垂直切分把不同业务的表分到不同库。读写分离最现实的问题是主从延迟。同步复制保证强一致但写性能腰斩异步复制性能好但有延迟通常几十毫秒高峰期能到几百毫秒。业务上的表现是「刚下单刷新订单列表看不到」。解决办法不是改成同步复制而是识别出必须读主库的场景写操作之后的 N 秒内、支付回调、库存扣减校验这几类请求在代码里打上强制走主库的标记。我一般把窗口设成 1 秒覆盖 99% 的延迟情况。分库分表的核心决策是路由键。选错了跨库 join 和分布式事务会把你拖死。经验规则路由键必须是最常用的查询维度且尽量保证一个业务操作只落一个库。电商场景用买家 ID 做订单表路由键因为用户查自己订单是最高频操作商家后台按商家维度查是次要场景可以用异构索引表解决而不是改路由键。路由算法上取模简单但扩容时要全量迁移一致性 Hash 加了虚拟节点能减少迁移量但实现复杂度上升。下面是最小可用的取模路由import hashlib def shard_of(uid, shards16): 按用户 ID 路由到 16 个库返回 0~15 # 用 md5 而不是 python 内置 hash内置 hash 在不同进程间不稳定 digest hashlib.md5(str(uid).encode()).hexdigest() return int(digest, 16) % shards # 扩容到 32 库时只有约一半的数据需要迁移但取模做不到 # 一致性 Hash 的虚拟节点版本才能把迁移量压到 1/N注意这里用 md5 而不是 Python 内置的hash()内置 hash 对字符串在每个进程里加盐不同多实例部署会导致同一用户路由到不同库这是一个很难排查的坑。分片数选 16 而不是 10 的倍数是为了避免后续扩容到 32、64 时出现倍数关系导致的分布不均。5. 避坑与排查上线前我必查的 5 个血泪记录架构图再漂亮落地时踩的坑都在细节里。这一章记录的是我在几个项目里真实遇到过、并且反复在新项目里复查的五类问题。每条按现象、原因、解决三段写建议对照自己的系统过一遍。5.1 线上问题的定位顺序先入口再依赖后资源故障发生时最浪费时间的不是修复是找方向。我的固定顺序是四步先看入口 QPS 和错误率曲线确认是流量涨了还是错误涨了再看依赖的响应时间确认是自身问题还是下游拖累再看资源水位CPU、连接池、GC确认瓶颈位置最后才看单机日志。这个顺序的理由是错误率突增但 QPS 平稳八成是依赖挂了或者发布引入了 bugQPS 突增但错误率平稳是流量问题看限流和扩容QPS 和错误率同时突增通常是雪崩前兆先限流再排查。反过来如果一上来就翻单机日志很容易被大量超时堆栈误导以为是代码问题实际是数据库连接池被打满。排查时还有一个必备动作确认监控本身是可信的。有一次故障排查了两小时最后发现是采集 agent 自己挂了面板上的曲线是最后一次上报的残留值。从那以后我养成了一个习惯每次故障先找一个已知正常的指标做对照确认监控链路是活的。5.2 五条踩坑记录第一条健康检查只探端口进程假死照样接流量。现象是负载均衡把流量分到一台机器那台机器上所有请求都超时但面板显示节点状态正常故障持续了十几分钟才被人发现。原因是健康检查配的是 TCP 端口探测进程还在、端口还在监听但内部线程池已经被打满请求全部排队。解决是把检查改成应用层的/health接口且这个接口必须校验线程池水位和数据库连通性连续 3 次失败或超时 1 秒就摘除节点。这个改动做完之后单机假死对用户的影响从分钟级降到秒级。第二条批量预热用了同一个 TTL晚高峰整点集体失效。现象是每天晚上 8 点整数据库 QPS 突然翻十倍持续十几秒后恢复。原因是凌晨预热时用一个 for 循环批量写入缓存所有 key 的 TTL 都设成 12 小时到期时间自然全部落在晚 8 点。解决是预热时给每个 key 的 TTL 加随机抖动同时把预热时间打散到多个时间点。这个问题的隐蔽之处在于它只在流量高峰期暴露低峰期集体失效也没什么影响很容易被当成「偶发慢查询」忽略过去。第三条多层重试叠加下游被打成三倍流量。现象是一个下游服务超时后上游重试三次结果那个服务彻底挂掉原本只是慢接口变成不可用。原因是调用链上有三层都配了自动重试2 的 3 次方就是 8 倍放大而且重试打在了非幂等的下单接口上产生了重复订单。解决是只在链路最外层保留一次重试中间层一律不重试非幂等接口显式关闭重试开关同时在下游加熔断错误率超过 50% 直接快速失败不再把请求送过去。这个坑的教训是重试是给偶发抖动准备的不是给故障准备的。第四条应用连接池总量超过数据库连接上限。现象是高峰期应用侧报连接超时数据库侧报连接数超限。原因是 30 台应用服务器每台连接池最大 50 个合计 1500 个潜在连接而数据库的最大连接数只配了 800。平时用不到上限所以没暴露流量一涨瞬间打满。解决是按数据库上限倒推800 个连接留 20% 给运维和批处理剩下 640 除以 30 台每台配 20 个同时给连接池设获取超时 1 秒超过就快速失败而不是无限排队。这个数字必须在扩容时同步重算否则每加一批机器就逼近一次上限。第五条全链路超时时间倒挂。现象是大促时前端报 30 秒超时但用户等了半分钟看到的还是错误页。排查发现配置是前端 30 秒、网关 10 秒、应用 5 秒、下游服务 8 秒——下游比上游还长意味着应用已经放弃了下游还在继续跑白白占用资源。解决是超时时间必须从最下游往上递增每层留 20% 余量下游 800ms、应用 1s、网关 1.2s、前端 1.5s。这样任何一层超时上层都能及时拿到失败结果并触发降级而不是干等。6. 把三个九跑成演练清单灰度、混沌与压测的收口动作可用性指标不是写在 PPT 上的是演练出来的。先把数字换算清楚99.9% 一年允许 8.76 小时不可用99.99% 是 52.6 分钟99.99% 往上每加一个 9允许的故障窗口就缩小一个数量级。多数业务做到 99.99% 已经是成本和收益的平衡点再往上要靠多机房和多活投入会陡增。知道自己站在哪个 9 上之后落地动作是三步。第一步是灰度新版本先放 5% 流量观察 15 分钟的错误率和 P99无异常再放到 50%、100%每一档都要有明确的回滚触发条件。第二步是混沌演练主动杀掉一个应用节点、断开一个从库、清空一次缓存看系统的自动恢复能力——注意要在流量低谷做并且提前通知。第三步是压测用生产流量的录制回放打到 3 倍峰值重点看扩容触发时间和限流是否生效。演练前我会跑一遍节点健康巡检确认所有节点真的健康避免把「本来就有问题」当成演练结果#!/usr/bin/env bash # 演练前置巡检确认所有节点 /health 返回 200且响应时间低于 100ms # 用法./precheck.sh nodes.txt NODES$(cat $1) FAIL0 for node in $NODES; do # -m 1 建连超时 1s-w 输出总耗时%{http_code} 取状态码 result$(curl -s -o /dev/null -m 1 -w %{http_code} %{time_total} http://${node}/health) code$(echo $result | awk {print $1}) cost$(echo $result | awk {print $2}) # 状态码非 200 或耗时超过 0.1 秒都记为异常 if [ $code ! 200 ] || (( $(echo $cost 0.1 | bc -l) )); then echo [FAIL] ${node} code${code} cost${cost}s FAIL$((FAIL 1)) fi done echo 异常节点: ${FAIL} exit $FAIL这个脚本的价值不在多复杂而在于把「巡检」变成一个有退出码的动作可以直接挂到发布流水线里异常节点不为零就阻断发布。参数上1 秒建连超时对应内网环境跨机房要放宽到 2 秒100ms 的阈值对应应用层的健康检查接口如果这个接口本身要查数据库阈值要按实际 P99 调整否则会误报。最后说一个我踩过的坑。有次演练我按计划杀掉一个应用节点系统确实没受影响于是结论是「高可用没问题」。但两周后真实故障时挂的是一整个机柜的交换机同机柜的四个节点同时掉线负载均衡剩下的容量不够直接雪崩。单节点故障和高关联故障完全是两回事演练必须包含「按机柜/可用区批量摘除」的场景。从那以后我每次做演练都强制走一遍关联性故障的用例而不是只杀一个 Pod 交差。希望帮到你。本文还有配套的精品资源点击获取