Hystrix详解

Posted 长头发的程序猿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Hystrix详解相关的知识,希望对你有一定的参考价值。

前言

Hystrix基于Feign,想熟悉Hystrix,必须先熟悉Feign。

Feign(简介和使用):

Feign(简介和使用)_长头发的程序猿的博客-CSDN博客

Hystrix简介

hystrix对应的中文名字是“豪猪”,豪猪周身长满了刺,能保护自己不受天敌的伤害,代表了一种防御机制。

这与hystrix本身的功能不谋而合,因此Netflix团队将该框架命名为Hystrix,并使用了对应的卡通形象做作为logo。

在一个分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,如何能够保证在一个依赖出问题的情况下,不会导致整体服务失败,这个就是Hystrix需要做的事情。

Hystrix提供了熔断、隔离、Fallback、cache、监控等功能,能够在一个、或多个依赖同时出现问题时保证系统依然可用。

代码示例

1、 在 “服务A” 的API工程中新建Hystrix类,命名为 XXXClientFallback

2、 同样实现 XXXClient 接口,只是此时只需返回对应数据即可,不需要再定义为 Controller

3、 代码如下

import org.springblade.core.tool.api.R;
import org.springblade.demo.entity.Blog;
import java.time.LocalDateTime;
public class BlogClientFallback implements BlogClient 
	@Override
	public R<Blog> detail(Integer id) 
		Blog blog = new Blog();
		blog.setBlogTitle("Hystrix");
		blog.setBlogContent("FallBack Success");
		blog.setBlogDate(LocalDateTime.now());
		blog.setIsDeleted(0);
		return R.data(blog);
	

4、 修改BlogClient,增加Hystrix配置

@FeignClient(
//定义Feign指向的service-id
value = CommonConstant.APPLICATION_DEMO_NAME,
//定义hystrix配置类
fallback = BlogClientFallback.class
)
public interface BlogClient 

	/**
	* 接口前缀
	*/
	String API_PREFIX = "/api/blog";

	/**
	* 获取详情
	* @param id 主键
	* @return
	*/
	@GetMapping(API_PREFIX + "/detail")
	R<Blog> detail(@RequestParam("id") Integer id);

5、 增加 FallBack 自动配置(不新建配置,直接在BlogClientFallback类上加@Component注解也可以)

6、 这时我们去“服务A” 的 BlogClientImpl 模拟异常

@RestController
@AllArgsConstructor
public class BlogClientImpl implements BlogClient 

	private BlogService service;

	@Override
	@GetMapping(API_PREFIX + "/detail")
	public R<Blog> detail(Integer id) 
		int cnt = 100 / 0;
		return R.data(service.getById(id));
	

7、 使用Postman调用API查看,发现Hystrix配置生效

Hystrix使用及其配置详解

目录

1、简介

2、正文

2.1 Hystrix使用场景

2.2 Hystrix处理异常

2.3 Hystrix处理超时

2.4 Hystrix断路器阈值设置

2.5 Hystrix与Fegin集成


1、简介

在微服务中,服务与服务之间的调用经常出现两个不确定性因素:

  1. 网络延迟
  2. 服务异常

延迟在微服务中是一个非常重要的性能指标,随着服务的增加,调用链越来越复杂,此时低延迟往往是微服务系统架构中首要目标;高网络延迟可能会拖垮整个微服务,这是不允出现的。此外服务内部可能会发生未知异常,或者未捕获的异常,这时异常如果没有得到正确的处理,将会沿着调用链往上抛出,这对上传调用链来说也是致命的,因为往往这个时候上层调用方它不知道该如何处理未知异常。
对于服务异常,我们应该在系统架构时满足维加斯规则(Vegas Rule):在微服务中发生的事情,就留在该微服务中。通俗点说,微服务中发生的异常要自己处理,不应该给其他微服务返回非约定交互报文之外的任何信息。
对于网络延迟,这是无法避免的,CAP理论中也谈到过分布式架构中网络分区无法避免,用于可能发生;因此我们只能在可能发生网络延迟的地方,做超时设置、超时后的副本处理等操作。

Hystrix用于解决上面两个问题。(注意,它并不能让错误不发生或者让网络延迟不发生,它只是提供了后备行为和自校正功能,可以用于优雅的处理错误和网络延迟。)Hystrix的工作原理很简单,被保护的方法可以设定失败阈值,在给定的失败阈值内方法发生失败(异常/延迟),通过调用一个预先准备的后备方法来返回预先准备的数据报文(本质上仍然是通过切面实现)。Hystrix有三种状态,分别是关闭状态、打开状态、半开状态。

  1. 关闭状态(closed),Hystrix默认为关闭状态
  2. 打开状态(open),超过设定的失败阈值后,熔断机制打开,Hystrix进入打开状态,此时所有请求直接请求提供熔断方法,不再请求正常服务
  3. 半开状态(half open),Hystrix进入打开状态之后,超过circuitBreaker.sleepWindowInMilliseconds时间周期,Hystrix进入半打开状态,此时尝试调用正常服务,如果服务调用失败会重置为失败状态

