Hystrix服务容错

引言

Hystrix is a latency and fault tolerance library designed to isolate points of access to remote systems, services and 3rd party libraries, stop cascading failure and enable resilience in complex distributed systems where failure is inevitable.

翻译:Hystrix是一个延迟和容错库,旨在隔离对远程系统,服务和第三方库的访问点,停止级联故障,并在不可避免发生故障的复杂分布式系统中实现弹性。

—— Hystrix GitHub官网 介绍

Hystrix介绍

Hystrix [hɪst’rɪks],中文含义是豪猪,因其背上长满棘刺,从而拥有了自我保护的能力。本文所说的Hystrix是Netflix开源的一款容错框架,同样具有自我保护能力。为了实现容错和自我保护,下面我们看看Hystrix如何设计和实现的。

Hystrix设计目标:

  • 对来自依赖的延迟和故障进行防护和控制——这些依赖通常都是通过网络访问的
  • 阻止故障的连锁反应
  • 快速失败并迅速恢复
  • 回退并优雅降级
  • 提供近实时的监控与告警

Hystrix遵循的设计原则:

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

Hystrix如何实现这些设计目标?

  • 使用命令模式将所有对外部服务(或依赖关系)的调用包装在HystrixCommand或HystrixObservableCommand对象中,并将该对象放在单独的线程中执行;
  • 每个依赖都维护着一个线程池(或信号量),线程池被耗尽则拒绝请求(而不是让请求排队)。
  • 记录请求成功,失败,超时和线程拒绝。
  • 服务错误百分比超过了阈值,熔断器开关自动打开,一段时间内停止对该服务的所有请求。
  • 请求失败,被拒绝,超时或熔断时执行降级逻辑。
  • 近实时地监控指标和配置的修改。

—— AbeJeffrey的个人空间

在spring cloud 中使用Hystrix

  • 添加 Hystrix 依赖
1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
  • 启动类加上开启 Hystrix 注解 @EnableHystrix
1
2
3
4
5
6
7
8
@EnableHystrix
@SpringBootApplication
public class SpringcloudHystrixApplication {

public static void main(String[] args) {
SpringApplication.run(SpringcloudHystrixApplication.class, args);
}
}
  • 测试 Hystrix
1
2
3
4
5
6
7
8
9
10
@GetMapping("/call/hello")
@HystrixCommand(fallbackMethod = "defaultCallHello")
public String callHello(){
String result = restTemplate.getForObject("http://localhost:8081/hello",String.class);
return result;
}

public String defaultCallHello(){
return "fail";
}

当我们没有启动 localhost:8081/hello 的服务时

请求 /call/hello 返回了 “fail”。说明 Hystrix 的熔断机制试用成功了。

配置详解

官网:https://github.com/Netflix/Hystrix/wiki/Configuration

如果我们想配置隔离策略为线程隔离

可在 @HystrixComman注解中增加 commandProperties 来进行配置。

1
2
3
@HystrixCommand(fallbackMethod = "defaultCallHello", commandProperties = {
@HystrixProperty(name = "execution.isolation.strategy",value = "THREAD")
})

