在分布式系统下,微服务之间不可避免地会发生相互调用,但是没有一个系统能够保证自身运行的绝对正确。微服务在调用过程中,很可能会面临依赖服务失效的问题,这些问题的发生有很多原因,有可能是因为微服务之间的网络通信出现较大的延迟,或者是被调用的微服务发生了调用异常,还有可能是因为依赖的微服务负载过大无法及时响应请求等 。总而言之服务若在调用时出现故障会导致连 锁效应,也就是可能会让整个系统变得不可用,这种情况我们称之为服务雪崩效应。
Hystrix 是 Netflix 针对微服务分布式系统采用的熔断保护中间件,相当于电路中的保险丝。它能够在依赖服务失效的情况下,通过隔离系统依赖服务的方式,防止服务级联失败;同时 Hystrix 提供失败回滚机制,使系统能够更快地从异常中恢复。这样可以阻止故障的连锁效应,能够让接口调用快速失败并迅速恢复正常,或者回退并优雅降级。
spring-cloud-netflix-hystrix 对 Hystrix 进行封装和适配,使 Hystrix 能够更好地运行于 Spring Cloud 环境中,为微服务间的调用提供强有力的容错机制。
Spring Cloud Hystrix 简单使用
添加 Hystrix 依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
在启动类上添加 @EnableHystrix 或者 @EnableCircuitBreaker 注解
@EnableHystrix 中包含了 @EnableCircuitBreaker
编写一个接口,在上面增加一个 @HystrixCommand 注解,用于指定调用延迟或失败时调用的方法。
@GetMapping("hello")
@HystrixCommand(fallbackMethod = "defalutHello")
public String hello(String name) throws Exception {
if(name == null){
throw new Exception();
}
return "hello";
}
public String defalutHello(String name){
return "fail hello";
}
当调用失败触发熔断是就是调用 defalutHello 方法来回退具体的内容。
Feign 整合 Hystrix
OpenFeign 中自带 Hystrix,但是默认没有打开,需要添加如下配置:
feign:
hystrix:
enabled: true
fallback 方式
在 Feign 的客户端类上的@FeignClient 注解中指定 fallback 进行回退。
@FeignClient(value = "order-service", fallback = OrderClientFallback.class)
public interface OrderClient {
@GetMapping("/order/add")
String addOrder();
}
@Component
public class OrderClientFallback implements OrderClient{
@Override
public String addOrder() {
return "fail";
}
}
FallbackFactory 方式
通过 fallback 可以实现服务不可用时回退的功能,但如果想知道触发回退的原因,可以使用 FallbackFactory 来实现回退功能。
@FeignClient(value = "order-service", fallbackFactory = OrderClientFallbackFactory.class)
public interface OrderClient {
@GetMapping("/order/add")
String addOrder();
}
@Component
public class OrderClientFallbackFactory implements FallbackFactory<OrderClient> {
@Override
public OrderClient create(Throwable cause) {
cause.printStackTrace();
return new OrderClient() {
@Override
public String addOrder() {
return "fail";
}
};
}
}
Hystrix 监控
在微服务架构中,Hystrix 除了实现容错外,还提供了实时监控功能。在服务调用时 Hystrix 会实时累积关于 HystrixCommand 的执行信息,比如每秒的请求数、成功数等。
更多的指标信息请查看官方文档:https://github.com/Netflix/Hystrix/wiki/Metrics-and-Monitoring
Hystrix 监控需要两个必备条件:
- 必须有 Actuator 的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 必须有 Hystrix 的依赖,切必须添加 @EnableHystrix 开启 Hystrix
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
放开 hystrix 监控数据访问端点:
management:
endpoints:
web:
exposure:
include: hystrix.stream
访问端点地址:http://localhost:9010/actuator/hystrix.stream
可以看到一直在输出“ ping : ”,出现这种情况是因为还没有数据,等 HystrixCommand 执行了之后就可以看到具体数据了。
整合 Dashboard 查看监控数据
Hystrix 提供了监控的功能,可以通过 hystrix.stream 端点来获取监控 数据 ,但是这些数据是以字符串的形式展现的,实际使用中不方便查看。我们可以借助 hystrix-dashboard 对监控进行图形化展示。
新建一个项目,然后添加以下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
在启动类中添加如下注解:
@SpringBootApplication
@EnableHystrixDashboard
在属性配置文件中只需要配置服务名称和服务端口:
spring:
application:
name: hystrix-monitor-demo
server:
port: 8081
然后启动服务,访问 http://localhost:8081/hystrix,就可以看到 dashboard 页面,如下图:
在页面上有三个地方可以填写:
- 第一行是监控的 stream 地址
- delay 时间,表示用多少毫秒同步一次监控信息
- title 标题,随便填写
我们把前面创建的 hystrix 应用的监控地址填入,然后点击监控
- 圆圈: 代表流量的大小和流量的健康,有绿色,黄色,橙色,红色,通过这些颜色的标识,可以快速发现故障,具体的实例,请求压力等。
- 曲线:代表 2 分钟内流量的变化,可以根据它发现请求的浮动趋势。
- 左边的7个数字:
- 绿色:请求成功数
- 黄色:超时的请求数
- 深蓝色:熔断数
- 紫色:线程池拒绝数
- 天蓝色:错误的请求数
- 红色:失败的请求数
- 灰色:最近 10 秒内错误的比率
- Host&Cluster:代表机器和集群的请求频率
- Circuit:断路器状态,open/closed
- Hosts&Median&Mean: 集群下的报告,百分位延迟数。
- Thread Pools: 线程池的指标,核心线程池指标,队列大小等。
Turbine 聚合 Hystrix
上面尝试了单个实例的 Hystrix Dashboard,但在整个系统和集群的情况下不是特别有用,所以需要一种方式来聚合整个集群下的监控状况,Turbine 就是来聚合所有相关的 hystrix.stream 流的方案, 然后在 Hystrix Dashboard 中显示。
继续在上面 hystrix-dashboard 工程上改造。首先添加 Turbine 依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
添加配置
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
turbine:
## 需要聚合的服务名称,多个逗号隔开
app-config: feign-demo
## 需要聚合的集群名称
cluster-name-expression: "'default'"
clusterNameExpression 默认表达式appName,此时:turbine.aggregator.clusterConfig 需要配置想要监控的应用名称(必须大写)
当clusterNameExpression: default时,turbine.aggregator.clusterConfig可以不写,因为默认就是default
当clusterNameExpression: metadata['cluster']时,假设想要监控的应用配置了eureka.instance.metadata-map.cluster: ABC,则需要配置,同时turbine.aggregator.clusterConfig: ABC
修改启动类添加 @EnableTurbine 和 @EnableDiscoveryClient 注解
@SpringBootApplication
@EnableHystrixDashboard
@EnableTurbine
@EnableDiscoveryClient
public class HystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardApplication.class,args);
}
}
这样咱们就可以使用 http://localhost:8081/turbine.stream 来访问集群的监控数据了。 Turbine 会通过 Eureka 中查找的服务 homePageUrl 加上 hystrix.stream 来获取服务的监控数据。
context-path 导致监控失败
如果被监控的服务中设置了 context-path ,则会导致 Turbine 无法获取监控数据。
这个时候需要在 Turbine 中指定 turbine.instanceUrlSuffix 来解决这个问题:
turbine:
instanceUrlSuffix: /api/actuator/hystrix.stream
上面这种方式是全局配置,会有一个问题,就是一般我们在使用中会用一个集群去监控多个服务,如果每个服务的 context-path 都不一样,这个时候有一些就会出问题,那么就需要对每个服务做一个集群,然后配置集群对应的。
修改需要监控的服务设置集群名称:
eureka:
instance:
metadata-map:
cluster: feign-demo
修改 turbine 配置
turbine:
## 需要聚合的服务名称,多个逗号隔开
app-config: feign-demo
## 用于指定集群名称,当服务数量非常多的时候,可以启动多个
cluster-name-expression: metadata['cluster']
aggregator:
## 多个逗号隔开
cluster-config: feign-demo
instanceUrlSuffix:
feign-demo: /api/actuator/hystrix.stream
Hystrix 异常机制和处理
Hystrix 的异常处理中,有 5 种出错的情况下会被 fallback 所截获,从而触发 fallback。
- FAILURE:执行失败,抛出异常
- TIMEOUT:执行超时
- SHORT CIRCUITED:断路器打开
- THREAD POOL REJECTED:线程池拒绝
- SEMAPHORE REJECTED:信号量拒绝 。
有一种类型的异常是不会触发 fallback 且不会被计数进入熔断的,它是 BAD_REQUEST, 会抛出 HystrixBadRequestException,这种异常一般对应的是由非法参数或者一些非系统异常引起的,对于这类异常可以根据响应创建对应的异常进行异常封装或者直接处理
Hystrix 配置说明
properties 配置
# hystrix.command.default和hystrix.threadpool.default中的default为默认CommandKey,CommandKey默认值为服务方法名。
# 在properties配置中配置格式混乱,如果需要为每个方法设置不同的容错规则,建议使用yml文件配置。
# Execution相关的属性的配置:
# 隔离策略,默认是Thread, 可选Thread|Semaphore
hystrix.command.default.execution.isolation.strategy=THREAD
#命令执行超时时间,默认1000ms,只在线程池隔离中有效。
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=1000
# 执行是否启用超时,默认启用true,只在线程池隔离中有效。
hystrix.command.default.execution.timeout.enabled=true
# 发生超时是是否中断,默认true,只在线程池隔离中有效。
hystrix.command.default.execution.isolation.thread.interruptOnTimeout=true
# 最大并发请求数,默认10,该参数当使用ExecutionIsolationStrategy.SEMAPHORE策略时才有效。如果达到最大并发请求数,请求会被拒绝。# 理论上选择semaphore的原则和选择thread一致,但选用semaphore时每次执行的单元要比较小且执行速度快(ms级别),否则的话应该用thread。# semaphore应该占整个容器(tomcat)的线程池的一小部分。
hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests=10
# 如果并发数达到该设置值,请求会被拒绝和抛出异常并且fallback不会被调用。默认10。# 只在信号量隔离策略中有效,建议设置大一些,这样并发数达到execution最大请求数时,会直接调用fallback,而并发数达到fallback最大请求数时会被拒绝和抛出异常。
hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests=10
# ThreadPool 相关参数
# 并发执行的最大线程数,默认10
hystrix.threadpool.default.coreSize=10
# BlockingQueue的最大队列数,当设为-1,会使用SynchronousQueue,值为正时使用LinkedBlcokingQueue。# 该设置只会在初始化时有效,之后不能修改threadpool的queue size,除非reinitialising thread executor。默认-1。
hystrix.threadpool.default.maxQueueSize=-1
# 即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝。
hystrix.threadpool.default.queueSizeRejectionThreshold=20
# 线程存活时间,单位是分钟。默认值为1。
hystrix.threadpool.default.keepAliveTimeMinutes=1
# Fallback相关的属性
# 当执行失败或者请求被拒绝,是否会尝试调用fallback方法 。默认true hystrix.command.default.fallback.enabled=true # Circuit Breaker相关的属性 # 是否开启熔断器。默认true hystrix.command.default.circuitBreaker.enabled=true # 一个rolling window内最小的请求数。如果设为20,那么当一个rolling window的时间内(比如说1个rolling window是10毫秒)收到19个请求# 即使19个请求都失败,也不会触发circuit break。默认20
hystrix.command.default.circuitBreaker.requestVolumeThreshold=20
# 触发短路的时间值,当该值设为5000时,则当触发circuit break后的5000毫秒内都会拒绝远程服务调用,也就是5000毫秒后才会重试远程服务调用。默认5000
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds=5000
# 错误比率阀值,如果错误率>=该值,circuit会被打开,并短路所有请求触发fallback。默认50
hystrix.command.default.circuitBreaker.errorThresholdPercentage=50
# 强制打开熔断器
hystrix.command.default.circuitBreaker.forceOpen=false
# 强制关闭熔断器
hystrix.command.default.circuitBreaker.forceClosed=false
yaml 配置
hystrix:
command:
# default代表全部服务配置,如果为某个具体服务定制配置,使用:'服务接口名#方法名(参数类型列表)'的方式来定义。
# 如:'FirstClientFeignService#test(int)'。如果接口名称在应用中唯一,可以只写simpleName。
# 如果接口名称在应用中不唯一,需要写fullName(包名.类名)
"FirstClientFeignService#testFeign()":
fallback:
enabled: true