熔断降级:Hystrix 与 Sentinel 的容错哲学 — editorial cover photo

熔断降级:Hystrix 与 Sentinel 的容错哲学

分布式系统的"保险丝"——当依赖故障,熔断保护自己不被拖垮,降级保证核心可用。

2025年7月29日·返回文章列表
文章大纲

蜂控平台有一次差点全站宕机,起因是一个不起眼的下游接口。情报中心有个查询依赖一个外部数据源,那天外部数据源响应从 50 毫秒飙到 8 秒,情报中心的调用线程在一个个堆起来,线程池打满后请求开始排队,Tomcat 工作线程也被拖住,最后整个服务响应超时,连带着上游的风控决策也跟着失败。一个跟核心链路关系不大的查询接口,拖垮了整条决策链路。

这就是雪崩。微服务调用链上,一个下游服务变慢,上游调用它的线程被占满,进而拖垮上游,逐级蔓延整个系统。故障从一个点扩散成全站,往往只需要几分钟。熔断降级是防雪崩的保险丝,故障发生时快速失败,保护系统核心可用。这篇文章讲熔断降级的设计逻辑,以及为什么这套东西不是可选项。

图:熔断保护下游,降级保障用户体验

雪崩的传导机制

理解熔断之前要先看清雪崩是怎么传导的。一个服务变慢,调用它的服务会在等待响应时把请求线程占住。每个等待中的请求都占一个线程,下游一直慢,线程就一直不释放,新的请求进来拿不到线程,于是上游也开始排队,上游也变慢。这个变慢沿着调用链一路上传,每一层都因为等待下一层而耗尽自己的线程池。

PlantUML
正在加载图表…
PlantUML diagram

图:下游慢依赖耗尽中间服务线程池,逐级向上蔓延直至全链路瘫痪,中间没有自然减速带

这里的关键是线程是稀缺资源。一个服务可能就几百个工作线程,下游慢一秒,这一秒内堆积的请求就把线程占满了。没有隔离机制的话,一个依赖的故障会无差别地拖垮所有依赖,因为它们共享同一个线程池。雪崩的可怕在于传导速度,从单点变慢到全链路瘫痪,中间没有自然减速带。

熔断器是故障的隔离墙

熔断器的思路借鉴自电路保险丝。正常时电路通畅,电流过载时保险丝熔断切断电路,保护电器不被烧毁。软件里的熔断器做同样的事,下游故障率超过阈值时跳闸,直接拒绝请求不再调用下游,既给下游恢复的时间,也保护自己的线程不被继续消耗。

PlantUML
正在加载图表…
PlantUML diagram

图:熔断器在关闭(正常)、打开(快速失败)、半开(试探恢复)三态间流转,故障时主动断开而非傻等

熔断器有三个状态。关闭态是正常调用,请求照常转发给下游,同时统计失败率。打开态是熔断触发,失败率超过阈值后直接拒绝所有请求,根本不发起对下游的调用,这就是快速失败。半开态是试探恢复,熔断打开一段时间后放少量请求去探测下游,探测成功说明下游恢复了,切回关闭态,探测失败说明还没好,重新打开。

这套状态机的价值在于,它能在下游故障时主动断开,而不是傻等。傻等的代价是线程堆积,主动断开的代价是这些请求直接失败,但线程立刻释放,系统的其他部分还能正常工作。用局部的失败换取全局的存活,这是容错设计的核心取舍。

资源隔离防单点拖垮全局

熔断是反应式的,等故障发生了才断。资源隔离是预防式的,在故障发生前就限制了它能影响的范围。思路是不同依赖用独立的资源池,一个依赖拖垮的只是它自己的池,不波及其他调用。

Hystrix 的线程池隔离是这套思路的代表。服务 A 调用 B 和 C,分别用两个独立线程池。C 变慢时,调 C 的请求都堆在 C 的线程池里,把这个池耗尽,但调 B 的请求用的是另一个池,不受影响,A 仍能正常服务 B 的请求。故障被隔离在 C 的范围内。

资源隔离的代价是开销。每个依赖一个独立线程池,线程切换和队列维护都有成本。Sentinel 走的是另一条路,用信号量隔离而不是线程池。信号量是一个计数器,拿到信号量才能调用,调用完释放。它没有线程切换开销,性能更好,但代价是调用还是在调用方线程上执行,不能像线程池那样自动给调用加超时。两种方案各有取舍,看场景选,对性能敏感用信号量,需要严格超时控制用线程池。