其他配置及说明如下:

  • Command Properties
    • Execution
      • execution.isolation.strategy 指定隔离策略,具体策略有两种
        • THREAD 线程隔离,在单独的线程上执行,并发请求受线程池大小的控制。
        • SEMAPHORE 信号量隔离,在调用线程上执行,并发请求受信号量计数器的限制。
        • execution.isolation.thread.timeoutInMilliseconds 配置执行超时时间设置。当Hystrix Command 执行的时间超过了设定值就会进入服务降级处理,单位是毫秒,默认值1000
        • execution.timeout.enabled 用于确定是否启用 execution.isolation.thread.timeoutInMilliseconds 的超时时间设置,默认值为 true。
        • execution.isolation.thread.interruptOnTimeout 用于确定 Hystrix Command 执行超时后是否需要中断它,默认值为 true
        • execution.isolation.thread.interruptOnCancel 用于确定 Hystrix Command 执行被取消时是否需要中断它,默认值为 false
        • execution.isolation.semaphore.maxConcurrentRequests 用于确定 Hystrix 使用信号量策略时最大的请求并发数。
    • Fallback
      - fallback.isolation.semaphore.maxConcurrentRequests 用于如果并发数达到该设定值,请求会被拒绝和抛出异常并且 fallback 不会被调用,默认值为 10
      - fallback.enabled 用于确定当执行失败或者请求被拒绝时,是否会尝试调用 HystrixCommand.getFallback(),默认值为 ture
    • CircuitBreaker
      - circuitBreaker.enabled 用来跟踪 circuit 的健康性,如果未达标则让 request 短路,默认值为 true
      - circuitBreaker.requestVolumeThreshold 用于设置一个 rolling window 内最小的请求数。如果设定为 20 ,那么当一个 rolling window 的时间内(比如一个 rolling window 是10秒)收到19个请求,即使19个请求都失败,也不会触发 circuit break,默认值为 20.
      - circuitBreaker.sleepWindowInMilliseconds 用于设置一个触发短路的时间值,当该值设定为 5000 时,则当触发 circuit break 后的 5000 毫秒内都会拒绝 request ,也就是 5000 毫秒后才会关闭 circuit .默认值为 5000.
      - circuitBreaker.errorThresholdPercentage 用于设置错误率阈值,当错误里超出此值时,所有请求都会触发 fallback ,默认值为 50 .
      - circuitBreaker.forceOpen 如果配置为 true ,则强制打开熔断器,在这个状态下将拒绝所有请求,默认值为 false.
      - circuitBreaker.forceClosed 如果配置为 true 则强制关闭熔断器,在这个状态下,不管错误率有多高,都允许请求,默认值为 false.
    • Metrics
      - metrics.rollingStats.timeInMilliseconds 设置统计时间窗口值,单位为毫秒。circuit break 的打开会根据 1 个 rolling window 的统计来计算。若 rolling window 被设定为 10000 毫秒,则 rolling window 会被分成多个 buckets,每个 bucket 包含 success、failure、timeout、rejection 的次数的统计信息。默认值为 10000 毫秒。
      - metrics.rollingStats.numBuckets 设定一个 rolling window 被划分的数量,若 numBuckets=10、rolling window=10000,那么一个burket的时间即为1秒。必须符合 rolling window % numberBurkets==0 .默认值为 10 .
      - metrics.rollingPercentile.enabled 是否开启指标的计算和跟踪,默认值为 true。
      - metrics.rollingPercentile.timeInMilliseconds 设置 rolling percentile window 的时间,默认值为 60000 毫秒。
      - metrics.rollingPercentile.numBuckets 设置 rolling percentile window numBuckets 默认值 6 。
      - metrics.rollingPercentile.bucketSize 如果设置 bucketSize=100 ,window=10 秒,若这10 秒内有 500 次执行,只有最后 100 次执行会被统计到 bucket 里去。增加该值会增加内存开销。默认值为 100
      - metrics.healthSnapshot.intervalInMilliseconds 用来计算影响断路器状态的健康快照的间隔等待时间,默认值为 500 毫秒。
    • Request Context
      - requestCache.enabled 是否开启请求缓存功能,默认值为 true
      - requestLog.enabled 记录日志到 HystrixRequestLog,默认值为 true
  • Collapser Properties
    • maxRequestsInBatch 单次批处理的最大请求数,达到该数量触发批处理,默认值为 Integer.MAX_VALUE。
    • timerDelayInMilliseconds 触发批处理的延迟,延迟也可以为创建批处理的时间与该值的和,默认值为 10 毫秒
    • requestCache.enabled 是否启用对 HytrixCollapser.execute() 和 HytrixCollapser.queue() 的请求缓存,默认值为 true
  • Thread Pool Properties
    - coreSize 并发请求核心线程数,默认值 10
    - maximumSize 在1.5.9中添加。此属性设置最大线程池大小。这是在不开始拒绝HystrixCommands的情况下可以支持的最大并发量。请注意,只有同时设置了allowMaximumSizeToDivergeFromCoreSize,此设置才会生效。在1.5.9之前,核心和最大大小始终相等
    - maxQueueSize BlockingQueue 的最大队列数,当设置为 -1 时,会使用 SynchronousQueue ; 值为正数时,会使用 LinkedBlockingQueue.该设置只会在初始化时有效,之后不能修改 threadpool 的 queue size。默认值为 -1.
    - queueSizeRejectionThreshold 即使没有达到 maxQueueSize 但若达到了 queueSizeRejectionThreshold 该值后,请求也会被拒绝。因为 maxQueueSize 不能被动态修改,而 queueSizeRejectionThreshold 参数将允许我们动态设置该值,如果 maxQueueSize=-1,该字段将不起作用。
    - keepAliveTimeMinutes 设置存活时间,单位为分钟,如果 coreSize 小于 maximumSize 那么该实例控制一个线程从使用完成到被释放的时间(闲置时间)。默认值为 1 分钟
    - allowMaximumSizeToDivergeFromCoreSize 该属性允许 maximumSize 的配置生效。那么该值可以等于或高于 coreSize 。设置 coreSize 小于 maximumSize 会创建一个线程池,该线程池可以支持 maximumSize 并发,但在相对不活动期间将向系统返回线程。默认值为 false。
    - metrics.rollingStats.timeInMilliseconds 设置滚动时间窗的时间,单位为毫秒,默认值是 10000
    - metrics.rollingStats.numBuckets 设置滚动时间窗划分桶的数量。默认值为 10

