Hystrix原理与Dubbo整合
断路器使用命令模式将要执行的目标Tag方法,封装到HystrixCommand中执行(线程池、信号量),为微服务架构提供服务熔断、限流、降级机制的框架工具。
Hystrix提供多种隔离方式:
- 线程池隔离
- 信号量隔离
- 熔断
- 降级回退
Hystrix防止雪崩效应
- Hystrix将请求逻辑封装,隔离在单独线程中执行
- Hystrix有自动超时策略,如果外部请求超过阀值,Hystrix会以快速超时失败处理
- Hystrix为每一个分组服务,提供一个线程池,当线程池满载时不会线程排队,会直接快速失败
- Hystrix在管理的服务失效超过一定配置比例时,会自动切断服务一段时间。
Hystrix 工作原理
执行步骤如下:
- 创建HystrixCommand或者HystrixObservableCommand对象,将目标Tag方法封装到命令模式中。
- 执行命令execute()、queue()、observe()、toObservable()
- 如果请求结果缓存特性开启(不建议开启),并且命中缓存则从缓存获取Observable返回。
- 检查熔断状态,如果熔断机制启动,则执行getFallback命令,否则执行这个命令
- 检查执行命令的线程池或者信号量是否满载,如果满载则执行getFallback命令
- 执行HystrixCommand.run()或HystrixObservableCommand.construct(),如果超时或者执行失败则执行getFallback命令
- Hystrix 会将请求成功,失败,被拒绝或超时信息报告给熔断器,熔断器维护一些用于统计数据用的计数器
Circuit Breaker 断路器
HystrixCircuitBreaker断路器实现接口:
public interface HystrixCircuitBreaker {
/**
* Every {@link HystrixCommand} requests asks this if it is allowed to proceed or not. It is idempotent and does
* not modify any internal state, and takes into account the half-open logic which allows some requests through
* after the circuit has been opened
*
* @return boolean whether a request should be permitted
*/
boolean allowRequest();
/**
* Whether the circuit is currently open (tripped).
*
* @return boolean state of circuit breaker
*/
boolean isOpen();
/**
* Invoked on successful executions from {@link HystrixCommand} as part of feedback mechanism when in a half-open state.
*/
void markSuccess();
/**
* Invoked on unsuccessful executions from {@link HystrixCommand} as part of feedback mechanism when in a half-open state.
*/
void markNonSuccess();
/**
* Invoked at start of command execution to attempt an execution. This is non-idempotent - it may modify internal
* state.
*/
boolean attemptExecution();
}
实现子类有两个:
- NoOpCircuitBreaker:断路器空实现
- HystrixCircuitBreakerImpl:完整的断路器实现,在AbstractCommand构造函数中初始化HystrixCircuitBreaker(通过HystrixCircuitBreaker.Factory构造)。
HystrixCircuitBreaker 工厂
CircuitBreaker 工厂中维护了基于HystrixCommandKey的单例断路器对象实例。
private static ConcurrentHashMap<String, HystrixCircuitBreaker> circuitBreakersByCommand = new ConcurrentHashMap<String, HystrixCircuitBreaker>();
Factory代码如下:
ublic static class Factory {
//用一个ConcurrentHashMap来保存HystrixCircuitBreaker对象
private static ConcurrentHashMap<String, HystrixCircuitBreaker> circuitBreakersByCommand = new ConcurrentHashMap<String, HystrixCircuitBreaker>();
//Hystrix首先会检查ConcurrentHashMap中有没有对应的缓存的断路器,如果有的话直接返回。如果没有的话就会新创建一个HystrixCircuitBreaker实例,将其添加到缓存中并且返回
public static HystrixCircuitBreaker getInstance(HystrixCommandKey key, HystrixCommandGroupKey group, HystrixCommandProperties properties, HystrixCommandMetrics metrics) {
HystrixCircuitBreaker previouslyCached = circuitBreakersByCommand.get(key.name());
if (previouslyCached != null) {
return previouslyCached;
}
HystrixCircuitBreaker cbForCommand = circuitBreakersByCommand.putIfAbsent(key.name(), new HystrixCircuitBreakerImpl(key, group, properties, metrics));
if (cbForCommand == null) {
return circuitBreakersByCommand.get(key.name());
} else {
return cbForCommand;
}
}
public static HystrixCircuitBreaker getInstance(HystrixCommandKey key) {
return circuitBreakersByCommand.get(key.name());
}
static void reset() {
circuitBreakersByCommand.clear();
}
}
执行原理
HystrixCommand在执行的时候会与HystrixCircuitBreaker交互,执行之前会根据断路器状态来决定后续流程,执行命令成功、超时、失败又会向断路器汇报,断路器根据这些数据修改自身状态。
断路器流程图:
断路器可以通过6个参数配置:
- circuitBreaker.enabled:是否启动断路器,默认时true
- circuitBreaker.forceOpen:是否强制打开,始终保持打开状态。
- circuitBreaker.forceClosed:强制断路器关闭,始终保持关闭
- circuitBreaker.errorThresholdPercentage:设定错误比例,一个时间窗口内错误请求超过给配置则断路器打开
- circuitBreaker.requestVolumeThreshold:默认20,至少有20个请求参与errorThresholdPercentage错误百分比计算,这个参数是断路器起作用的关键
// check if we are past the statisticalWindowVolumeThreshold
if (health.getTotalRequests() < properties.circuitBreakerRequestVolumeThreshold().get()) {
// we are not past the minimum volume threshold for the statisticalWindow so we'll return false immediately and not calculate anything
return false;
}
if (health.getErrorPercentage() < properties.circuitBreakerErrorThresholdPercentage().get()) {
return false;
}
- circuitBreaker.sleepWindowInMilliseconds:半开试探时间默认值5000ms。断路器开启该配置时间后,会尝试放部分量过去,确定以来服务是否可用
- metrics.rollingStats.timeInMilliseconds:设置滑动窗口统计时间,默认10s
- metrics.rollingStats.numBuckets:设置滑动统计桶数量,默认10
command可以配置参数:
- execution.isolation.strategy:执行隔离策略THREAD|SEMAPHORE;THREAD策略每次在一个线程中执行,并发请求数限制于线程池中线程数量;SEMAPHORE策略在调用线程中执行,并发请求书受限于semaphore信号量的值
- execution.isolation.thread.timeoutInMilliseconds:请求超时时间,默认1s
- execution.timeout.enabled:是否开启超时,默认true
- execution.isolation.thread.interruptOnTimeout:超时情况下是否中断HystrixCommand.run()执行,默认true
- fallback.enabled:是否开启fallback,默认true
Command 线程池配置参数
- coreSize:设置线程池最大并发数量,默认10
- maximumSize:设置线程池最大并发数量,默认与coreSize一致
- maxQueueSize:最大队列长度,默认-1 SynchronizeQueue 实现
- queueSizeRejectionThreshold:设置拒绝请求的临界值,当线程池占满+临界值占满时,请求会立即拒绝
- keepAliveTimeMinutes:设置keep-alive时间,默认1分钟,只有当maximumSize起作用的时候才会生效,该配置控制一个线程多久没有使用后释放
- allowMaximumSizeToDivergeFromCoreSize:确认maximumSize参数是否起作用,默认false
Dubbo 集成 Hystrix
引入hystrix到pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>1.4.4.RELEASE</version>
</dependency>
Application类上增加@EnableHystrix来启用hystrix starter
@SpringBootApplication
@EnableHystrix
public class ProviderApplication {
配置Provider端
在Dubbo的Provider上增加@HystrixCommand配置,这样子调用就会经过Hystrix代理。
@Service(version = "1.0.0")
public class HelloServiceImpl implements HelloService {
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000") })
@Override
public String sayHello(String name) {
// System.out.println("async provider received: " + name);
// return "annotation: hello, " + name;
throw new RuntimeException("Exception to show hystrix enabled.");
}
}
配置Consumer端
对于Consumer端,则可以增加一层method调用,并在method上配置@HystrixCommand。当调用出错时,会走到fallbackMethod = "reliable"的调用里。
@Reference(version = "1.0.0")
private HelloService demoService;
@HystrixCommand(fallbackMethod = "reliable")
public String doSayHello(String name) {
return demoService.sayHello(name);
}
public String reliable(String name) {
return "hystrix fallback value";
}