Bootstrap

hystrix的理解和使用

一、概述

Hystrix是一个用于处理分布式系统的延迟和容错开源库,在分布式系统中,许多依赖不可避免的会调用失败,比如超时,异常等,Hystrix能保证在一个依赖出现问题时,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性

二、Hystrix能做什么

1、对调用其他服务造成的异常和超时提供保护和控制
2、在复杂的分布式系统中防止级联失败
3、快速失败和迅速恢复
4、当必要时fallback和优雅的降级
5、提供实时监控,警告和可选择的控制

三、hystrix怎么去执行它的目标

1、将外部系统的所有调用包装在HystrixCommand对象中,该对象通常在一个单独线程中执行,这是命令模式的一种示例。
2、可以自定义超时阈值,虽然有默认值。
3、对每个依赖(微服务)维持一个小的线程池(或信号量semaphore),如果它满了,请求会立即被拒绝而不是排队等候。
4、跳闸断路器,在一段时间内停止对特定服务的所有请求,如果服务的错误百分比超过阈值。
5、执行fallback,当请求失败、拒绝、熔断或超时等等。
6、实时的监控配置和指标的改变

四、理论介绍

在分布式系统中,一个微服务响应超时或异常,如果不进行处理,那么,前端不断请求,势必会造成链路地阻塞和资源地耗尽,会导致应用系统的其他微服务也无法使用,造成系统雪崩,为了防止这种情况的发生,hystrix提供了五种理论技术,如下:
1、降级:当调用出现异常或超时等无法返回正常数据时,返回一个合理的结果或实现fallback方法,针对客户端而言。

2、熔断:当失败率达到阈值自动触发降级,通俗理解为:熔断就是具有特定条件的降级,当出现熔断时在设定的时间内不在请求。熔断有自动恢复机制,如:当熔断器启动后,每隔5秒,尝试将新的请求发送给service,如果服务可正常执行并返回结果,则关闭熔断器,恢复服务。如果仍调用失败,则继续返回fallback,熔断器持续开启。

3、请求缓存:服务A调用服务B,如果在A中添加请求缓存,第一次请求后走缓存,不在访问微服务B,即使出现大量请求,不会对B产生高负荷。请求缓存可以使用spring cache实现。

4、请求合并:当服务A调用服务B时,设定在5毫秒内所有请求合并到一起,对于服务B的负荷就会减少。使用@HystrixCollapser。方法返回值必须为Future

5、隔离:隔离分为线程池隔离合信号量隔离。通过判断线程池或信号量是否满,超过容量的请求直接降级,从而达到限流。

熔断中的熔断器

状态改变:CLOSE->OPEN
1、如果容量超过了阈值(线程池的大小或信号量)

(HystrixCommandProperties.circuitBreakerRequestVolumeThreshold())...

2、如果错误百分比超过了阈值

(HystrixCommandProperties.circuitBreakerErrorThresholdPercentage())...

状态改变:OPEN->CLOSE
当过了一段时间,默认是5秒(可以自定义) ,下一个单一请求允许通过(这是一个half-open的状态),如果请求还是失败的,在sleep window的时间段内断路器返回OPEN的状态。如果请求成功,断路器转变为CLOSE关闭状态,继续接受请求。

HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds() 

隔离

隔离分为了线程池隔离和信号量隔离。
线程池隔离:它的目的是为每个微服务设置各自的线程池,互不影响,这样,如果某个微服务的线程池满了,不会影响其他微服务的线程池,而是调用fallback方法返回。引入线程池势必会造成一定的系统压力,因为线程池的上下文切换,调度,排队等会增加系统开销,但是hystrix在设计之初,认为它提供的好处远远可以忽略这些开销。
信号量隔离:它的目的是为每个微服务限流,能够限制并发的请求数。信号量隔离使用的其实就是并发框架中的semaphore信号量类,我们通过设定semaphore的信号数,相当于设置了此微服务的并发请求数,每一个微服务进来,都会执行semaphore的acquire方法来获得信号量,同时,返回结果后执行release方法释放信号量。