2、正文

2.1 Hystrix使用场景

Hystrix多用于有网络延时的场景,因此其使用场景也是那些容易出现网络延迟的方法,比如说:

  1. 远程服务调用,rest请求
  2. 数据库访问
  3. 复杂且耗时的计算场景

2.2 Hystrix处理异常

Hystrix用于微服中,因此使用Hystrix之前,需要准备一个简单的微服务环境,指定Spring Cloud版本和Spring Boot版本,此外引入web依赖用于模拟微服务间调用。

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.3.4.RELEASE</version>
</parent>

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
</dependencies>

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-dependencies</artifactId>
      <version>Hoxton.RELEASE</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

Hystrix依赖导入

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

配置服务启动端口

server:
  port: 18888

启动类增加@EnableHystrix注解

@SpringBootApplication
@EnableHystrix
public class ServiceApplication 

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


方法一:
编写使用Hystrix保护的方法,这里使用@HystrixCommand注解注释需要受Hystrix保护的方法,并且指定fallbackMethod属性的值为fallback,fallback是一个提前预置的方法,该方法与受保护的方法返回值一致,用于服务断路器打开时备用。我在demo方法中,直接抛出了一个RuntimeException,模拟服务调用失败。

@RestController
@RequestMapping("/fallback")
public class FallbackMethodController 

    @GetMapping
    @HystrixCommand(fallbackMethod = "fallback")
    public ResponseEntity<String> demo() 
        // 模拟服务异常
       throw new RuntimeException("Error.");
    

    private ResponseEntity<String> fallback() 
        return new ResponseEntity<>("Hello World!", HttpStatus.OK);
    


对该rest接口发起请求,此时无论请求多少次都会得到Hello World!返回值。

方法二:
除了上面这种直接在方法指定后备方法之外,还可以采用另外一种方法,直接在Controller类上定义默认的后备方法,这样整个Controller需要受保护的方法,无需每个都明确指定后备方法了。(区别:@HystrixCommand无需再指定fallbackMethod)

@RestController
@RequestMapping("/defaultFallback")
// 整体定义后备方法
@DefaultProperties(defaultFallback = "defaultFallback")
public class DefaultFallbackMethodController 

    @GetMapping
    @HystrixCommand
    public ResponseEntity<String> demo() 
        throw new RuntimeException("Error.");
    

    public ResponseEntity<String> defaultFallback() 
        return new ResponseEntity<>("Hello World.", HttpStatus.OK);
    

2.3 Hystrix处理超时

Hystrix除了能优雅的处理未知异常之外,其另外一个能力就是方法执行延迟的处理, @HystrixCommand注解默认情况下设置了1秒的超时时间,如果1秒内方法未返回,将会执行预置的后备方法。1秒的超时时间不一定满足所有的业务场景,或者有些方法它就是硬不要设置超时时间,关于这些需求Hystrix都提供了相应的配置项。

@HystrixCommand注解中提供了commandProperties属性,它是一个HystrixProperty数组,因此@HystrixProperty可以定义多个;其中name指定要配置的项,value指定对应配置项的值。

@RestController
@RequestMapping("/timeout")
public class TimeoutController 

    @GetMapping
    @HystrixCommand(
            fallbackMethod = "fallback",
            commandProperties = 
                    @HystrixProperty(
                            name = "execution.isolation.thread.timeoutInMilliseconds",
                            value = "2000"
                    )
            )
    public ResponseEntity<String> demo() 
        try 
            // 模拟接口请求,时间设置为3秒
            TimeUnit.SECONDS.sleep(3);
         catch (InterruptedException e) 
            e.printStackTrace();
        
        return new ResponseEntity<>("Hello!", HttpStatus.OK);
    


    private ResponseEntity<String> fallback() 
        return new ResponseEntity<>("Timeout!", HttpStatus.OK);
    


接口并未返回Hello!,而是返回了后备方法的返回值Timeout!,这是因为我们设值的超时时间是2秒,而 TimeUnit.SECONDS.sleep(3)睡眠了3秒,导致熔断器打开,返回了后备方法。

image.png

Hystrix的方法超时时间也可以关闭,@HystrixProperty提供了关闭的开关如下所示:

@RestController
@RequestMapping("/closeTimeout")
public class TimeoutDisableController 


    @GetMapping
    @HystrixCommand(
            fallbackMethod = "fallback",
            commandProperties = 
                    @HystrixProperty(
                            name = "execution.timeout.enabled",
                            value = "false"
                    )
            )
    public ResponseEntity<String> demo() 
        try 
            // 模拟接口请求
            TimeUnit.SECONDS.sleep(3);
         catch (InterruptedException e) 
            e.printStackTrace();
        
        return new ResponseEntity<>("Hello!", HttpStatus.OK);
    


    private ResponseEntity<String> fallback() 
        return new ResponseEntity<>("Timeout!", HttpStatus.OK);
    


此时/closeTimeout接口无论多久多不会触发超时保护(理论上不会这样玩儿!)

