咱们今天不讲童话,咱们讲**"系统保命学"**。
- 在微服务架构里,服务之间就像是一群互相借钱的酒肉朋友。平时你好我好大家好,一旦有个"朋友"(服务A)破产了(挂了)或者赖账(超时),如果不加控制,这种恐慌会瞬间传染给借钱给他的B、C、D,最后导致整个朋友圈(整个系统)集体跳楼。这就是雪崩效应。
为了防止集体跳楼,我们需要一种机制:熔断与降级 。今天的主角是 Hystrix (虽然老了,但它是祖师爷,原理通用)和 Sentinel(阿里出品,青出于蓝)。
第一层:雪崩效应------多米诺骨牌的惨案
底层原理 :
假设你有 100 个线程(Tomcat 线程池)。
服务 A 调用 服务 B。
突然,服务 B 卡死了(比如数据库死锁),每个请求都要耗时 5 秒才能返回。
- 第 1 秒 :来了 10 个请求,A 调 B,B 没反应。A 的 10 个线程被阻塞(挂起),等待 B。
- 第 2 秒:又来了 10 个请求,A 的线程池被占用了 20 个。
- 第 5 秒 :来了 100 个请求。A 的 100 个线程全部 在等 B 的回应。线程池耗尽!
- 第 6 秒:用户 C 访问服务 A 的"登录"功能(这功能本来不需要调 B)。但是!因为 A 的线程池满了,C 的请求连 A 的门都进不去!
结果:B 挂了,导致 A 也挂了。A 挂了,导致整个网关挂了。全站宕机。
第二层:熔断器的三种状态------"渣男"心理变化史
熔断器本质上是一个状态机。我们可以把它想象成一个被渣男(下游故障服务)伤透了心的女神(熔断器)。
1. 关闭状态(CLOSED)------ 热恋期
- 状态:一切正常。
- 行为:女神对渣男言听计从,请求直接放行。
- 底层逻辑:计数器记录失败次数。只要失败次数 < 阈值(比如 5 次),就一直是 CLOSED。
2. 打开状态(OPEN)------ 分手期
- 触发:渣男连续 5 次迟到/撒谎(请求超时/异常),达到了阈值。
- 行为 :女神瞬间黑化。
- 底层逻辑 :
- 熔断器状态变为 OPEN。
- 快速失败(Fail Fast) :后续来的所有请求,根本不会去调用下游服务,直接抛出异常(或者执行降级逻辑)。
- 目的:给下游服务"喘息"的时间,也保护上游线程池不被拖死。
3. 半开状态(HALF_OPEN)------ 考察期
- 触发:在 OPEN 状态停留了一段时间(比如 5 秒,这叫"熔断时长")。
- 行为:女神心软了,决定给渣男最后一次机会。
- 底层逻辑 :
- 状态变为 HALF_OPEN。
- 放行一个请求(探测请求)去试试水。
- 情况 A :如果这个请求成功 了 -> 渣男改邪归正 -> 状态变回 CLOSED。
- 情况 B :如果这个请求失败 了 -> 渣男无可救药 -> 状态变回 OPEN,继续闭关。
第三层:代码层面的"保命"实现
咱们用伪代码(类似 Sentinel/Hystrix 的逻辑)来看看底层是怎么实现的。
核心代码:熔断器逻辑
public class CircuitBreaker {
// 状态枚举
enum Status { CLOSED, OPEN, HALF_OPEN }
private Status status = Status.CLOSED;
private int failureCount = 0;
private int failureThreshold = 5; // 失败阈值
private long lastFailureTime = 0;
private long retryTimeWindow = 5000; // 重试时间窗口(5秒)
// 核心方法:执行请求
public Object execute(Request request) {
// 1. 检查是否需要尝试恢复(从 OPEN -> HALF_OPEN)
if (status == Status.OPEN) {
if (System.currentTimeMillis() - lastFailureTime > retryTimeWindow) {
status = Status.HALF_OPEN;
System.out.println("进入半开状态,试探一下...");
} else {
// 还在冷却期,直接拒绝
throw new RuntimeException("熔断器已打开,拒绝访问!");
}
}
try {
// 2. 真正调用下游服务
Object result = callRemoteService(request);
// 3. 调用成功后的处理
onSuccess();
return result;
} catch (Exception e) {
// 4. 调用失败后的处理
onFailure();
throw e;
}
}
private void onSuccess() {
failureCount = 0; // 重置计数器
status = Status.CLOSED; // 如果是半开状态,现在也变回关闭
}
private void onFailure() {
failureCount++;
lastFailureTime = System.currentTimeMillis();
// 5. 判断是否达到熔断阈值
if (failureCount >= failureThreshold) {
status = Status.OPEN;
System.out.println("达到阈值!熔断器打开!");
}
}
}
第四层:降级------熔断后的"备胎"计划
熔断只是止损 (我不调你了),但用户请求还在啊,你得给用户个交代。这就是降级。
降级不是"报错",而是"妥协"。
- 场景:双11大促,库存服务挂了。
- 不降级:页面显示"系统异常,500 Error"。(用户骂娘)
- 降级:页面显示"库存加载中...",或者显示一个默认的兜底数据,或者把非核心的"推荐商品"隐藏,只保留"购买按钮"。(用户理解)
代码实现(伪代码):
try {
// 尝试调用核心服务
return userService.getUserInfo(userId);
} catch (CircuitBreakerOpenException e) {
// 熔断发生了,执行降级逻辑
return getLocalCacheUserInfo(userId); // 返回本地缓存的旧数据
} catch (Exception e) {
// 其他异常
return new User("默认用户", "头像.jpg"); // 返回默认值
}
第五层:Hystrix vs Sentinel ------ 祖师爷 vs 进化体
虽然原理一样,但实现细节上有代差。
Hystrix(祖师爷)停止维护
- 隔离策略 :线程池隔离 。
- 它给每个依赖服务都开一个独立的线程池。
- 优点:彻底隔离,一个服务挂了不占主线程。
- 缺点 :太重了! 线程上下文切换极其消耗性能。
- 熔断算法 :基于滑动窗口的计数器。
- 改个熔断阈值(比如从 50% 改到 60%)?对不起,通常得重启服务,或者你费劲巴拉地集成 Archaius 配置中心。
- 只能做熔断 和降级。它就像个保安,只会说"这人病了,别让他进"
Sentinel(进化体)
- 隔离策略 :信号量隔离 (主要)+ 线程池隔离。
- 默认使用信号量(Semaphore),其实就是控制并发数(比如只允许 10 个请求同时调 B 服务)。
- 优点 :轻量级,没有线程切换开销,性能极高。
- 流量控制 :Sentinel 不仅仅是熔断,它还能做限流(QPS 控制)。
- 底层数据结构 :使用了滑动时间窗口(LeapArray) 。
- 它不是简单的记个数,而是把时间切成很多小格子(比如 1 秒切成 50 个格子,每个格子 20ms)。
- 这样可以更精准地统计"最近 1 秒"的失败率,而不是"从系统启动到现在"的失败率。
- 自带控制台(Dashboard) 。你在网页上拖个滑块,改个数字,秒级生效,无需重启。运维和运营人员都能自己改,这才是生产环境需要的。
- Java 和 Go 双修。现在的微服务架构很多是混合语言(Java 写核心,Go 写网关或旁路),Sentinel 能通吃。
- 不仅能熔断,还能限流 (QPS 控制)、系统自适应保护 (CPU 飙高自动降级)、热点参数限流(比如限制同一个用户 ID 的访问频率)。它不仅是保安,还是交通指挥官。
| 维度 | Hystrix (老前辈) | Sentinel (新霸主) | 胜出者 |
|---|---|---|---|
| 维护状态 | 🛑 停止维护 | ✅ 活跃更新 | Sentinel |
| 隔离方式 | 线程池(开销大,重) | 信号量(开销小,轻) | Sentinel |
| 熔断策略 | 仅基于异常比例 | 异常比例、慢调用、异常数 | Sentinel |
| 限流功能 | 弱(需结合其他组件) | 强(QPS、并发数、热点) | Sentinel |
| 规则配置 | 代码/配置文件(需重启) | 控制台/API(实时生效) | Sentinel |
| 监控面板 | 需搭建 Turbine(简陋) | 原生 Dashboard(强大) | Sentinel |
| 生态整合 | Spring Cloud Netflix | Spring Cloud Alibaba | Sentinel |
建议
场景一:新项目(Greenfield Project)
- 选 Sentinel。毫无疑问。
- 配合 Spring Cloud Alibaba ,利用它的
@SentinelResource注解,几行代码就能实现熔断、降级、限流。 - 部署一个 Sentinel Dashboard,系统稳定性监控一目了然。
场景二:老项目维护(Legacy Project)
- 如果老项目已经用了 Hystrix 且运行稳定,不要为了换而换。重构是有风险的。
- 但如果有新模块开发,或者准备做架构升级,建议逐步迁移到 Sentinel。
场景三:Go 语言项目
- 选 Sentinel (或者 Go 原生的限流库如
go-resilience),Hystrix 的 Go 版本(go-hystrix)也是很久没维护了。
一句话总结 :
Hystrix 是上个时代的英雄,值得尊敬;但 Sentinel 才是这个时代的王者,能帮你打赢现在的流量战争。拥抱 Sentinel,别回头。
总结
- 雪崩是线程池耗尽导致的连锁反应。
- 熔断 是状态机(CLOSED -> OPEN -> HALF_OPEN),目的是快速失败,保护系统。
- 降级 是熔断后的兜底方案 ,目的是用户体验。
- Hystrix 用线程池隔离,稳但重;Sentinel 用信号量隔离,快且轻,还带流量控制。
一句话 :"在分布式系统里,承认'由于下游故障,我无法提供服务'并不丢人,硬撑着直到把自己拖死才是最大的愚蠢。"