组件与框架设计面试
组件与框架设计面试
中间件
【中等】如何设计一个网关?⭐⭐⭐⭐
【困难】如何设计一个 RPC 框架?⭐⭐⭐⭐
【困难】如何设计一个 MQ?⭐⭐⭐⭐
分布式组件
【困难】如何设计一个分布式锁?⭐⭐⭐⭐
【中等】如何设计一个分布式 ID 发号器?⭐⭐⭐⭐
🎯 目标等级:L3-L4 | ⏱ 建议用时: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】UUID 有什么新演进?
UUIDv7(RFC 9562,2024 年发布)把毫秒时间戳放在高位、随机位放低位,兼顾"本地生成、无中心依赖"与"时间有序、索引友好",正在缓解传统 UUIDv4 做主键的页分裂问题。
128 位存储空间仍是它相对 64 位雪花 ID 的劣势。
【L3】雪花 10 位机器 ID 不够用怎么办?
途径 做法 代价 借时间戳 41 位 → 38 位 可用年限缩短到 8 年 借序列号 从序列号借位 降低单机发号能力 换方案 用号段模式替代 本质是空间分配的重新权衡 1024 节点不够时才需要做这类取舍。
【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:机器 ID/实例号用 ZK 自动分配。
兜底:主发号器故障切备用雪花引擎(Leaf 双模式思路),两引擎 ID 空间不重叠。
🔀 发散问题
Q:异地多活下 ID 空间如何划分?
→ 号段按单元分配不重叠区间,见「如何设计异地多活/容灾架构」。
Q:发号器作为基础服务,配置与开关如何管理?
→ 配置中心集中管理动态生效,见 分布式治理面试 - 如何实现一个配置中心。
【中等】如何设计一个配置中心?⭐⭐⭐
可观测性
【中等】如何实现链路追踪?⭐⭐⭐
可靠性
【困难】如何实现分布式事务?⭐⭐⭐⭐
【困难】如何实现流量控制?⭐⭐⭐⭐
【中等】服务重启时,如何避免客户端重连引发的流量洪峰?⭐⭐⭐
🎯 目标等级:L3 | ⏱ 建议用时:10 min | 🏷 标签:分布式设计 / 无损发布
💎 关键结论
服务重启后大量客户端同时重连会瞬间产生流量洪峰,核心是错峰连接与服务平滑上线,需客户端与服务端协同:客户端指数退避 + 随机抖动 + 限制重试;服务端优雅启停、延迟注册、渐进放量、入口限流;架构上用 K8s Readiness/PreStop 做无损发布,并提前按客户端规模预估瞬时流量扩容。
⚡ 记忆卡片
- 口诀:客户端退避抖动错峰,服务端预热限流渐进,注册中心延迟上下线,无损发布保平稳
- 关键词:指数退避 / 随机抖动 / 延迟注册 / 渐进放量 / Readiness 探针
- 链路:重启 → 客户端退避抖动重连 → 服务端延迟注册渐进放量 → 限流兜底 → 平稳恢复
📖 核心知识
客户端策略:重连退避与抖动
客户端必须实现智能重连机制,而不是失败后立即重试:
- 指数退避:每次重连失败后,等待时间指数增长(如 1s、2s、4s、8s...),避免集中重试。
- 随机抖动:在退避时间基础上加入随机因子(如 ±50%),防止多个客户端因相同退避策略而同时重连。
- 限制重试次数:设置最大重试次数或最大退避时间,避免无限制重连。
服务端策略:平滑重启与流量控制
- 优雅启停:服务关闭前先注销注册中心,拒绝新流量,处理完存量请求后再退出。启动时,先完成缓存预热、连接池初始化等,再对外提供服务。
- 延迟注册与渐进式放量:服务启动后不立即注册(等待如 30 秒确保内部就绪);注册后通过负载均衡权重逐步放量(如先 10% 流量,观察稳定后提升)。
- 服务端限流:在网关或入口层配置限流规则(如令牌桶),即使客户端瞬间涌入,也能保护后端不被冲垮。
- 客户端主动降级:若服务端返回限流或过载响应,客户端触发本地降级或友好提示,并继续执行重连退避。
架构层面:无损发布与容量规划
- 无损发布平台:借助 K8s 的 Readiness 探针和 PreStop 钩子,确保新实例完全就绪后才接入流量,旧实例在流量切走后优雅退出。
- 容量预估:根据客户端规模预估重启后的瞬时流量,提前扩容实例数量,留足余量。
🔬 扩展知识
详情
【L3】K8s 无损下线的时序细节?
Pod 删除时"从 Endpoint 摘除"与"容器收到 SIGTERM"是并行异步发生的——流量摘除的传播(kube-proxy/网关/注册中心刷新)需要时间。
所以 PreStop 钩子要先 sleep 数秒等待摘除传播,再开始优雅停机。
Pod 删除 -> Endpoint 摘除传播 // kube-proxy/网关/注册中心刷新,需要时间 Pod 删除 -> 容器收到 SIGTERM // 与上面的摘除并行异步 PreStop -> sleep 数秒 // 先等摘除传播完成,再开始优雅停机 宽限期到期 -> SIGKILL 强杀 // 异常点:前面的优雅逻辑全部作废terminationGracePeriodSeconds必须大于 "PreStop sleep + 存量请求处理上限",否则宽限期一到容器被 SIGKILL 强杀,前面所有优雅逻辑全部作废。这是无损发布配置中最常见的翻车点。【L4】长连接场景的重连风暴为什么会被放大?
与短连接 HTTP 不同,网关/中间件与后端之间的长连接在服务端重启瞬间全部断开,客户端同时发起重建(TCP + TLS 握手 + 连接池预热),洪峰是"请求洪峰 + 握手洪峰"的叠加。
服务端要按"客户端连接总数 × 握手开销"预估重建成本,配合分批滚动重启(一次只重启一部分实例)控制同时重连的规模。
🔀 发散问题
Q:服务端优雅启停在 RPC 框架中如何实现?
→ 先下线注册中心、等存量请求完成再断连,见本文档「如何设计一个 RPC 框架」。
Q:重连洪峰与限流体系如何配合?
→ 入口限流 + 429 + 客户端退避是闭环,见 分布式调度面试 - 如何实现流量控制。
【困难】如何设计一个高可用系统?⭐⭐⭐⭐⭐
🎯 目标等级:L3-L4 | ⏱ 建议用时: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 的构成是什么?各段如何压缩?
构成段 压缩手段 发现时间 监控告警 1 分钟内触达 定位时间 链路追踪 + 预案化诊断手册 恢复时间 一键回滚/降级开关/自动切换 MTTR = 发现时间 + 定位时间 + 恢复时间,三段都有预案才能支撑 4 个 9。
【L3】可用性依赖链怎么计算?
应用 99.99% × DB 99.99% × 缓存 99.9% ≈ 99.88% // 串联依赖可用性相乘,短板决定整体提可用性要么消除依赖(降级兜底),要么把短板变并联(多活/多副本)。
【L3】为什么要先做强弱依赖梳理?
对每条核心链路列出依赖清单并标注强/弱:
依赖类型 故障时的处理 弱依赖 不得影响主流程,必须配降级开关并演练过 强依赖 只能快速失败 + 切换预案(多副本/多机房) 不做强弱依赖梳理,"降级预案"就是拍脑袋清单,故障时不知道哪个能关、哪个不能关。
【L4】故障演练怎么做才不引发真故障?
分级注入:先在预发环境注入,再在生产低峰期小范围(单实例)注入,全程监控 + 一键终止开关。
混沌工程工具(ChaosBlade)限定爆炸半径;演练结论回写预案。
【L4】SLA 与错误预算如何联动变更节奏?
对外承诺 SLA、对内定更严的 SLO,两者差值即错误预算;预算充足时允许快速迭代与实验,预算耗尽则冻结非必要变更、专项投入稳定性。
这把"可用性"从口号变成研发节奏的资源约束,是稳定性建设体系化的标志。
🏭 实战场景
详情
量化与权衡
| 可用性目标 | 年停机 | 所需能力 |
|---|---|---|
| 3 个 9 | ≤ 8.8 小时 | 单机集群 + 监控 |
| 4 个 9 | ≤ 52 分钟 | 自动故障切换 + 多可用区 |
| 5 个 9 | ≤ 5 分钟 | 异地多活 + 秒级切流 + 常态化演练 |
5 个 9 的成本是 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:高可用中的限流熔断降级如何落地?
→ 四种算法 + 完整流控体系,见 分布式调度面试 - 如何实现流量控制。