Hystrix熔断器

一、Hystrix简介
Hystrix是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性、容错性与局部应用的弹性,是一个实现了超时机制和断路器模式的工具类库。

二、Hystrix的设计原则
防止任何单独的依赖耗尽资源(线程)
过载立即切断并快速失败,防止排队
尽可能提供回退以保护用户免受故障
使用隔离技术(例如隔板,泳道和断路器模式)来限制任何一个依赖的影响
通过近实时的指标,监控和告警,确保故障被及时发现
通过动态修改配置属性,确保故障及时恢复
防止整个依赖客户端执行失败,而不仅仅是网络通信

三、Hystrix的工作原理
使用命令模式将所有对外部服务(或依赖关系)的调用包装在HystrixCommand或HystrixObservableCommand对象中,并将该对象放在单独的线程中执行。
每个依赖都维护着一个线程池(或信号量),线程池被耗尽则拒绝请求(而不是让请求排队)。
记录请求成功,失败,超时和线程拒绝。
服务错误百分比超过了阈值,熔断器开关自动打开,一段时间内停止对该服务的所有请求。
请求失败,被拒绝,超时或熔断时执行降级逻辑。
近实时地监控指标和配置的修改。
当使用Hystrix封装每个基础依赖项时,每个依赖项彼此隔离,受到延迟时发生饱和的资源的限制,并包含回退逻辑,该逻辑决定了在依赖项中发生任何类型的故障时做出什么响应。

Hystrix特性
请求熔断: 当Hystrix Command请求后端服务失败数量超过一定比例(默认50%), 断路器会切换到开路状态(Open). 这时所有请求会直接失败而不会发送到后端服务. 断路器保持在开路状态一段时间后(默认5秒), 自动切换到半开路状态(HALF-OPEN).这时会判断下一次请求的返回情况, 如果请求成功, 断路器切回闭路状态(CLOSED), 否则重新切换到开路状态(OPEN). Hystrix的断路器就像我们家庭电路中的保险丝, 一旦后端服务不可用, 断路器会直接切断请求链, 避免发送大量无效请求影响系统吞吐量, 并且断路器有自我检测并恢复的能力.

服务降级:Fallback相当于是降级操作. 对于查询操作, 我们可以实现一个fallback方法, 当请求后端服务出现异常的时候, 可以使用fallback方法返回的值. fallback方法的返回值一般是设置的默认值或者来自缓存.告知后面的请求服务不可用了,不要再来了。
依赖隔离(采用舱壁模式,Docker就是舱壁模式的一种):在Hystrix中, 主要通过线程池来实现资源隔离. 通常在使用的时候我们会根据调用的远程服务划分出多个线程池.比如说,一个服务调用两外两个服务,你如果调用两个服务都用一个线程池,那么如果一个服务卡在哪里,资源没被释放后面的请求又来了,导致后面的请求都卡在哪里等待,导致你依赖的A服务把你卡在哪里,耗尽了资源,也导致了你另外一个B服务也不可用了。这时如果依赖隔离,某一个服务调用A B两个服务,如果这时我有100个线程可用,我给A服务分配50个,给B服务分配50个,这样就算A服务挂了,我的B服务依然可以用。

请求缓存:比如一个请求过来请求我userId=1的数据,你后面的请求也过来请求同样的数据,这时我不会继续走原来的那条请求链路了,而是把第一次请求缓存过了,把第一次的请求结果返回给后面的请求。
请求合并:我依赖于某一个服务,我要调用N次,比如说查数据库的时候,我发了N条请求发了N条SQL然后拿到一堆结果,这时候我们可以把多个请求合并成一个请求,发送一个查询多条数据的SQL的请求,这样我们只需查询一次数据库,提升了效率。
Hystrix流程图
在这里插入图片描述

Hystrix流程说明:

1:每次调用创建一个新的HystrixCommand,把依赖调用封装在run()方法中.

2:执行execute()/queue做同步或异步调用.

3:判断熔断器(circuit-breaker)是否打开,如果打开跳到步骤8,进行降级策略,如果 关闭进入步骤.

4:判断线程池/队列/信号量是否跑满,如果跑满进入降级步骤8,否则继续后续骤.

5:调用HystrixCommand的run方法.运行依赖逻辑

5a:依赖逻辑调用超时,进入步骤8.

6:判断逻辑是否调用成功

6a:返回成功调用结果

6b:调用出错,进入步骤8.

