四、SpringCloud--Hystrix

x33g5p2x  于2021-12-30 转载在 Spring  
字(11.8k)|赞(0)|评价(0)|浏览(175)

1.服务熔断Hystrix入门

1.1服务容错核心知识

1.1.1雪崩效应

在微服务架构中,一个请求需要调用多个服务是非常常见的。如客户端访问A服务,而A服务需要调用B服务,B服务需要调用C服务,由于网络原因或者自身的原因,如果B服务或者C服务不能及时响应,A服务将处于阻塞状态,直到B服务C服务响应。此时若有大量的请求涌入,容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,造成连锁反应,会对整个微服务系统造成灾难 性的严重后果,这就是服务故障的“雪崩”效应。

雪崩是系统中的蝴蝶效应导致其发生的原因多种多样,有不合理的容量设计,或者是高并发下某一个方法响应变慢,亦或是某台机器的资源耗尽。从源头上我们无法完全杜绝雪崩源头的发生,但是雪崩的根本原因来源于服务之间的强依赖,所以我们可以提前评估,做好熔断,隔离,限流。

1.1.2服务隔离

顾名思义,它是指将系统按照一定的原则划分为若干个服务模块,各个模块之间相对独立,无强依赖。当有故障发生时,能将问题和影响隔离在某个模块内部,而不扩散风险,不波及其它模块,不影响整体的系统服务。

1.1.3熔断降级

熔断这一概念来源于电子工程中的断路器(Circuit Breaker)。在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。

所谓降级,就是当某个服务熔断之后,服务器将不再被调用,此时客户端可以自己准备一个本地的fallback回调,返回一个缺省值。 也可以理解为兜底。

1.1.4服务限流

限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。一般来说系统的吞吐量是可以被测算的,为了保证系统的稳固运行,一旦达到的需要限制的阈值,就需要限制流量并采取少量措施以完成限制流量的目的。比方:推迟解决,拒绝解决,或者者部分拒绝解决等等。

1.2Hystrix介绍

        Hystrix是由Netflflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。Hystrix主要通过以下几点实现延迟和容错。

      **  包裹请求:**使用HystrixCommand包裹对依赖的调用逻辑,每个命令在独立线程中执行。这使用了设计模式中的“命令模式”。

       ** 跳闸机制:**当某服务的错误率超过一定的阈值时,Hystrix可以自动或手动跳闸,停止请求该服务一段时间。
        **资源隔离:**Hystrix为每个依赖都维护了一个小型的线程池(或者信号量)。如果该线程池已满, 发往该依赖的请求就被立即拒绝,而不是排队等待,从而加速失败判定。

        **监控:**Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时、以及被拒绝的请求等。
        **回退机制:**当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑由开发人员自行提供,例如返回一个缺省值。

       ** 自我修复:**断路器打开一段时间后,会自动进入“半开”状态。

1.3Rest实现服务熔断

复制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。

        

  • 因为熔断的降级逻辑方法必须跟正常逻辑方法保证:相同的参数列表和返回值声明
  • 在 findProduct方法上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

1.4Feign实现服务熔断

SpringCloud Fegin默认已为Feign整合了hystrix,所以添加Feign依赖后就不用在添加hystrix,那么怎么才能让Feign的熔断机制生效呢,只要按以下步骤开发:

复制shop_service_order项目并命名为shop_service_order_feign_hystrix;

**修改application.ymlFegin中开启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声明降级方法。

2.服务熔断Hystrix高级

我们知道,当请求失败,被拒绝,超时的时候,都会进入到降级方法中。但进入降级方法并不意味着断路器已经被打开。那么如何才能了解断路器中的状态呢?

2.1Hystrix的监控平台

除了实现容错功能,Hystrix还提供了近乎实时的监控,HystrixCommand和HystrixObservableCommand在执行时,会生成执行结果和运行指标。比如每秒的请求数量,成功数量等。这些状态会暴露在Actuator提供的/health端点中。只需为项目添加spring-boot-actuator依 赖,重启项目,访问http://localhost:9001/actuator/hystrix.stream,即可看到实时的监控数据。

2.1.1搭建Hystrix DashBoard监控

刚刚讨论了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);
        }
    }

        访问测试
**

**

2.1.2断路器聚合监控Turbine

在微服务架构体系中,每个服务都需要配置Hystrix DashBoard监控。如果每次只能查看单个实例的监控数据,就需要不断切换监控地址,这显然很不方便。要想看这个系统的Hystrix Dashboard数据就需要用到Hystrix Turbine。Turbine是一个聚合Hystrix监控数据的工具,他可以将所有相关微服务的Hystrix 监控数据聚合到一起,方便使用。引入Turbine后,整个监控系统架构如下:(省)