下面我们来看看hystrix的整体执行流图,如下:
在这里插入图片描述
第一步:构造一个HystrixCommand或HystrixObservableCommand对象来表示对某个依赖项(微服务)的请求。传入必要的参数,下面只演示HystrixCommand。

HystrixCommand command = new HystrixCommand(arg1, arg2);

第二步:执行Command,execute()方法和queue()方法都能够执行command。
execute():返回从依赖项收到的单个响应(或在出现错误时引发异常)
queue():返回可以从依赖项获得单个响应的Future

K             value   = command.execute();
Future<K>     fValue  = command.queue();

第三步:检查是否有缓存。如果我们开启了请求缓存且缓存了响应数据,那么,此次就能够直接获取缓存。注意,范围是一次HTTP请求。

第四步:检查熔断是否开启。上面讲过,触发熔断的两个条件是:时间段内超过错误百分比阈值或线程池(信号量)超过阈值,如果开启了熔断,则直接调用fallback方法,如果没开启,则继续往后执行。

第五步:检查信号量或线程池是否满。如果我们开启了信号量或线程池隔离,且满了,则直接调用fallback方法,否则,继续往后执行。

第六步:执行调用。HystrixCommand对象执行run()方法请求微服务(依赖),它会返回一个异常或正常的响应对象,如果返回异常,执行fallback方法,执行期间,如果超时了,也会触发fallback方法。

五、技术实现

我们还是根据之前的Eureka、Ribbon的环境,创建一个OpenFeign-Hystrix模块。
导入依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

主方法

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableHystrix
@EnableCircuitBreaker
@EnableHystrixDashboard
public class FeignHystrixApplication {

    public static void main(String[] args) {
        SpringApplication.run(FeignHystrixApplication.class, args);
    }

}

演示熔断降级

//调用端控制层
//调用provider服务中的home方法,模拟hystrix的熔断,降级
    @HystrixCommand(fallbackMethod = "homeFallback", commandProperties = {
        @HystrixProperty(name =HystrixPropertiesManager.CIRCUIT_BREAKER_ENABLED,value = "true"),  //开启熔断机制
        @HystrixProperty(name =HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS,value = "10000"),  //时间段
        @HystrixProperty(name =HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE,value = "40"),  //错误百分比阈值
        @HystrixProperty(name =HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD,value = "5"),  //请求数量阈值
    })
    @RequestMapping("useHome")
    public String useHome(){
        String res = fhService.home();
        return res;
    }
//调用端服务层
@Service
@FeignClient("provider")
public interface FhService {

    @RequestMapping("client")
    public String home();

//提供端控制层
@RequestMapping("/client")
    public String home() {
        int i = 1/0;  //异常
        return "this is home function";
    }

上述演示了10秒超过百分之40的错误后,将开启熔断,在此期间,针对请求,直接返回fallback方法,而不会去请求提供端。但把提供端的异常注掉,一段时间后,请求正常。

演示请求合并

//请求端controller层
@RequestMapping("/client2")
    public String client(String name){
        try{
            Future<String> res1 = clientService.demo3("张三");
            Future<String> res3 = clientService.demo3("王五");
            System.out.println(res1.get());
            System.out.println(res3.get());
        }catch(Exception e){
            e.printStackTrace();
        }
        return "OK";
    }
   
//请求端service层
Future<String> demo3(String name);

//请求端serviceImplement层
@HystrixCollapser(batchMethod = "mybatch",scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,
            collapserProperties = {
                    @HystrixProperty(name= HystrixPropertiesManager.TIMER_DELAY_IN_MILLISECONDS,value = "10"),
                    @HystrixProperty(name = HystrixPropertiesManager.MAX_REQUESTS_IN_BATCH,value = "5")
            }
    )
    @Override
    public Future<String> demo3(String name) {
        System.out.println("此方法恒不执行");
        RestTemplate restTemplate = new RestTemplate();
        String result= restTemplate.getForObject("http://localhost:8082/demo9?name={1}",String.class,name);
        return null;
    }