7:计算熔断器状态,所有的运行状态(成功, 失败, 拒绝,超时)上报给熔断器,用于统计从而判断熔断器状态.

8:getFallback()降级逻辑.以下四种情况将触发getFallback调用:

(1):run()方法抛出非HystrixBadRequestException异常。

(2):run()方法调用超时

(3):熔断器开启拦截调用

(4):线程池/队列/信号量是否跑满

8a:没有实现getFallback的Command将直接抛出异常

8b:fallback降级逻辑调用成功直接返回

8c:降级逻辑调用失败抛出异常

9:返回执行成功结果

Hystrix 是 Netflix 开源的一款容错系统

Hystrix 具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包(request collapsing,即自动批处理),以及监控和配置等功能。
Hystrix具备服务降级、服务熔断、线程隔离、请求缓存、请求合并及服务监控等强大功能。

微服务通常出现的服务雪崩效应:
在这里插入图片描述
解决方案:
 解决灾难性雪崩效应的方式通常有:降级、隔离、熔断、请求缓存、请求合并。

在Spring cloud中处理服务雪崩效应,都需要依赖hystrix组件。在pom文件中都需要引入下述依赖:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>

通常来说,开发的时候,使用ribbon处理服务灾难雪崩效应,开发的成本低。维护成本高。使用feign技术处理服务灾难雪崩效应,开发的成本较高,维护成本低。

降级
  降级是指,当请求超时、资源不足等情况发生时进行服务降级处理,不调用真实服务逻辑,而是使用快速失败(fallback)方式直接返回一个托底数据,保证服务链条的完整,避免服务雪崩。

解决服务雪崩效应,都是避免application client请求application service时,出现服务调用错误或网络问题。处理手法都是在application client中实现。我们需要在application client相关工程中导入hystrix依赖信息。并在对应的启动类上增加新的注解@EnableCircuitBreaker,这个注解是用于开启hystrix熔断器的,简言之,就是让代码中的hystrix相关注解生效。

引入依赖:

<!-- hystrix依赖, 处理服务灾难雪崩效应的。 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>

启动器代码:

/**
 * @EnableCircuitBreaker - 开启断路器。就是开启hystrix服务容错能力。
 * 当应用启用Hystrix服务容错的时候,必须增加的一个注解。
 */
@EnableCircuitBreaker
@EnableEurekaClient
@SpringBootApplication
public class HystrixApplicationClientApplication {
   
    public static void main(String[] args) {
   
      SpringApplication.run(HystrixApplicationClientApplication.class, args);
    }
}

在调用application service相关代码中,增加新的方法注解@HystrixCommand,代表当前方法启用Hystrix处理服务雪崩效应。

@HystrixCommand注解中的属性:fallbackMethod - 代表当调用的application service出现问题时,调用哪个fallback快速失败处理方法返回托底数据。

@Service
public class HystrixService {
   

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    /**
     * 服务降级处理。
     * 当前方法远程调用application service服务的时候,如果service服务出现了任何错误(超时,异常等)
     * 不会将异常抛到客户端,而是使用本地的一个fallback(错误返回)方法来返回一个托底数据。
     * 避免客户端看到错误页面。
     * 使用注解来描述当前方法的服务降级逻辑。
     * @HystrixCommand - 开启Hystrix命令的注解。代表当前方法如果出现服务调用问题,使用Hystrix逻辑来处理。
     *  重要属性 - fallbackMethod
     *      错误返回方法名。如果当前方法调用服务,远程服务出现问题的时候,调用本地的哪个方法得到托底数据。
     *      Hystrix会调用fallbackMethod指定的方法,获取结果,并返回给客户端。
     * @return
     */
    @HystrixCommand(fallbackMethod="downgradeFallback")
    public List<Map<String, Object>> testDowngrade() {
   
        System.out.println("testDowngrade method : " + Thread.currentThread().getName());
        ServiceInstance si = 
                this.loadBalancerClient.choose("eureka-application-service");
        StringBuilder sb = new StringBuilder();
        sb.append("http://").append(si.getHost())
            .append(":").append(si.getPort()).append("/test");
        System.out.println("request application service URL : " + sb.toString());
        RestTemplate rt = new RestTemplate();
        ParameterizedTypeReference<List<Map<String, Object>>> type = 
                new ParameterizedTypeReference<List<Map<String, Object>>>() {
   
        };
        ResponseEntity<List<Map<String, Object>>> response = 
                rt.exchange(sb.toString(), HttpMethod.GET, null, type);
        List<Map<String, Object>> result = response.getBody();
        return result;
    }