2.2熔断器的状态

     熔断器有三个状态 CLOSED、OPEN、HALF_OPEN熔断器默认关闭状态,当触发熔断后状态变更为OPEN ,在等待到指定的时间,Hystrix会放请求检测服务是否开启,这期间熔断器会变为HALF_OPEN半开启状态,熔断探测服务可用则继续变更为 CLOSED关闭熔断器。

  •         Closed:关闭状态(断路器关闭),所有请求都正常访问。代理类维护了最近调用失败的次数, 如果某次调用失败,则使失败次数加1。如果最近失败次数超过了在给定时间内允许失败的阈值, 则代理类切换到断开(Open)状态。此时代理开启了一个超时时钟,当该时钟超过了该时间,则切 换到半断开(Half-Open)状态。该超时时间的设定是给了系统一次机会来修正导致调用失败的错 误。
  •         Open:打开状态(断路器打开),所有请求都会被降级。Hystix会对请求情况计数,当一定时间 内失败请求百分比达到阈值,则触发熔断,断路器会完全关闭。默认失败比例的阈值是50%,请求 次数最少不低于20次。
  •         Half Open:半开状态,open状态不是永久的,打开后会进入休眠时间(默认是5S)。随后断路器会自动进入半开状态。此时会释放1次请求通过,若这个请求是健康的,则会关闭断路器,否则继续保持打开,再次进行5秒休眠计时。

为了能够精确控制请求的成功或失败,我们在 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

解读:

  • requestVolumeThreshold:触发熔断的最小请求次数,默认20;
  • errorThresholdPercentage:触发熔断的失败请求最小占比,默认50%;
  • sleepWindowInMilliseconds:熔断多少秒后去尝试请求。

当我们疯狂访问id为1的请求时(超过10次),就会触发熔断。断路器会端口,一切请求都会被降级处理。此时你访问id为2的请求,会发现返回的也是失败,而且失败时间很短,只有20毫秒左右。

2.3熔断器的隔离策略

微服务使用Hystrix熔断器实现了服务的自动降级,让微服务具备自我保护的能力,提升了系统的稳定性,也较好的解决雪崩效应。其使用方式目前支持两种策略:

**        线程池隔离策略:**使用一个线程池来存储当前的请求,线程池对请求作处理,设置任务返回处理超时时间,堆积的请求堆积入线程池队列。这种方式需要为每个依赖的服务申请线程池,有一定的资源消耗,好处是可以应对突发流量(流量洪峰来临时,处理不完可将数据存储到线程池队里慢慢处理)。

**        信号量隔离策略:**使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,请求来先判断计数器的数值,若超过设置的最大线程个数则丢弃改类型的新请求,若不超过则执行计数操作请求来计数器+1,请求返回计数器-1。这种方式是严格的控制线程且立即返回模式,无法应对突发流量(流量洪峰来临时,处理的线程超过数量,其他的请求会直接返回,不继续去请求依赖的服务)。
        线程池和型号量两种策略功能支持对比如下:

**        **

  • hystrix.command.default.execution.isolation.strategy:配置隔离策略

  • ExecutionIsolationStrategy.SEMAPHORE信号量隔离

  • ExecutionIsolationStrategy.THREAD线程池隔离

  • hystrix.command.default.execution.isolation.maxConcurrentRequests:最大信号量上限

2.4Hystrix的核心源码

Hystrix 底层基于RxJava,RxJava是响应式编程开发库,因此Hystrix的整个实现策略简单说即:把一个HystrixCommand封装成一个Observable(待观察者),针对自身要实现的核心功能,对Observable进行各种装饰,并在订阅各步装饰的Observable,以便在指定事件到达时,添加自己的业务。

Hystrix主要有4****种调用方式:

  • toObservable() 方法 :未做订阅,只是返回一个Observable。
  • observe() 方法 :调用 #toObservable()方法,并向Observable注册rx.subjects.ReplaySubject 发起订阅。
  • queue() 方法 :调用#toObservable()方法的基础上,调用:Observable#toBlocking()和 BlockingObservable#toFuture() 返回Future对象
  • execute() 方法 :调用#queue()方法的基础上,调用Future#get()方法,同步返回#run()的执行结果。

主要的执行逻辑:
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.返回执行成功结果

2.4.1HystrixCommand注解

在实际应用过程通过@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注解的方法将会执行切面处理。

2.4.2环绕通知增强

在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);
            }
        }
    }

        此方法通过环绕通知的形式对目标方法进行增强,主要作用如下:

  • HystrixInvokable:定义了后续真正执行HystrixCommand的GenericCommand实例;
  • 定义metaHolder,包含了当前被注解方法的所有相关有效信息;
  • 执行方法:在进入执行体前,其有一个判断条件,判断其是否是一个Observable模式(在Hystrix中,其实现大量依赖RXJAVA,会无处不在的看到Observable,其是一种观察者模式的实现,具体可以到RxJava项目官方做更多了解)

3.服务熔断Hystrix的替换方案(省略)

相关文章