03月28, 2019

【Hystrix】流程分析及断路器工作原理

本篇文章主要借鉴官方文档介绍Hystrix的工作流程及断路器的原理,最后说一下与SpringCloud的简单集成。

How it Works

先上一个官方的流程图: alt

这个图从各种场景走到降级,先经缓存再到断路器,包括与计数器的交互,再到最后的兜底策略,大概描述了Hystrix的具体工作流程。

按照图中红色数字的步骤:

1、创建一个HystrixCommand对象

HystrixCommand command = new HystrixCommand(arg1, arg2);

或HystrixObservableCommand

HystrixObservableCommand command = new HystrixObservableCommand(arg1, arg2);

实际上在项目中通过注解的方式集成更加方便快捷。

2、通过以下四种方法执行命令(前两种方法仅适用于简单HystrixCommand对象且不可用HystrixObservableCommand):

  • execute():阻塞的,返回从依赖项收到的单个响应(或在出现错误时抛出异常)

  • queue(): 非阻塞的,返回一个可以从依赖项中获取单个响应的方法的Future对象。

  • observe(): 订阅Observable表示来自依赖项的响应,并返回Observable复制该源的响应Observable

  • toObservable(): 返回一个Observable,如果订阅了,会执行Hystrix命令并发出其响应

K             value   = command.execute();
Future<K>     fValue  = command.queue();
Observable<K> ohValue = command.observe();         //hot observable
Observable<K> ocValue = command.toObservable();    //cold observable

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、成功返回

断路器

还是先上一个官方给出的逻辑决策图,包括计数器如何决定断路器的开闭。 alt

点击查看大图

说明:

这个图不大清晰,上部分是断路器开关的具体逻辑,下边是计数器,用来决定断路器的开闭状态。

决策逻辑:

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

扫鸭扫鸭,求关注

alt

Hystrix官方文档:Netflix-Hystrix官方WIKI 更多SpringCloud基础教程访问:程序员DD老司机的SpringCloud教程

本文已在版权印备案,如需转载请访问版权印06630244

本文链接:http://blog.keepting.cn/blog//post/Hystrix流程分析及断路器工作原理.html

-- EOF --

Comments