系统在压力下露出真面目的四个现场

四个系统在高负载或故障下暴露问题的真实场景——每个都指向一组反应式模式的适用位置

本页目录

重试风暴把半死不活的服务彻底打死

订单服务响应变慢,从 200ms 升到 2 秒。网关的超时时间是 3 秒,还没触发超时,但客户端已经开始重试了。

每个超时请求被重试 3 次,流量瞬间翻了 4 倍。订单服务本来只是慢,现在 4 倍流量直接把线程池打满了。返回全是 503。

网关检测到大量 503,触发了告警脚本——脚本的逻辑是"发现异常就调诊断接口拉一次全量状态"。诊断请求又进了订单服务的线程池。

三层叠加:正常请求 + 重试请求 + 诊断请求。一个"稍微慢了一点"的问题在两分钟内升级成全站不可用。

反应式的处理方式:Circuit Breaker 在检测到下游错误率上升后熔断调用,让请求走降级路径。退避策略替代固定重试——第一次等 1 秒,第二次等 4 秒,第三次等 16 秒。回压机制让下游告诉上游"慢点发"。三层防线任何一层生效,雪崩就不会发生。

数据库连接池耗尽但 CPU 闲着

监控面板上 CPU 使用率 15%,内存充裕,磁盘 IO 正常。但服务持续返回超时。

排查发现所有线程都在等数据库连接。连接池大小 50,50 个连接全被占着——因为每个请求都在做一个跨三张表的联合查询,每个查询要 800ms。QPS 超过 60 的时候,连接就不够用了。

传统做法:加大连接池。但数据库那边也有连接上限,池子加到 200,数据库先扛不住。

反应式的处理方式:用异步非阻塞的数据库驱动,线程不再等连接。一个线程发出查询后去处理别的请求,查询结果回来时再继续。同样 50 个连接,吞吐量可以提升 3-5 倍,因为等待时间不再占用线程。

代价是编程模型从"写完一行执行一行"变成了回调或响应式流。代码更难读,但资源利用率显著提高。

一个慢接口拖垮了所有接口

商品详情页聚合了四个下游服务:商品信息、价格、库存、评价。四个调用共享一个线程池。

评价服务因为全文索引重建变慢了,响应时间从 50ms 升到 5 秒。评价相关的请求占满了线程池。商品信息、价格、库存的请求排不上队。

整个详情页全部超时——不是因为商品信息服务有问题,而是评价服务的慢请求把共享线程池耗尽了。

Bulkhead 模式:给每个下游服务分配独立的线程池或信号量。评价服务的线程池满了,只有评价这一个维度不可用。商品信息、价格、库存照常返回。详情页降级为"没有评价的详情页"而不是"完全打不开"。

隔离的代价是资源利用率下降——四个独立线程池的总容量通常大于一个共享线程池。但这个代价换来的是故障隔离能力。

消息堆积到内存爆掉

事件驱动架构里,上游服务每秒产生 10000 条事件。下游消费者正常处理速度是 8000 条/秒。剩下 2000 条堆在内存缓冲区里。

白天流量稳定的时候,消费者能在低峰期把积压消化掉。但有一天上游做了活动,峰值到了 30000 条/秒。缓冲区在 15 分钟内从 1GB 涨到 8GB。OOM。消费者重启。积压的消息再次涌入。OOM。循环。

问题的根源是上游不知道下游的处理能力。回压机制解决这个问题的方式不是限流——不是上游单方面决定发多少,而是下游告诉上游"我现在能处理多少"。

Reactive Streams 的 request(n) 模型:消费者通知生产者"给我 1000 条",处理完了再通知"再给 1000 条"。生产者据此调整发送速率。内存使用量变得可预测。

同分类继续看