文章目录
容错
雪崩效应
由“基础服务故障”导致“级联故障”的现象被称为雪崩效应。雪崩效应描述的是生产者不可用导致消费者不可用,并将不可用逐渐放大的过程。
如何容错
想要防止雪崩效应,必须有一个强大的容错机制。需实现以下两点:
- 为网络请求设置超时:通常情况下,一次远程调用对应一个线程/进程。如果响应太慢,这个线程/进程就得不到释放。而线程/进程对应着系统资源,如果得不到释放的线程/进程越积越多,资源就会逐渐被耗尽,最终导致服务不可用。
- 使用断路器模式:断路器可以实现微服务的快速失败,如果它在一段时间内检查到许多类似的错误(如超时),就会在之后的一段时间内,强迫对该微服务的调用快速失败。断路器也可自动诊断依赖的服务是否已经恢复正常。如果发现依赖的服务已经恢复正常,那么就恢复请求该服务。
断路器状态转换逻辑:- 正常情况下,断路器关闭,可正常请求依赖的服务
- 当一段时间内,请求失败率达到一定阈值,断路器就会打开。此时不会再去请求依赖的服务
- 断路器打开一段时间后,会自动进入“半开”状态。此时,断路器可允许一个请求访问依赖的服务。若访问成功,则关闭断路器;否则,断路器继续保持打开状态。
Hystrix
Hytrix
简介
Hystrix是由Netflix开源的一个延迟与容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。
- 包裹请求:使用HystrixCommand包裹对依赖的调用逻辑,每个命令在独立线程中执行。使用了设计模式中的“命令模式”。
- 跳闸机制:当某服务的错误率超过一定阈值时,可以自动或手动跳闸,停止请求该服务一段时间。
- 资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(或者信号量)。如果该线程池已满,发往该依赖的请求就被立即拒绝,而不是排队等候,从而加速失败判定。
- 监控:Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时和被拒绝的请求等。
- 回退机制:当请求失败、超时、被拒绝或当断路器打开时,执行回退逻辑。
- 自我修复:断路器打开一段时间后,会自动进入“半开”状态。
Spring Cloud整合Hystrix
- 在消费者pom文件中增加Hystrix依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
- 消费者启动类上增加注解
@EnableHystrix
或@EnableCircuitBreaker
,从而为项目启用断路器支持:
@SpringBootApplication
@EnableCircuitBreaker
public class EurekaClientHystrixProducerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientHystrixProducerApplication.class, args);
}
}
- 在需要实现容错功能的方法上加
@HystrixCommand
注解:
@RestController
@Log4j2
public class TestController {
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "findByIdFallback")
@GetMapping("findById/{id}")
public String findById(@PathVariable Long id) {
log.info("开始调用");
//使用虚拟主机名(eureka-client-hystrix-producer)请求,需要加入ribbon依赖,并且在RestTemplate的bean上加上@LoadBalanced注解
String forObject = restTemplate.getForObject("http://eureka-client-hystrix-producer/findById/" + id, String.class);
log.info("调用成功,返回值:" + forObject);
return "{'id':"+id+",'status':'success'}";
}
/**
* findUserInfo方法的回退方法
* @param id
*/
public String findByIdFallback(Long id) {
log.info("回退方法");
return "{'id':"+id+",'status':'fail'}";
}
}
测试:先按顺序正常启动,运行调用正常。然后关闭生产者,再访问链接,发现进入回退方法。
注意:
- 进入回退方法并不表示断路器已经打开,只有错误率超过一定阈值时才会打开。
- 使用
@HystrixCommand
注解中的fallbackMethod
属性,设置了一个回退方法findByIdFallback,该方法与findById方法必须具有相同的参数与返回类型。 - 获得造成回退的原因,只需在fallback方法上添加一个Throwable 参数即可:
public String findByIdFallback(Long id,Throwable throwable) {
log.info("回退原因:",throwable);
//TODO
}
- 当发生业务异常时,不想触发fallback方案:
1、继承HystrixBadRequestException类:该类是一个特殊的异常类,当异常发生时,不会触发回退。因此,可将自定义的业务异常继承该类,从而达到业务异常不回退的效果。
2、使用@HystrixCommand
注解的ignoreExceptions
属性,忽略不想执行回退的异常类:
@HystrixCommand(fallbackMethod = "findByIdFallback",ignoreExceptions = {
IllegalStateException.class
})
@HystrixCommand注解
使用说明:https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-javanica#configuration
@HystrixCommand的配置可使用commandProperties和@HystrixProperty来配置。如,配置HystrixCommand执行的超时时间,单位为毫秒:
@HystrixCommand(fallbackMethod = "findByIdFallback",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "500")
})
@GetMapping("findById/{id}")
public String findById(@PathVariable Long id) {
//TODO
}
配置说明
public @interface HystrixCommand {
// HystrixCommand 命令所属的组的名称:默认注解方法类的名称
String groupKey() default "";
// HystrixCommand 命令的key值,默认值为注解方法的名称
String commandKey() default "";
// 线程池名称,默认定义为groupKey
String threadPoolKey() default "";
// 定义回退方法的名称, 此方法必须和hystrix的执行方法在相同类中
String fallbackMethod() default "";
// 配置hystrix命令的参数
HystrixProperty[] commandProperties() default {};
// 配置hystrix依赖的线程池的参数
HystrixProperty[] threadPoolProperties() default {};
// 如果hystrix方法抛出的异常包括RUNTIME_EXCEPTION,则会被封装HystrixRuntimeException异常。我们也可以通过此方法定义哪些需要忽略的异常
Class<? extends Throwable>[] ignoreExceptions() default {};
// 定义执行hystrix observable的命令的模式,类型详细见ObservableExecutionMode
ObservableExecutionMode observableExecutionMode() default ObservableExecutionMode.EAGER;
// 如果hystrix方法抛出的异常包括RUNTIME_EXCEPTION,则会被封装HystrixRuntimeException异常。此方法定义需要抛出的异常
HystrixException[] raiseHystrixExceptions() default {};
// 定义回调方法:但是defaultFallback不能传入参数,返回参数和hystrix的命令兼容
String defaultFallback() default "";
}
属性说明
配置属性说明:https://github.com/Netflix/Hystrix/wiki/Configuration
Command属性主要用来控制HystrixCommand命令的行为,它主要分下面的类别:
-
Execution:用来控制HystrixCommand.run()的执行
execution.isolation.strategy:该属性用来设置HystrixCommand.run()执行的隔离策略。默认为THREAD。
execution.isolation.thread.timeoutInMilliseconds:该属性用来配置HystrixCommand执行的超时时间,单位为毫秒。
execution.timeout.enabled:该属性用来配置HystrixCommand.run()的执行是否启用超时时间。默认为true。
execution.isolation.thread.interruptOnTimeout:该属性用来配置当HystrixCommand.run()执行超时的时候是否要它中断。
execution.isolation.thread.interruptOnCancel:该属性用来配置当HystrixCommand.run()执行取消时是否要它中断。
execution.isolation.semaphore.maxConcurrentRequests:当HystrixCommand命令的隔离策略使用信号量时,该属性用来配置信号量的大小。当最大并发请求达到该设置值时,后续的请求将被拒绝。 -
Fallback:用来控制HystrixCommand.getFallback()的执行
fallback.isolation.semaphore.maxConcurrentRequests:该属性用来设置从调用线程中允许HystrixCommand.getFallback()方法执行的最大并发请求数。当达到最大并发请求时,后续的请求将会被拒绝并抛出异常。
fallback.enabled:该属性用来设置服务降级策略是否启用,默认是true。如果设置为false,当请求失败或者拒绝发生时,将不会调用HystrixCommand.getFallback()来执行服务降级逻辑。 -
Circuit Breaker:用来控制HystrixCircuitBreaker的行为。
circuitBreaker.enabled:确定当服务请求命令失败时,是否使用断路器来跟踪其健康指标和熔断请求。默认为true。
circuitBreaker.requestVolumeThreshold:用来设置在滚动时间窗中,断路器熔断的最小请求数。例如,默认该值为20的时候,如果滚动时间窗(默认10秒)内仅收到19个请求,即使这19个请求都失败了,断路器也不会打开。
circuitBreaker.sleepWindowInMilliseconds:用来设置当断路器打开之后的休眠时间窗。休眠时间窗结束之后,会将断路器设置为“半开”状态,尝试熔断的请求命令,如果依然时候就将断路器继续设置为“打开”状态,如果成功,就设置为“关闭”状态。
circuitBreaker.errorThresholdPercentage:该属性用来设置断路器打开的错误百分比条件。默认值为50,表示在滚动时间窗中,在请求值超过requestVolumeThreshold阈值的前提下,如果错误请求数百分比超过50,就把断路器设置为“打开”状态,否则就设置为“关闭”状态。
circuitBreaker.forceOpen:该属性默认为false。如果该属性设置为true,断路器将强制进入“打开”状态,它会拒绝所有请求。该属性优于forceClosed属性。
circuitBreaker.forceClosed:该属性默认为false。如果该属性设置为true,断路器强制进入“关闭”状态,它会接收所有请求。如果forceOpen属性为true,该属性不生效。 -
Metrics:该属性与HystrixCommand和HystrixObservableCommand执行中捕获的指标相关。
metrics.rollingStats.timeInMilliseconds:该属性用来设置滚动时间窗的长度,单位为毫秒。该时间用于断路器判断健康度时需要收集信息的持续时间。断路器在收集指标信息时会根据设置的时间窗长度拆分成多个桶来累计各度量值,每个桶记录了一段时间的采集指标。例如,当为默认值10000毫秒时,断路器默认将其分成10个桶,每个桶记录1000毫秒内的指标信息。
metrics.rollingStats.numBuckets:用来设置滚动时间窗统计指标信息时划分“桶”的数量。默认值为10。
metrics.rollingPercentile.enabled:用来设置对命令执行延迟是否使用百分位数来跟踪和计算。默认为true,如果设置为false,那么所有的概要统计都将返回-1。
metrics.rollingPercentile.timeInMilliseconds:用来设置百分位统计的滚动窗口的持续时间,单位为毫秒。
metrics.rollingPercentile.numBuckets:用来设置百分位统计滚动窗口中使用桶的数量。
metrics.rollingPercentile.bucketSize:用来设置每个“桶”中保留的最大执行数。
metrics.healthSnapshot.intervalInMilliseconds:用来设置采集影响断路器状态的健康快照的间隔等待时间。 -
Request Context:涉及HystrixCommand使用HystrixRequestContext的设置。
requestCache.enabled:用来配置是否开启请求缓存。
requestLog.enabled:用来设置HystrixCommand的执行和事件是否打印到日志的HystrixRequestLog中。
隔离策略
Hystrix的隔离策略有两种,分别是线程隔离和信号量隔离:
- THREAD(线程隔离):HystrixCommand将在单独的线程上执行,并发请求受到线程池中的线程数量的限制。Hystrix中默认并且推荐使用线程隔离,因为这种方式有一个除网络超时以外的额外保护层。
- SEMAPHORE(信号量隔离):HystrixCommand将在调用线程上执行,开销相对较小,并发请求受到信号量个数的限制。一般来说,只有当调用负载非常高时(例如每个实例每秒调用数百次)才需要使用信号量隔离,因为在这种场景下使用线程隔离开销会比较高。信号量隔离一般仅适用于非网络调用的隔离。
可使用
execution.isolation.strategy
属性指定隔离策略。
如果发生找不到上下文的运行异常,可以考虑将隔离策略设置为SEMAPHORE。
Feign使用Hystrix
- 在消费者配置文件application.yml中,增加配置feign打开hystrix支持:
feign:
hystrix:
# feign打开hystrix支持,默认为false
enabled: true
- 编写Feign接口:
/**
* 使用@FeignClient的fallback属性指定回退类
*/
@FeignClient(name = "eureka-client-hystrix-producer"
,fallback = UserFeignClient.FeignClientFallback.class)
public interface UserFeignClient {
@RequestMapping("findById/{id}")
String queryUserInfo(@PathVariable("id") Long id);
/**
* 回退类需要实现UserFeignClient接口
* FeignClientFallback方法作用域可以是public,无区别
*/
@Component
class FeignClientFallback implements UserFeignClient {
@Override
public String queryUserInfo(Long id) {
return "{'id':"+id+",'status':'fail'}";
}
}
}
3.修改controller类,方法上不需要增加 @HystrixCommand
注解:
@RestController
@Log4j2
public class TestController {
@Autowired
private UserFeignClient userFeignClient;
@GetMapping("findById/{id}")
public String findById(@PathVariable Long id) {
log.info("开始调用");
String forObject = userFeignClient.queryUserInfo(id);
log.info("调用成功,返回值:" + forObject);
return forObject;
}
}
通过Fallback Factory检查回退原因
修改 UserFeignClient 类的内容:
/**
* 使用@FeignClient的fallback属性指定回退类
*/
@FeignClient(name = "eureka-client-hystrix-producer"
,fallbackFactory = UserFeignClient.FeignClientFallbackFactory.class)
public interface UserFeignClient {
@RequestMapping("findById/{id}")
String queryUserInfo(@PathVariable("id") Long id);
/**
* FeignClientFallbackFactory类需要实现FallbackFactory接口,重载create方法
* FeignClientFallback方法作用域可以是public,无区别
*/
@Component
@Log4j2
class FeignClientFallbackFactory implements FallbackFactory<UserFeignClient> {
@Override
public UserFeignClient create(Throwable throwable) {
return new UserFeignClient() {
@Override
public String queryUserInfo(Long id) {
//为了引用启动时不打印日志,日志最好放在各个fallback方法中,而不要直接放在create方法中
log.info("fallback;reason was:", throwable);
return "{'id':"+id+",'status':'fail'}";
}
};
}
}
}
为Feign禁用Hystrix
很多场景下,不需要Feign使用断路器包裹Feign客户端的所有方法。
- 为指定Feign客户端禁用Hystrix:借助Feign的自定义配置。
新建Feign配置类:
@Configuration
public class FeignDisableHystrixConfiguration {
@Bean
@Scope("prototype")
public Feign.Builder builder() {
return Feign.builder();
}
}
为想要禁用Hystrix的@FeignClient,引用该配置类即可。
@FeignClient(name = "eureka-client-hystrix-producer"
,configuration = FeignDisableHystrixConfiguration.class)
public interface UserFeignClient {
//TODO
}
- 全局禁用Hystrix,默认就是全局禁用:
feign:
hystrix:
enabled: false
监控
Hystrix监控
- 增加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2.修改配置文件:
management:
endpoints:
# 启用所有端点
enabled-by-default: true
web:
exposure:
# 暴露所有端点
include: "*"
- 访问 http://localhost:8080/actuator/hystrix.stream 获取Hystrix的监控信息
使用Hystrix Dashboard可视化监控
1.新建项目
2. 添加依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
- 在启动类上增加
@EnableHystrixDashboard
注解
启动项目,访问 http://localhost:8080/hystrix ,出现如下页面:
使用Turbine聚合监控数据
Turbine是一个聚合Hystrix监控数据的工具,它可将所有相关/actuator/hystrix.stream端点的数据聚合到一个组合的/turbine.stream中,从而让集群的监控更加方便。
使用
- 新建项目
- 增加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 在启动类上增加注解
@EnableTurbine
- 编辑配置文件
server:
port: 8031
spring:
application:
name: hystrix-turbine
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
turbine:
# 监控的应用名称,多个使用“,”隔开,如:app1,app2
app-config: eureka-client-hystrix-consumer
cluster-name-expression: "'default'"
Turbine会在Eureka Server中找到微服务,并聚合微服务的监控数据。
- 依次启动Euraka Server、生产者、消费者、turbine和dashboard
- 调用消费者包含hystrix的方法,产生监控数据
- 打开Hystrix Dashboard首页,在url地址栏填入 http://localhost:8031/turbine.stream