SpringCloud进击 | 四浅出:断路器与容错(Hystrix)【Finchley版本】


1.前言

上一节:SpringCloud进击 | 三浅出:服务消费者(Feign)【Finchley版本】

断路器:Hystrix 客户端,字面上理解,就是断开一端和另一端之间的链路。当某个服务单元发生故障之后,通过断路器的故障监控,向调用方返回一个预留的、可处理的备选响应,即服务降级(FallBack),而不是长时间的等待,或者抛出调用方无法处理的异常。

在微服务架构中,根据业务来拆分成一个一个的服务,服务与服务之间可以相互调用(RPC)。在 Spring Cloud 中可以用 RestTemplate+Ribbon 或者 Feign 来调用。但是为了保证它的高可用性,单个服务通常会集群部署。

 

2.准备

因为断路/熔断只是作用在服务消费/调用这一端,因此根据上一篇的示例代码,我们只需要改动 wei-consumer-feign 模块,或者 wei-consumer-ribbon 模块相关代码就可以。

三个角色,依旧使用前面章节已经创建好的工程:

  • 服务注册中心:wei-eureka-server,端口 8090,启动一个(无需修改,正常启动)
  • 服务提供者:wei-service-provider,分别以 8010、8011 端口启动一次,获得两个实例的集群(以端口号正常启动)
  • 服务消费者:改造 wei-consumer-ribbon 或者 wei-consumer-feign模块(改造对象)

 

3.进击

Hystrix [hɪst’rɪks] 的中文含义是 “豪猪”,豪猪周身长满了刺,能保护自己不受天敌的伤害,代表了一种防御机制,这与 Hystrix 本身的功能不谋而合,因此 Netflix 团队将该框架命名为 Hystrix,并使用了对应的卡通形象做作为 logo。

SpringCloud进击 | 四浅出:断路器与容错(Hystrix)【Finchley版本】_第1张图片

 

3.1.在Ribbon中使用Hystrix

在 Ribbon 中使用 Hystrix 断路器,需要改造我们在第二节做好的 wei-consumer-ribbon 应用模块。这里我们将其重命名为 wei-consumer-ribbon-hystrix,端口设置为 8021。

3.1.1.改造pom.xml依赖

文件 pom.xml 依赖中引入 Hystrix 的起步依赖:spring-cloud-starter-netflix-hystrix

        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-hystrix
        

当前模块完整依赖如下:

    
        
            org.springframework.cloud
            spring-cloud-starter-netflix-eureka-client
        

        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-ribbon
        

        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-hystrix
        

        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    

3.1.2.改造Service类

@HystrixCommand由名为“javanica”的Netflix contrib库提供。Spring Cloud 使用该注释在连接到Hystrix断路器的代理中自动包装Spring bean。断路器计算何时打开和关闭电路,以及在发生故障时应该做什么。

方法加上 @HystrixCommand 注解,该注解对该方法创建了熔断器的功能,并指定 fallbackMethod 熔断方法,当服务不可用时会执行快速失败,直接返回预定值,而不是等待响应超时,这很好的控制了容器的线程阻塞。

package com.wei.service.demo;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class DemoRibbonService {

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 创建一个新接口用来消费服务提供者提供的接口
     * 用程序名替代了具体的url地址,在ribbon中它会根据服务名来选择具体的服务实例,根据服务实例在请求的时候会用具体的url替换掉服务名
     *
     * 方法加上@HystrixCommand注解,该注解对该方法创建了熔断器的功能,并指定fallbackMethod熔断方法(在Ribbon中使用熔断器)
     */
    @HystrixCommand(fallbackMethod = "returnError")
    public String getDemoRibbonServiceName(String name) {
        String result = restTemplate.getForObject("http://wei-service-provider/demo/info?name=" + name, String.class);
        result += "[Ribbon + REST]";
        System.out.println(result);
        return result;
    }

    /**
     * 当服务不可用时会执行快速失败,直接返回一组字符串,而不是等待响应超时,这很好的控制了容器的线程阻塞
     *
     * @return 自定义字符串
     */
    private String returnError() {
        String result = "[Ribbon] 我是自定义值:我是Hystrix熔断器的回调方法返回值,我的出现代表服务已出现故障";
        System.out.println(result);
        return result;
    }
}