    /**
     * fallback方法。本地定义的。用来处理远程服务调用错误时,返回的基础数据。
     */
    private List<Map<String, Object>> downgradeFallback(){
   
        List<Map<String, Object>> result = new ArrayList<>();
        
        Map<String, Object> data = new HashMap<>();
        data.put("id", -1);
        data.put("name", "downgrade fallback datas");
        data.put("age", 0);
        result.add(data);
        return result;
    }    
}

缓存
  缓存是指请求缓存。通常意义上说,就是将同样的GET请求结果缓存起来,使用缓存机制(如redis、mongodb)提升请求响应效率。

使用请求缓存时,需要注意非幂等性操作对缓存数据的影响。

请求缓存是依托某一缓存服务来实现的。在案例中使用redis作为缓存服务器,那么可以使用spring-data-redis来实现redis的访问操作。需要在application client相关工程中导入下述依赖:

<!-- hystrix依赖, 处理服务灾难雪崩效应的。 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
        
<!-- spring-data-redis spring cloud中集成的spring-data相关启动器。 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

在Spring Cloud应用中,启用spring对cache的支持,需要在启动类中增加注解@EnableCaching,此注解代表当前应用开启spring对cache的支持。简言之就是使spring-data-redis相关的注解生效,如:@CacheConfig、@Cacheable、@CacheEvict等。

启动器:

/**
 * @EnableCircuitBreaker - 开启断路器。就是开启hystrix服务容错能力。
 * 当应用启用Hystrix服务容错的时候,必须增加的一个注解。
 */
@EnableCircuitBreaker
/**
 * @EnableCaching - 开启spring cloud对cache的支持。
 * 可以自动的使用请求缓存,访问redis等cache服务。
 */
@EnableCaching
@EnableEurekaClient
@SpringBootApplication
public class HystrixApplicationClientApplication {
   
    public static void main(String[] args) {
   
        SpringApplication.run(HystrixApplicationClientApplication.class, args);
    }
}

spring cloud会检查每个幂等性请求,如果请求完全相同(路径、参数等完全一致),则首先访问缓存redis,查看缓存数据,如果缓存中有数据,则不调用远程服务application service。如果缓存中没有数据,则调用远程服务,并将结果缓存到redis中,供后续请求使用。

如果请求是一个非幂等性操作,则会根据方法的注解来动态管理redis中的缓存数据,避免数据不一致。

注意:使用请求缓存会导致很多的隐患,如:缓存管理不当导致的数据不同步、问题排查困难等。在商业项目中,解决服务雪崩效应不推荐使用请求缓存。

实现类:

/**
 * 在类上,增加@CacheConfig注解,用来描述当前类型可能使用cache缓存。
 * 如果使用缓存,则缓存数据的key的前缀是cacheNames。
 * cacheNames是用来定义一个缓存集的前缀命名的,相当于分组。
 */
@CacheConfig(cacheNames={
   "test.hystrix.cache"})
@Service
public class HystrixService {
   

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    /**
     * 请求缓存处理方法。
     * 使用注解@Cacheable描述方法。配合启动器中的相关注解,实现一个请求缓存逻辑。
     * 将当期方法的返回值缓存到cache中。
     * 属性 value | cacheNames - 代表缓存到cache的数据的key的一部分。
     * 可以使用springEL来获取方法参数数据,定制特性化的缓存key。
     * 只要方法增加了@Cacheable注解,每次调用当前方法的时候,spring cloud都会先访问cache获取数据,
     * 如果cache中没有数据,则访问远程服务获取数据。远程服务返回数据,先保存在cache中,再返回给客户端。
     * 如果cache中有数据,则直接返回cache中的数据,不会访问远程服务。
     * 
     * 请求缓存会有缓存数据不一致的可能。
     * 缓存数据过期、失效、脏数据等情况。
     * 一旦使用了请求缓存来处理幂等性请求操作。则在非幂等性请求操作中必须管理缓存。避免缓存数据的错误。
     * @return
     */
    @Cacheable("testCache4Get")
    public List<Map<String, Object>> testCache4Get() {
   
        System.out.println("testCache4Get method thread name : " + Thread.currentThread().getName());
        ServiceInstance si = 
                this.loadBalancerClient.choose("eureka-application-service");
        StringBuilder sb = new StringBuilder();
        sb.append("http://").append(si.getHost())
            .append(":").append(si.getPort()).append("/test");
        System.out.println("request application service URL : " + sb.toString(
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值