2449 字
约 8 分钟
22
S17-X04 · Hystrix 熔断降级:与 Sentinel 的机制对照

熔断器的代码都很短,短到让人以为它只是个 try/catch 加计数器。真正决定效果的是三个藏在参数里的决定:窗口多长、阈值多少、打开后多久试探。Hystrix 与 Sentinel 在这三处的默认值和语义并不一致,照抄参数往往得到"看起来在工作、实际没拦住"的熔断器。

1. 三态与滑动窗口:熔断先是一个统计问题

关闭(CLOSED)→ 打开(OPEN)→ 半开(HALF_OPEN)→ 关闭,是标准状态机。判断"要不要打开"靠滑动窗口里的健康统计,Hystrix 把这套统计做成 10 秒 10 个桶:

参数 默认 含义
metrics.rollingStats.timeInMilliseconds 10000 统计窗口总长
metrics.rollingStats.numBuckets 10 桶数,每桶 1 秒
circuitBreaker.requestVolumeThreshold 20 窗口内最少请求数,不足不熔断
circuitBreaker.errorThresholdPercentage 50 错误率阈值
circuitBreaker.sleepWindowInMilliseconds 5000 打开后多久进半开
circuitBreaker.forceOpen false 强制打开,排障用

requestVolumeThreshold=20 最常被忽略:窗口里只有 19 个请求时,全部失败也不熔断。低流量接口上这个默认值意味着"熔断器永远不工作",需按实际 QPS 调小。

numBuckets 与窗口长度必须整除,改窗口不改桶数会让桶宽变化,统计粒度变粗。

2. 线程池隔离 vs 信号量隔离

维度 THREAD SEMAPHORE
执行位置 独立线程池 调用方线程
能否超时中断 能,Future.get(timeout) 后 cancel(true) 不能,只能计数
上下文传递 ThreadLocal 丢失,需 HystrixConcurrencyStrategy 保留
开销 线程切换 + 队列 + 池维护 一个计数器
适用 网络调用、第三方 SDK 内存计算、本地缓存

线程池能"超时中断"是它唯一的硬优势:下游 hang 住时可以把工作线程打断,把调用方线程还回去。代价是两次上下文切换,且线程池本身占内存——coreSize=20 的路由就是 20 个常驻线程。

信号量隔离做不到超时。信号量只保证"同时进入的调用不超过 N 个",一旦拿到许可的调用卡住,调用方线程也跟着卡住,timeoutInMilliseconds 形同虚设。

hystrix:
  command:
    default:
      execution:
        isolation:
          strategy: THREAD
          thread:
            timeoutInMilliseconds: 800
      circuitBreaker:
        requestVolumeThreshold: 20
        errorThresholdPercentage: 50
        sleepWindowInMilliseconds: 5000
  threadpool:
    default:
      coreSize: 20
      maxQueueSize: 10
      queueSizeRejectionThreshold: 10

maxQueueSize=-1(默认)时用的是 SynchronousQueue,池满立即 reject;设成正数才变成有界队列,此时 queueSizeRejectionThreshold 才是真正的排队上限。

3. @HystrixCommand 与 fallback 的边界

@HystrixCommand(
    fallbackMethod = "queryFallback",
    commandKey = "orderQuery",
    groupKey = "order",
    commandProperties = {
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "800"),
        @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")
    })
public Order query(Long id) {
    return orderClient.get(id);
}

private Order queryFallback(Long id, Throwable t) {
    log.warn("fallback id={} cause={}", id, t.toString());
    return Order.empty(id);
}

三条约束必须记住:

  1. fallback 方法签名要与原方法一致,末尾可多一个 Throwable 参数;
  2. fallback 与原方法必须在同一个类里,代理调用不生效;
  3. fallback 里再抛异常,异常会直接冒给调用方,HystrixRuntimeException 会包一层。

fallback 返回空对象时注意别让它在业务上变成合法值——降级数据被打上正常标记,比直接报错更难查。

4. Hystrix 停更之后

Hystrix 最后一个版本是 1.5.18(2018 年),Netflix 随后宣布进入维护模式,不再加新功能。Spring Cloud 侧的连锁反应:

时间点 变化
2018-11 Netflix 宣布 Hystrix 进入维护模式
Spring Cloud Greenwich 官方推荐 Resilience4j,Hystrix 仍可用
Spring Cloud 2020.0 spring-cloud-starter-netflix-hystrix 移除
现在 新项目用 spring-cloud-starter-circuitbreaker-resilience4j

Resilience4j 是函数式的,模块拆成 CircuitBreaker / RateLimiter / Bulkhead / TimeLimiter / Retry,可自由组合。缺点是它没有线程池隔离——Bulkhead 只有信号量语义,超时中断得靠 TimeLimiter + CompletableFuture。

5. Sentinel 的流量控制:先限流,再熔断

Sentinel 的定位比 Hystrix 宽:它先做流量整形,再做熔断降级。资源是一等公民,规则通过 FlowRule 描述:

FlowRule rule = new FlowRule("queryOrder");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);        // 1=QPS, 0=线程数
rule.setCount(100);
rule.setStrategy(RuleConstant.STRATEGY_DIRECT);    // 直接 / 关联 / 链路
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
FlowRuleManager.loadRules(Collections.singletonList(rule));
概念 取值 语义
grade QPS / 线程数 按每秒通过量还是按并发占用
strategy 直接 / 关联 / 链路 关联=资源 A 忙则限 B;链路=只限某入口
controlBehavior 快速失败 / 预热 / 匀速排队 拒绝、冷启动爬坡、漏桶排队
count 数值 阈值