Feign整合Hystrix服务容错

需要EurekaClient,Feign,Hystrix的依赖

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

属性文件中开启 Feign 对 Hytrix 的支持

1
2
#开启feign对Hystrix的支持
feign.hystrix.enabled=true

Fallback 方式

  • UserRemoteClient
1
2
3
4
5
6
7
@FeignClient(value = "eureka-client-service" ,fallback = UserRemoteClientFallBack.class)
public interface UserRemoteClient {

@GetMapping("/user/hello")
String hello();

}
  • UserRemoteClientFallBack
1
2
3
4
5
6
7
@Component
public class UserRemoteClientFallBack implements UserRemoteClient {
@Override
public String hello() {
return "null";
}
}
  • 启动类feign 扫包
1
2
3
4
5
6
7
8
9
@EnableHystrix
@EnableFeignClients(basePackages = "com.study.feignclient")
@SpringBootApplication
public class SpringcloudHystrixApplication {

public static void main(String[] args) {
SpringApplication.run(SpringcloudHystrixApplication.class, args);
}
}
  • 控制器调用
1
2
3
4
5
6
7
8
9
10
11
@RestController
public class Controller {

@Autowired
UserRemoteClient userRemoteClient;

@GetMapping("/call/hello")
public String cellHello(){
return userRemoteClient.hello();
}
}

当我们停掉所有 eureka-client-service 服务后调用 发现返回了 “null” 这正是我们所预见的,

FallbackFactory 方式

  • UserRemoteClientFallbackFactory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import feign.hystrix.FallbackFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class UserRemoteClientFallbackFactory implements FallbackFactory<UserRemoteClient> {

private Logger logger = LoggerFactory.getLogger(UserRemoteClientFallbackFactory.class);

@Override
public UserRemoteClient create(Throwable cause) {
logger.error("UserRemoteClient回退:", cause);
return new UserRemoteClient() {

@Override
public String hello() {
return "fail";
}
};
}
}
  • 修改 UserRemoteClient 的 @FeignClient 注解属性配置
1
2
3
4
5
6
7
@FeignClient(value = "eureka-client-service" ,fallbackFactory = UserRemoteClientFallBack.class)
public interface UserRemoteClient {

@GetMapping("/user/hello")
String hello();

}

注释掉前面 UserRemoteClientFallBack 类的代码 再次测试 发现得到相同的结果。

Feign 中禁用 Hystrix

  • 方式一
1
feign.hystrix.enabled=false
  • 方式二
1
2
3
4
5
6
7
8
9
@Configuration
public class FeignConfiguration {

@Bean
@Scope("prototype")
public Feign.Builder feignBuilder(){
return Feign.builder();
}
}

Hystrix 监控

