Hystrix流程分析及断路器工作原理
上一篇转载的文章主要讲了Hystrix的应用场景、基础组件概念以及从源码的角度阐述了基本应用。本篇文章主要借鉴官方文档介绍Hystrix的工作流程及断路器的原理,最后说一下与SpringCloud的简单集成。
How it Works
先上一个官方的流程图:
这个图大概描述了Hystrix的工作流程:
按照图中绿色标识的步骤:
1、创建一个HystrixCommand或HystrixObservableCommand对象;
2、通过以下四种方法执行命令(前两种方法仅适用于简单HystrixCommand对象且不可用HystrixObservableCommand):
- execute():阻塞的,返回从依赖项收到的单个响应(或在出现错误时抛出异常)
- queue(): 非阻塞的,返回一个可以从依赖项中获取单个响应的方法的Future对象。
- observe(): 订阅Observable表示来自依赖项的响应,并返回Observable复制该源的响应Observable
- toObservable(): 返回一个Observable,如果订阅了,会执行Hystrix命令并发出其响应
3、如果为此请求设置了缓存,首先判断缓存是否是可用的(即是否可以从缓存得到本次请求的响应),如果可以得到则直接返回,得不到再走下一步。
4、判断断路器是否打开,如果打开了说明之前的请求失败了(或者说之前满足了断路器的打开条件,具体满足的开闭条件下一模块再说),则直接调用重写的fallback()函数;如果断路器未打开再到下一步。
5、判断与该command关联的线程池和队列(或信号量)是否已满,如果已经满了,则Hystrix不会执行该Command,直接调用fallback()返回;否则到下一步。
6、此时通过HystrixObservableCommand.construct()或HystrixCommand.run()执行目的方法调用对依赖项的请求,如果执行失败或超时则直接执行fallback()。
7、健康状况统计
Hystrix在执行过程中会记录断路器的成功,失败,拒绝和超时状态,Hystrix维护一个记录这些数据的计数器,计数器的数据决定断路器的开闭状态。
8、当命令失败时Hystrix都会尝试回退,一般情况下,如果实现了HystrixCommand.getFallback()会返回单个回退值,或者实现HystrixObservableCommand.resumeWithFallback()会发出一个或多个回退值的Observable。如果没有实现fallback方法或在执行fallback方法时抛出了异常,Hystrix仍然会返回一个Observable,但不会返回任何内容,并立即终止并发出onError通知。通过此onError通知,导致命令失败的异常被传回给调用者。
9、成功返回
断路器
还是先上一个官方给出的逻辑决策图,包括计数器如何决定断路器的开闭。
说明:
1、如果请求数达到了设置的请求阈值或者请求失败的比例超过了设置的比例,则断路器将从close状态转到open状态,这时所有的请求都会被阻止。
2、sleep一段时间后,下一个请求将被放过,这时断路器处于半开半闭状态,目的是为了验证一下后边的路是否通畅,如果请求失败,则断路器回到open状态;如果成功了则断路器切换到closed状态并且返回响应的结果。
图中下方描述了计数器维护的数据存储结构及工作原理:大概意思是它维护10个桶(bucket),每个桶中记录第i秒请求状态(success、failure、timeout、rejection)的数量,当新的一秒请求记录来的时候,计数器会丢掉时间最靠前的桶。
SpringCloud简单集成
这个比较简单,注册中心选用Eureka(Consul的可以自己去测),直接上代码
model-1:eureka-server
Application.java
@EnableEurekaServer
@SpringBootApplication
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
}
application.properties
spring.application.name=eureka-server
server.port=1001
eureka.instance.hostname=localhost
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
logging.file=${spring.application.name}.log
model-2:eureka-consumer-ribbon-hystrix
Application.java
@EnableCircuitBreaker
@EnableDiscoveryClient
@SpringBootApplication
public class Application {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
}
DcController.java
@RestController
public class DcController {
@Autowired
ConsumerService consumerService;
@GetMapping("/consumer")
public String dc() {
return consumerService.consumer();
}
@Service
class ConsumerService {
@Autowired
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "fallback")
public String consumer() {
return restTemplate.getForObject("http://eureka-client/dc", String.class);
}
public String fallback() {
return "fallbck";
}
}
}
application.properties
spring.application.name=eureka-consumer-ribbon-hystrix
server.port=2101
eureka.client.serviceUrl.defaultZone=http://localhost:1001/eureka/
logging.file=${spring.application.name}.log
model-3:eureka-client
Application.java
@EnableDiscoveryClient
@SpringBootApplication
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
}
DcController.java
@RestController
public class DcController {
@Autowired
DiscoveryClient discoveryClient;
@GetMapping("/dc")
public String dc() {
String services = "Services: " + discoveryClient.getServices();
System.out.println(services);
return services;
}
}
application.properties
spring.application.name=eureka-client
server.port=2001
eureka.client.serviceUrl.defaultZone=http://localhost:1001/eureka/
#eureka.client.serviceUrl.defaultZone=http://peer1:1001/eureka/,http://peer2:1002/eureka/
logging.file=${spring.application.name}.log
Hystrix官方文档:Netflix-Hystrix官方WIKI
更多SpringCloud基础教程访问:程序员DD老司机的SpringCloud教程