Soul网关源码阅读(十四)—— Hystrix插件详解

Soul网关源码阅读(十四)—— Hystrix插件详解

概要

上一篇介绍了hystrix插件的使用方法,这一篇我们来详细介绍一下hystrix的原理及使用方法,并实现一个简单的demo。

服务熔断、降级的场景

微服务架构下,客户端发起一个业务请求,通常情况下会在后端进行多次服务之间的调用。

试想,如果后端服务的调用顺序为A->B->C,如果C宕机了无法响应,那么B的请求线程作为调用方也会阻塞,最终可能导致服务器线程池中的线程爆掉,而导致B服务也不可用,直到整个系统崩溃,导致服务雪崩

img

为了解决以上问题,我们可以进行服务熔断降级处理。熔断和降级一般都是成对出现的,但是他们又有一些区别。

熔断是指依赖的外部接口出现故障的情况断绝和外部接口的关系。

降级是指由于自身不能提供正常服务而采取的迫不得已的处理手段。

打个比方就是A调用B,B宕机了不能正常响应,A尝试了几次都没能正常访问B,于是A决定断绝与B的交互。这个过程叫熔断

但是A的可能也是服务方,它接收客户端C的调用请求,由于A熔断了B不能提供正常服务,但是它还是得给C一个交代,迫不得已采取一个替代方案,诸如返回一些报错信息给A,是整个调用流程不受阻塞。这个过程叫服务降级

搞清楚我们面临的问题过后,我们来思考一下对应的解决方案。

  1. 解决因为服务B不可用,而导致服务A因为线程阻塞而被打爆的问题
  2. 服务A如何判定服务B不可用,也就是需要一个抽象的熔断规则,当满足熔断条件就关闭与B的调用,反之就开打。
  3. 熔断后,需要一个代替方案,需要定义熔断后的降级策略

以上三个问题的核心在于问题1,如何避免服务A因为线程阻塞且增长导致的宕机?

容易想到的办法就是将A调用B的线程,从服务器(如tomcat)接管过来,不让tomcat直接调用B,而是先交给我们的熔断器进行管理和处理,熔断器有权不进行服务B的调用,而采取降级策略。

Hystrix原理

Hystrix是解决以上场景的解决方案,下图展示了当你用使用 Hystrix 封装后的客户端请求一个服务时的流程。其中抽象的概念后面在一一解释。

流程图

在这里插入图片描述

1. 创建 HystrixCommand 或 HystrixObservableCommand 对象

​ 这两个对象则是我们请求的委托的对象,他们负责发起请求。对于他们的区别,暂时先记住:

  • HystrixCommand用在依赖服务返回单个操作结果的时候。有两种执行方式

- execute():同步执行。从依赖的服务返回一个单一的结果对象,或是在发生错误的时候抛出异常。

- queue():异步执行。直接返回一个Future对象,其中包含了服务执行结束时要返回的单一结果对象。

  • HystrixObservableCommand 用在依赖服务返回多个操作结果的时候。也实现了两种执行方式

- observe():返回Obervable对象,他代表了操作的多个结果,他是一个HotObservable

- toObservable():同样返回Observable对象,也代表了操作多个结果,但它返回的是一个Cold Observable。

2. 执行 command

一共有四种方式可以执行 command,其中前两种方式都只适用于简单的 HystrixCommand 对象:

  • excute() — 以阻塞方式运行,并返回返回其包装对象的响应值,或者抛出异常
  • queue() — 返回一个 Future 对象,你可以选择在适当时机 get
  • observe() —
  • toObservable() —
K             value   = command.execute();
Future     fValue  = command.queue();
Observable ohValue = command.observe();         //hot observable
Observable ocValue = command.toObservable();    //cold observable

实际上,同步方法 execute() 底层逻辑是调用 queue().get(),然后 queue() 实际上是调用了 toObservable().toBlocking().toFuture(),也就是说所有 HystrixCommand 的逻辑都是走 Observable 实现

3. 请求是否使用缓存

如果开启了请求缓存,并且该响应可以在缓存中找到,那就立刻返回缓存的响应值,而不会再走远程调用逻辑

4. 是否开启熔断

当执行 command 时,Hystrix 会判断熔断是否开启,如果是开启状态则走 (8) 进行 Fallback 降级策略,如果未开启则走 (5) ,继续下一步判断是否可以执行 command

5. 线程池\队列\信号量 是否已满

如果上述三者已达到阈值,Hystrix 就会直接走 (8) 进行 Fallback 降级策略