在微服务架构中,Hystrix 除了提供容错外,还提供了实时监控功能。在服务调用时, Hystrix 会实时累积关于 HystrixCommand 的执行信息,比如每秒的请求数,成功数等。更多的指标信息请查看官方文档:https://github.com/Netflix/Hystrix/wiki/Metrics-and-Monitoring

除了 Hystrix 本身的 依赖还需 Actuator 的依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

启动服务,访问地址:http://localhost:8087/actuator/hystrix.stream

(注意:这里不要用接口测试工具访问。直接在浏览器中访问)

如果浏览器 返回 404

那就在配置文件中配置:

1
2
#暴露所有端点
management.endpoints.web.exposure.include=*

如果浏览器页面一直显示

1
2
3
4
5
6
ping: 

ping:

ping:

说明成功了。只是没有检测到数据。这时我们访问下 控制器接口。就会发现已下类似的数据了

image-20200509173423944

为了方便阅读数据,我们可以借助 dashborad 来查看监控数据

整合 Dashboard 查看监控数据

  • 添加 dashboard 依赖
1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
  • 启动类加上注解 @EnableHystrixDashboard
1
2
3
4
5
6
7
8
9
10
@EnableHystrix
@EnableFeignClients(basePackages = "com.study.feignclient")
@EnableHystrixDashboard
@SpringBootApplication
public class SpringcloudHystrixApplication {

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

重启服务,浏览器访问:http://localhost:8087/hystrix

image-20200509174332525

填写完后 点击下方的按钮

image-20200509174713800

如果页面没有数据。同上面一样访问下 控制器的接口 再过来看就有数据了

Turbine 聚合集群数据

集群监控数据流:http://localhost:8087/turbine.stream

监控面板:http://localhost:8087/hystrix/

同前面一样,进入监控面板,填写监控数据流的地址,时间间隔,和标题。我看到类似如下界面。

hystrix监控面板说明

这里我简单说下我的几个服务。

我这里发布的 8081 端口服务。实例名为 eureka-client-service 并提供了两个接口服务,/test 和 /call/hello

8082、8084、8087 作为 spring-cloud-hystrix 集群消费 8081的服务 /call/hello 接口

8085 作为 feign-provider 实例,消费 8081 服务的 /test 和 /call/hello 接口

所以上图中可看到。#test() 下只有一个host。 #hello() 下有4个 host.

(8087的服务是我们提供集群监控的服务。从我上面的访问地址就可以看出来。我这里同时把 8087 也作为消费者并加入集群。实际生产中我们的的监控服务可能会单独出来了并不会加入业务的消费中。我们只要配置好它单独的实例名称即可。 )

  • 8087端口。我的监控服务的监控相关的配置。
1
2
3
4
5
6
#监控的服务实例名,多个用逗号分隔
turbine.appConfig=spring-cloud-hystrix,feign-provider
turbine.aggregator.clusterConfig=default
turbine.clusterNameExpression=new String("default")
#使用同一主机上的服务通过主机号与端口号组合进行区分
turbine.combine-host-port=true

注意:

所有希望被监控的 feign 客户端的集群实例

  • 必须加入 hystrix、actuator 依赖。
1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
  • 启动类必须加上 @EnableHystrix 注解

  • 属性配置必须开启 feign 对 actuator 的支持

1
2
#开启feign对Hystrix的支持
feign.hystrix.enabled=true
  • 必须暴露相关端点
1
2
#暴露所有端点   (根据自己的情况暴露端点)
#management.endpoints.web.exposure.include=*

context-path 导致监控失败

在《spring cloud 微服务 入门、进阶与实战》书中描述了如果被监控的服务中设置了 context-path ,则会导致 Turbine 无法获取监控数据。

这个时候要在 Turbine 中指定 turbine.instanceUrlSuffix 来解决这个问题:

1
turbine.instanceUrlSuffix=/sub/hystrix.stream

sub 用于监控服务的 context-path 上面这种方式是全局配置,会有一个问题,就是一般我们在使用中会用一个集群去监听多个服务,如果每个服务的 context-path 都不一样,这个时候有一些就会出问题,那么就需要对每一个服务做一个集群,然后配置集群对应的 context-path:

1
turbine.instanceUrlSuffix.集群名称=/sub/hystrix.stream