组件与框架设计面试
组件与框架设计面试
中间件
【中等】如何设计一个网关?⭐⭐⭐⭐
🎯 目标等级:L2 | ⏱ 建议用时:12 min | 🏷 标签:分布式设计 / 网关
💎 关键结论
网关是系统的统一流量入口,承担路由转发、认证鉴权、限流熔断、协议转换、灰度发布、可观测性六大职责。选型上主流是异步非阻塞模型(Spring Cloud Gateway/自研 Netty),实践中常见两层网关:OpenResty 做流量入口 + 自研网关做业务网关。网关自身 RT 控制在 10ms 内,热数据放本地缓存,扩展用插件化 Filter 链。
⚡记忆卡片
- 口诀:统一入口六职责,两层网关内外分,插件 Filter 链可扩展
- 关键词:路由鉴权 / 限流熔断 / 协议转换 / 灰度发布 / TraceId
- 链路:客户端 → OpenResty 入口层 → 业务网关(鉴权/限流/转换)→ 后端服务
📖 核心知识
核心职责
| 职责 | 说明 |
|---|---|
| 路由转发 | 动态路由、负载均衡、跨集群转发 |
| 认证鉴权 | Token 校验、签名验证、IP 黑白名单 |
| 限流熔断 | 令牌桶/漏桶限流、熔断降级,保护下游不被打垮 |
| 协议转换 | 对外 HTTP/JSON,对内 RPC(Dubbo/gRPC),请求聚合与拆分(BFF) |
| 灰度发布 | 按用户、Header、权重路由到不同版本 |
| 可观测性 | 统一日志、监控埋点、链路追踪(TraceId 在网关生成并透传) |
技术选型
| 方案 | 底层技术 | 编程模型 | 特点与适用场景 |
|---|---|---|---|
| Spring Cloud Gateway | Netty + Reactor | 异步非阻塞 | 与 Spring 生态集成好,社区主流选择 |
| Zuul 1.x | Servlet(Tomcat) | 同步阻塞 | 线程模型有瓶颈,已停止维护 |
| Nginx/OpenResty | epoll + Lua | 异步事件驱动 | 性能极高,适合流量入口与静态资源 |
| 自研网关 | Netty | 异步非阻塞 | 大厂普遍选择,协议定制能力强 |
实践中常见两层网关:OpenResty 做流量入口(静态资源、IP 限流),自研网关做业务网关(鉴权、协议转换)。
性能设计
- 单实例基于 Netty 异步非阻塞 IO,可支撑数万 QPS;长连接容量需控制在十万级。
- 网关自身 RT 应控制在 10ms 以内(不含后端耗时),避免成为全链路瓶颈。
- 路由规则、黑白名单等热数据放本地缓存 + 变更事件刷新,避免每请求查库。
扩展性设计(插件化):将路由、鉴权、限流、日志等功能做成可插拔的 Filter 链,通过 SPI 或配置动态加载,业务方无需修改网关源码即可扩展;参考 Spring Cloud Gateway 的 GlobalFilter/GatewayFilter 机制与责任链编排。
容错设计:后端超时重试(仅幂等请求)、熔断降级;网关自身集群多可用区部署 + 自动扩容;网关故障影响全局,发布必须支持热更新路由、灰度与快速回滚。
🔬 扩展知识
详情
- 【L3】网关限流与应用限流如何分工?网关层限总量与恶意流量(IP/用户维度,保护整体);应用层按接口/依赖维度限流(保护自身与下游);两层阈值要匹配:网关阈值 ≥ 应用集群总容量。
- 【L3】网关的高可用为何比后端更苛刻?网关故障影响全局流量,需多 AZ 部署 + 自动扩容,配置变更支持热更新与秒级回滚,路由等热数据本地缓存兼做中心不可用时的兜底。
📚 延伸阅读:网关
🔀 发散问题
- Q:网关上的限流熔断具体怎么实现? → 四种限流算法 + 分层流控体系,见本文档「如何实现流量控制」。
- Q:网关对内的 RPC 协议如何设计? → 协议、序列化、多路复用是 RPC 框架的核心,见本文档「如何设计一个 RPC 框架」。
【困难】如何设计一个 RPC 框架?⭐⭐⭐⭐
🎯 目标等级:L2 | ⏱ 建议用时:20 min | 🏷 标签:分布式设计 / RPC
💎 关键结论
RPC 框架自下而上分三层能力:基础三模块(通信传输、协议与序列化、动态代理)解决 P2P 调用;集群模式需要服务治理(服务发现、负载均衡、路由、容错、配置管理);扩展性靠 SPI 微内核 + 插件架构,形成核心功能体系 + 插件体系两大体系。关键设计点:序列化选型、IO 线程绝不做业务、超时重试仅幂等、优雅停机。
⚡记忆卡片
- 口诀:通信协议加代理,治理集群靠发现,SPI 插件保扩展
- 关键词:动态代理 / 序列化 / 服务发现 / 容错策略 / 优雅停机
- 链路:P2P 三模块 → 集群加服务治理 → SPI 微内核插件化 → 核心 + 插件两大体系
📖 核心知识
设计一个 RPC 框架,可以自下而上梳理一下所需要的能力:
- 通信传输模块:RPC 本质上就是一个远程调用,那肯定就需要通过网络来传输数据。
- 协议模块:传输的数据如何定义,就需要通过协议和序列化方式来确定。此外,为了减少传输数据的大小,可以加入压缩功能。
- 代理模块:为了屏蔽用户的感知,让用户更聚焦于自身业务,需要引入动态代理来托管远程调用。
以上,是一个 RPC 框架的基础能力,适用于 P2P 场景。
但是,如果面对集群模式,以上能力就不够了。同一个服务可能有多个提供者。消费者选择调用哪个提供者?消费者怎么找到提供者的访问地址?请求提供者失败了如何处理?这些都依赖于服务治理的能力。
服务治理,需要很多个模块的能力:服务发现、负载均衡、路由、容错、配置管理等。

具备了这些能力就万事大吉了吗?RPC 框架很难一开始就面面俱到,但作为基础能力,在实际应用中,难免会有定制化的要求。这就要求 RPC 框架具备良好的扩展性。
通常来说,框架软件可以通过 SPI 技术来实现微内核+插件架构。根据依赖倒置原则,框架应该先将每个功能点都抽象成接口,并提供默认实现。然后,利用 SPI 机制,可以动态地为某个接口寻找服务实现。
加上了插件功能之后,我们的 RPC 框架就包含了两大核心体系——核心功能体系与插件体系,如下图所示:

