弹性工程¶
面试追问地图¶
| 主问题 | 必讲关键点 | 下一层追问 |
|---|---|---|
| 超时预算 | 端到端 deadline、逐跳预算、取消传播 | 连接/读取超时、排队时间 |
| 重试 | 幂等、退避、jitter、重试预算 | 多层重试、retry storm |
| 舱壁 | 并发隔离、线程池/信号量、拒绝 | 与限流的区别 |
| 并发限制 | 在途请求、Little 定律、自适应 | 队列越长越好吗 |
| 组合顺序 | 入口限流、隔离、单次超时、重试预算 | 指标和降级语义 |
一、时间预算与重试¶
超时和 deadline 应该怎样设计?¶
超时限制一次操作最多等待多久;deadline 是整次业务调用的绝对截止时刻。链路 A → B → C 若每跳都独立配置 1 秒超时,A 的实际等待可能远超 1 秒。更可靠的做法是入口生成 deadline,向下游传播剩余预算,并为网络抖动、重试和结果组装预留时间。
应分别理解连接、请求、读取和连接池获取超时。线程在连接池排队 800ms 后再执行 500ms,即使读取超时设为 500ms,用户也已等待 1.3 秒。超时发生后还要传播取消信号;只让上游返回而下游继续计算,会制造“幽灵请求”持续消耗容量。
重试为什么可能放大故障?¶
重试只适合瞬时故障、操作幂等且剩余 deadline 足够的场景。5 层调用每层重试 3 次,最坏可能把底层调用放大到 3^5=243 次。应只在一个明确层级重试,并设置总尝试次数或重试预算。
- 指数退避降低持续冲击,jitter 打散大量客户端的同步重试。
429/503可结合Retry-After;参数错误、权限错误不应重试。- 写操作使用幂等键、状态条件或去重表,不能把“网络超时”直接解释为“下游未执行”。
- 重试指标要区分首次成功、重试成功和最终失败,否则会掩盖正在恶化的依赖。
二、容量隔离¶
舱壁隔离解决什么问题?¶
舱壁把不同依赖或业务的并发资源隔开,避免一个慢下游占满全部线程、连接或许可。常见实现是:
- 信号量舱壁:限制同时进入的调用数,不切换线程,开销低。
- 线程池舱壁:独立线程池和有界队列,隔离阻塞,但增加排队、上下文切换和上下文传播成本。
舱壁满时应快速拒绝或执行明确降级,不应使用无界队列把过载变成高延迟和 OOM。隔离粒度太细会浪费容量,太粗又会让故障相互影响,应按依赖风险和业务优先级划分。
并发限制和限流有什么区别?¶
限流约束单位时间进入多少请求,并发限制约束当前有多少请求正在执行。根据 Little 定律,在稳定系统中 并发数 ≈ 吞吐量 × 平均耗时;下游变慢时,即使 QPS 不变,在途请求也会增加,所以只做 QPS 限流仍可能压垮线程池。
固定并发上限来自容量压测;自适应并发限制则根据延迟和排队变化收缩或放宽。队列只用于吸收短暂突发,长度必须有界,并把排队时间计入 deadline。
三、策略组合¶
超时、重试、熔断、舱壁和限流如何组合?¶
典型顺序是:入口限流保护总容量 → 舱壁隔离依赖 → 单次调用受剩余 deadline 约束 → 对符合条件的失败做预算内重试 → 熔断器根据结果快速阻断持续故障 → 返回业务允许的降级结果。
顺序不是固定装饰器模板。例如把重试放在熔断统计内部,会让一次用户请求贡献多次失败;放在外部则每次熔断拒绝也可能被重试。必须明确统计单位和期望行为,并监控拒绝数、在途数、队列时间、超时、重试放大率、熔断状态和降级命中率。