线程数模式统计的是当前占用线程数而非排队长度,在慢接口上比 QPS 模式更稳。预热模式(WARM_UP)适合刚启动、缓存还是空的场景:count 被当作稳态值,前若干秒按比例放行。

6. 熔断、热点与系统自适应

Sentinel 的熔断有三种策略,语义比 Hystrix 的"错误率"细:

grade 策略 count 含义 额外参数
0 慢调用比例 慢调用占比阈值(0–1) slowRatioThreshold=最大 RT(ms)
1 异常比例 异常占比阈值(0–1) minRequestAmount
2 异常数 窗口内异常个数 statIntervalMs
DegradeRule slow = new DegradeRule("queryOrder")
    .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType())
    .setCount(0.5)                 // 一半以上是慢调用就熔断
    .setSlowRatioThreshold(200)    // 超过 200ms 算慢
    .setMinRequestAmount(10)
    .setStatIntervalMs(10000)
    .setTimeWindow(10);            // 打开 10 秒
DegradeRuleManager.loadRules(List.of(slow));

慢调用比例是最实用的策略:它不依赖"异常",专治下游不报错但变慢。Hystrix 没有等价能力,只能用超时把慢变成异常,副作用是超时值必须设得比正常 RT 高很多。

热点参数限流(ParamFlowRule)按参数值单独计数,比如"同一个 userId 每秒最多 5 次",配合 @SentinelResource 的 blockHandler 返回友好错误。系统自适应保护(SystemRule)则用 load1、整体 RT、线程数、入口 QPS、CPU 使用率五个维度做兜底,阈值通常设成容量的 80% 左右。

7. 规则持久化:别把规则写在代码里

Sentinel 默认把规则放在内存,重启即丢。生产要用数据源:

@PostConstruct
public void init() {
    Properties p = new Properties();
    p.put(PropertyKeyConst.SERVER_ADDR, "127.0.0.1:8848");
    p.put(PropertyKeyConst.NAMESPACE, "prod");
    ReadableDataSource<String, List<FlowRule>> ds = new NacosDataSource<>(
        p, "sentinel-group", "order-flow-rules",
        src -> JSON.parseObject(src, new TypeReference<List<FlowRule>>() {}));
    FlowRuleManager.register2Property(ds.getProperty());
}
数据源 类 说明
Nacos NacosDataSource 最常用,支持 namespace/group
Apollo ApolloDataSource 键值监听,适合已有 Apollo 的团队
ZooKeeper ZkDataSource 强一致场景
本地文件 FileRefreshableDataSource 单机兜底

规则推送到客户端是秒级延迟,改完阈值立刻观察,会看到"还有一小段时间按旧值走"。

8. 能力对照

能力 Hystrix 1.5.18 Sentinel Resilience4j
线程池隔离 有 无(只有并发数限制) Bulkhead(信号量)
超时中断 有 无 TimeLimiter
熔断策略 错误率 慢调用/异常比例/异常数 失败率/慢调用
限流 无(靠线程池) QPS/线程数/热点/系统 RateLimiter
规则动态化 Archaius Nacos/Apollo/ZK 配置中心 + 自定义
控制台 Dashboard(已停更) 独立控制台 无官方控制台
维护状态 停止 活跃 活跃

9. 排错:五种"配了但没生效"

现象 原因 处理
熔断从不触发 窗口内请求数 < requestVolumeThreshold 按实际 QPS 调小到 5–10
熔断触发后立刻恢复 sleepWindow 太短,半开放行又全失败 按下游恢复时间设 5–30 秒
fallback 不执行 fallback 与原方法不同类,或签名不匹配 同类别私有方法,参数一致
Sentinel 规则重启丢失 只调了 loadRules,没接数据源 接 NacosDataSource 并 register2Property
@SentinelResource 不生效 方法被内部调用,绕过代理 走 Bean 调用,或改用 SphU.entry
限流后错误码是 500 未捕获 BlockException blockHandler 或全局异常处理返回 429

10. 边界:这些场景不该靠熔断解决

场景 更合适的手段 理由
下游持续慢但不出错 超时 + 连接池上限 + 慢调用熔断 熔断只能减少调用,不能提速
容量不足导致的排队 扩容 / 限流 熔断会把正常请求也拒掉
单机热点 key 热点参数限流 / 本地缓存 全局熔断粒度太粗
上下游强一致事务 不做降级,直接失败回滚 降级数据会污染事务
消息消费重试 消费端重试 + 死信队列 熔断对拉取模型不适用

一句话选型:要线程级隔离与超时中断,Hystrix 那一代最直接但已停更;要限流、热点、系统保护与动态规则,Sentinel 覆盖面最宽;要在 Spring Cloud 2020+ 上零负担,Resilience4j 是默认答案。

11. 自测

  1. 窗口里只有 19 个请求、全部失败,Hystrix 为什么没熔断?
  2. 信号量隔离为什么做不到超时中断?
  3. 线程池隔离下 ThreadLocal 为什么丢,怎么补?
  4. Sentinel 的"线程数"模式与 Hystrix 信号量在语义上差在哪?
  5. 慢调用比例熔断的 count 和 slowRatioThreshold 分别是什么?
  6. 规则只调 loadRules 不接数据源,重启后会发生什么?
S17-X04 · Hystrix 熔断降级:与 Sentinel 的机制对照
https://www.clxhxhhr.top/posts/5654/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。