写点什么

Hystrix

作者:李子捌
  • 2021 年 12 月 15 日
  • 本文字数:5603 字

    阅读完需:约 18 分钟

Hystrix

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@EnableHystrixpublic 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 秒,导致熔断器打开,返回了后备方法。



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 // 支持Hystrixpublic 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();
}
@Componentclass 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!



发布于: 3 小时前阅读数: 6
用户头像

李子捌

关注

华为云享专家 2020.07.20 加入

公众号【李子捌】

评论

发布
暂无评论
Hystrix