3.1.3.改造启动类

在启动类上添加注解 @EnableHystrix,开启 Hystrix 熔断功能。

package com.wei;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * 注解@EnableHystrix,开启Hystrix熔断功能
 */
@SpringBootApplication
@EnableEurekaClient
@EnableHystrix
public class WeiConsumerRibbonApplication {

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

    /**
     * 注解@Bean,向程序注入一个Bean
     * 注解@LoadBalanced,开启RestTemplate的负载均衡功能
     * 初始化RestTemplate,用来发起 REST 请求
     */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

3.1.4.确认配置文件

文件 application.yml 除了修改端口号为8021外,无需其它修改。

server:
  port: 8021
spring:
  application:
    name: wei-consumer-ribbon-hystrix    # 指定进行服务注册时该服务的名称,服务与服务之间相互调用一般都是根据这个name
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8090/eureka/    # 指定进行服务注册的地址

 

好了,到此,在 Ribbon 中使用 Hystrix 功能的实现已全部改造OK,接下来就快验证一下吧。

启动。

 

3.1.5.测试

确认已经正常启动准备工作中的 服务注册中心 和 服务提供者。

启动 wei-consumer-ribbon-hystrix 应用模块的启动类,然后反复请求URL:http://localhost:8021/demo/info?name=tester

会看到浏览器依旧还是像之前一样,在8010和8011服务之间打印输出:

Hi,tester,我是服务,我被调用了,服务名为:wei-service-provider,端口为:8010[Ribbon + REST]

Hi,tester,我是服务,我被调用了,服务名为:wei-service-provider,端口为:8011[Ribbon + REST]

但是,如果关掉一个实例呢?

停掉8011端口的实例(这里可以假想服务提供者8011是因为种种原因而导致它的不可用),再访问URL:http://localhost:8021/demo/info?name=tester

[Ribbon] Sorry, tester,我是Hystrix熔断器的回调方法返回值,我的出现代表服务已出现故障

Hi,tester,我是服务,我被调用了,服务名为:wei-service-provider,端口为:8010[Ribbon + REST]

有木有!当请求通过负载均衡被分发到服务提供者8011节点上的时候,它返回了通过 Hystrix 定义的 fallbackMethod 容错方案,执行快速了失败。而再次请求时,请求被分发到了正常的8010节点上。且一段时间之后,8011故障节点被剔除,之后不会被调用者访问到。

 


3.2.在Feign中使用Hystrix

在 Ribbon 中使用 Hystrix 断路器,需要改造我们在第三节做好的 wei-consumer-feign 应用模块。这里我们将其重命名为 wei-consumer-feign-hystrix,端口设置为 8031。

因为 Feign 中已经依赖了 Hystrix,所以在 pom 依赖上不用做任何改动。但是它没有默认打开,需要在配置文件中设置并开启它。

3.2.1.开启Hystrix

改造配置文件 application.yml,添加配置:feign.hystrix.enabled=true。Spring Cloud Finchley.SR1 版本的配置文件 application.yml 提示找不到 feign 属性,但我们可以不用理会,项目跑起来一样有效果。

feign:
  hystrix:
    enabled: true

当前模块完整配置如下:

server:
  port: 8031
spring:
  application:
    name: wei-consumer-feign-hystrix    # 指定进行服务注册时该服务的名称,服务与服务之间相互调用一般都是根据这个name
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8090/eureka/    # 指定进行服务注册的地址
feign:
  hystrix:
    enabled: true    # 开启Hystrix熔断功能

3.2.2.创建回调类

创建一个回调类(DemoFallback)作为容错方案,实现并重载 IDemoFeignService 接口中的服务消费方法。

package com.wei.service.hystrix;

import com.wei.service.demo.IDemoFeignService;
import org.springframework.stereotype.Component;

@Component
public class DemoFallback implements IDemoFeignService {