失效场景:注册中心不可用时,若客户端无本地地址缓存兜底,所有调用直接失败——必须设计"注册中心挂了但存量调用可用"(本地缓存 + 降级直连)。
🔬 扩展知识
详情
- 【L3】序列化选型:JSON 可读性好但体积大性能一般,适合开放接口;Protobuf 体积小、序列化快、需 IDL 先行,适合内部高性能场景;Hessian2/Java 原生序列化兼容性好但各有缺陷(原生序列化性能差且有安全风险)。实测同数据 Protobuf 体积约为 JSON 的 1/3~1/5。
- 【L3】网络与线程模型:Netty Reactor,IO 线程只做编解码不做业务;请求-响应通过 requestId 关联(CompletableFuture)实现连接复用与多路复用;耗时业务提交业务线程池,避免阻塞 IO 线程。
- 【L3】超时与重试:超时设毫秒级(P99 的 2-3 倍);仅幂等请求自动重试,重试需切换到其他提供者,指数退避。
- 【L3】容错策略(Dubbo Cluster 五种):Failover 失败切换(默认)、Failfast 快速失败(写操作)、Failsafe 忽略异常(日志类)、Failback 后台重试、Forking 并行调用取最快。
- 【L3】长连接 vs 短连接怎么选?高频调用用长连接 + 多路复用(省去频繁建连的 TCP 握手开销,内网 RTT 亚毫秒级下握手成本占比高);低频跨机房调用可用短连接简化资源管理;连接池大小按"并发请求数 / 单连接复用度"估算。
- 【L3】负载均衡策略有哪些?随机/轮询(实例同质)、加权轮询(配置异构)、最少活跃调用数(自适应慢节点)、一致性哈希(有状态/会话亲和);配合预热期权重递增,避免新实例被瞬时打满。
- 【L4】序列化兼容性如何保证?Protobuf 字段只增不删、不复用已废弃的 field number;接口方法新增参数用可选字段;版本不兼容变更通过新接口 + 双跑灰度迁移,禁止直接改存量字段语义。
🏭 实战场景
详情
方案权衡与踩坑:多路复用单连接吞吐高,但一条连接的抖动会影响其上所有请求(连接级队头阻塞),需连接池 + 快速重建连接缓解;自定义二进制协议性能优于 HTTP,但跨语言/调试成本高,跨团队开放场景应保留 HTTP 通道。曾有线上事故:IO 线程中同步调用了慢业务逻辑(查 DB),所有请求编解码被阻塞,全服务雪崩——IO 线程绝不做业务,耗时操作必须提交业务线程池。
优雅停机:先从注册中心下线,等待存量请求处理完(如 10s)再关闭,避免发布期间报错。可观测性:内置链路追踪埋点(TraceId 透传)、RT/成功率指标上报、慢调用采样。
场景:公司自研 RPC 框架日均调用 50 亿次,近期多次出现"下游发版瞬间上游报错 1%"。根因是发布时提供者直接断连,存量请求失败 + 注册中心下线通知有延迟窗口(秒级)。方案:提供者优雅停机——先从注册中心下线,等待订阅方地址更新传播(如 10s)→ 拒绝新请求、处理完存量(设置等待上限)→ 再断连;消费者侧:调用失败自动重试到其他提供者(仅幂等请求)+ 本地摘除故障地址;启动侧:延迟注册 + 预热期权重递增。验收指标:发布窗口错误率 < 0.01%。
⚠️ 常见误区
详情
常见误区:
- ❌ "注册中心高可用,调用就不会失败" → 注册中心挂了时若无本地地址缓存兜底,所有新调用直接失败,必须设计本地缓存 + 降级直连。
- ❌ "超时后重试总能提高成功率" → 非幂等写操作重试会重复执行;重试还会在故障期放大下游压力,需指数退避 + 切换提供者。
- ❌ "IO 线程顺手做点业务逻辑没关系" → IO 线程被慢逻辑阻塞会导致所有请求编解码停滞,全服务雪崩。
🔀 发散问题
- Q:RPC 之上的流量治理(限流/熔断)怎么做? → 分层流控体系,见本文档「如何实现流量控制」。
- Q:服务重启/发版时的流量洪峰如何避免? → 优雅启停 + 延迟注册 + 渐进放量,见本文档「服务重启时,如何避免客户端重连引发的流量洪峰」。
【困难】如何设计一个 MQ?⭐⭐⭐⭐
🎯 目标等级:L2 | ⏱ 建议用时:15 min | 🏷 标签:分布式设计 / 消息队列
💎 关键结论
设计 MQ 按六个维度展开:消息模型(点对点/发布订阅)、存储(顺序写 + 分段 + 索引 + 刷盘策略)、推拉模型(Long Polling 折中)、高可用(主从复制 + 多副本 ISR)、可靠性(ACK + 重试 + 死信队列)、性能(分区/零拷贝/页缓存/批处理/压缩)。顺序写是吞吐量的根基,ACK 双向确认是不丢不重的底线。
⚡记忆卡片
- 口诀:模型定流向,顺序写打底,推拉长轮询,副本保高可用,ACK 防丢重
- 关键词:顺序写 / 分段存储 / ISR / 死信队列 / 零拷贝
- 链路:生产者 ACK → 顺序写分段存储 → 副本同步 → 消费者拉取 → ACK 提交 offset
📖 核心知识
消息模型设计——存什么?
| 模型 | 特点 | 适用场景 | 代表产品 |
|---|---|---|---|
| 点对点(Queue) | 一条消息只被一个消费者消费 | 任务队列 | Kafka、RabbitMQ |
| 发布订阅(Topic) | 一条消息被所有订阅者消费 | 事件广播 | Kafka、RocketMQ |
存储设计——放哪儿? 要点:顺序写 + 分段存储 + 索引 + 刷盘策略
- 顺序写(性能关键):一般采用日志文件存储,所有消息顺序追加,不分 Topic。
- 分段存储:每个文件固定大小(如 1GB),写满后建新文件。
- 索引机制:一般记录消息在日志中的 Offset。
- 刷盘策略:同步刷盘(消息落盘才返回成功,安全);异步刷盘(先写内存,批量刷盘,性能)。
推拉模型设计——怎么取?
| 模式 | 特点 | 实现方式 |
|---|---|---|
| Push | Broker 主动推,实时性高 | 长连接推送(易压垮消费端) |
| Pull | 消费端主动拉,控制性强 | 定时拉取(延迟可调) |
| Long Polling | 拉不到就等一会儿 | 综合两者优点 |
高可用设计——挂了怎么办? 要点:主从复制 + 故障转移 + 多副本
- 复制(Kafka/RocketMQ):Master 处理读写;Slave 从 Master 同步数据,Master 挂掉后接管。
- 多副本(Kafka):每个 Partition 有多个 Replica,Leader 负责读写;ISR 机制保证数据一致性。
可靠性设计——消息不丢不重 要点:ACK 机制 + 重试 + 死信队列
- 生产者 ACK:发送后等待 Broker 确认。
- 消费者 ACK:处理完才提交 offset。
- 重试队列:消费失败的消息重试 N 次。
- 死信队列:超过重试次数,人工介入。
性能设计——如何快
| 优化项 | 做法 | 效果 |
|---|---|---|
| 分区 | 分而治之+负载均衡 | 使负载在集群中尽量均衡 |
| 零拷贝 | 使用 mmap 或 sendfile | 减少数据拷贝次数 |
| 页缓存 | 充分利用 OS Page Cache | 读写性能提升 |
| 批处理 | 批量发送、批量刷盘 | TPS 大幅提升 |
| 压缩 | 消息体压缩(gzip/lz4) | 网络带宽节省 |
🔬 扩展知识
详情
- 【L3】顺序写为什么快?磁盘顺序写避免了磁头寻道(机械盘)与随机写放大(SSD),吞吐可接近内存级网络带宽上限;Kafka 把所有消息不分 Topic 顺序追加到同一日志,正是把顺序写发挥到极致。
- 【L3】Push 模式为什么实际多是"伪 Push"?Broker 无法感知消费端处理能力,直推容易压垮消费端;RocketMQ 的 Push 本质是客户端封装的长轮询拉取,兼顾实时性与消费速率控制。
- 【L4】消费端不丢不重的工程配套:幂等是底线(至少投递一次 + 业务唯一键去重),消费失败指数退避重试后进死信队列告警,事件内容只加字段不删改保持向后兼容。
🔀 发散问题
- Q:MQ 如何保证本地事务与消息发送一致? → 本地消息表/事务消息,见「如何设计领域事件与事件驱动架构」。
- Q:MQ 在分布式事务中扮演什么角色? → 可靠消息最终一致是主流方案,见本文档「如何实现分布式事务」。
- Q:削峰后的积压会不会反噬? → 消费速度 < 生产速度时积压会引发磁盘写满/重投风暴,见「如何设计一个高并发系统」。
分布式组件
【困难】如何设计一个分布式锁?⭐⭐⭐⭐
🎯 目标等级:L2 | ⏱ 建议用时:18 min | 🏷 标签:分布式设计 / 分布式锁
💎 关键结论
分布式锁四大要求:互斥、高可用、防死锁、可重入。选型看场景:Redis 性能高但主从切换可能丢锁,ZooKeeper/etcd 一致性强但性能低,数据库最简单但性能最差。Redis 锁的关键细节:原子 SET NX PX + 唯一标识 + Lua 释放 + 看门狗续期。要清醒认识:锁只能降低双写概率,关键写操作需幂等/唯一索引兜底。
⚡记忆卡片
- 口诀:原子加锁、标识防误删、Lua 释放、看门狗续期
- 关键词:SET NX PX / requestId / Lua 脚本 / 看门狗 / 临时顺序节点
- 链路:原子加锁 → 持锁执行业务 → 看门狗续期 → Lua 校验身份释放
📖 核心知识
分布式锁的核心要求:互斥性(同一时刻只有一个客户端持有锁)、高可用(锁服务不能单点)、防死锁(持锁方宕机后锁能自动释放)、可重入(按需)。
方案对比
| 方案 | 实现要点 | 优点 | 缺点 |
|---|---|---|---|
| Redis | SET key value NX PX timeout + Lua 脚本释放 | 性能高,实现简单 | 主从切换可能丢锁,需续期机制 |
| ZooKeeper | 创建临时顺序节点 + watch 监听前一个节点 | 一致性强,自动释放 | 性能较低(每次创建删除节点) |
| 数据库 | 唯一索引插入 / SELECT ... FOR UPDATE | 实现最简单 | 性能差,存在单点和死锁风险 |
| etcd | Lease 租约 + Revision 全局递增 + watch | 一致性强,租约自动续期 | 运维成本较高 |
Redis 分布式锁的关键细节(高频追问)
- 原子加锁:必须用
SET key requestId NX PX timeout一条命令,不能先 SETNX 再 EXPIRE(非原子,可能死锁)。 - value 用唯一标识(如 requestId/线程标识):释放时校验身份,防止误删他人的锁;释放必须用 Lua 脚本保证"判断 + 删除"原子性。
- 锁续期(看门狗):业务执行时间超过锁超时时间会导致锁提前失效,Redisson 用后台线程每隔 timeout/3 自动续期,解决该问题。
- 主从切换丢锁:master 加锁后未同步到 slave 就宕机,slave 升主后锁丢失。Redlock 方案尝试向 N 个独立 Redis 实例加锁取多数派,但其依赖时钟的假设存在争议(Martin Kleppmann vs Antirez 之争),强一致场景建议直接用 ZooKeeper/etcd。
ZooKeeper 实现要点
- 在锁路径下创建临时顺序节点,序号最小者持锁;其余节点 watch 前一个节点,前驱删除时收到通知重新竞争(避免羊群效应)。
- 临时节点随会话失效自动删除,天然防死锁。
工程注意事项
- 锁粒度尽量小(按业务 ID 加锁),避免全局锁成为瓶颈。
- 加锁必须设置合理超时;获取锁失败要有退避重试或快速失败策略。
- 锁只用于兜底,能用数据库乐观锁/唯一约束解决的优先用更简单的方案。
失效场景:看门狗续期依赖持锁进程存活,进程 GC 停顿超过锁超时时锁仍会被他人获取——锁本质上无法保证绝对互斥,只能降低概率,关键写操作需配合幂等/唯一索引兜底。
🔬 扩展知识
详情
- 【L3】Redlock 的争议核心是什么?Kleppmann 指出 Redlock 依赖各节点时钟大致同步的假设,GC 停顿或时钟跳变都可能破坏安全性;它只提升了"锁丢失"的概率而非消除。结论:能容忍极小概率双写就用 Redis + 幂等兜底,不能容忍就用共识类组件(ZK/etcd)。
- 【L3】分布式锁 vs 数据库乐观锁怎么选?并发低且已有 DB 的场景优先乐观锁(
UPDATE ... WHERE version = ?)或唯一索引,无额外组件依赖;跨服务/跨库互斥、高频竞争才用分布式锁。原则:能用数据层约束解决就不用锁。 - 【L4】锁超时时间设多少合理?按业务执行时间 P99 的 2~3 倍设置;无法预估时用看门狗续期;超时设过短导致锁提前失效(并发安全破坏),设过长导致持锁方宕机后恢复慢——两者矛盾用续期机制缓解。
📚 延伸阅读:分布式锁
🏭 实战场景
详情
量化与方案权衡:Redis 锁操作微秒级,单机可支撑十万级加锁 QPS,适合高频低风险场景;ZooKeeper 每次加锁创建/删除节点,性能千级 QPS,但临时节点会话失效自动释放比 Redis TTL 更可靠,适合低频强一致(如选主)。曾有线上事故:Redis 主从切换丢锁,两个实例同时执行对账任务导致数据双写错乱——对一致性敏感的任务,要么用 ZK/etcd,要么加 DB 唯一约束做最后防线。
场景:定时对账系统,5 台实例部署,每日凌晨全量对账(耗时 40 分钟),要求同一时刻只有一台执行、执行机宕机后 2 分钟内另一台接管。设计:用 Redis 分布式锁(SET NX PX 5min)竞争执行权,看门狗每 100s 续期;持锁方宕机后看门狗停止,锁最多 5 分钟过期,其他实例每分钟重试竞争即可在 2 分钟内接管(可将锁 TTL 设为 2 分钟 + 高频续期压缩接管窗口);接管方需从断点续跑(对账任务记录已处理位点),避免从头重跑;兜底:对账结果写 DB 时用唯一约束防双写。若对一致性要求极高,改用 ZK 临时节点锁,会话断开即释放,接管更快。
⚠️ 常见误区
详情
常见误区:
- ❌ "先 SETNX 再 EXPIRE 也一样" → 两步非原子,SETNX 后宕机会留下永不过期的死锁。
- ❌ "上了 Redlock 就绝对互斥" → Redlock 依赖时钟假设,GC 停顿/时钟跳变仍可能破坏安全性,强一致场景应选 ZK/etcd。
- ❌ "有锁就万事大吉" → 锁只能降低双写概率,关键写操作必须幂等/唯一索引兜底。
🔀 发散问题
- Q:不用锁能不能解决并发互斥? → 能,低并发场景用乐观锁/唯一索引更简单,原则是能用数据层约束就不用锁。
- Q:选主这类强一致场景用什么? → ZK/etcd 的临时节点 + watch,会话失效自动释放比 TTL 更可靠,见本文档「如何设计一个配置中心」(同属 ZK/etcd 生态选型)。
【中等】如何设计一个分布式 ID 发号器?⭐⭐⭐⭐
🎯 目标等级:L2 | ⏱ 建议用时:15 min | 🏷 标签:分布式设计 / ID 发号器
💎 关键结论
分布式 ID 四大需求:全局唯一、趋势递增(利于 B+ 树索引)、高性能、高可用。主流选型两条路:号段模式(简单可靠,DB 压力降千倍,双 Buffer 防阻塞)与雪花算法(本地生成性能极高,但依赖时钟需处理回拨)。机器 ID 必须自动化分配,手工配置必出事故;两者可双引擎互为兜底。
⚡记忆卡片
- 口诀:号段简单稳,雪花快但怕回拨;机器 ID 自动发,双引擎互兜底
- 关键词:趋势递增 / 双 Buffer / 时钟回拨 / workerID / Leaf
- 链路:需求(唯一/递增/高性能)→ 选型(号段 vs 雪花)→ 机器 ID 自动分配 → 双引擎兜底
📖 核心知识
分布式 ID 的需求:全局唯一、趋势递增(利于 B+ 树索引,避免页分裂)、高性能(万级~百万级 TPS)、高可用。
方案对比
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| UUID | 本地随机生成 128 位 | 本地生成,无中心依赖 | 无序、太长,做主键性能差,信息不安全 |
| 数据库自增 | 单表 AUTO_INCREMENT | 简单 | 单点瓶颈,可用性差 |
| 号段模式 | 批量取一段 ID(如每次 1000 个)缓存本地 | DB 压力降千倍,可用性高 | 重启浪费号段,趋势递增非严格连续 |
| Redis INCR | 原子自增 | 性能高 | 依赖 Redis 可用性,持久化有风险 |
| 雪花算法 | 64 位:时间戳 + 机器 ID + 序列号 | 本地生成,性能极高,趋势递增 | 依赖时钟,时钟回拨需处理 |
号段模式(美团 Leaf-segment)
- 从 DB 批量取号段缓存内存,DB 访问量降为原来的 1/1000。
- 双 Buffer 优化:当前号段用到 10% 时就异步预取下一个号段,避免取号阻塞。
- DB 部署多实例,各自取不同号段,避免单点。
雪花算法(Snowflake)
- 64 位结构:1 位符号位 + 41 位毫秒时间戳(可用约 69 年)+ 10 位机器 ID(最多 1024 节点)+ 12 位序列号(单机每毫秒 4096 个)。
- 时钟回拨处理:回拨小(几 ms)则等待时钟追上;回拨大则切换到备用机器 ID 或报警拒绝服务。
- 机器 ID 分配:可用 ZooKeeper、数据库或配置中心管理,避免手动分配冲突。
- 变体:百度 UidGenerator(ringbuffer 预生成,借用未来时间)、美团 Leaf-snowflake(ZK 管理 workerID)。
选型建议:追求简单可靠选号段模式;追求极限性能且能处理时钟问题选雪花;两者可双引擎互为兜底(Leaf 双模式)。
失效场景:时钟回拨(NTP 校时/虚拟机迁移)导致雪花生成重复或拒绝发号;号段模式实例重启浪费号段,ID 出现空洞(业务需接受非连续)。
🔬 扩展知识
详情
- 【L3】趋势递增 vs 严格连续,业务上差别多大?B+ 树索引只需趋势递增即可避免页分裂,空洞无影响;只有对外展示编号(订单号给用户看)才需连续性考虑,此时可用号段模式。用雪花做订单号时,可拼接业务前缀/日期弱化不连续感。
- 【L3】雪花 10 位机器 ID 不够用怎么办?1024 节点不够时从时间戳借位(如 41 位→ 38 位,可用年限缩短到 8 年)或从序列号借位(降低单机发号能力);也可以号段模式替代,本质是空间分配的重新权衡。
- 【L4】为什么不直接用数据库自增?单实例自增是单点 + 写入瓶颈(几千 TPS),且每次发号都要访问 DB;若非要 DB 方案,至少用号段模式批量取号。另外自增 ID 可被外部枚举猜测订单量,对外单号需混淆。
📚 延伸阅读:分布式 ID
🏭 实战场景
详情
量化与踩坑:雪花算法单机理论上限 409.6 万 ID/秒(每毫秒 4096),号段模式 DB 压力降为 1/1000(每次取 1000 号段)。曾有线上事故:容器化迁移后雪花机器 ID 手工配置重复,两实例生成重复订单号,唯一索引报错大量建单失败——机器 ID 必须自动化分配(ZK/配置中心),禁止手工配置。
场景:订单中心日新增订单 2000 万,峰值 5 万 TPS 建单,订单号需对外展示且不可被竞争对手推算单量。方案:选号段模式(趋势递增、无时钟问题),双 Buffer 预取避免阻塞,每实例每次取 1 万号段,DB 发号压力仅几十 QPS;对外展示层叠加混淆(如日期位 + 校验位 + 分段重排),使单号不可推算总量与增速;机器 ID/实例号用 ZK 自动分配;兜底:主发号器故障切备用雪花引擎(Leaf 双模式思路),两引擎 ID 空间不重叠。
🔀 发散问题
- Q:异地多活下 ID 空间如何划分? → 号段按单元分配不重叠区间,见「如何设计异地多活/容灾架构」。
- Q:发号器作为基础服务,配置与开关如何管理? → 配置中心集中管理动态生效,见本文档「如何设计一个配置中心」。
【中等】如何设计一个配置中心?⭐⭐⭐
🎯 目标等级:L2 | ⏱ 建议用时:12 min | 🏷 标签:分布式设计 / 配置中心
💎 关键结论
配置中心解决四个问题:配置与代码分离、集中管理、动态生效(不重启)、安全与审计。核心设计是推送机制:长轮询 + 服务端通知(Apollo)、长连接推送(Nacos 2.x)、推拉结合兜底。客户端容灾是关键:本地内存缓存 + 磁盘快照,配置中心宕机时应用重启可用快照恢复。
⚡记忆卡片
- 口诀:环境命名空间分层,长轮询长连接推送,本地快照保命
- 关键词:namespace / 动态推送 / 灰度发布 / 长轮询 / 本地快照
- 链路:配置变更 → 服务端通知/长连接推送 → 客户端秒级生效 → 本地快照兜底
📖 核心知识
配置中心解决的核心问题:配置与代码分离、集中管理、动态生效(不重启)、安全与审计。
核心功能
- 配置管理:按环境(dev/test/prod)、命名空间(namespace)、dataId 分层组织;支持版本管理与一键回滚。
- 动态推送:配置变更后秒级推送到客户端应用,无需重启。
- 灰度发布:按 IP/集群/标签灰度下发配置,验证后再全量。
- 权限审计:变更需审批,记录操作日志。
推送机制(核心设计)
- 长轮询 + 服务端通知(Apollo):客户端发起长轮询请求(hold 30-60s),有变更服务端立即返回,否则超时返回,兼顾实时性与服务端压力。
- 长连接推送(Nacos 2.x gRPC):服务端通过长连接主动推送,实时性更高。
- 推拉结合:推送保证实时性,定时拉取(如 5 分钟)作为兜底,防止推送丢失。
客户端容灾设计(高频追问)
- 本地内存缓存 + 磁盘快照:配置中心宕机时,应用重启可用本地快照恢复。
- 启动时优先从服务端拉取,失败则降级读本地快照并告警。
选型对比
| 方案 | 推送机制 | 特点 |
|---|---|---|
| Apollo | 长轮询 + 通知 | 功能最全(权限、灰度、审计),组件较多 |
| Nacos | 长连接(2.x) | 配置中心 + 服务发现一体,部署简单 |
| Spring Cloud Config | Git 存储 + Bus 推送 | 依赖 Git 和 MQ,实时性弱,逐渐被替代 |
| ZooKeeper/etcd | watch 机制 | 适合小规模、动态开关,功能较基础 |
🔬 扩展知识
详情
- 【L3】长轮询为什么比短轮询好?短轮询频繁建连且大部分请求无变更(空转);长轮询 hold 住请求,有变更立即返回、无变更超时返回,兼顾实时性与连接成本。
- 【L3】配置变更引发故障怎么办?灰度下发 + 一键回滚是标配;变更需审批与审计日志,关键配置(限流阈值、开关)支持秒级生效与秒级回滚。
🔀 发散问题
- Q:限流阈值等配置动态下发后如何验证? → 阈值必须来自压测容量,动态调整需配合监控验证,见本文档「如何实现流量控制」。
- Q:配置中心与服务发现常二合一(如 Nacos),注册中心挂了怎么办? → RPC 客户端需本地地址缓存兜底,见本文档「如何设计一个 RPC 框架」。
可观测性
【中等】如何实现链路追踪?⭐⭐⭐
🎯 目标等级:L2 | ⏱ 建议用时:10 min | 🏷 标签:分布式设计 / 可观测性
💎 关键结论
链路追踪解决一次请求跨多服务后的调用链可视化与问题定位。核心概念:Trace(全局链路,TraceId 唯一)与 Span(工作单元,ParentSpanId 成树)。实现五步:入口生成 TraceId 透传 → 无侵入埋点(Java Agent)→ 异步场景手动透传 → 异步批量上报 → 存储查询。全量上报压力大,靠头部/尾部采样控制。
⚡记忆卡片
- 口诀:TraceId 全链透传,Span 父子成树,异步上报慢采样
- 关键词:TraceId / Span / Java Agent / 尾部采样 / OTLP
- 链路:入口生成 TraceId → Header 透传 → 埋点采集 → 上报 Collector → ES/ClickHouse 查询
📖 核心知识
链路追踪解决分布式系统中一次请求跨多个服务后的调用链可视化与问题定位问题。
核心概念
- Trace:一次完整请求的全局链路,用全局唯一的
TraceId标识。 - Span:链路中的一个工作单元(一次 RPC、一次 DB 查询),有
SpanId与ParentSpanId,记录开始/结束时间、状态。 - 调用关系通过 ParentSpanId 形成树状结构。
实现原理
- 埋点透传:入口(网关)生成 TraceId,放入 HTTP Header(如
traceparent)/RPC 上下文/MDC,随调用链逐层透传。 - 无侵入埋点:通过 Java Agent(字节码增强,如 SkyWalking Agent)自动拦截 HTTP 客户端、RPC、JDBC 调用;或基于 SDK/中间件拦截器手动埋点(如 Sleuth/Micrometer Tracing)。
- 异步透传:线程池、MQ 场景需手动包装 Runnable/消息 Header,防止 TraceId 丢失(TransmittableThreadLocal 可解决线程池传递)。
- 采集上报:客户端异步批量上报到 Collector(OTLP 协议),避免影响业务 RT。
- 存储查询:存 ES/ClickHouse,提供拓扑图、瀑布图、慢调用查询。
采样策略(高频追问):全量上报存储压力大,通常采样:头部采样(入口按比例决定,如 1%)或尾部采样(链路结束后只保留错误/慢请求,更精准但实现复杂)。
业界方案:OpenTelemetry(标准与 SDK)、SkyWalking(无侵入)、Jaeger、Zipkin;可组合(OTel SDK + Jaeger 后端)。
价值:快速定位慢在哪一跳(跨服务延迟分解)、错误传播路径、服务依赖拓扑治理。
🔀 发散问题
- Q:链路追踪在慢接口排查中怎么用? → 先看链路确定慢在哪一跳再深入该层,见「接口响应慢如何排查」。
- Q:TraceId 在哪里生成最合适? → 网关统一生成并透传,是可观测性入口职责,见本文档「如何设计一个网关」。
可靠性
【困难】如何实现分布式事务?⭐⭐⭐⭐
🎯 目标等级:L2 | ⏱ 建议用时:18 min | 🏷 标签:分布式设计 / 分布式事务
💎 关键结论
分布式事务本质是在 CAP 约束下做取舍:强一致(2PC)还是最终一致(柔性事务),互联网业务绝大多数选最终一致。选型原则:能用本地事务绝不用分布式事务;优先异步消息最终一致;资金类强一致用 TCC;跨库查询用数据冗余而非事务。
⚡记忆卡片
- 口诀:能本地不分布式,能异步不强一致;资金 TCC,其余靠消息
- 关键词:2PC / TCC / 可靠消息 / Saga / 幂等空回滚悬挂
- 链路:业务一致性需求 → 判断容忍窗口 → 选方案(消息/TCC/Saga)→ 幂等 + 对账兜底
📖 核心知识
方案对比
| 方案 | 一致性 | 性能 | 业务侵入 | 适用场景 |
|---|---|---|---|---|
| 2PC/XA | 强一致 | 低 | 无 | 传统数据库跨库,金融核心 |
| TCC | 最终一致 | 较高 | 高 | 对一致性要求高的资金类业务 |
| 可靠消息(最终一致) | 最终一致 | 高 | 中 | 异步解耦场景(下单后发积分) |
| Saga | 最终一致 | 高 | 中 | 长事务、跨多服务流程 |
| 本地消息表 | 最终一致 | 高 | 中 | 无事务型 MQ 时的可靠消息方案 |
2PC/XA:协调者先 prepare 再 commit。问题是同步阻塞、协调者单点、数据锁定时间长;MySQL XA 性能差,互联网场景基本不用。
TCC:业务需实现 Try(预留资源,如冻结余额)、Confirm(确认)、Cancel(回滚)三个接口。注意三个幂等问题:空回滚(Try 未执行就收到 Cancel)、悬挂(Cancel 先于 Try 到达)、幂等重试(Confirm/Cancel 可能重复调用)。
可靠消息最终一致:
- 本地事务 + 消息原子提交:RocketMQ 事务消息(半消息 + 回查)是标准实现;或本地消息表(事务内写业务表 + 消息表,定时任务扫描发送)。
- 消费端必须幂等(唯一键去重);消费失败重试 + 死信队列 + 告警。
Saga:长事务拆成多个本地事务,每步定义补偿操作,失败时反向补偿。适用于跨多服务、无隔离要求的长流程(如旅行预订)。
选型原则:能用本地事务就绝不用分布式事务;优先异步消息最终一致;资金类强一致需求用 TCC;跨库查询类需求考虑数据冗余/异构索引而非分布式事务。
业界实现:Seata(AT/TCC/Saga/XA 多模式)、RocketMQ 事务消息。
失效场景:可靠消息方案中若 MQ 与业务 DB 不在同一事务域,消息发送与本地提交仍可能不一致,需事务消息或本地消息表解决;Saga 补偿无法撤销副作用(已发短信/已出库),不可逆操作必须放最后。
🔬 扩展知识
详情
- 【L3】为什么不直接用 Seata AT 模式解决一切?AT 靠全局锁实现写隔离,高并发下锁竞争成为瓶颈,且依赖代理 SQL 解析,复杂 SQL 支持有限;它适合中低频、中等一致性需求,资金类高频写场景仍需 TCC 或本地事务 + 对账。
- 【L3】本地消息表方案的扫描任务如何不给 DB 加压?消息表与业务表同库同事务写入,扫表任务按状态 + 创建时间建索引,只扫"未发送且创建超过 N 秒"的少量记录;发送成功后置位,定期归档已处理记录,表保持小体量。
- 【L4】下单减库存到底要不要分布式事务?主流做法是不用:下单本地事务内建单,扣库存走事件/可靠消息最终一致,配合对账修复偏差;只有"超卖即资损"的强一致需求才考虑 TCC 冻结/确认模式。先问业务能容忍多久不一致,再选方案。
📚 延伸阅读:分布式事务
🏭 实战场景
详情
量化与踩坑:2PC/XA 在 prepare 后锁定资源直到 commit,高并发下锁等待时间可放大 RT 数倍,MySQL XA 实测吞吐只有本地事务的 1/3~1/10;TCC 业务侵入高,一个转账逻辑要写三个接口,开发成本翻三倍。曾有线上事故:TCC 的空回滚未处理,网络超时导致 Cancel 先于 Try 到达,资源被错误解冻后 Try 又到达执行,造成资金悬挂——TCC 三接口必须各自处理幂等/空回滚/悬挂。
场景:跨行转账系统,A 银行扣款、B 银行入账,日转账 200 万笔,要求资金零差错、单笔端到端 < 3 秒。方案:资金类选 TCC:Try 阶段双方冻结对应金额(用户可用余额减少但不消失),Confirm 完成实际划转,Cancel 解冻;三接口各自幂等 + 空回滚/悬挂防护(事务控制表记录分支状态);事务协调器多活部署并持久化事务日志,宕机后重放未完成分支;端到端 3 秒内:Try 并行发起两行接口,超时即触发 Cancel;兜底:日终双边对账,差错账人工处理。
⚠️ 常见误区
详情
常见误区:
- ❌ "分布式事务就要强一致" → 互联网业务绝大多数可接受秒级/分钟级不一致窗口,最终一致 + 对账是主流,强一致代价极高。
- ❌ "TCC 写了三个接口就行" → 不处理空回滚/悬挂/幂等重试,网络异常下必出资损。
- ❌ "跨库查询也要分布式事务" → 读场景用数据冗余/异构索引解决,不要为查询引入事务。
🔀 发散问题
- Q:可靠消息的投递一致性如何保证? → 本地消息表/事务消息 + 消费幂等,见「如何设计领域事件与事件驱动架构」。
- Q:TCC 的资源冻结与分布式锁有什么关系? → 冻结本质是业务层预留资源,比分布式锁更适合长流程资金场景,见本文档「如何设计一个分布式锁」。
【困难】如何实现流量控制?⭐⭐⭐⭐
🎯 目标等级:L2 | ⏱ 建议用时:18 min | 🏷 标签:分布式设计 / 流控
💎 关键结论
限流的目标是保护系统不被瞬时流量打垮(防雪崩)。四种算法:固定窗口(临界突刺)、滑动窗口(Sentinel)、漏桶(绝对平滑)、令牌桶(允许突发,最常用)。完整流控体系 = 限流 + 熔断 + 降级 + 隔离 + 排队;分层限流从入口到 DB 层层设阈。铁律:限流阈值必须来自压测容量,而非猜测。
⚡记忆卡片
- 口诀:令牌桶最常用,漏桶强平滑;阈值来自压测,熔断防扩散
- 关键词:令牌桶 / 滑动窗口 / Redis Lua / 熔断降级 / 429
- 链路:压测定容量 → 分层设阈 → 网关总闸 → 应用接口级 → 依赖并发级 → 限流/熔断/降级兜底
📖 核心知识
四种经典算法
| 算法 | 原理 | 特点 |
|---|---|---|
| 固定窗口 | 时间窗口内计数,超阈拒绝 | 简单,但有临界突刺问题(窗口边界双倍流量) |
| 滑动窗口 | 窗口细分为多个小格滑动统计 | 解决临界问题,Sentinel 采用 |
| 漏桶 | 请求入桶,恒定速率流出 | 流量绝对平滑,但无法应对突发流量 |
| 令牌桶 | 恒定速率放令牌,请求取令牌通过 | 允许突发(桶内预存令牌),最常用 |
单机限流:Guava RateLimiter(令牌桶,可预热 WarmUp)、信号量限并发、滑动窗口计数器。
分布式限流:Redis + Lua 脚本保证"取令牌/计数"原子性;或网关层统一限流(Nginx limit_req、Sentinel 网关适配)。
分层限流思想:从入口到 DB 层层设阈,漏斗式过滤——网关层限总 QPS,应用层按接口/用户维度限流,依赖层(DB/下游 RPC)按连接数/并发数限流。
限流之外的完整流控体系:
- 熔断:下游错误率/RT 超阈时快速失败(Sentinel/Resilience4j),防止故障扩散。
- 降级:非核心功能开关关闭、返回兜底数据。
- 隔离:线程池隔离/信号量隔离,避免单一依赖拖垮全局(舱壁模式)。
- 排队:MQ 削峰,将瞬时流量转化为平滑消费。
实践要点:限流阈值基于压测容量设定(如系统容量的 80%);被限流的请求返回明确错误码(如 429),前端友好提示与重试退避;阈值支持动态调整(配置中心下发)。
失效场景:限流只能保护入口,若瓶颈在下游(DB 连接数),入口限流后下游仍可能被其他路径打垮,需每层按自身容量独立限流;熔断误触(抖动期错误率瞬间超阈)需半开探测 + 最小请求数门槛避免误判。
🔬 扩展知识
详情
- 【L3】网关限流与应用限流如何分工?网关层限总量与恶意流量(IP/用户维度,保护整体);应用层按接口/依赖维度限流(保护自身与下游);两层阈值要匹配:网关阈值 ≥ 应用集群总容量,否则要么浪费容量要么保护失效。
- 【L3】热点用户/热点商品怎么限流?单用户维度限流(如单 UID 10 QPS)+ 热点探测(滑动窗口统计 Top N),热点 key 自动降级到本地处理/排队;通用维度限流无法解决热点倾斜,必须细粒度到 key。
- 【L4】被限流后的用户体验与重试策略?返回明确错误码(429)+ Retry-After 建议;客户端指数退避重试,禁止立即重试(否则加剧过载);前端友好提示"前方拥堵",而非报错堆栈。
📚 延伸阅读:流量控制
🏭 实战场景
详情
方案权衡与踩坑:令牌桶允许突发(桶内存量令牌一次性放出),对下游瞬时承压敏感的场景(如 DB 直连)应选漏桶强制平滑;分布式限流每请求一次 Redis 调用增加约 1ms,超高频接口可用本地限流 + 总量分摊代替。曾有线上事故:限流阈值拍脑袋设 5000 QPS,实际压测容量只有 3000,限流形同虚设被流量击穿——限流阈值必须来自压测容量,而非猜测。
场景:开放平台 API,总容量 2 万 QPS,接入 500 个开发者,要求保障付费客户 SLA(占容量 70%),免费客户共享剩余 30%,单个开发者不得独占。设计:分层限流——网关层总闸 2 万 QPS 兜底;按开发者维度配额限流(付费客户按合同分配固定额度,免费客户共享池内按账号限 100 QPS);算法选令牌桶(支持突发)+ Redis Lua 分布式计数;超额返回 429 + 配额用量提示;关键设计:配额支持动态调整(配置中心下发),付费客户升级即时生效;监控维度:按开发者聚合限流触发率,持续触顶的客户引导扩容或优化调用。
⚠️ 常见误区
详情
常见误区:
- ❌ "限流阈值按经验拍个数字就行" → 阈值必须来自压测容量,拍脑袋的阈值要么形同虚设要么浪费容量。
- ❌ "入口限了流就安全了" → 瓶颈在下游时,其他路径仍能打垮下游,需每层按自身容量独立限流。
- ❌ "固定窗口够用" → 窗口边界可能出现双倍流量突刺,生产环境应用滑动窗口/令牌桶。
🔀 发散问题
- Q:限流阈值背后的压测容量怎么得出? → 单机压测定拐点、全链路压测验证,见「如何做系统容量评估与压测」。
- Q:被限流/熔断与高可用体系是什么关系? → 限流熔断降级是高可用"隔离限影响"的三板斧,见本文档「如何设计一个高可用系统」。
【中等】服务重启时,如何避免客户端重连引发的流量洪峰?⭐⭐
🎯 目标等级:L2 | ⏱ 建议用时:10 min | 🏷 标签:分布式设计 / 无损发布
💎 关键结论
服务重启后大量客户端同时重连会瞬间产生流量洪峰,核心是错峰连接与服务平滑上线,需客户端与服务端协同:客户端指数退避 + 随机抖动 + 限制重试;服务端优雅启停、延迟注册、渐进放量、入口限流;架构上用 K8s Readiness/PreStop 做无损发布,并提前按客户端规模预估瞬时流量扩容。
⚡记忆卡片
- 口诀:客户端退避抖动错峰,服务端预热限流渐进,注册中心延迟上下线,无损发布保平稳
- 关键词:指数退避 / 随机抖动 / 延迟注册 / 渐进放量 / Readiness 探针
- 链路:重启 → 客户端退避抖动重连 → 服务端延迟注册渐进放量 → 限流兜底 → 平稳恢复
📖 核心知识
客户端策略:重连退避与抖动
客户端必须实现智能重连机制,而不是失败后立即重试:
- 指数退避:每次重连失败后,等待时间指数增长(如 1s、2s、4s、8s...),避免集中重试。
- 随机抖动:在退避时间基础上加入随机因子(如 ±50%),防止多个客户端因相同退避策略而同时重连。
- 限制重试次数:设置最大重试次数或最大退避时间,避免无限制重连。
服务端策略:平滑重启与流量控制
- 优雅启停:服务关闭前先注销注册中心,拒绝新流量,处理完存量请求后再退出。启动时,先完成缓存预热、连接池初始化等,再对外提供服务。
- 延迟注册与渐进式放量:服务启动后不立即注册(等待如 30 秒确保内部就绪);注册后通过负载均衡权重逐步放量(如先 10% 流量,观察稳定后提升)。
- 服务端限流:在网关或入口层配置限流规则(如令牌桶),即使客户端瞬间涌入,也能保护后端不被冲垮。
- 客户端主动降级:若服务端返回限流或过载响应,客户端触发本地降级或友好提示,并继续执行重连退避。
架构层面:无损发布与容量规划
- 无损发布平台:借助 K8s 的 Readiness 探针和 PreStop 钩子,确保新实例完全就绪后才接入流量,旧实例在流量切走后优雅退出。
- 容量预估:根据客户端规模预估重启后的瞬时流量,提前扩容实例数量,留足余量。
🔀 发散问题
- Q:服务端优雅启停在 RPC 框架中如何实现? → 先下线注册中心、等存量请求完成再断连,见本文档「如何设计一个 RPC 框架」。
- Q:重连洪峰与限流体系如何配合? → 入口限流 + 429 + 客户端退避是闭环,见本文档「如何实现流量控制」。
【困难】如何设计一个高可用系统?⭐⭐⭐⭐
🎯 目标等级:L2 | ⏱ 建议用时:18 min | 🏷 标签:分布式设计 / 高可用
💎 关键结论
高可用 = 尽量不发生 + 发生了快速恢复,量化公式:可用性 = MTBF/(MTBF+MTTR),4 个 9 意味年故障约 52 分钟。四支柱:冗余消单点、隔离限影响、监控快发现 + 预案快恢复、变更要灰度。可用性目标必须由业务定级,不是越高越好;没演练过的预案等于没有。
⚡记忆卡片
- 口诀:冗余消单点,隔离限影响,监控快发现,预案快恢复,变更要灰度
- 关键词:MTBF / MTTR / 自动故障切换 / 舱壁模式 / 灰度发布
- 链路:冗余 → 隔离防护 → 监控告警发现 → 预案快速恢复 → 发布灰度防引入
📖 核心知识
冗余:消除单点
- 接入层:DNS 多入口 + Nginx/LVS 集群 + 健康检查自动摘除坏节点。
- 应用层:无状态服务多实例部署,任意单实例宕机不影响整体。
- 数据层:MySQL 主从 + 自动故障切换(MHA/Orchestrator)、Redis Sentinel/Cluster、MQ 多副本(ISR)。
- 机房级:同城双活 → 异地多活,核心链路单元化闭环。
隔离与防护:限制故障影响面
- 线程池/连接池隔离(舱壁模式)、按业务分集群部署。
- 限流、熔断、降级三板斧,防雪崩。
快速恢复:缩短 MTTR
- 完善的监控告警(指标、日志、链路三支柱),故障分钟级发现。
- 预案化:一键扩容、一键降级开关、快速回滚;定期故障演练(混沌工程,如 ChaosBlade)验证预案有效性。
发布安全:灰度发布、蓝绿部署、无损上下线(优雅停机 + 延迟注册),把"变更引入的故障"降到最低(线上故障大多由变更引发)。
失效场景:健康检查过于激进会误杀慢节点(如 GC 停顿被摘除),摘除后剩余节点压力放大引发连锁故障,需"慢摘快恢"策略;多可用区部署下跨 AZ 延迟增加 1~2ms,对延迟敏感接口需同 AZ 优先路由。
🔬 扩展知识
详情
- 【L3】MTTR 的构成是什么?各段如何压缩?MTTR = 发现时间 + 定位时间 + 恢复时间。压缩发现:监控告警 1 分钟内触达;压缩定位:链路追踪 + 预案化诊断手册;压缩恢复:一键回滚/降级开关/自动切换。三段都有预案才能支撑 4 个 9。
- 【L3】可用性依赖链怎么计算?串联依赖可用性相乘:应用 99.99% × DB 99.99% × 缓存 99.9% ≈ 99.88%,短板决定整体。提可用性要么消除依赖(降级兜底),要么把短板变并联(多活/多副本)。
- 【L4】故障演练怎么做才不引发真故障?分级注入:先在预发环境注入,再在生产低峰期小范围(单实例)注入,全程监控 + 一键终止开关;混沌工程工具(ChaosBlade)限定爆炸半径;演练结论回写预案。
🏭 实战场景
详情
量化与权衡:3 个 9(年停机 ≤ 8.8 小时)靠单机集群 + 监控即可;4 个 9(≤ 52 分钟)需自动故障切换 + 多可用区;5 个 9(≤ 5 分钟)需异地多活 + 秒级切流 + 常态化演练,成本是 4 个 9 的数倍——可用性目标必须由业务定级,不是越高越好。曾有线上事故:自动故障切换预案从未演练,真实故障时切换脚本因权限问题失败,MTTR 从预期 2 分钟拖到 40 分钟——没演练过的预案等于没有。
场景:出行平台早晚高峰打车下单峰值 3000 TPS,要求核心链路全年可用 4 个 9,预算不支持异地多活。方案:同城双机房双活(MySQL 半同步复制、应用无状态双部署),单机房故障负载均衡秒级切流;应用层多实例跨 AZ 部署,任意单实例宕机无感;Redis Sentinel/Cluster 自动故障切换,缓存故障降级为直查 DB(限流保护);MQ 多副本(ISR≥2);核心链路依赖做降级预案(如司机位置服务故障时用最后已知位置兜底);MTTR 保障:监控 1 分钟告警 + 一键降级开关 + 每季度真实切流演练。预算约束下放弃异地容灾,用同城双活 + 数据备份支撑 4 个 9。
⚠️ 常见误区
详情
常见误区:
- ❌ "可用性越高越好,目标五个 9" → 每多一个 9 成本数倍增长,目标必须由业务定级,过度冗余是浪费。
- ❌ "写了预案就等于有了预案" → 没演练过的预案在真实故障时大概率失效(权限、依赖变化),必须常态化演练。
- ❌ "多实例部署就是高可用" → 还要看依赖链:串联依赖可用性相乘,短板(如单点 DB)决定整体。
🔀 发散问题
- Q:机房级的高可用(双活/多活)具体怎么设计? → 同城双活起步,单元化演进,见「如何设计异地多活/容灾架构」。
- Q:高可用中的限流熔断降级如何落地? → 四种算法 + 完整流控体系,见本文档「如何实现流量控制」。