官网
官网图示
步骤说明
创建HystrixCommand 或者HystrixObservableCommand
在使用Hystrix的过程中,会对依赖服务的调用请求封装成命令对象,Hystrix 对 命令对象抽象了两个抽象类:HystrixCommand 和HystrixObservableCommand 。
HystrixCommand 表示的命令对象会返回一个唯一返回值:
public class QueryOrderCommand extends HystrixCommand<Order> {
private String orderId;
public QueryOrderCommand(String orderId){
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("hystrix-order-group"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("hystrix-thread-order"))
.andCommandKey(HystrixCommandKey.Factory.asKey("hystrix-pay-order"))
.andCommandPropertiesDefaults(HystrixCommandProperties.defaultSetter())
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.defaultSetter()
.withCoreSize(10)
.withQueueSizeRejectionThreshold(15)
)
);
this.orderId = orderId;
}
@Override
protected Order run() throws Exception {
System.out.println("fetching order info via service call");
return new Order();
}
}
class Order{
private String orderId;
private String productId;
private String status;
}
HystrixObservableCommand 表示的命令对象 会返回多个返回值:
执行命令
Hystrix中共有4种方式执行命令,如下所示:
这四种命令中,exeucte()、queue()、observe()的表示也是通过toObservable()实现的,其转换关系如下图所示:
K value = command.execute();
// 等价语句:
K value = command.execute().queue().get();
Future<K> fValue = command.queue();
//等价语句:
Future<K> fValue = command.toObservable().toBlocking().toFuture();
Observable<K> ohValue = command.observe(); //hot observable,立刻订阅,命令立刻执行
//等价语句:
Observable<K> ohValue = command.toObservable().subscribe(subject);
// 上述执行最终实现还是基于`toObservable()`
Observable<K> ocValue = command.toObservable(); //cold observable,延后订阅,订阅发生后,执行才真正执行
返回结果是否被缓存?
如果当前命令对象配置了允许从结果缓存中取返回结果,并且在结果缓存中已经缓存了请求结果,则缓存的请求结果会立刻通过Observable的格式返回。具体Hystrix的缓存策略,请参考``
断路器是否打开?
如果第3步没有缓存没有命中,则判断一下当前断路器的断路状态是否打开。如果断路器状态为打开状态,则Hystrix将不会执行此Command命令,直接执行步骤8 调用Fallback;
如果断路器状态是关闭,则执行 步骤5 检查是否有足够的资源运行 Command命令
资源(线程池/队列/信号量)是否已满?
如果当前要执行的Command命令 先关连的线程池 和队列(或者信号量)资源已经满了,Hystrix将不会运行 Command命令,直接执行 步骤8的Fallback降级处理;如果未满,表示有剩余的资源执行Command命令,则执行步骤6
执行 HystrixObservableCommand.construct() 或者 HystrixCommand.run()
当经过步骤5 判断,有足够的资源执行Command命令时,本步骤将调用Command命令运行方法,基于不同类型的Command,有如下两种两种运行方式:
如果run() 或者construct()方法 的真实执行时间超过了Command设置的超时时间阈值, 则当前则执行线程(或者是独立的定时器线程)将会抛出TimeoutException。抛出超时异常TimeoutException,后,将执行步骤8的Fallback降级处理。即使run()或者construct()执行没有被取消或中断,最终能够处理返回结果,但在降级处理逻辑中,将会抛弃run()或construct()方法的返回结果,而返回Fallback降级处理结果。
注意事项 需要注意的是,Hystrix无法强制 将正在运行的线程停止掉—Hystrix能够做的最好的方式就是在JVM中抛出一个InterruptedException。如果Hystrix包装的工作不抛出中断异常InterruptedException, 则在Hystrix线程池中的线程将会继续执行,尽管调用的客户端已经接收到了TimeoutException。这种方式会使Hystrix 的线程池处于饱和状态。大部分的Java Http Client 开源库并不会解析 InterruptedException。所以确认HTTP client 相关的连接和读/写相关的超时时间设置。 如果Command命令没有抛出任何异常,并且有返回结果,则Hystrix将会在做完日志记录和统计之后会将结果返回。 如果是通过run()方式运行,则返回一个Obserable对象,包含一个唯一值,并且发送一个onCompleted通知;如果是通过consturct()方式运行 ,则返回一个Observable对象。
计算断路器的健康状况
Hystrix 会统计Command命令执行执行过程中的成功数、失败数、拒绝数和超时数,将这些信息记录到断路器(Circuit Breaker)中。断路器将上述统计按照时间窗的形式记录到一个定长数组中。断路器根据时间窗内的统计数据去判定请求什么时候可以被熔断,熔断后,在接下来一段恢复周期内,相同的请求过来后会直接被熔断。当再次校验,如果健康监测通过后,熔断开关将会被关闭。
获取Fallback
当以下场景出现后,Hystrix将会尝试触发Fallback:
步骤6 Command执行时抛出了任何异常; 步骤4 断路器已经被打开 步骤5 执行命令的线程池、队列或者信号量资源已满 命令执行的时间超过阈值
返回成功结果
如果 Hystrix 命令对象执行成功,将会返回结果,或者以Observable形式包装的结果。根据步骤2的command 调用方式,返回的Observable 会按照如下图说是的转换关系进行返回:
断路器工作原理
- 断路器时间窗内的请求数 是否超过了请求数断路器生效阈值circuitBreaker.requestVolumeThreshold,如果超过了阈值,则将会触发断路,断路状态为开启
例如,如果当前阈值设置的是20,则当时间窗内统计的请求数共计19个,即使19个全部失败了,都不会触发断路器。 - 并且请求错误率超过了请求错误率阈值errorThresholdPercentage
- 如果两个都满足,则将断路器由关闭迁移到开启
- 如果断路器开启,则后续的所有相同请求将会被断路掉;
- 直到过了沉睡时间窗sleepWindowInMilliseconds后,再发起请求时,允许其通过(此时的状态为半开起状态)。如果请求失败了,则保持断路器状态为开启状态,并更新沉睡时间窗。如果请求成功了,则将断路器状态改为关闭状态;
核心的逻辑如下:
@Override
public void onNext(HealthCounts hc) {
// check if we are past the statisticalWindowVolumeThreshold
if (hc.getTotalRequests() < properties.circuitBreakerRequestVolumeThreshold().get()) {
// we are not past the minimum volume threshold for the stat window,
// so no change to circuit status.
// if it was CLOSED, it stays CLOSED
// if it was half-open, we need to wait for a successful command execution
// if it was open, we need to wait for sleep window to elapse
} else {
if (hc.getErrorPercentage() < properties.circuitBreakerErrorThresholdPercentage().get()) {
//we are not past the minimum error threshold for the stat window,
// so no change to circuit status.
// if it was CLOSED, it stays CLOSED
// if it was half-open, we need to wait for a successful command execution
// if it was open, we need to wait for sleep window to elapse
} else {
// our failure rate is too high, we need to set the state to OPEN
if (status.compareAndSet(Status.CLOSED, Status.OPEN)) {
circuitOpened.set(System.currentTimeMillis());
}
}
}
}
断路器相关配置:
Key值的配置问题 默认配置:上述Key值之前要加上hystrix.command.default.前缀拼接 实例配置:上述Key值之前要加上hystrix.command.. 前缀拼接