在微服务架构中,一个请求需要调用多个服务是非常常见的。如客户端访问A服务,而A服务需要调用B服务,B服务需要调用C服务,由于网络原因或者自身的原因,如果B服务或者C服务不能及时响应,A服务将处于阻塞状态,直到B服务C服务响应。此时若有大量的请求涌入,容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,造成连锁反应,会对整个微服务系统造成灾难 性的严重后果,这就是服务故障的“雪崩”效应。
雪崩是系统中的蝴蝶效应导致其发生的原因多种多样,有不合理的容量设计,或者是高并发下某一个方法响应变慢,亦或是某台机器的资源耗尽。从源头上我们无法完全杜绝雪崩源头的发生,但是雪崩的根本原因来源于服务之间的强依赖,所以我们可以提前评估,做好熔断,隔离,限流。
顾名思义,它是指将系统按照一定的原则划分为若干个服务模块,各个模块之间相对独立,无强依赖。当有故障发生时,能将问题和影响隔离在某个模块内部,而不扩散风险,不波及其它模块,不影响整体的系统服务。
熔断这一概念来源于电子工程中的断路器(Circuit Breaker)。在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。
所谓降级,就是当某个服务熔断之后,服务器将不再被调用,此时客户端可以自己准备一个本地的fallback回调,返回一个缺省值。 也可以理解为兜底。
限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。一般来说系统的吞吐量是可以被测算的,为了保证系统的稳固运行,一旦达到的需要限制的阈值,就需要限制流量并采取少量措施以完成限制流量的目的。比方:推迟解决,拒绝解决,或者者部分拒绝解决等等。
Hystrix是由Netflflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。Hystrix主要通过以下几点实现延迟和容错。
** 包裹请求:**使用HystrixCommand包裹对依赖的调用逻辑,每个命令在独立线程中执行。这使用了设计模式中的“命令模式”。
** 跳闸机制:**当某服务的错误率超过一定的阈值时,Hystrix可以自动或手动跳闸,停止请求该服务一段时间。
**资源隔离:**Hystrix为每个依赖都维护了一个小型的线程池(或者信号量)。如果该线程池已满, 发往该依赖的请求就被立即拒绝,而不是排队等待,从而加速失败判定。
**监控:**Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时、以及被拒绝的请求等。
**回退机制:**当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑由开发人员自行提供,例如返回一个缺省值。
** 自我修复:**断路器打开一段时间后,会自动进入“半开”状态。
复制shop_service_order项目并命名为shop_service_order_rest_hystrix;
**配置依赖:**在shop_service_order_rest_hystrix工程中添加Hystrix的相关依赖;
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
**开启熔断:**在启动类OrderApplication中添加@EnableCircuitBreaker注解开启对熔断器的支持。
@EntityScan("cn.itcast.entity") //@EnableCircuitBreaker //开启熔断器 //@SpringBootApplication
@SpringCloudApplication
public class OrderApplication {
//创建RestTemplate对象
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
可以看到,我们类上的注解越来越多,在微服务中,经常会引入上面的三个注解,于是Spring就提供了一个组合注解:@SpringCloudApplication。
配置熔断降级业务逻辑:
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
//下订单
@GetMapping("/product/{id}")
@HystrixCommand(fallbackMethod = "orderFallBack")
public Product findProduct(@PathVariable Long id) {
return restTemplate.getForObject("http://shop-service- product/product/1", Product.class);
}//降级方法
public Product orderFallBack(Long id) {
Product product = new Product();
product.setId(-1l);
product.setProductName("熔断:触发降级方法");
return product;
}
}
有代码可知,为 findProduct方法编写一个回退方法fifindProductFallBack,该方法与findProduct方法具有相同的参数与返回值类型,该方法返回一个默认的错误信息。
在 Product方法上,使用注解@HystrixCommand的fallbackMethod属性,指定熔断触发的降级方法是 findProductFallBack。
当shop-service-product微服务正常时,浏览器访问 http://localhost:9001/order/product/1,可以正常调用服务提供者获取数据。当将商品微服务停止时继续访问。
此时Hystrix配置已经生效进入熔断降级方法。
默认的****Fallback:
我们刚才把fallback写在了某个业务方法上,如果这样的方法很多,那岂不是要写很多。所以我们可以把Fallback配置加在类上,实现默认fallback:
超时设置:在之前的案例中,请求在超过1秒后都会返回错误信息,这是因为Hystix的默认超时时长为1,我们可以通过配置修改这个值。
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 2000
SpringCloud Fegin默认已为Feign整合了hystrix,所以添加Feign依赖后就不用在添加hystrix,那么怎么才能让Feign的熔断机制生效呢,只要按以下步骤开发:
复制shop_service_order项目并命名为shop_service_order_feign_hystrix;
**修改application.yml在Fegin中开启hystrix:**在Feign中已经内置了hystrix,但是默认是关闭的需要在工程的application.yml中开启对hystrix的支持。
feign:
hystrix:
#在feign中开启hystrix熔断
enabled: true
配置FeignClient接口的实现类:基于Feign实现熔断降级,那么降级方法需要配置到FeignClient接口的实现类中。
@Component
public class ProductFeginClientCallBack implements ProductFeginClient {
/*** 降级方法 */
public Product findById(Long id) {
Product product = new Product();
product.setId(-1l);
product.setProductName("熔断:触发降级方法");
return product;
}
}
**修改FeignClient添加****hystrix熔断:**在@FeignClient注解中添加降级方法;
//指定需要调用的微服务名称
@FeignClient(name = "shop-service-product", fallback = ProductFeginClientCallBack.class)
public interface ProductFeginClient {
//调用的请求路径
@RequestMapping(value = "/product/{id}", method = RequestMethod.GET)
public Product findById(@PathVariable("id") Long id);
}
@FeignClient注解中以fallback声明降级方法。
我们知道,当请求失败,被拒绝,超时的时候,都会进入到降级方法中。但进入降级方法并不意味着断路器已经被打开。那么如何才能了解断路器中的状态呢?
除了实现容错功能,Hystrix还提供了近乎实时的监控,HystrixCommand和HystrixObservableCommand在执行时,会生成执行结果和运行指标。比如每秒的请求数量,成功数量等。这些状态会暴露在Actuator提供的/health端点中。只需为项目添加spring-boot-actuator依 赖,重启项目,访问http://localhost:9001/actuator/hystrix.stream,即可看到实时的监控数据。
刚刚讨论了Hystrix的监控,但访问/hystrix.stream接口获取的都是已文字形式展示的信息。很难通过文字直观的展示系统的运行状态,所以Hystrix官方还提供了基于图形化的DashBoard(仪表板)监控平台。Hystrix仪表板可以显示每个断路器(被@HystrixCommand注解的方法)的状态。
导入依赖
<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>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
**添加EnableHystrixDashboard注解:**在启动类使用@EnableHystrixDashboard注解激活仪表盘项目。
@EnableHystrixDashboard
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
访问测试
**
**
在微服务架构体系中,每个服务都需要配置Hystrix DashBoard监控。如果每次只能查看单个实例的监控数据,就需要不断切换监控地址,这显然很不方便。要想看这个系统的Hystrix Dashboard数据就需要用到Hystrix Turbine。Turbine是一个聚合Hystrix监控数据的工具,他可以将所有相关微服务的Hystrix 监控数据聚合到一起,方便使用。引入Turbine后,整个监控系统架构如下:(省)
熔断器有三个状态 CLOSED、OPEN、HALF_OPEN熔断器默认关闭状态,当触发熔断后状态变更为OPEN ,在等待到指定的时间,Hystrix会放请求检测服务是否开启,这期间熔断器会变为HALF_OPEN半开启状态,熔断探测服务可用则继续变更为 CLOSED关闭熔断器。
为了能够精确控制请求的成功或失败,我们在 shop_service_product的调用业务中加入一段逻辑:
@GetMapping("/{id}")
public Product findById(@PathVariable Long id) {
if (id != 1) {
throw new RuntimeException("太忙了");
}
return productService.findById(id);
}
这样如果参数是id为1,一定失败,其它情况都成功。
我们准备两个请求窗口:
一个请求:http://localhost:8080/consumer/1,注定失败;
一个请求:http://localhost:8080/consumer/2,肯定成功。
熔断器的默认触发阈值是20次请求,不好触发。休眠时间时5秒,时间太短,不易观察,为了测试方便,我们可以通过配置修改熔断策略。
circuitBreaker.requestVolumeThreshold=5
circuitBreaker.sleepWindowInMilliseconds=10000
circuitBreaker.errorThresholdPercentage=50
解读:
当我们疯狂访问id为1的请求时(超过10次),就会触发熔断。断路器会端口,一切请求都会被降级处理。此时你访问id为2的请求,会发现返回的也是失败,而且失败时间很短,只有20毫秒左右。
微服务使用Hystrix熔断器实现了服务的自动降级,让微服务具备自我保护的能力,提升了系统的稳定性,也较好的解决雪崩效应。其使用方式目前支持两种策略:
** 线程池隔离策略:**使用一个线程池来存储当前的请求,线程池对请求作处理,设置任务返回处理超时时间,堆积的请求堆积入线程池队列。这种方式需要为每个依赖的服务申请线程池,有一定的资源消耗,好处是可以应对突发流量(流量洪峰来临时,处理不完可将数据存储到线程池队里慢慢处理)。
** 信号量隔离策略:**使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,请求来先判断计数器的数值,若超过设置的最大线程个数则丢弃改类型的新请求,若不超过则执行计数操作请求来计数器+1,请求返回计数器-1。这种方式是严格的控制线程且立即返回模式,无法应对突发流量(流量洪峰来临时,处理的线程超过数量,其他的请求会直接返回,不继续去请求依赖的服务)。
线程池和型号量两种策略功能支持对比如下:
** **
hystrix.command.default.execution.isolation.strategy:配置隔离策略
ExecutionIsolationStrategy.SEMAPHORE信号量隔离
ExecutionIsolationStrategy.THREAD线程池隔离
hystrix.command.default.execution.isolation.maxConcurrentRequests:最大信号量上限
Hystrix 底层基于RxJava,RxJava是响应式编程开发库,因此Hystrix的整个实现策略简单说即:把一个HystrixCommand封装成一个Observable(待观察者),针对自身要实现的核心功能,对Observable进行各种装饰,并在订阅各步装饰的Observable,以便在指定事件到达时,添加自己的业务。
Hystrix主要有4****种调用方式:
主要的执行逻辑:
1.每次调用创建一个新的HystrixCommand,把依赖调用封装在run()方法中.
2.执行execute()/queue做同步或异步调用.
3.判断熔断器(circuit-breaker)是否打开,如果打开跳到步骤8,进行降级策略,如果关闭进入步骤.
4.判断线程池/队列/信号量是否跑满,如果跑满进入降级步骤8,否则继续后续步骤.
5.调用HystrixCommand的run方法.运行依赖逻辑,依赖逻辑调用超时,进入步骤8.
6.判断逻辑是否调用成功。返回成功调用结果;调用出错,进入步骤8.
7.计算熔断器状态,所有的运行状态(成功,失败,拒绝,超时)上报给熔断器,用于统计从而判断熔断器
状态.
8. getFallback()降级逻辑。以下四种情况将触发getFallback调用:
1. run()方法抛出非HystrixBadRequestException异常。
2. run()方法调用超时
3. 熔断器开启拦截调用
4. 线程池/队列/信号量是否跑满
5. 没有实现getFallback的Command将直接抛出异常,fallback降级逻辑调用成功直接返回,降级逻辑调用失败抛出异常.
9.返回执行成功结果
在实际应用过程通过@HystrixCommand注解能够更加简单快速的实现Hystrix的应用,那么我们就直接从@HystrixCommand注解入手,其中包含了诸多参数配置,如执行隔离策略,线程池定义等,这些参数就不一一说明了,我们来看看其是如何实现服务降级的。
public @interface HystrixCommand {
String groupKey() default "";
String commandKey() default "";
String threadPoolKey() default "";
String fallbackMethod() default "";
HystrixProperty[] commandProperties() default {};
HystrixProperty[] threadPoolProperties() default {};
Class<? extends Throwable>[] ignoreExceptions() default {};
ObservableExecutionMode observableExecutionMode() default ObservableExecutionMode.EAGER;
HystrixException[] raiseHystrixExceptions() default {};
String defaultFallback() default "";
}
其定义了fallbackMethod方法名,正如其名,其提供了一个定义回退方法映射,在异常触发时此方法名对应的method将被触发执行,从而实现服务的降级。那么@HystrixCommand注解又是如何被执行的呢,我们找到 HystrixCommandAspect.java,其切点定义如下:
@Aspect
public class HystrixCommandAspect {
private static final Map<HystrixCommandAspect.HystrixPointcutType, HystrixCommandAspect.MetaHolderFactory> META_HOLDER_FACTORY_MAP;
public HystrixCommandAspect() {
}
@Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixC ommand)")
public void hystrixCommandAnnotationPointcut() {
}
@Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixC ollapser)")
public void hystrixCollapserAnnotationPointcut() {
}
@Around("hystrixCommandAnnotationPointcut() || hystrixCollapserAnnotationPointcut()")
public Object methodsAnnotatedWithHystrixCommand(ProceedingJoinPoint joinPoint) throws Throwable {
//略
}
}
可以看到被@HystrixCommand注解的方法将会执行切面处理。
在HystrixCommandAspect的methodsAnnotatedWithHystrixCommand方法中我们可以看到如下:
@Around("hystrixCommandAnnotationPointcut() || hystrixCollapserAnnotationPointcut()")
public Object methodsAnnotatedWithHystrixCommand(ProceedingJoinPoint joinPoint) throws Throwable {
Method method = AopUtils.getMethodFromTarget(joinPoint);
Validate.notNull(method, "failed to get method from joinPoint: %s", new Object[]{joinPoint});
if (method.isAnnotationPresent(HystrixCommand.class) && method.isAnnotationPresent(HystrixCollapser.class)) {
throw new IllegalStateException("method cannot be annotated with HystrixCommand and HystrixCollapser annotations at the same time");
} else {
HystrixCommandAspect.MetaHolderFactory metaHolderFactory = (HystrixCommandAspect.MetaHolderFactory) META_HOLDER_FACTORY_MAP.get(HystrixComma ndAspect.HystrixPointcutType.of(method));
MetaHolder metaHolder = metaHolderFactory.create(joinPoint);
HystrixInvokable invokable = HystrixCommandFactory.getInstance().create(metaHolder);
ExecutionType executionType = metaHolder.isCollapserAnnotationPresent() ? metaHolder.getCollapserExecutionType() : metaHolder.getExecutionType();
try {
Object result;
if (!metaHolder.isObservable()) {
result = CommandExecutor.execute(invokable, executionType, metaHolder);
} else {
result = this.executeObservable(invokable, executionType, metaHolder);
}
return result;
} catch (HystrixBadRequestException var9) {
throw var9.getCause();
} catch (HystrixRuntimeException var10) {
throw this.hystrixRuntimeExceptionToThrowable(metaHolder, var10);
}
}
}
此方法通过环绕通知的形式对目标方法进行增强,主要作用如下:
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://yangchuntao.blog.csdn.net/article/details/120604020
内容来源于网络,如有侵权,请联系作者删除!