降级是熔断后的兜底

熔断把请求快速失败了,但失败之后呢,直接给用户一个 500 错误,体验是灾难。降级解决的就是这个问题,失败时返回兜底数据而不是报错。

降级策略看业务形态选。推荐服务挂了,返回一份默认推荐列表,用户至少能看到东西。实时数据挂了,返回上一次的缓存,数据稍旧但能用。完整功能挂了,返回简化版本,核心能用就行。还有一种降级是异步重试,失败的请求转异步慢慢处理,先给用户一个受理成功的响应。

降级必须分级

不是所有功能都能降级。核心交易链路,下单、支付、风控决策,这些降级了业务就没法做了,必须保持可用。能降级的是锦上添花的非核心功能,推荐、评价、统计、运营位。设计时必须先分清哪些是核心链路、哪些是辅助功能,只对辅助功能配降级。把核心交易也降级了,等于自废武功。

Hystrix 和 Sentinel 的取舍

这两个是 Java 生态里最主流的容错框架,但处境完全不同。

PlantUML
正在加载图表…
PlantUML diagram

图:Hystrix(线程池隔离,已停维护)与 Sentinel(信号量隔离,活跃维护)在隔离方式、能力、维护状态上的取舍

Hystrix 是 Netflix 出品,线程池隔离的经典实现,熔断加降级的能力齐全。问题是它已经停止维护了,Netflix 在 2018 年进入维护模式,不再加新功能。对于一个基础设施级的组件,停止维护意味着新场景没人适配,已知问题不会修,长期看是技术债。

Sentinel 是阿里开源,功能比 Hystrix 全,熔断、限流、系统自适应保护一体。它用信号量隔离,性能更好,还在活跃维护。生产里我倾向用 Sentinel,原因之一是它能做系统自适应保护,根据 CPU、Load、响应时间、线程数这些全局指标自动调节入口流量。单接口熔断只能看局部,系统自适应保护能看全局,当系统整体压力过大时自动降流入,这是更宏观的保护层。

Sentinel 的三种熔断策略

Sentinel 支持三种触发熔断的策略,对应不同的故障形态。

慢调用比例策略针对的是下游变慢但没报错的情况。设定一个响应时间阈值,超过这个阈值的算慢调用,统计窗口内慢调用占比超过设定值就熔断。比如 RT 大于 500 毫秒算慢调用,慢调用比例超过 50% 持续 10 秒触发熔断 30 秒。这策略适合保护那些慢但不挂的依赖。

异常比例策略针对的是下游开始报错的情况。异常请求占比超过阈值就熔断。异常数策略是异常请求的绝对数超过阈值就熔断,适合流量大但容错低的场景,比如 1 分钟异常超过 100 次就断。

三种策略不是选一个,是按依赖的故障模式组合配置。一个依赖可能既会变慢又会报错,那就慢调用和异常都配。

手动降级开关是另一条防线

自动熔断是系统自己反应,但有些压力是可预知的。大促前知道流量会涨,提前把一些非核心功能关掉腾出资源,这种事不该等熔断自动触发,应该人工干预。手动降级开关就是为这种场景准备的。

把开关放在配置中心,运营或值班人员一键关闭某个功能。关闭评论、关闭推荐、简化搜索、暂停报表导出,每关一个就腾出一批资源给核心链路。这套开关要提前在大促预案里规划好,分级配置,压力到哪一级关哪些功能,都写在预案里,值班时照着执行,而不是现场拍脑袋。

熔断必须有配套的降级逻辑

很多团队配了熔断规则,却没想好熔断后返回什么。结果熔断一触发,用户看到的全是报错,体验比不熔断还差。每个熔断点都必须配套明确的降级逻辑,要么返回缓存,要么返回默认值,要么走异步重试。光熔断不降级,等于把下游的故障原封不动甩给用户。这套闭环没做全,容错体系就是残缺的。

Continue Reading

关联文档推荐

查看全部
限流降级治理:统一规则中心与执行链路 — editorial cover photo限流算法全景:从计数器到令牌桶的工程实现 — editorial cover photo降级开关治理:统一管控与一键熔断 — editorial cover photo
作者:archy.shawn
声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。