2.4 Hystrix断路器阈值设置

上面有说到Hystrix断路器的三个状态,在默认情况下,Hystrix保护的方法,在10秒内,请求次数超过了20次,50%以上的请求发生失败, 断路器将会进入打开状态,5秒后断路器进入半开状态,尝试重新调用原始的方法,如果调用失败,断路器直接变为打开状态。

Hystrix断路器阈值,默认配置:
在给定的时间范围内,方法应该被调用的次数

circuitBreaker.requestVolumeThreshold = 20

在给定时间范围内,方法调用产生失败的百分比

circuitBreaker.errorThresholdPercentage = 50%

请求量和错误百分比的滚动时间周期

metrics.rollingStats.timeInMilliseconds = 10000

处于打开状态的断路器,要经过多长时间才会进入半开状态,进入半开状态之后,将会再次尝试原始方法

circuitBreaker.sleepWindowInMilliseconds = 5000

如下将默认断路器阈值进行修改,修改后60秒内,请求次数超过4次,50%以上的请求失败,断路器就会进入打开状态,并且60秒后断路器才会进入半开状态,尝试调用原始方法。我这里设置成这样是为了方便测试。

@RestController
@RequestMapping("/circuitBreaker")
public class CircuitBreakConfigController 


    @GetMapping
    @HystrixCommand(
            fallbackMethod = "fallback",
            commandProperties = 
                    @HystrixProperty(
                            name = "execution.isolation.thread.timeoutInMilliseconds",
                            value = "1000"
                    ),
                    @HystrixProperty(
                            name = "circuitBreaker.requestVolumeThreshold",
                            value = "4"
                    ),
                    @HystrixProperty(
                            name = "circuitBreaker.errorThresholdPercentage",
                            value = "50"
                    ),
                    @HystrixProperty(
                            name = "metrics.rollingStats.timeInMilliseconds",
                            value = "60000"
                    ),
                    @HystrixProperty(
                            name = "circuitBreaker.sleepWindowInMilliseconds",
                            value = "60000"
                    )
            )
    public ResponseEntity<String> demo() 
        try 
            // 模拟接口请求
            TimeUnit.SECONDS.sleep(2);
         catch (InterruptedException e) 
            e.printStackTrace();
        
        return new ResponseEntity<>("Hello!", HttpStatus.OK);
    


    private ResponseEntity<String> fallback() 
        return new ResponseEntity<>("Timeout!", HttpStatus.OK);
    



将超时时间设置为1秒,方法中执行TimeUnit.SECONDS.sleep(2);使得线程阻塞2秒,显然每次调用都会失败,因此在第四之后(60s内)的请求,都会直接执行后备方法。

2.5 Hystrix与Fegin集成

很多时候我们会使用Open Fegin来向服务端请求数据,这个时候我们可以使用Hystrix来包含Fegin Client,集成方式也十分简单。

OpenFeign中已经集成了Hystrix,因此不需要再单独导入Hystrix的依赖

<!--openfeign 中已经依赖了Hystrix-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

启动类中添加@EnableFeignClients注解,该注解默认支持Hystrix

@SpringBootApplication
@EnableFeignClients // 支持Hystrix
public class ConsumerApplication 

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


开启Feign对Hystix的支持,此外由于Feign集成的Ribbon,Ribbon也有默认的请求超时时间,因此我们要想正确的使用Hystrix带来的熔断保护,就应该将Ribbon的超时时间设定的比Hystrix的超时时间大。(两者默认超时时间都是1秒)

server:
  port: 19999

# 开启feign对hystrix的支持
feign:
  hystrix:
    enabled: true
  client:
    config:
      default:
        readTimeout: 3000
        connectTimeout: 3000

定义一个Feign Client,并指定fallback类,该类需要实现Feign Client才能提供熔断服务。注意,Feign Client的实现类需要添加到容器中。

@FeignClient(value = "service", url = "http://localhost:18888/fallback", fallback = ServerClientFallback.class)
public interface ServerClient 

    @GetMapping
    ResponseEntity<String> demo();



@Component
class ServerClientFallback implements ServerClient 

    @Override
    public ResponseEntity<String> demo() 
        return new ResponseEntity<>("Client FallBack!", HttpStatus.OK);
    

定义一个测试controller,我们不启动1888服务,模拟服务端不可用的情况!

@RestController
@RequestMapping("/feign")
public class FeignController 

    @Autowired
    private ServerClient serverClient;

    @GetMapping
    public ResponseEntity<String> demo() 
        return serverClient.demo();
    

此时触发服务降级,直接返回Client FallBack!

 👇🏻 关注公众号 我们一起进大厂👇🏻    

以上是关于Hystrix详解的主要内容,如果未能解决你的问题,请参考以下文章

Hystrix详解

Hystrix使用及其配置详解

Hystrix使用及其配置详解

六Hystrix详解一Hystrix的基本使用

如何从 Actuator /metrics 端点中排除 Hystrix Metrics?

SpringCloud05_Hystrix的概述案例详解服务降级服务熔断服务监控hystrixDashboard