    @HystrixCommand
    public List<String> mybatch(List<String> names){
        System.out.println("合并后的参数:"+names);
        RestTemplate restTemplate = new RestTemplate();
        List<String> res = restTemplate.postForObject("http://localhost:8082/demo9",names,List.class);
        System.out.println("取出来的结果:"+res);
        return res;
    }

上述其实就是在用demo3方法收集在时间段内的所有该请求并合并请求参数,然后调用批处理方法mybatch执行并返回结果。但是注意,demo3返回的必须是Future方法。

演示线程池隔离

//请求端controller层
@RequestMapping("/thread")
    public String thread(){
        clientService.thread1();
        clientService.thread2();
        return "OK";
    }

//请求端service层
String thread1();
String thread2();

//请求端serviceimple层
@HystrixCommand(groupKey = "jqk",commandKey = "abc",threadPoolKey = "jqk",threadPoolProperties = {
            @HystrixProperty(name=HystrixPropertiesManager.CORE_SIZE,value = "8"),
            @HystrixProperty(name=HystrixPropertiesManager.MAX_QUEUE_SIZE,value = "5"),
            @HystrixProperty(name=HystrixPropertiesManager.KEEP_ALIVE_TIME_MINUTES,value = "2"),
            @HystrixProperty(name=HystrixPropertiesManager.QUEUE_SIZE_REJECTION_THRESHOLD,value = "5"),

    })
    @Override
    public String thread1() {
        System.out.println(Thread.currentThread().getName());
        return null;
    }

    @Override
    public String thread2() {
        System.out.println(Thread.currentThread().getName());
        return null;
    }

演示信号量隔离

@HystrixCommand(fallbackMethod = "semaphoreFallback",commandProperties = {
            @HystrixProperty(name=HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY,value="SEMAPHORE"),
            @HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS,value="2"),
            @HystrixProperty(name=HystrixPropertiesManager.FALLBACK_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS,value="2"),
            @HystrixProperty(name=HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS,value = "2000")
    })
    @Override
    public String semaphore() {
        try{
            Thread.sleep(3000L);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "OK";
    }

这里需要使用jmeter,不做演示

演示请求缓存

//请求端service层
@Service
public class CacheService {
    @CacheResult  //该注解用来标记请求命令返回的结果应该被缓存,它必须与@HystrixCommand注解结合使用
    @HystrixCommand(commandKey = "commandKey2")
    public Integer getRandomInteger(@CacheKey Integer id){
        RestTemplate restTemplate = new RestTemplate();
        //此次结果会被缓存
        return restTemplate.getForObject("http://localhost:8082/testCache", Integer.class);
    }

    /**
     * 使用注解清除缓存 方式3
     * @CacheRemove 该注解用来让请求命令的缓存失效,失效的缓存根据commandKey进行查找。
     */
    @CacheRemove(commandKey = "commandKey2")
    @HystrixCommand
    public void flushCache(Integer id){
        //这个@CacheRemove注解直接用在更新方法上效果更好
    }
}

六、总结

1、在项目中一般使用的是Hystrix的熔断和降级。

2、Hystrix的设计目的就是为了提高系统的弹性和高可用。弹性主要体现在Hystrix的熔断、降级、快速恢复和实时监控的功能,当设置了Hystrix的熔断和降级,由于某种原因导致服务熔断后,时间段内之间返回fallback,时间段之后Hystrix成半打开状态,检测服务是否可用,如果可用,熔断关闭,接收请求,否则,时间段内继续保持熔断开启。

3、Hystrix使用了舱壁模式命令模式。舱壁模式主要体现在它的隔离理论技术,涉及到线程池隔离和信号量隔离,其中心思想就是将各个微服务独立开,互补干扰,解耦合。命令模式体现在Hystrix使用一个@HystrixCommand就将外部系统的所有调用用HystrixCommand对象包裹,该对象通常在一个单独线程中执行。

4、虽然现在Hystrix官网宣布进入维护模式,停更了,也是很老的微服务技术,现有很多的技术取而代之,但是,Hystrix的思想一直在,现在很多的技术仍然是借鉴的Hystrix理论知识,学习Hystrix,能让我们更好的学习其他的微服务知识。

;