概述
Hystrix 为 微服务架构提供了一整套服务隔离、服务熔断和服务降级的解决方案。它是熔断器的一种实现,主要用于解决微服务架构的高可用及服务雪崩等题目
Hystrix 的特性如下:
- 服务熔断:Hystrix 熔断器就像家中的安全阀一样,一旦某个服务不可用,熔断器就会直接切断该链路上的请求,避免大量的无效请求影响系统稳定,并且熔断器有自我检测和恢复的功能,在服务状态恢复正常后会自动关闭
- 服务降级:Hystrix 通过 falback 实现服务降级,在需要进行服务降级的类中界说一个 falback 方法,当请求的远程服务出现非常时,可以直接利用 fallback 方法返回非常信息,而不调用远程服务
- 依赖隔离:Hyslrix 通过线程池和信号量两种方式实现服务之间的依赖隔离,这样即使其中一个服务出现非常,资源迟迟不能释放,也不会影响其他业务线程的正常运行
- 线程池的隔离计谋:Hystrix 线程池的资源隔离为每个依赖的服务都分配一个线程池,每个线程池都处理特定的服务,多个服务之间的线程资源互不影响,以到达资源隔离的目标。当某个依赖服务非常时,只会阻塞这个依赖服务的线程资源,不影响其他依赖服务
- 信号量的隔离计谋:Hystrix 信号量的隔离计谋是为每个依赣的服务都分配一个信号量(原子计数器),当请求某个依赖服务时,先判定该服务的信号量值是否超过最大值。如果超过,则直接丢弃并返回错误提示,如果不超过,则在处理请求前执行“信号量+1”的操作,在请求返回后执行“信号量-1”的操作
- 请求缓存:Hystrix 按照请求参数把请求结果缓存起来,当背面有相同的请求时不会再走完备的调用链流程,而是把前次缓存的结果直接返回,以到达服务快速相应和性能优化的目的。同时,缓存可作为服务降级的数据源,当远程服务不可用时,直接返回缓存数据,对于消费者来说,只是可能获取了过期的数据,这样就优雅地处理了系统非常
- 请求合井:当微服务需要调用多个远程服务做结果的汇总时,需要利用请求归并。Hystrix 采用异步消息订阅的方式进行请求归并,当应用程序需要请求多个接口时,采用异步调用的方式提交请求,然后订阅返回值,应用程序的业务可以接着执行其他任务而不用阻塞等候,当全部请求都返回时,应用程序会得到一个关照,取出返回值归并即可
Hystrix 服务降级流程
- 当有服务请求时,首先会根据注解创建一个 HystrixCommand 指令对象,该对象设置了服务调用失败的场景和调用失败后服务降级的业务逻辑方法
- 熔斯器判定状态,当熔断器处于开路状态时,直接调用服务降级的业务逻辑方法返回调用失败的反馈信息
- 当熔断器处于半开路或者闭路状态时,服务会进行线程池和信号量等检查如果有可用资源,有则调用正常业务逻辑。如果调用正常业务逻辑成功,则返回成功后的息,如果失败,则调用降级的业务逻辑,进行服务降级
- 当熔断器处于半开路或者闭路状态时,如果在当前服务线程池和信号量中无可用资源,则执行服务降级的业务逻辑,返回调用失败的信息
- 当熔断器处于半开路状态并且本次服务执行失败时,熔断器会进入开路状态
- 当正常业务逻辑处理超时或者出现错误时,HystrixCommand 会执行服务降缓的业务逐辑,返回调用失败的信息
- 线程池和信号量的资源检查及正常业务逻辑会将本身的状态和调用结果反馈给监控,监控将服务状态反馈给熔断器,以便熔断器判定熔断状态
Hystrix 应用
Hystrix 的利用主要分为服务熔断、服务降级和服务监控三个方面
在 pom.xml 文件中引入 Hystrix 依赖,其中,spring-cloud-slarter-netflix-hystrix 和 hystrix-javanica 为 Hystrix 服务熔断所需的依赖,spring-cloud-netflix-hystrix-dashboard 为 Hystrix 服务监控所需的依赖- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-actuator</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-eureka</artifactId>
- <version>1.4.6.RELEASE</version>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
- <version>1.4.6.RELEASE</version>
- </dependency>
- <dependency>
- <groupId>com.netflix.hystrix</groupId>
- <artifactId>hystrix-javanica</artifactId>
- <version>RELEASE</version>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-netflix-hystrix-dashboard</artifactId>
- <version>1.4.6.RELEASE</version>
- </dependency>
复制代码 通过 @EnableHystrix 注解开启对服务熔断的支持,通过 @EnableHystrixDashboard 注解开启对服务监控的支持。注意,Hystrix 一样寻常和服务发现配合利用,这里使 @EnableEurekaClient 开启了对服务发现客户端的支持- @SpringBootApplication
- @EnableEurekaClient
- @EnableHystrix
- @EnableHystrixDashboard
- public class HystrixServiceApplication {
- public static void main(String[] args) {
- SpringApplication.run(HystrixServiceApplication.class, args);
- }
- @Bean
- public IRule ribbonRule() {
- return new RandomRule();
- }
- @Bean
- @LoadBalanced
- public RestTemplate restTemplate() {
- return new RestTemplate();
- }
- }
复制代码 配置 application.properties 文件- #服务名
- spring.application.name=hystrix
- #服务的端口
- server.port=9005
- #注册中心的地址
- eureka.client.serviceUrl.defaultZone=http://localhost:9001/eureka/
- eureka.client.registry-fetch-interval-seconds=30
复制代码 服务熔断和降级,界说了一个远程调用的方法 hystrixHandler(),并通过 @HystrixCommand(fallbackMethod="exceptionHandler") 在方法上界说了一个服务降级的命令,当远程方法调用失败时,Hystrix 会自动调用 fallbackMethod 来完成服务熔断和降级,这里会调用 exceptionHandler 方法- @Autowired
- private RestTemplate restTemplate;
- //定义服务降级命令
- @HystrixCommand(fallbackMethod = "exceptionHandler")
- @RequestMapping(value = "/service/hystrix", method = RequestMethod.GET)
- public String hystrixHandler() {
- return restTemplate.getForEntity("http://EUREKA-CLIENT/serviceProducer", String.class).getBody();
- }
- public String exceptionHandler() {
- return "提供者服务挂了";
- }
复制代码 异步处理
上节中的远程调用请求必须等到网络请求返回结果后,才会执行背面的代码,即阻塞运行。而在现实利用过程中,应用程序常常希望利用非阻塞 IO 来更优雅地实现功能.Hyslrix 为非阻塞 IO 提供了两种实现方式,分别是表示将来式的 Future 和表示回调式的 Callable
1. Future
界说 HystrixCommand- public class CommandFuture extends HystrixCommand<String> {
- private String name;
- private RestTemplate restTemplate;
- public CommandFuture(String name, RestTemplate restTemplate) {
- super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
- //1:通过 HystrixCommandKey 工厂定义依赖的名称
- .andCommandKey(HystrixComnandKey.Factory.asKey("HelloWorld"))
- //2:通过 HystrixThreadPoolKey 工厂定义线程池的名称
- .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("HelloWorldPool")));
- this.name = name;
- this.restTemplate = restTemplate;
- }
- //3:定义远程调用的方法体
- @Override
- protected String run() {
- String result = restTemplate.getForEntity("http://EUREKA-CLIENT/serviceProducer", String.class).getBody();
- return result;
- }
- //4:服务降级的处理逻辑
- @Override
- protected String getFallback() {
- return "远程服务异常";
- }
- }
复制代码 以上代码通过继承 HystrixCommand 界说了一个 CommandFuture 来实现异步请求,其中,正常业务执行的逻辑在覆写的 run() 方法体中被执行,服务降级的方法在 getFallback() 中被执行。需要注意的是,这里利用 andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld")) 实现了利用 HystrixCommandKey 工厂界说依赖的名称,每个 CommandKey 都代表一个依赖抽象,相同的依赖要利用相同的 CommandKey 名称。依赖隔离的本质敦是对相同
CommandKey 的依赖进行离,利用 andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("HelloWorldPool")) 实现了基于 HystrixThreadPoolKey 工厂界说线程池的名称。当对同一业务的依赖进行资源隔离时,利用 CommandGroup 进行区分,但是当对同一依赖进行差异的远程调用时(比方,一个是 Redis 服务,一个是 HTTP 服务),则可以利用 HystrixThreadPoolKey 进行隔离区分
利用 HystrixCommand- @RequeatMapping(value = "/service/hystrix/future", method = RequestMethod.GET)
- public String hystrixFutureHandler() throws ExecutionException, InterruptedException {
- //定义基于Future的异步调用,请求会以队列的形式在线程池中被执行
- Future<String> future = new CommandFuture("future", restTemplate).queue();
- return future.get();
- }
复制代码 2. Callable
预界说一个回调任务,Callable 在发出请求后,主线程继续执行,在请求执行完成并返回结果后,Callable 会自动调用回调任务
界说 HystrixObservableCommand
[code]public class CommandObservable extends HystrixObservabCommand { private String name; private RestTemplate restTemplate; public CommandObservable(String nane, RestTemplate restTemplate) { super(HystrixConmandGroupKey.Factory.asKey("ExampleGroup")); this.nane = name; this.restTemplate = restTemplate; } //基于观察者模式的请求发布 @Override protected Observable construct () { return Observable.create(new Observable.OnSubscribe() { @Override public void call(Subscriber |