6. HystrixObservableCommand.construct() 或 HystrixCommand.run()

执行调用逻辑。

7. 判断断路器健康状态

8. 进行降级处理

9. 接收响应

soul-plugin-hystrix实战

了解了hystrix的原理及使用流程过后我们来分析一下,soul中对hystrix的实现。

我们先看一下其目录结构:
在这里插入图片描述

  • HystrixBuilder

    它是一个构造器,用于构造我们创建HystrixCommand或者HystrixObservableCommand是的构造参数,它封装了我们的熔断规则

    /**
         * this is build HystrixObservableCommand.Setter.
         *
         * @param hystrixHandle {@linkplain HystrixHandle}
         * @return {@linkplain HystrixObservableCommand.Setter}
         */
        public static HystrixObservableCommand.Setter build(final HystrixHandle hystrixHandle) {
         
         
            //设置默认值
            initHystrixHandleOnRequire(hystrixHandle);
            //groupKey
            HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey(hystrixHandle.getGroupKey());
            HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey(hystrixHandle.getCommandKey());
            HystrixCommandProperties.Setter propertiesSetter =
                    HystrixCommandProperties.Setter()
                            .withExecutionTimeoutInMilliseconds((int) hystrixHandle.getTimeout())
                            .withCircuitBreakerEnabled(true)
                            .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)
                            .withExecutionIsolationSemaphoreMaxConcurrentRequests(hystrixHandle.getMaxConcurrentRequests())
                            .withCircuitBreakerErrorThresholdPercentage(hystrixHandle.getErrorThresholdPercentage())
                            .withCircuitBreakerRequestVolumeThreshold(hystrixHandle.getRequestVolumeThreshold())
                            .withCircuitBreakerSleepWindowInMilliseconds(hystrixHandle.getSleepWindowInMilliseconds());
            return HystrixObservableCommand.Setter
                    .withGroupKey(groupKey)
                    .andCommandKey(commandKey)
                    .andCommandPropertiesDefaults(propertiesSetter);
        }
    
        /**
         * this is build HystrixCommand.Setter.
         * @param hystrixHandle {@linkplain HystrixHandle}
         * @return {@linkplain HystrixCommand.Setter}
         */
        public static HystrixCommand.Setter buildForHystrixCommand(final HystrixHandle hystrixHandle) {
         
         
            initHystrixHandleOnRequire(hystrixHandle);
            HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey(hystrixHandle.getGroupKey());
            HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey(hystrixHandle.getCommandKey());
            HystrixCommandProperties.Setter propertiesSetter =
                    HystrixCommandProperties.Setter()
                            .withExecutionTimeoutInMilliseconds((int) hystrixHandle.getTimeout())
                            .withCircuitBreakerEnabled(true)
                            .withCircuitBreakerErrorThresholdPercentage(hystrixHandle.getErrorThresholdPercentage())
                            .withCircuitBreakerRequestVolumeThreshold(hystrixHandle.getRequestVolumeThreshold())
                            .withCircuitBreakerSleepWindowInMilliseconds(hystrixHandle.getSleepWindowInMilliseconds());
            HystrixThreadPoolConfig hystrixThreadPoolConfig = hystrixHandle.getHystrixThreadPoolConfig();
            HystrixThreadPoolProperties.Setter threadPoolPropertiesSetter =
                    HystrixThreadPoolProperties.Setter()
                            .withCoreSize(hystrixThreadPoolConfig.getCoreSize())
                            .withMaximumSize(hystrixThreadPoolConfig.getMaximumSize())
                            .withMaxQueueSize(hystrixThreadPoolConfig.getMaxQueueSize())
                            .withKeepAliveTimeMinutes(hystrixThreadPoolConfig.getKeepAliveTimeMinutes())
                            .withAllowMaximumSizeToDivergeFromCoreSize(true);
            return HystrixCommand.Setter
                    .withGroupKey(groupKey)
                    .andCommandKey(commandKey)
                    .andCommandPropertiesDefaults(propertiesSetter)
                    .andThreadPoolPropertiesDefaults(threadPoolPropertiesSetter);
        }
    
  • Command

    执行命令的接口,HystrixCommand和HystrixObservableCommand的扩展类需要实现它。

    它实现了一个默认的降级方法doFallback,主要逻辑是将请求跳转到降级的uri上进行处理。

    /**
     * do fall back when some error occurs on hystrix execute.
     * @param exchange {@link ServerWebExchange}
     * @param exception {@link Throwable}
     * @return {@code Mono<Void>} to indicate when request processing is complete.
     */
    default Mono<Void> doFallback(ServerWebExchange exchange, Throwable exception) {
         
         
        if (Objects.isNull(getCallBackUri())) {
         
         
            Object error;
            error = generateError(exchange, exception);
            return WebFluxResultUtils.result(exchange, error);
        }
        DispatcherHandler dispatcherHandler =
            SpringBeanUtils.getInstance().getBean(DispatcherHandler.class);
        ServerHttpRequest request = exchange.getRequest().mutate().uri(getCallBackUri()).build();
        ServerWebExchange mutated = exchange.mutate().request(request).build();
        return dispatcherHandler.handle(mutated);
    }
    
  • HystrixCommand(这里名字刚好取的相反,不要混淆了。)

    它是HystrixObservableCommand的扩展类,主要是实现器construct方法

     @Override
        protected Observable<Void> construct() {
         
         
            return RxReactiveStreams.toObservable(chain.execute(exchange));
        }
    
  • HystrixCommandOnThread

    它是HystrixCommand的扩展类,主要是实现run方法

      @Override
        protected Mono<Void> run() {
         
         
            RxReactiveStreams.toObservable(chain.execute(exchange)).toBlocking().subscribe();
            return Mono.empty();
        }
    
  • HystrixPlugin

    实现doEexcute方法,使用HystrixCommand或HystrixObservableCommand来调用请求。

    @Slf4j
    public class HystrixPlugin extends AbstractSoulPlugin {
         
         
    
        @Override
        protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
         
         
            final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
            assert soulContext != null;
            //构造从admin配置的规则封装成HystrixHandle对象
            final HystrixHandle hystrixHandle = GsonUtils.getInstance().fromJson(rule.getHandle(), HystrixHandle.class);
            if (StringUtils.isBlank(hystrixHandle.getGroupKey())) {
         
         
                hystrixHandle.setGroupKey(Objects.requireNonNull(soulContext).getModule());
            }
            if (StringUtils.isBlank(hystrixHandle.getCommandKey())) {
         
         
                hystrixHandle.setCommandKey(Objects.requireNonNull(soulContext).getMethod());
            }
            //根据HystrixIsolationModeEnum类型,选择构建command对象,信号量——HystrixCommand,线程池——HystrixCommandOnThread(见步骤1)
            Command command = fetchCommand(hystrixHandle, exchange, chain);
            return Mono.create(s -> {
         
         
    						//执行command,execute方法或者toObservable方法(见步骤2)
                Subscription sub = command.fetchObservable().subscribe(s::success,
                        s::error, s::success);
                s.onCancel(sub::unsubscribe);
               //如果熔断器打开会打印以下日志
                if (command.isCircuitBreakerOpen()) {
         
         
                    log.error("hystrix execute have circuitBreaker is Open! groupKey:{},commandKey:{}", hystrixHandle.getGroupKey(), hystrixHandle.getCommandKey());
                }
            }).doOnError(throwable -> {
         
         
                //异常处理
                log.error("hystrix execute exception:", throwable);
                exchange.getAttributes().put(Constants.CLIENT_RESPONSE_RESULT_TYPE, ResultEnum.ERROR.getName());
                chain.execute(exchange);
            }).then();
        }
    
        private Command fetchCommand(final HystrixHandle hystrixHandle, final ServerWebExchange exchange, final SoulPluginChain chain) {
         
         
            if (hystrixHandle.getExecutionIsolationStrategy() == HystrixIsolationModeEnum.SEMAPHORE.getCode()) {
         
         
                return new HystrixCommand(HystrixBuilder.build(hystrixHandle),
                    exchange, chain, hystrixHandle.getCallBackUri());
            }
            return new HystrixCommandOnThread(HystrixBuilder.buildForHystrixCommand(hystrixHandle),
                exchange, chain, hystrixHandle.getCallBackUri());
        }
    
       //...
    }
    

总结

这一篇粗略的介绍了服务熔断、降级的基本概念,以及hystrix的实现原理。

其中还有很多细节地方没有分析到,比如隔离策略线程池和信号量的实现原理是什么,他们的区别是什么?还有待继续深入分析。另外一点是由于hystrix是基于事件流rxjava库构建的,所以源码中使用了大量的链式调用、异步处理等逻辑,所以需要补充这两个基础知识:

  1. 线程隔离策略实现原理。
  2. rxjava语法规则及原理。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章