Appearance
Hystrix 详解:延迟与容错库(熔断器)
Hystrix 是 Netflix 开源的延迟和容错库,核心思想是断路器模式(Circuit Breaker Pattern)。在分布式系统中,服务间调用不可避免会出现失败,Hystrix 通过隔离服务间的访问点、阻止级联失败、提供降级兜底,来保证系统的弹性和稳定性。
在 Spring Cloud 生态中,Hystrix 曾是最主流的熔断器组件,后被 Sentinel 取代,但它的设计思想(断路器、线程隔离、请求合并)至今仍是分布式容错领域的经典范式。
一、为什么需要 Hystrix
1.1 服务雪崩效应
在微服务架构中,一个请求往往需要调用链上多个服务。如果链路中某个服务不可用,调用方会不断重试,线程被阻塞等待超时,最终线程池耗尽,导致整个系统崩溃——这就是服务雪崩。
请求 → 服务A → 服务B → 服务C (故障)
│
▼
服务B 线程阻塞等待超时
│
▼
服务B 线程池耗尽
│
▼
服务A 不可用
│
▼
整个系统崩溃1.2 没有熔断器时会发生什么
客户端请求 ──→ 订单服务 ──→ 库存服务 (响应慢,假设超时 5s)
│
│ Tomcat 线程池 (200 个线程)
│ 每个请求等待库存服务 5s
│ 200 个线程 × 5s = 全部阻塞
│
▼
订单服务完全不可用
甚至影响同一 JVM 中的其他健康接口1.3 Hystrix 提供的核心能力
| 能力 | 说明 | 解决的问题 |
|---|---|---|
| 断路器 | 失败达到阈值自动熔断,快速失败 | 防止级联失败,避免线程长时间等待 |
| 服务降级 | 调用失败时返回 fallback 兜底结果 | 保证核心链路可用,非核心功能有损服务 |
| 线程隔离 | 每个依赖服务独立线程池 | 某个服务出问题不拖垮其他服务 |
| 超时控制 | 调用超时自动中断 | 防止线程无限等待 |
| 请求缓存 | 同一请求上下文内缓存结果 | 减少重复调用,提升性能 |
| 请求合并 | 将多个单次请求合并为一个批量请求 | 减少网络开销,提高吞吐量 |
二、核心原理:断路器模式
2.1 断路器状态机
断路器是 Hystrix 最核心的设计,本质上是一个三态状态机:
┌──────────────────────────────┐
│ │
▼ │
┌──────────┐ │
┌──────→│ CLOSED │ ← 正常状态 │
│ │ (关闭) │ 请求正常通过 │
│ └─────┬────┘ 统计失败次数 │
│ │ │
│ │ 失败次数 ≥ 阈值 (默认 20 次) │
│ │ 且失败率 ≥ 阈值 (默认 50%) │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ OPEN │ ← 熔断状态 │
│ │ (打开) │ 所有请求直接拒绝 │
│ └─────┬────┘ 不调用真实服务 │
│ │ │
│ │ 休眠窗口结束 (默认 5s) │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │HALF_OPEN │ ← 半开状态 │
│ │ (半开) │ 放行少量请求试探 │
│ └─────┬────┘ │
│ │ │
│ ┌────────┼────────┐ │
│ │ 成功 │ 失败 │
│ ▼ │ │
│ CLOSED └──────────────────────┘
│ 恢复正常
│
└── 断路器重新关闭2.2 状态转换详解
| 状态 | 行为 | 触发条件 |
|---|---|---|
| CLOSED | 请求正常通过;统计失败次数和失败率 | 默认状态 |
| CLOSED → OPEN | 断路器打开,所有请求直接失败 | 时间窗口内失败次数 ≥ requestVolumeThreshold(20) 且失败率 ≥ errorThresholdPercentage(50%) |
| OPEN | 请求直接快速失败,不调用真实服务 | 一直保持,直到休眠窗口结束 |
| OPEN → HALF_OPEN | 放行部分请求探测服务是否恢复 | 休眠窗口时间到 (sleepWindowInMilliseconds,默认 5s) |
| HALF_OPEN → CLOSED | 断路器关闭,恢复正常 | 探测请求成功 |
| HALF_OPEN → OPEN | 断路器重新打开 | 探测请求失败,继续熔断 |
2.3 配置示例
yaml
hystrix:
command:
default:
circuitBreaker:
# 请求量阈值:时间窗口内至少多少个请求才触发统计
requestVolumeThreshold: 20
# 失败率阈值:失败百分比超过该值触发熔断
errorThresholdPercentage: 50
# 休眠窗口:熔断后多久进入 HALF_OPEN 状态(毫秒)
sleepWindowInMilliseconds: 5000
# 是否强制开启熔断(测试用)
forceOpen: false
# 是否强制关闭熔断(测试用)
forceClosed: false2.4 断路器状态判断逻辑(伪代码)
java
public boolean isCircuitBreakerOpen() {
HealthCounts health = metrics.getHealthCounts();
// 总请求数未达到阈值,不熔断
if (health.getTotalRequests() < properties.requestVolumeThreshold().get()) {
return false;
}
// 失败率未达到阈值,不熔断
if (health.getErrorPercentage() < properties.errorThresholdPercentage().get()) {
return false;
}
// 达到熔断条件,检查是否在休眠窗口内
if (circuitOpened.get() == -1) {
circuitOpened.set(System.currentTimeMillis());
return true;
}
// 休眠窗口已过,进入 HALF_OPEN
if (System.currentTimeMillis() > circuitOpened.get() + properties.sleepWindowInMilliseconds().get()) {
circuitOpened.set(-1);
return false; // 允许请求通过(HALF_OPEN 逻辑)
}
return true;
}三、线程隔离
3.1 为什么需要隔离
在单体应用中,多个功能共享同一个线程池。在微服务中,如果不做隔离,一个下游服务的延迟会导致所有调用该服务的线程被阻塞,进而影响其他正常的下游调用。
Hystrix 提供两种隔离策略:
3.2 线程池隔离(THREAD)
每个依赖服务分配独立的线程池,调用该服务时从对应线程池获取线程执行。
┌─────────────────────────────────────────────────────────┐
│ 调用方服务 │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 商品服务线程池 │ │ 订单服务线程池 │ │
│ │ coreSize: 10 │ │ coreSize: 10 │ │
│ │ maxSize: 10 │ │ maxSize: 10 │ │
│ │ queueSize: 10 │ │ queueSize: 10 │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
└───────────┼────────────────────┼────────────────────────┘
▼ ▼
┌──────────┐ ┌──────────┐
│ 商品服务 │ │ 订单服务 │
└──────────┘ └──────────┘优点:
- 完全隔离,一个服务慢不会拖垮其他服务
- 支持超时控制,可设置超时时间
- 支持异步调用,不阻塞主线程
缺点:
- 线程池有创建和切换开销
- 需要根据服务数量合理配置线程池大小
3.3 信号量隔离(SEMAPHORE)
不使用额外线程池,而是通过信号量(计数器)限制并发数。
请求线程 ──→ 尝试获取信号量
│
├── 获取成功 → 执行远程调用 → 释放信号量
│
└── 获取失败 → 直接降级优点:
- 轻量级,无线程切换开销
- 适合低延迟、高并发场景
缺点:
- 不支持异步,调用方线程会阻塞
- 不支持超时控制(超时由调用方自己控制)
3.4 对比总结
| 维度 | 线程池隔离 (THREAD) | 信号量隔离 (SEMAPHORE) |
|---|---|---|
| 隔离级别 | 完全隔离,独立线程池 | 并发数限制,共享线程 |
| 超时支持 | 支持,可中断执行 | 不支持 Hystrix 层超时 |
| 异步支持 | 支持 queue() / observe() | 不支持 |
| 线程切换 | 有上下文切换开销 | 无上下文切换开销 |
| 适用场景 | 网络调用、耗时操作 | 低延迟、高并发访问 |
| 配置复杂度 | 需配置线程池参数 | 仅需配置并发数 |
| 默认策略 | 否 | 是 |
| 并发限制 | 线程池大小 + 队列大小 | maxConcurrentRequests |
3.5 配置示例
yaml
hystrix:
command:
# 针对特定 command 的配置
"ProductService#getProduct":
execution:
isolation:
# 隔离策略:THREAD(线程池)或 SEMAPHORE(信号量)
strategy: THREAD
thread:
timeoutInMilliseconds: 3000
semaphore:
maxConcurrentRequests: 20
# 全局默认配置
default:
execution:
isolation:
strategy: THREAD
thread:
timeoutInMilliseconds: 1000
threadpool:
# 针对特定线程池的配置
"ProductService":
coreSize: 10 # 核心线程数
maxQueueSize: 20 # 等待队列最大大小
queueSizeRejectionThreshold: 15 # 队列大小拒绝阈值
default:
coreSize: 10
maxQueueSize: -1 # -1 表示使用 SynchronousQueue
queueSizeRejectionThreshold: 5四、服务降级(Fallback)
4.1 基本用法
@HystrixCommand 注解通过 fallbackMethod 指定降级方法:
java
@Service
public class ProductService {
@HystrixCommand(
fallbackMethod = "getProductFallback",
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
}
)
public Product getProduct(Long id) {
// 调用远程服务
return restTemplate.getForObject("http://product-service/product/" + id, Product.class);
}
// 降级方法:参数和返回值必须与原方法一致
private Product getProductFallback(Long id) {
return new Product(id, "降级默认商品", BigDecimal.ZERO);
}
}4.2 降级触发条件
以下情况会触发 fallback:
| 触发条件 | 说明 |
|---|---|
| 断路器打开 | 服务被熔断,直接走降级 |
| 线程池/信号量满 | 无可用资源,快速失败 |
| 请求超时 | 执行时间超过 timeoutInMilliseconds |
| 执行异常 | 业务方法抛出异常(非 HystrixBadRequestException) |
4.3 降级策略
策略一:返回默认值
java
@HystrixCommand(fallbackMethod = "getDefaultProduct")
public Product getProduct(Long id) {
return productClient.getProduct(id);
}
private Product getDefaultProduct(Long id) {
// 返回一个兜底的默认商品
return Product.builder()
.id(id)
.name("商品暂时不可用")
.price(BigDecimal.ZERO)
.build();
}策略二:返回缓存数据
java
@HystrixCommand(fallbackMethod = "getProductFromCache")
public Product getProduct(Long id) {
return productClient.getProduct(id);
}
private Product getProductFromCache(Long id) {
// 从本地缓存或 Redis 中获取旧数据
Product cached = cacheManager.get("product:" + id);
if (cached != null) {
log.warn("服务降级,返回缓存数据,productId={}", id);
return cached;
}
return new Product(id, "商品不存在", null);
}策略三:返回空列表
java
@HystrixCommand(fallbackMethod = "getEmptyList")
public List<Product> listProducts(Long categoryId) {
return productClient.listProducts(categoryId);
}
private List<Product> getEmptyList(Long categoryId) {
log.warn("商品列表查询降级,返回空列表,categoryId={}", categoryId);
return Collections.emptyList();
}策略四:调用备用服务
java
@HystrixCommand(fallbackMethod = "callBackupService")
public OrderResult createOrder(OrderRequest request) {
return primaryOrderService.create(request);
}
private OrderResult callBackupService(OrderRequest request) {
// 主服务不可用,降级到备用服务
log.warn("主订单服务不可用,切换到备用服务");
return backupOrderService.create(request);
}4.4 获取异常信息
降级方法可以接收 Throwable 参数,用于区分不同异常的处理逻辑:
java
@HystrixCommand(fallbackMethod = "handleFailure")
public Result<String> placeOrder(OrderRequest request) {
return orderClient.place(request);
}
private Result<String> handleFailure(OrderRequest request, Throwable e) {
if (e instanceof HystrixTimeoutException) {
log.warn("下单超时,订单号暂未返回,请稍后查询");
return Result.fail("系统繁忙,请稍后重试");
}
if (e instanceof HystrixRuntimeException) {
log.error("下单失败,原因:{}", e.getMessage());
return Result.fail("下单失败,请重试");
}
return Result.fail("未知错误");
}4.5 降级配置
yaml
hystrix:
command:
default:
fallback:
isolation:
semaphore:
# Fallback 的最大并发请求数(默认 10)
# 如果降级方法也调用远程服务,需适当调大
maxConcurrentRequests: 20
# 是否启用降级
enabled: true五、超时控制
5.1 超时如何工作
Hystrix 在线程池隔离模式下,通过独立线程执行远程调用,并在主线程中设置超时定时器。一旦超时,执行线程被中断,主线程立即触发 fallback。
时间轴 ─────────────────────────────────────────────────────→
主线程: 提交任务 ─→ 等待... ─→ 超时检测 ─→ 触发 Fallback ─→ 返回
│
执行线程: ───────────→ 执行远程调用 ───────────→ 被中断
↑ ↑
0ms timeoutInMilliseconds (默认 1000ms)5.2 配置示例
yaml
hystrix:
command:
default:
execution:
isolation:
thread:
# 超时时间(毫秒),默认 1000ms
timeoutInMilliseconds: 3000
# 是否启用超时
timeoutEnabled: true
# 超时时是否中断执行线程
interruptOnTimeout: true
# 取消时是否中断执行线程
interruptOnCancel: true5.3 超时 vs 断路器
| 维度 | 超时控制 | 断路器 |
|---|---|---|
| 粒度 | 单个请求级别 | 服务级别 |
| 判断依据 | 执行时间 | 失败率统计 |
| 行为 | 超时后中断请求,走 fallback | 熔断后所有请求直接走 fallback |
| 恢复方式 | 下一个请求正常执行 | 需要等待休眠窗口结束 |
| 配置项 | timeoutInMilliseconds | errorThresholdPercentage / sleepWindowInMilliseconds |
两者配合使用: 超时是单次请求的"保镖",断路器是服务的"医生"。超时发现单个请求的问题,断路器从统计层面发现服务的整体健康状态。
六、请求缓存(Request Cache)
6.1 原理
在同一个 HystrixRequestContext 中,如果多次调用同一个 HystrixCommand 且参数相同,Hystrix 会直接返回缓存结果,不再重复执行远程调用。
同一个请求上下文内:
第一次调用 getProduct(1001) → 执行远程调用 → 缓存结果
第二次调用 getProduct(1001) → 命中缓存 → 直接返回
第三次调用 getProduct(1001) → 命中缓存 → 直接返回6.2 代码示例
java
@Service
public class ProductService {
@CacheResult
@HystrixCommand(fallbackMethod = "getProductFallback")
public Product getProduct(@CacheKey Long id) {
log.info("执行远程调用,productId={}", id);
return restTemplate.getForObject("http://product-service/product/" + id, Product.class);
}
/**
* 更新商品后,清除缓存
*/
@CacheRemove(commandKey = "getProduct")
@HystrixCommand
public void updateProduct(@CacheKey("id") Product product) {
restTemplate.put("http://product-service/product/" + product.getId(), product);
}
private Product getProductFallback(Long id) {
return new Product(id, "降级商品", null);
}
}
// 使用时必须初始化 HystrixRequestContext
@RestController
public class OrderController {
@Autowired
private ProductService productService;
@GetMapping("/order/detail")
public Result<OrderDetail> getOrderDetail(@RequestParam Long orderId) {
// 初始化请求上下文(通常在 Filter 中统一处理)
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
Order order = orderService.getOrder(orderId);
// 多次调用同一个 productId,仅第一次发起远程调用
Product product1 = productService.getProduct(order.getProductId());
Product product2 = productService.getProduct(order.getProductId());
// product1 == product2,且只打印一次 "执行远程调用"
return Result.success(new OrderDetail(order, product1));
} finally {
context.shutdown();
}
}
}6.3 统一初始化(Servlet Filter)
java
@Component
public class HystrixRequestContextFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
chain.doFilter(request, response);
} finally {
context.shutdown();
}
}
}6.4 注意事项
- 缓存仅在同一请求上下文内有效,跨请求不共享
- 缓存 Key 默认使用所有参数,可通过
@CacheKey指定 - 缓存不适用于数据实时性要求高的场景
- 需确保
HystrixRequestContext被正确初始化和清理
七、请求合并(Request Collapsing)
7.1 原理
将短时间内多个独立的单个请求合并为一个批量请求,减少网络连接开销,提高吞吐量。
合并前(N 次网络调用):
请求1: getProduct(1) ──→ 网络 ──→ 商品服务
请求2: getProduct(2) ──→ 网络 ──→ 商品服务 ← N 次网络开销
请求3: getProduct(3) ──→ 网络 ──→ 商品服务
合并后(1 次网络调用):
请求1: getProduct(1) ─┐
请求2: getProduct(2) ─┼──→ 网络 ──→ 商品服务 /products?ids=1,2,3
请求3: getProduct(3) ─┘
│
▼
拆分结果返回给各自请求7.2 代码示例
java
@Service
public class ProductService {
/**
* 批量查询方法(被合并后的实际调用)
*/
@HystrixCommand
public List<Product> getProducts(List<Long> ids) {
log.info("批量查询商品,ids={}", ids);
return restTemplate.getForObject(
"http://product-service/products?ids=" +
ids.stream().map(String::valueOf).collect(Collectors.joining(",")),
new ParameterizedTypeReference<List<Product>>() {}
);
}
/**
* 请求合并器:将单个 getProduct 调用合并为 getProducts 批量调用
*/
@HystrixCollapser(
batchMethod = "getProducts", // 批量方法
collapserProperties = {
@HystrixProperty(name = "timerDelayInMilliseconds", value = "20"), // 合并时间窗口
@HystrixProperty(name = "maxRequestsInBatch", value = "200") // 最大合并数
}
)
public Product getProduct(Long id) {
// 此方法不会被真正调用,Hystrix 会拦截并合并后调用 batchMethod
return null;
}
}7.3 配置详解
yaml
hystrix:
collapser:
default:
# 合并时间窗口(毫秒):等待多久后执行批量请求
timerDelayInMilliseconds: 20
# 最大合并请求数:达到这个数量立即执行,不等时间窗口
maxRequestsInBatch: 200
# 是否启用请求缓存(合并器范围内的缓存)
requestCache:
enabled: true7.4 适用场景
| 适用 | 不适用 |
|---|---|
| 高频调用同一个接口,参数不同 | 低频调用,合并窗口内凑不够批量 |
| 下游服务支持批量查询 | 下游服务不支持批量接口 |
| 网络延迟是主要瓶颈 | 需要极低延迟(合并会增加等待时间) |
| 读操作(幂等,可合并) | 写操作(语义不同,慎用) |
八、核心配置大全
8.1 断路器配置
yaml
hystrix:
command:
default:
circuitBreaker:
# 是否启用断路器
enabled: true
# 时间窗口内最少请求数,不足则不触发熔断
requestVolumeThreshold: 20
# 休眠窗口:熔断后多久进入 HALF_OPEN(毫秒)
sleepWindowInMilliseconds: 5000
# 错误率阈值(百分比),超过触发熔断
errorThresholdPercentage: 50
# 强制开启熔断(测试用)
forceOpen: false
# 强制关闭熔断(测试用)
forceClosed: false8.2 线程池配置
yaml
hystrix:
threadpool:
default:
# 核心线程数
coreSize: 10
# 最大线程数(仅当 maxQueueSize == -1 时,此配置无效)
maximumSize: 10
# 空闲线程存活时间(分钟)
keepAliveTimeMinutes: 1
# 最大队列大小
# -1: 使用 SynchronousQueue(无缓冲,直接交给线程)
# >0: 使用 LinkedBlockingQueue
maxQueueSize: -1
# 队列大小拒绝阈值(即使队列未满,达到此值也拒绝)
queueSizeRejectionThreshold: 5
# 等待队列的超时时间(毫秒)
allowMaximumSizeToDivergeFromCoreSize: false8.3 执行配置
yaml
hystrix:
command:
default:
execution:
isolation:
# 隔离策略:THREAD 或 SEMAPHORE
strategy: THREAD
thread:
# 超时时间(毫秒)
timeoutInMilliseconds: 1000
# 是否启用超时
timeoutEnabled: true
# 超时时是否中断线程
interruptOnTimeout: true
semaphore:
# 信号量最大并发数
maxConcurrentRequests: 108.4 降级配置
yaml
hystrix:
command:
default:
fallback:
# 是否启用降级
enabled: true
isolation:
semaphore:
# Fallback 方法的最大并发数
maxConcurrentRequests: 108.5 指标采集配置
yaml
hystrix:
command:
default:
metrics:
# 滚动统计窗口时间(毫秒),每次滚动 1/N
rollingStats:
timeInMilliseconds: 10000
numBuckets: 10
# 滑动窗口的桶数,和 timeInMilliseconds 配合使用
# 每个桶的时间 = timeInMilliseconds / numBuckets
# 此处每个桶为 1 秒
rollingPercentile:
enabled: true
timeInMilliseconds: 60000
numBuckets: 6
bucketSize: 100
# 健康检查间隔(毫秒)
healthSnapshot:
intervalInMilliseconds: 5008.6 Java 配置方式
java
@Configuration
public class HystrixConfig {
@Bean
public HystrixCommandAspect hystrixCommandAspect() {
return new HystrixCommandAspect();
}
/**
* 也可以通过代码方式配置
*/
@PostConstruct
public void init() {
ConfigurationManager.getConfigInstance().setProperty(
"hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds", 3000);
ConfigurationManager.getConfigInstance().setProperty(
"hystrix.command.default.circuitBreaker.requestVolumeThreshold", 20);
ConfigurationManager.getConfigInstance().setProperty(
"hystrix.threadpool.default.coreSize", 10);
}
}九、Hystrix Dashboard + Turbine 监控
9.1 启用 Hystrix 监控端点
xml
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>yaml
# application.yml
management:
endpoints:
web:
exposure:
include: hystrix.stream
endpoint:
hystrix:
stream:
enabled: truejava
// 启动类
@SpringBootApplication
@EnableCircuitBreaker // 启用断路器
@EnableHystrixDashboard // 启用 Dashboard
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}9.2 Dashboard 访问
- Dashboard 面板:
http://localhost:8080/hystrix - 监控数据流:
http://localhost:8080/actuator/hystrix.stream
在 Dashboard 面板中输入本服务的 stream 地址,即可看到实时的熔断器监控面板。
9.3 Turbine 聚合监控
当有多个服务实例时,Turbine 可以将多个 /actuator/hystrix.stream 聚合为一个 stream,Dashboard 只需订阅 Turbine 的聚合流即可看到所有服务的熔断状态。
┌──────────────────────────────────────────────────────────┐
│ Hystrix Dashboard │
│ http://localhost:8080/hystrix │
└────────────────────────┬─────────────────────────────────┘
│ 订阅
▼
┌──────────────────────────────────────────────────────────┐
│ Turbine Server │
│ http://localhost:9000/turbine.stream │
└────────────────────────┬─────────────────────────────────┘
│ 聚合
┌─────────────┼─────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 服务实例1 │ │ 服务实例2 │ │ 服务实例3 │
│ :8081 │ │ :8082 │ │ :8083 │
│ /actuator │ │ /actuator │ │ /actuator │
│ /hystrix │ │ /hystrix │ │ /hystrix │
│ .stream │ │ .stream │ │ .stream │
└──────────┘ └──────────┘ └──────────┘Turbine 配置:
xml
<!-- Turbine 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>java
@SpringBootApplication
@EnableTurbine
public class TurbineApplication {
public static void main(String[] args) {
SpringApplication.run(TurbineApplication.class, args);
}
}yaml
# Turbine application.yml
spring:
application:
name: turbine-server
server:
port: 9000
turbine:
# 聚合的服务名列表
app-config: order-service,product-service
# 集群名称
cluster-name-expression: new String("default")
# 同一主机上的实例是否合并
combine-host-port: true
# 注册中心
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/十、Hystrix vs Sentinel
10.1 对比总览
| 维度 | Hystrix | Sentinel |
|---|---|---|
| 隔离策略 | 线程池隔离 / 信号量隔离 | 信号量隔离(线程池隔离不推荐) |
| 熔断策略 | 异常比例(仅此一种) | 慢调用比例 / 异常比例 / 异常数 |
| 流量控制 | 无(仅熔断,不提供限流) | 丰富:QPS / 线程 / 关联 / 链路 / 热点参数 |
| 系统保护 | 无 | 支持:Load / CPU / RT / 入口 QPS |
| 实时监控 | Dashboard + Turbine | Sentinel 控制台(功能更强大) |
| 规则动态修改 | 有限支持(需配置中心辅助) | 原生支持,实时生效 |
| 规则持久化 | 不原生支持 | 支持 Nacos / Apollo / ZooKeeper |
| 自适应 | 无 | 支持系统自适应保护 |
| 维护状态 | 停止维护(2018 年进入维护模式) | 活跃维护(阿里巴巴持续投入) |
| 开源社区 | Netflix(已停止) | 阿里巴巴(活跃) |
| 性能 | 线程池模式有线程切换开销 | 信号量模式,轻量级 |
| 中文生态 | 英文文档为主 | 中文文档丰富,国内社区活跃 |
| Spring Cloud 集成 | spring-cloud-starter-netflix-hystrix | spring-cloud-starter-alibaba-sentinel |
10.2 为什么 Hystrix 被 Sentinel 取代
- Netflix 停止维护:2018 年 Netflix 宣布 Hystrix 进入维护模式,不再开发新功能,仅修复 Bug
- 功能单一:Hystrix 只有熔断,没有限流、系统保护等能力,需要配合其他组件
- Sentinel 功能丰富:流控、熔断、系统保护一体化,规则动态生效,控制台可视化强大
- 国内生态:Sentinel 是阿里巴巴开源项目,中文文档和社区支持更好
- 性能优势:Sentinel 默认信号量隔离,轻量无锁,资源占用更少
10.3 Hystrix 仍值得学习的原因
尽管 Hystrix 已停维,但其核心设计思想(断路器模式、线程隔离、请求合并)是分布式容错领域的经典范式。Sentinel 的熔断降级也借鉴了 Hystrix 的设计。理解 Hystrix 有助于深入理解 Sentinel 和任何断路器类组件。
十一、Hystrix 工作流程
11.1 完整请求流程图
┌─────────────────────────────────────────────────────────────┐
│ Hystrix 请求处理流程 │
│ │
│ 1. 构造 HystrixCommand 或 HystrixObservableCommand │
│ │ │
│ ▼ │
│ 2. 执行命令 │
│ ├── execute() → 同步阻塞,返回单个结果 │
│ ├── queue() → 异步,返回 Future │
│ └── observe() → 异步,返回 Observable │
│ │ │
│ ▼ │
│ 3. 请求缓存是否命中? ──── 是 ────→ 返回缓存结果 │
│ │ │
│ 否 │
│ │ │
│ ▼ │
│ 4. 断路器是否打开? ──── 是 ────→ 直接走 Fallback │
│ │ │
│ 否 │
│ │ │
│ ▼ │
│ 5. 线程池/信号量是否已满? ──── 是 ────→ 直接走 Fallback │
│ │ │
│ 否 │
│ │ │
│ ▼ │
│ 6. 执行 HystrixCommand.run() 或 construct() │
│ │ │
│ ├── 执行成功 ──→ 7. 计算断路器健康指标 │
│ │ │ │
│ │ ▼ │
│ │ 8. 返回正常结果 │
│ │ │
│ └── 执行失败/超时 ──→ 9. 走 Fallback │
│ │ │
│ ├── Fallback 成功 ──→ 返回降级结果│
│ │ │
│ └── Fallback 失败 ──→ 抛出异常 │
│ │
└─────────────────────────────────────────────────────────────┘11.2 各步骤说明
| 步骤 | 说明 | 关键类/方法 |
|---|---|---|
| 1. 构造命令 | 创建 HystrixCommand 封装远程调用 | HystrixCommand, HystrixObservableCommand |
| 2. 执行命令 | 选择执行方式:同步/异步/响应式 | execute(), queue(), observe() |
| 3. 检查缓存 | 同一请求上下文内,相同参数是否已缓存 | RequestCache |
| 4. 检查断路器 | 断路器是否 OPEN,是则直接拒绝 | HystrixCircuitBreaker |
| 5. 检查资源 | 线程池/信号量是否有可用资源 | ThreadPoolExecutor, TryableSemaphore |
| 6. 执行方法 | 实际调用远程服务 | run(), construct() |
| 7. 计算健康 | 收集成功/失败/超时指标,更新断路器状态 | HealthCountsStream |
| 8. 返回结果 | 返回正常响应 | — |
| 9. 执行降级 | 调用 getFallback() 获取兜底结果 | getFallback() |
11.3 三种执行方式
java
// 1. 同步执行 - execute()
Product product = productCommand.execute(); // 阻塞直到返回
// 2. 异步执行 - queue()
Future<Product> future = productCommand.queue(); // 立即返回 Future
Product product = future.get(); // 需要时再阻塞获取
// 3. 响应式执行 - observe()
Observable<Product> observable = productCommand.observe(); // 立即返回 Observable
observable.subscribe(product -> {
// 处理结果
});十二、面试要点
Q1: 什么是服务雪崩?如何解决?
回答: 服务雪崩是指微服务调用链中,一个服务不可用导致调用方线程阻塞,进而耗尽线程池资源,最终拖垮整个系统的级联故障现象。
解决方案:
- 熔断:当失败率达到阈值时,自动熔断,快速失败,不再调用故障服务
- 降级:调用失败时返回兜底数据(默认值、缓存、空列表)
- 隔离:线程池隔离或信号量隔离,防止一个服务拖垮其他服务
- 超时:设置合理的超时时间,避免线程无限等待
- 限流:限制单位时间内的请求量,超过阈值直接拒绝
Q2: Hystrix 断路器有哪几种状态?如何转换?
回答: 三种状态:
- CLOSED(关闭):正常状态,请求正常通过,统计失败次数和失败率
- OPEN(打开):熔断状态,所有请求直接走 Fallback,不调用真实服务
- HALF_OPEN(半开):休眠窗口结束后进入,放行少量请求探测服务是否恢复
转换流程:
- CLOSED → OPEN:失败次数 ≥
requestVolumeThreshold且失败率 ≥errorThresholdPercentage - OPEN → HALF_OPEN:经过
sleepWindowInMilliseconds休眠窗口 - HALF_OPEN → CLOSED:探测请求成功,恢复正常
- HALF_OPEN → OPEN:探测请求失败,继续熔断
Q3: Hystrix 线程池隔离 vs 信号量隔离,有什么区别?
回答:
| 维度 | 线程池隔离 | 信号量隔离 |
|---|---|---|
| 原理 | 每个依赖服务独立线程池 | 信号量计数器限制并发 |
| 超时 | 支持,可中断执行线程 | 不支持,调用方线程阻塞 |
| 异步 | 支持 queue() / observe() | 不支持 |
| 开销 | 有线程创建和上下文切换开销 | 轻量,几乎无开销 |
| 适用场景 | 网络调用、耗时操作 | 低延迟、高并发(如缓存访问) |
选型建议: 对外部依赖(网络调用)用线程池隔离;对内部低延迟依赖(如本地缓存)用信号量隔离。
Q4: Hystrix 和 Sentinel 的主要区别?为什么 Sentinel 更优?
回答:
- Hystrix 已停止维护(2018 年),Sentinel 持续活跃开发
- 功能丰富度:Hystrix 仅有熔断;Sentinel 额外提供限流(QPS/线程/关联/热点参数)、系统保护、自适应保护
- 熔断策略:Hystrix 仅支持异常比例;Sentinel 支持慢调用比例、异常比例、异常数三种
- 规则管理:Sentinel 支持规则动态修改、实时生效、持久化到 Nacos/Apollo
- 控制台:Sentinel 控制台功能更强大,支持实时监控和规则管理
- 性能:Sentinel 默认信号量隔离,轻量无锁,性能更优
Q5: Hystrix 请求合并的原理是什么?有哪些优缺点?
回答: 请求合并(Request Collapsing)将短时间内多个独立的单个请求合并为一个批量请求,减少网络连接次数。
原理: @HystrixCollapser 在指定的时间窗口内(timerDelayInMilliseconds)收集单个请求,达到最大数量(maxRequestsInBatch)或时间窗口到期后,一次性调用批量方法,再将结果拆分返回给各个请求。
优点:
- 减少网络连接次数,降低网络开销
- 提高下游服务吞吐量
缺点:
- 增加单个请求的延迟(等待合并窗口)
- 需要下游服务支持批量查询接口
- 不适合需要极低延迟的实时场景
Q6: Hystrix 的降级策略有哪些?各自适用什么场景?
回答:
| 策略 | 代码示例 | 适用场景 |
|---|---|---|
| 返回默认值 | return new Product("默认商品") | 读操作,非关键数据 |
| 返回缓存数据 | return cache.get(key) | 有历史数据可用的场景 |
| 返回空列表 | return Collections.emptyList() | 列表查询,前端可处理空数据 |
| 调用备用服务 | return backupService.call() | 有主备架构的场景 |
| 抛出业务异常 | throw new BusinessException() | 写操作,不能返回假数据 |
核心原则: 降级结果必须对业务无害。写操作不能返回假成功,读操作返回空数据比抛异常更友好。