    /**
     * 通过@FeignClient("Eureka服务名称"),来指定调用消费哪个服务
     * 注解@RequestMapping映射微服务中的URL
     *
     * @return 案例
     */
    @Override
    public String getDemoFeignServiceName(String name) {
        String result = "[Feign] Sorry, " + name +",我是熔断器的回调方法返回值,我的出现代表服务已出现故障";
        System.out.println(result);
        return result;
    }
}

3.2.3.改造Service

在Service类中添加 fallback 属性,指定 fallback 类,以在服务熔断的时候返回 fallback 类中的内容。

package com.wei.service.demo;

import com.wei.service.hystrix.DemoFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * 通过@FeignClient("Eureka服务名称"),来指定调用消费哪个服务
 * fallback:指定 fallback 类,以在服务熔断的时候返回 fallback 类中的内容
 */
@FeignClient(value = "wei-service-provider", fallback = DemoFallback.class)
public interface IDemoFeignService {

    /**
     * 注解@RequestMapping,映射服务提供者中的URL
     * @param name 入参
     * @return
     */
    @RequestMapping(value = "/demo/info", method = RequestMethod.GET)
    String getDemoFeignServiceName(@RequestParam(value = "name") String name);

    // Feign客户端和Ribbon类似,同样实现了客户端的负载均衡
    // 与Ribbon不同的是,Feign的调用与本地接口的调用更加类似,并且更加便捷、更加优雅,传入参数较多时得以体现
}

 

好了,到此,在 Feign 中使用 Hystrix 功能的实现已全部改造OK,有没有很简单,接下来就快验证一下吧。

启动。

 

3.2.4.测试

确认已经正常启动准备工作中的 服务注册中心 和 服务提供者。

启动 wei-consumer-feign-hystrix 应用模块的启动类,然后反复请求URL:http://localhost:8031/demo/info?name=tester

会看到浏览器依旧还是像之前一样,在8010和8011服务之间打印输出:

Hi,tester,我是服务,我被调用了,服务名为:wei-service-provider,端口为:8010[Feign]

Hi,tester,我是服务,我被调用了,服务名为:wei-service-provider,端口为:8011[Feign]

 

同样,如果关掉一个实例呢?

停掉8010端口的实例(这里可以假想服务提供者8010节点是因为种种原因而导致它的不可用),再访问URL:http://localhost:8031/demo/info?name=tester

[Feign] Sorry, tester,我是熔断器的回调方法返回值,我的出现代表服务已出现故障[Feign]

Hi,tester,我是服务,我被调用了,服务名为:wei-service-provider,端口为:8011[Feign]

有木有!当请求通过负载均衡被分发到服务提供者8010节点上的时候,它返回了通过 Hystrix 定义的 fallback 容错方案,执行快速了失败。而再次请求时,请求被分发到了正常的8011节点上。且一段时间之后,8010故障节点被剔除,之后不会被调用者访问到。

 

4.总结

通过使用 Hystrix 断路器,我们能方便的防止雪崩效应,同时使系统具有自动降级和自动恢复服务的效果。

4.1.断路器:Hystrix客户端

Netflix开源了Hystrix组件,实现了断路器模式/熔断模式,Spring Cloud 对 Hystrix 进行了整合。在微服务架构中,通常有多层服务调用,一个请求需要调用多个服务是非常常见的。如下图:

SpringCloud进击 | 四浅出:断路器与容错(Hystrix)【Finchley版本】_第2张图片 【微服务消费图】

 

低级别的服务中的服务故障可能导致用户级联故障(即服务雪崩效应)。当对特定服务的调用达到一定阈值时(Hystrix中的默认值为5秒,20次故障),断路会被打开,返回服务提供的后备方案(Fallback)。

SpringCloud进击 | 四浅出:断路器与容错(Hystrix)【Finchley版本】_第3张图片 【Hystrix回退容错防止级联故障】

Hystrix回退/容错防止级联故障。如上图,断路打开后,服务返回备选方案(Fallback),这样可以有效避免级联故障。Fallback 回调方法可以直接返回一个预定值。

 

4.2.服务雪崩效应

服务雪崩效应是一种因 服务提供者 的不可用导致 服务调用者 的不可用,并将不可用逐渐放大的过程,最终导致服务级联故障。如果下图所示:

SpringCloud进击 | 四浅出:断路器与容错(Hystrix)【Finchley版本】_第4张图片 【服务雪崩效应】

上图中,A 为服务提供者,B 为 A 的服务调用者,C、D、E 是 B 的服务调用者。当 A 的不可用时,引起 B 的不可用,并将不可用逐渐放大到 C、D、E 时,就造成了一种崩塌现象,也就是服务雪崩的形成。

 

4.3.服务雪崩原因

如果把造成服务雪崩的角色分类,则可以划分为 服务提供者、服务消费者。而服务雪崩产生的过程则可以用以下三个阶段来分析形成的原因:

  1. 服务提供者不可用
  2. 重试加大流量
  3. 服务调用者不可用

服务雪崩的每个阶段都可能由不同的原因造成,但可以总结一下:
a) 造成 服务提供者不可用 的原因则可能为:

  • 网络/硬件故障:可能为硬件损坏造成的服务器主机宕机,网络故障造成的服务提供者的不可访问
  • 程序BUG:程序BUG会引发异常,处理不当也会造成服务的不可用
  • 大量请求:在秒杀和大促销开始前,如果准备不充分,用户发起大量请求也会造成服务提供者的不可用
  • 缓存击穿:缓存击穿一般发生在缓存应用重启,所有缓存被清空时,以及短时间内大量缓存失效时。大量的缓存不命中,使请求直击后端,造成服务提供者超负荷运行,引起服务不可用

b) 造成 重试加大流量 的原因则可能为:

  • 用户重试:由于服务的不可用,导致用户不断尝试请求,不断刷新页面甚至提交表单
  • 代码逻辑重试:服务调用者存在大量服务提供者异常后的重试逻辑

c) 造成 服务调用者不可用 的原因则可能为:

  • 资源耗尽:当服务调用者使用同步调用时,会产生大量的等待线程占用系统资源。一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态,于是导致服务雪崩效应产生了
  • 内存溢出

 

4.4.服务雪崩应对

针对造成服务雪崩的不同原因,可以使用不同的应对策略:

  1. 流量控制
  2. 改进缓存模式
  3. 服务自动扩容
  4. 服务调用者降级服务

a) 流量控制 的具体措施包括

  • 网关限流
  • 用户交互限流
  • 关闭重试

b) 改进缓存模式 的措施包括

  • 缓存预加载
  • 同步改为异步刷新

c) 服务自动扩容 的措施主要有

  • AWS 的 auto scaling

d) 服务调用者降级服务 的措施包括

  • 资源隔离:主要是对调用服务的线程池进行隔离
  • 对依赖服务进行分类
  • 不可用服务的调用快速失败
SpringCloud进击 | 四浅出:断路器与容错(Hystrix)【Finchley版本】_第5张图片 【服务雪崩导图】

 

到此,在 Ribbon 或者 Feign 中使用断路器与容错方案的开发全部完成。


下一节,请继续关注:SpringCloud进击 | 五浅出:服务网关 - 路由(Zuul Router)【Finchley版本】

 

6.物语

SpringCloud进击 | 一浅出:服务注册与发现(Eureka)【Finchley版本】
SpringCloud进击 | 二浅出:服务消费者(Ribbon+REST)【Finchley版本】
SpringCloud进击 | 三浅出:服务消费者(Feign)【Finchley版本】
SpringCloud进击 | 四浅出:断路器与容错(Hystrix)【Finchley版本】
SpringCloud进击 | 五浅出:服务网关 - 路由(Zuul Router)【Finchley版本】
SpringCloud进击 | 六浅出:服务网关 - 过滤器(Zuul Filter)【Finchley版本】
SpringCloud进击 | 七浅出:配置中心(Git配置与更新)【Finchley版本】
SpringCloud进击 | 一深入:配置中心(服务化与高可用)【Finchley版本】
SpringCloud进击 | 二深入:配置中心(消息总线)【Finchley版本】
SpringCloud进击 | 三深入:服务链路跟踪(Spring Cloud Sleuth)【Finchley版本】
SpringCloud进击 | 四深入:服务链路跟踪(Sleuth+Zipkin+RabbitMQ整合)【Finchley版本】
SpringCloud进击 | 五深入:断路器监控(Hystrix Dashboard)【Finchley版本】
SpringCloud进击 | 六深入:断路器聚合监控(Hystrix Turbine)【Finchley版本】
SpringCloud进击 | 七深入:高可用的服务注册中心【Finchley版本】


参考资料:https://springcloud.cc/spring-cloud-netflix.html
本节源码:https://github.com/itanping/wei-springcloud/tree/master/chapter04-hystrix


 

你可能感兴趣的:(Spring,Cloud)