SpringCloud 源码系列(16)— 熔断器Hystrix 之 基础入门篇

专栏系列文章:SpringCloud系列专栏java

系列文章:mysql

SpringCloud 源码系列(1)— 注册中心Eureka 之 启动初始化git

SpringCloud 源码系列(2)— 注册中心Eureka 之 服务注册、续约github

SpringCloud 源码系列(3)— 注册中心Eureka 之 抓取注册表web

SpringCloud 源码系列(4)— 注册中心Eureka 之 服务下线、故障、自我保护机制redis

SpringCloud 源码系列(5)— 注册中心Eureka 之 EurekaServer集群spring

SpringCloud 源码系列(6)— 注册中心Eureka 之 总结篇sql

SpringCloud 源码系列(7)— 负载均衡Ribbon 之 RestTemplate数据库

SpringCloud 源码系列(8)— 负载均衡Ribbon 之 核心原理缓存

SpringCloud 源码系列(9)— 负载均衡Ribbon 之 核心组件与配置

SpringCloud 源码系列(10)— 负载均衡Ribbon 之 HTTP客户端组件

SpringCloud 源码系列(11)— 负载均衡Ribbon 之 重试与总结篇

SpringCloud 源码系列(12)— 服务调用Feign 之 基础使用篇

SpringCloud 源码系列(13)— 服务调用Feign 之 扫描@FeignClient注解接口

SpringCloud 源码系列(14)— 服务调用Feign 之 构建@FeignClient接口动态代理

SpringCloud 源码系列(15)— 服务调用Feign 之 结合Ribbon进行负载均衡请求

熔断器 Hystrix

分布式系统高可用问题

在分布式系统中,服务与服务之间的依赖错综复杂,某些服务出现故障,可能致使依赖于它们的其余服务出现级联阻塞故障。

例以下图,某个请求要调用 Service-A,Service-A 又要调用 Service-B、Service-D,Service-B 又要再调用 Service-C,Service-C 又依赖于数据库、Redis等中间件。若是在调用 Service-C 时,因为 Service-C 自己业务问题或数据库宕机等状况,就可能会致使 Service-B 的工做线程所有占满致使不可用,进而又致使级联的 Service-A 资源耗尽不可用,这时就会致使整个系统出现大面积的延迟或瘫痪。

在高并发的状况下,单个服务的延迟会致使整个请求都处于延迟阻塞状态,最终的结果就是整个服务的线程资源消耗殆尽。服务的依赖性会致使依赖于该故障服务的其余服务也处于线程阻塞状态,最终致使这些服务的线程资源消耗殆尽,直到不可用,从而致使整个微服务系统都不可用,即雪崩效应。

例如,对于依赖 30 个服务的应用程序,每一个服务的正常运行时间为 99.99%,对于单个服务来讲,99.99% 的可用是很是完美的。有 99.99^30 = 99.7% 的可正常运行时间和 0.3% 的不可用时间,那么 10 亿次请求中就有 3000000 次失败,实际的状况可能比这更糟糕。

若是不设计整个系统的韧性,即便全部依赖关系表现良好,单个服务只有 0.01% 的不可用,因为整个系统的服务相互依赖,最终对整个系统的影响是很是大的。

Hystrix 简介

一、简介

Hystrix是由Netflix开源的一个针对分布式系统容错处理的开源组件,旨在隔离远程系统、服务和第三方库,阻止级联故障,在复杂的分布式系统中实现恢复能力,从而提升了整个分布式系统的弹性。

例如在上图中,若是在 Service-B 调用 Service-C 时,引入 Hystrix 进行资源隔离,Service-C 故障或调用超时就自动降级返回,从而隔离了 Service-C 对 Service-B 的级联影响,进而保证了整个系统的稳定性。

二、Hystrix的设计原则

总的来讲Hystrix 的设计原则以下:

  • 防止单个服务的故障耗尽整个服务的Servlet容器(例如Tomcat)的线程资源。
  • 快速失败机制,若是某个服务出现了故障,则调用该服务的请求快速失败,而不是线程等待。
  • 提供回退(fallback)方案,在请求发生故障时,提供设定好的回退方案。
  • 使用熔断机制,防止故障扩散到其余服务。
  • 提供熔断器的监控组件Hystrix Dashboard,能够实时监控熔断器的状态。

三、官方文档

GitHub:github.com/Netflix/Hys…

官方文档:github.com/Netflix/Hys…

注意:Hystrix 再也不开发新功能,目前处于维护模式,最新稳定版本是 1.5.18

使用前在 spring cloud 项目中引入 netflix 相关依赖:我本地使用的 spring-cloud-netflix-hystrix 版本为 2.2.5.RELEASE

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
复制代码

四、参考书籍:

  • 《深刻理解 Spring Cloud 与微服务构建(第 2 版)》
  • 《从新定义Spring Cloud实战》

Hystrix的工做机制

Hystrix的工做机制能够参考官方【How-it-Works】。下面这幅图展现了 Hystrix 的核心工做原理。

核心的原理以下:

  • ① 将对依赖服务的请求调用封装到 HystrixCommand 或者 HystrixObservableCommand 中,这个访问请求通常会在独立的线程中执行。区别在于:

    • HystrixCommand 是用来获取一条数据的。
    • HystrixObservableCommand 是设计用来获取多条数据的,返回类型是 Observable。
  • ② 执行请求,有四个方法能够调用:execute()、queue()、observe()、toObservable(),HystrixCommand 四个均可以调用,HystrixObservableCommand 只能调用 observe()toObservable()

  • ③ 若是这个命令启用了缓存,且缓存中存在,就直接返回缓存中的数据。

  • ④ 检查断路器是否打开了,若是断路器打开了就直接走快速失败的逻辑返回。

  • ⑤ 检查线程池(线程池隔离)或队列(信号量隔离)是否已满,满了就直接走快速失败的逻辑返回。

  • ⑥ 执行请求调用远程服务,若是执行执行失败或超时,就走降级逻辑返回。不然成功返回数据。

  • ⑦ 每次执行请求时,线程池是否拒绝、执行是否失败、超时等,都会进行统计,而后计算是否打开断路器。

    • 当某个接口的失败次数超过设定的阈值时,Hystrix 断定该 API 接口出现了故障,打开熔断器,这时请求该 API 接口会执行快速失败的逻辑,不执行业务逻辑,请求的线程不会处于阻塞状态。
    • 处于打开状态的熔断器,一段时间后会处于半打开状态,并放行必定数量的请求执行正常逻辑。剩余的请求会执行快速失败。
    • 若执行正常逻辑的请求失败了,则熔断器继续打开;若成功了,则将熔断器关闭。这样熔断器就具备了自我修复的能力。
  • ⑧ 执行快速失败的逻辑,也就是降级逻辑,若是降级逻辑执行成功,则返回;若是失败须要自行处理,或抛出异常。

HystrixCommand

关于 HystrixCommand 或者 HystrixObservableCommand 的用法官方文档【How To Use】已经介绍得很详细了,这里就跟着官方文档中提供的例子来了解下 HystrixCommand 的使用方式以及 Hystrix 的特性、配置等。

构建 HystrixCommand

一、构建 HystrixCommand

继承 HystrixCommand,将业务逻辑封装到 HystrixCommand 的 run() 方法中,在 getFallback() 方法中实现错误回调的逻辑。

class CommandHello extends HystrixCommand<String> {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    private final String name;
    private final long timeout;

    protected CommandHello(String group, String name, long timeout) {
        // 指定命令的分組,同一组使用同一个线程池
        super(HystrixCommandGroupKey.Factory.asKey(group));
        this.name = name;
        this.timeout = timeout;
    }

    // 要封装的业务请求
    @Override
    protected String run() throws Exception {
        logger.info("hystrix command execute");
        if (name == null) {
            throw new RuntimeException("data exception");
        }
        Thread.sleep(timeout); // 休眠
        return "hello " + name;
    }

    // 快速失败的降级逻辑
    @Override
    protected String getFallback() {
        logger.info("return fallback data");
        return "error";
    }
}
复制代码

二、构建 HystrixObservableCommand

继承 HystrixObservableCommand,将业务逻辑封装到 HystrixObservableCommand 的 construct() 方法中,在 resumeWithFallback() 方法中实现错误回调的逻辑。

class CommandObservableHello extends HystrixObservableCommand<String> {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    protected CommandObservableHello(String group) {
        // 指定命令的分組,同一组使用同一个线程池
        super(HystrixCommandGroupKey.Factory.asKey(group));
    }

    @Override
    protected Observable<String> construct() {
        logger.info("hystrix command execute");
        return Observable.create(new Observable.OnSubscribe<String>() {
            @Override
            public void call(Subscriber<? super String> subscriber) {
                // 发送多条数据
                subscriber.onNext("hello world");
                subscriber.onNext("hello hystrix");
                subscriber.onNext("hello command");
                subscriber.onCompleted();
            }
        });
    }

    // 快速失败的降级逻辑
    @Override
    protected Observable<String> resumeWithFallback() {
        logger.info("return fallback data");
        return Observable.just("error");
    }
}
复制代码

执行 HystrixCommand

一、执行请求的方法

每次执行 HystrixCommand 都须要从新建立一个 HystrixCommand 命令,而后调用 execute()queue()observe()toObservable() 中的一个方法执行请求。HystrixCommand 能够调用四个方法,HystrixObservableCommand 只能调用 observe()、toObservable() 两个方法。

四个方法的区别在于:

  • execute():同步调用,直到返回单条结果,或者抛出异常
  • queue():异步调用,返回一个 Future,能够异步的作其它事情,后面能够经过 Future 获取单条结果
  • observe():返回一个订阅对象 Observable,当即执行。能够经过 Observable.toBlocking() 执行同步请求。
  • toObservable():返回一个 Observable,只有订阅后才会执行

进入 execute() 方法能够发现,execute() 调用了 queue().get();再进入 queue() 方法能够发现,queue() 实际又调用了 toObservable().toBlocking().toFuture(),也就是说,不管是哪一种方式执行command,最终都是依赖 toObservable() 去执行的。

二、HystrixCommand 执行请求

能够看到 HystrixCommand 中的业务执行是在一个单独的线程中执行的,线程名称为 hystrix-ExampleGroup-1,这就实现了资源的隔离了。

/** * 运行结果: * * 14:56:03.699 [hystrix-ExampleGroup-1] INFO com.lyyzoo.hystrix.CommandHello - hystrix command execute * 14:56:04.206 [main] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - execute result is: hello hystrix */
@Test
public void test_HystrixCommand_execute() {
    CommandHello command = new CommandHello("ExampleGroup", "hystrix", 500);
    // 同步执行
    String result = command.execute();
    logger.info("execute result is: {}", result);
}

/** * 运行结果: * * 14:56:19.269 [main] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - do something... * 14:56:19.279 [hystrix-ExampleGroup-1] INFO com.lyyzoo.hystrix.CommandHello - hystrix command execute * 14:56:19.785 [main] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - queue result is: hello hystrix */
@Test
public void test_HystrixCommand_queue() throws Exception {
    CommandHello command = new CommandHello("ExampleGroup", "hystrix", 500);
    // 异步执行,返回 Future
    Future<String> future = command.queue();
    logger.info("do something...");
    logger.info("queue result is: {}", future.get());
}

/** * 运行结果 * * 14:59:56.748 [hystrix-ExampleGroup-1] INFO com.lyyzoo.hystrix.CommandHello - hystrix command execute * 14:59:57.252 [main] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - observe result is: hello hystrix */
@Test
public void test_HystrixCommand_observe_single() {
    CommandHello command = new CommandHello("ExampleGroup", "hystrix", 500);
    Observable<String> observable = command.observe();
    // 获取请求结果,toBlocking() 是为了同步执行,不加 toBlocking() 就是异步执行
    String result = observable.toBlocking().single();
    logger.info("observe result is: {}", result);
}

/** * 运行结果: * * 15:00:58.921 [hystrix-ExampleGroup-1] INFO com.lyyzoo.hystrix.CommandHello - hystrix command execute * 15:00:59.424 [main] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - subscribe result is: hello hystrix * 15:00:59.425 [main] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - completed */
@Test
public void test_HystrixCommand_observe_subscribe() {
    CommandHello command = new CommandHello("ExampleGroup", "hystrix", 500);
    Observable<String> observable = command.observe();
    // 订阅结果处理
    observable.toBlocking().subscribe(new Subscriber<String>() {
        @Override
        public void onCompleted() {
            logger.info("completed");
        }

        @Override
        public void onError(Throwable e) {
            logger.info("error", e);
        }

        @Override
        public void onNext(String s) {
            logger.info("subscribe result is: {}", s);
        }
    });
}
复制代码

三、HystrixObservableCommand 执行请求

HystrixObservableCommand 与 HystrixCommand 是相似的,区别在于 HystrixObservableCommand 的业务逻辑是封装到 construct() 方法中,且 HystrixObservableCommand 只能调用 observe()toObservable() 两个方法执行命令。

/** * 运行结果: * * 15:22:49.306 [main] INFO com.lyyzoo.hystrix.CommandObservableHello - hystrix command execute * 15:22:49.316 [main] INFO com.lyyzoo.hystrix.Demo02_HystrixObservableCommand - last result: hello command */
@Test
public void test_HystrixObservableCommand_observe() {
    CommandObservableHello command = new CommandObservableHello("ExampleGroup");

    String result = command.observe().toBlocking().last();
    logger.info("last result: {}", result);
}

/** * 运行结果: * * 15:23:08.685 [main] INFO com.lyyzoo.hystrix.CommandObservableHello - hystrix command execute * 15:23:08.691 [main] INFO com.lyyzoo.hystrix.Demo02_HystrixObservableCommand - result data: hello world * 15:23:08.691 [main] INFO com.lyyzoo.hystrix.Demo02_HystrixObservableCommand - result data: hello hystrix * 15:23:08.691 [main] INFO com.lyyzoo.hystrix.Demo02_HystrixObservableCommand - result data: hello command * 15:23:08.691 [main] INFO com.lyyzoo.hystrix.Demo02_HystrixObservableCommand - completed */
@Test
public void test_HystrixObservableCommand_observe_subscribe() {
    CommandObservableHello command = new CommandObservableHello("ExampleGroup");

    command.observe().subscribe(new Observer<String>() {
        @Override
        public void onCompleted() {
            logger.info("completed");
        }

        @Override
        public void onError(Throwable e) {
            logger.info("error");
        }

        @Override
        public void onNext(String o) {
            logger.info("result data: {}", o);
        }
    });
}
复制代码

容错和降级回调

一、容错模式

HystrixCommand 执行有两种容错模式,fail-fastfail-silent

  • fail-fast:不给fallback降级逻辑,run() 方法报错后直接抛异常,抛出到主工做线程。
  • fail-silent:给一个fallback降级逻辑,run() 报错了会走fallback降级。

基本不会用 fail-fast 模式,通常来讲都会实现降级逻辑,在抛出异常后作一些兜底的事情。

二、实现降级方法

HystrixCommand 能够经过 getFallback() 方法返回降级的数据,HystrixObservableCommand 能够经过 resumeWithFallback() 返回降级的数据。当快速失败、超时、断路器打开的时候,就能够进入降级回调方法中,例如从本地缓存直接返回数据,避免业务异常、阻塞等状况。

经过文档能够了解到,有5中状况会进入降级回调方法中,分别是 业务异常、业务超时、断路器打开、线程池拒绝、信号量拒绝

三、Examples:

/** * 运行结果: * * 14:44:43.199 [hystrix-ExampleGroup-1] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - hystrix command execute * 14:44:43.203 [hystrix-ExampleGroup-1] DEBUG com.netflix.hystrix.AbstractCommand - Error executing HystrixCommand.run(). Proceeding to fallback logic ... * java.lang.RuntimeException: data exception * at com.lyyzoo.hystrix.Demo01_HystrixCommand$CommandHello.run(Demo01_HystrixCommand.java:75) * at com.lyyzoo.hystrix.Demo01_HystrixCommand$CommandHello.run(Demo01_HystrixCommand.java:61) * at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:302) * ........ * 14:44:43.210 [hystrix-ExampleGroup-1] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - return fallback data * 14:44:43.214 [main] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - result is: error */
@Test
public void test_HystrixCommand_exception_fallback() {
    CommandHello command = new CommandHello("ExampleGroup", null, 500);
    // 抛出异常,返回降级逻辑中的数据
    String result = command.execute();
    logger.info("result is: {}", result);
}

/** * 运行结果: * * 14:51:27.114 [hystrix-ExampleGroup-1] INFO com.lyyzoo.hystrix.CommandHello - hystrix command execute * 14:51:28.113 [HystrixTimer-1] INFO com.lyyzoo.hystrix.CommandHello - return fallback data * 14:51:28.119 [main] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - result is: error */
@Test
public void test_HystrixCommand_timeout_fallback() {
    CommandHello command = new CommandHello("ExampleGroup", "hystrix", 1500);
    // 请求超时,返回降级逻辑中的数据
    String result = command.execute();
    logger.info("result is: {}", result);
}
复制代码

四、多级降级

多级降级其实就是在降级逻辑中再嵌套一个 command,command 嵌套 command 的形式。好比先从 MySQL 获取数据,抛出异常时,降级从 redis 获取数据。另外,不一样的降级策略建议使用不一样的线程池,由于若是 command 调用远程服务时耗尽了线程池,降级 command 使用一样的线程池时就会被拒绝。

Examples:

能够看到,首先调用 CommandMySQL,run() 方法正常时就直接返回结果,抛出异常后就进入降级方法中,在降级方法中又用 CommandRedis 执行请求。

public class Demo03_HystrixCommand_MultiFallback {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    /** * 请求结果: * 22:28:46.313 [hystrix-MySqlPool-1] INFO com.lyyzoo.hystrix.CommandMySQL - get data from mysql * 22:28:46.319 [main] INFO com.lyyzoo.hystrix.Demo03_HystrixCommand_MultiFallback - result: mysql-number-1 * 22:28:46.320 [hystrix-MySqlPool-2] INFO com.lyyzoo.hystrix.CommandMySQL - get data from mysql * 22:28:46.324 [hystrix-MySqlPool-2] DEBUG com.netflix.hystrix.AbstractCommand - Error executing HystrixCommand.run(). Proceeding to fallback logic ... * java.lang.RuntimeException: data not found in mysql * at com.lyyzoo.hystrix.CommandMySQL.run(Demo03_HystrixCommand_MultiFallback.java:50) * at com.lyyzoo.hystrix.CommandMySQL.run(Demo03_HystrixCommand_MultiFallback.java:29) * at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:302) * ...... * 22:28:46.332 [hystrix-MySqlPool-2] INFO com.lyyzoo.hystrix.CommandMySQL - coming mysql fallback * 22:28:46.344 [hystrix-RedisPool-1] INFO com.lyyzoo.hystrix.CommandRedis - get data from redis * 22:28:46.344 [main] INFO com.lyyzoo.hystrix.Demo03_HystrixCommand_MultiFallback - result: redis-number-2 */
    @Test
    public void test_HystrixCommand_multi_fallback() {
        CommandMySQL command = new CommandMySQL("ExampleGroup", 1);
        logger.info("result: {}", command.execute());

        CommandMySQL command2 = new CommandMySQL("ExampleGroup", 2);
        logger.info("result: {}", command2.execute());
    }

}

class CommandMySQL extends HystrixCommand<String> {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    private final String group;
    private final Integer id;

    public CommandMySQL(String group, Integer id) {
        super(
            HystrixCommand.Setter
                .withGroupKey(HystrixCommandGroupKey.Factory.asKey(group))
                // 指定不一样的线程池
                .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("MySqlPool"))
        );
        this.group = group;
        this.id = id;
    }

    @Override
    protected String run() throws Exception {
        logger.info("get data from mysql");
        if (id % 2 == 0) {
            throw new RuntimeException("data not found in mysql");
        }
        return "mysql-number-" + id;
    }

    // 快速失败的降级逻辑
    @Override
    protected String getFallback() {
        logger.info("coming mysql fallback");
        // 嵌套 Command
        HystrixCommand<String> command = new CommandRedis(group, id);
        return command.execute();
    }
}

class CommandRedis extends HystrixCommand<String> {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    private final Integer id;

    public CommandRedis(String group, Integer id) {
        super(
            HystrixCommand.Setter
                .withGroupKey(HystrixCommandGroupKey.Factory.asKey(group))
                // 指定不一样的线程池
                .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("RedisPool"))
        );
        this.id = id;
    }

    @Override
    protected String run() throws Exception {
        logger.info("get data from redis");
        return "redis-number-" + id;
    }

    // 快速失败的降级逻辑
    @Override
    protected String getFallback() {
        logger.info("coming redis fallback");
        return "error";
    }
}
复制代码

HystrixCommand 维度划分

一、配置分组名称

默认状况下,就是经过 command group 来定义一个线程池,同一个 command group 中的请求,都会进入同一个线程池中,并且还会经过command group来聚合一些监控和报警信息。经过 HystrixCommandGroupKey.Factory.asKey(group) 指定组名。

class CommandHello extends HystrixCommand<String> {

    protected CommandHello(String group) {
        super(HystrixCommandGroupKey.Factory.asKey(group));
    }
}
复制代码

二、配置命令名称

command 名称不配置默认就是类名,能够经过 andCommandKey(HystrixCommandKey.Factory.asKey("CommandName")) 配置。

例如 CommandHystrixConfig:

public CommandHystrixConfig() {
    super(
        Setter
            // 分组名称
            .withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
            // 命令名称,默认为类名 getClass().getSimpleName()
            //.andCommandKey(HystrixCommandKey.Factory.asKey("ExampleName"))
    );
}
复制代码

未配置名称时,默认就是类名:

将注释放开,就是指定的名称:

三、配置线程池名称

线程池名称默认是分组名称,若是不想用分组名称,能够手动设置线程池名称,配置了线程名称后,经过 .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("ExampleThread")) 配置。

public CommandHystrixConfig() {
    super(
        Setter
            // 分组名称
            .withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
            // 命令名称,默认为类名 getClass().getSimpleName()
            .andCommandKey(HystrixCommandKey.Factory.asKey("ExampleName"))
            // 线程名称
            .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("ExampleThread"))
    );
}
复制代码

四、线程池隔离划分

综上来看,一个 command 的执行划分为三个维度:command threadpool -> command group -> command key。

  • command key:通常对应到一个依赖服务的接口调用。
  • command group:通常对应到一个服务的全部接口,包含同一个服务的多个接口,便于对同一个服务的接口调用状况聚合统计。
  • command threadpool:command 执行的线程池,通常一个 group 对应一个 threadpool,但若是想细粒度拆分,command 能够指定不一样的线程池。

配置资源隔离策略

Hystrix 核心的一项功能就是资源隔离,要解决的最核心的问题,就是将多个依赖服务的调用分别隔离到各自本身的资源池内,避免对某一个依赖服务的调用,由于依赖服务的接口调用的延迟或者失败,致使服务全部的线程资源所有耗费在这个服务的接口调用上,一旦某个服务的线程资源所有耗尽,就可能致使服务崩溃,甚至故障会不断蔓延。

Hystrix 有线程池隔离信号量隔离两种资源隔离技术:

  • 线程池隔离:适合绝大多数的场景,依赖服务调用、网络请求,被调用方可能会不可用、超时等,用线程池隔离
  • 信号量隔离:适合不是对外部依赖的访问,而是对内部的一些比较复杂的业务逻辑的访问,不涉及任何的网络请求,只要作信号量的普通限流就能够了,在高并发的状况下作限流。

一、启用线程池隔离

public CommandHystrixConfig() {
    super(
        Setter
            // 分组名称
            .withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
            .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                    // 设置隔离策略
                    .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)
                    // 超时时间,默认 1000 毫秒
                    .withExecutionTimeoutInMilliseconds(1000)
                    // 是否启用降级策略
                    .withFallbackEnabled(true)
                    // 是否启用缓存
                    .withRequestCacheEnabled(true)
            )
    );
}
复制代码

二、启用信号量隔离

在设置 .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD) 时,只须要将 THREAD 改成 SEMAPHORE 便可,这个时候 run() 方法将在主线程中执行。

三、资源容量大小控制

  • withCoreSize(10):设置线程池的大小,默认是10,通常设置10个就足够了
  • withQueueSizeRejectionThreshold(5):command 在提交到线程池以前会先进入一个队列中,这个队列满了以后,才会reject,这个参数能够控制队列拒绝的阈值。
public CommandHystrixConfig() {
    super(
        Setter
            .withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
            .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("DemoPool"))
            .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                    // 设置隔离策略
                    .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)
                    // 超时时间,默认 1000 毫秒
                    .withExecutionTimeoutInMilliseconds(1000)
            )
            .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
                    // 核心线程池大小,默认10个
                    .withCoreSize(10)
                    // 最大线程数,默认10个
                    .withMaximumSize(10)
                    // 队列数量达到多少后拒绝请求
                    .withQueueSizeRejectionThreshold(5)
            )
    );
}
复制代码
  • withExecutionIsolationSemaphoreMaxConcurrentRequests(10):信号量隔离时,设置信号量并发数,与线程池大小的设置相似。
public CommandHystrixConfig() {
    super(
        Setter
            .withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
            .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("DemoPool"))
            .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                    // 设置隔离策略
                    .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)
                    // 信号量隔离时最大并发量,默认10
                    .withExecutionIsolationSemaphoreMaxConcurrentRequests(10)
            )
    );
}
复制代码

开启请求缓存

在一次请求上下文中,可能会建立多个 command 去执行远程调用,若是调用的参数同样、接口同样,hystrix 支持缓存来减小执行一样的 command,从而提高性能。

一、开启缓存

开启缓存只须要在 Command 中实现 getCacheKey() 方法返回缓存的 key 就能够开启缓存了。

// 指定缓存的key
@Override
protected String getCacheKey() {
    return name;
}
复制代码

二、Examples

Hystrix 的缓存须要本身管理缓存上下文,须要本身初始化缓存上下文 HystrixRequestContext.initializeContext(),请求结束后须要关闭缓存上下文 HystrixRequestContext.getContextForCurrentThread().shutdown()。通常在web应用中,能够经过增长前置过滤器来开启 Hystrix 请求上下文,增长后置过滤器来关闭 Hystrix 上下文。

从验证结果来看,最后两个 command 执行时,因为缓存已经存在一样的 key 了,就没有进入 run 方法了,并且能够验证就算是不一样组,只要key相同就会被缓存。

/** * 运行结果: * * 20:55:43.759 [hystrix-ExampleGroup-1] INFO com.lyyzoo.hystrix.CommandHello - hystrix command execute * 20:55:43.878 [main] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - result: hello hystrix * 20:55:43.879 [hystrix-ExampleGroup-2] INFO com.lyyzoo.hystrix.CommandHello - hystrix command execute * 20:55:43.985 [main] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - result: hello ribbon * 20:55:43.989 [main] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - result: hello hystrix * 20:55:43.989 [main] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - result: hello hystrix */
@Test
public void test_HystrixCommand_cache() {
    // 先初始化上下文
    HystrixRequestContext context = HystrixRequestContext.initializeContext();

    try {
        CommandHello command1 = new CommandHello("ExampleGroup", "hystrix", 100);
        CommandHello command2 = new CommandHello("ExampleGroup", "ribbon", 100);
        CommandHello command3 = new CommandHello("ExampleGroup", "hystrix", 100);
        CommandHello command4 = new CommandHello("ExampleGroupTwo", "hystrix", 100);

        logger.info("result: {}", command1.execute());
        logger.info("result: {}", command2.execute());
        logger.info("result: {}", command3.execute());
        logger.info("result: {}", command4.execute());
    } finally {
        //HystrixRequestContext.getContextForCurrentThread().shutdown();
        context.shutdown();
    }
}
复制代码

三、手动清除缓存

当数据更新时,咱们指望可以手动清理掉缓存,能够经过以下方式清理指定 command 的缓存。

public static void flushCache(String key) {
    HystrixRequestCache.getInstance(HystrixCommandKey.Factory.asKey(CommandHello.class.getSimpleName()),
            HystrixConcurrencyStrategyDefault.getInstance()).clear(key);
}
复制代码

开启断路器

一、Hystrix 断路器的基本原理以下

  • 首先请求达到一个阈值后,才会判断是否开启断路器,经过 HystrixCommandProperties.circuitBreakerRequestVolumeThreshold() 设置,默认为 20。
  • 超过阈值后,判断错误比率是否超过阈值,经过 HystrixCommandProperties.circuitBreakerErrorThresholdPercentage() 设置,默认为 50%。
  • 超过 50% 后就会打开断路器,从 CLOSE 状态变为 OPEN
  • 当断路器打开了以后,全部的请求都会被拒绝,直接进入降级逻辑。
  • 休眠一段时间后,经过 HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds() 设置,默认 5000毫秒;会进入半打开(half-open)状态,放一个请求过去,若是这个请求仍是失败,断路器就继续维持打开的状态;若是请求成功,就关闭断路器,自动恢复,转到 CLOSE 状态。

二、下面用一个 Demo 简单测试下

断路器配置以下:

// 超时时间1000毫秒
.withExecutionTimeoutInMilliseconds(1000)
// 启用断路器
.withCircuitBreakerEnabled(true)
// 限流阈值,超过这个值后才会去判断是否限流,默认20
.withCircuitBreakerRequestVolumeThreshold(4)
// 请求失败百分比阈值,默认50
.withCircuitBreakerErrorThresholdPercentage(50)
// 断路器打开后休眠多久,默认5000毫秒
.withCircuitBreakerSleepWindowInMilliseconds(5000)
复制代码

Examples:

经过测试能够发现,请求阈值设置为4,第五个请求会继续执行,不过也失败了,这个时候就会判断失败比率超过50%了,断路器打开,以后的全部请求就直接进入降级;休眠5秒后,进入半打开状态,放一个请求过去,仍是失败,断路器继续打开。

public class Demo05_HystrixCommand_CircuitBreaker {
    @Test
    public void test_CircuitBreaker() throws InterruptedException {
        for (int i = 1; i <= 10; i++) {
            CommandCircuitBreaker command = new CommandCircuitBreaker(1500L, i);
            command.execute();
            if (i == 7) {
                Thread.sleep(5000); // 休眠5秒
            }
        }
    }
}

/** * 运行结果: * * 10:53:30.910 [hystrix-ExampleGroup-1] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [1] execute command * 10:53:31.920 [HystrixTimer-1] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [1] execute fallback * 10:53:31.925 [hystrix-ExampleGroup-2] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [2] execute command * 10:53:32.936 [HystrixTimer-1] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [2] execute fallback * 10:53:32.937 [hystrix-ExampleGroup-3] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [3] execute command * 10:53:33.940 [HystrixTimer-2] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [3] execute fallback * 10:53:33.942 [hystrix-ExampleGroup-4] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [4] execute command * 10:53:34.950 [HystrixTimer-1] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [4] execute fallback * 10:53:34.952 [hystrix-ExampleGroup-5] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [5] execute command * 10:53:35.962 [HystrixTimer-3] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [5] execute fallback * 10:53:35.964 [main] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [6] execute fallback * 10:53:35.965 [main] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [7] execute fallback * 10:53:40.977 [hystrix-ExampleGroup-6] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [8] execute command * 10:53:41.986 [HystrixTimer-2] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [8] execute fallback * 10:53:41.988 [main] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [9] execute fallback * 10:53:41.989 [main] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [10] execute fallback */
class CommandCircuitBreaker extends HystrixCommand<String> {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    private final Long timeoutMill;
    private final Integer id;

    protected CommandCircuitBreaker(Long timeoutMill, Integer id) {
        super(
            Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                    // 超时时间1000毫秒
                    .withExecutionTimeoutInMilliseconds(1000)
                    // 启用断路器
                    .withCircuitBreakerEnabled(true)
                    // 限流阈值,超过这个值后才会去判断是否限流,默认20
                    .withCircuitBreakerRequestVolumeThreshold(4)
                    // 请求失败百分比阈值,默认50
                    .withCircuitBreakerErrorThresholdPercentage(50)
                    // 断路器打开后休眠多久,默认5000毫秒
                    .withCircuitBreakerSleepWindowInMilliseconds(5000)
                )
        );
        this.timeoutMill = timeoutMill;
        this.id = id;
    }

    @Override
    protected String run() throws Exception {
        logger.info("[{}] execute command", id);
        if (timeoutMill != null) {
            Thread.sleep(timeoutMill);
        }
        return "number-" + id;
    }

    @Override
    protected String getFallback() {
        logger.info("[{}] execute fallback", id);
        return "error-" + id;
    }
}
复制代码

基于注解的方式使用 HystrixCommand

在 springboot 程序中,还能够在组件方法上使用 @HystrixCommand 等注解将方法调用封装到 hystrix 中去执行。同时还须要在启动类上加上 @EnableHystrix 来启用 Hystrix 的功能。

@Component
public class ProducerService {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @HystrixCommand( groupKey = "ExampleGroup", commandKey = "demo-producer", threadPoolKey = "ExamplePool", fallbackMethod = "queryFallback" )
    public String query(Integer id) {
        logger.info("execute query");
        if (id % 2 == 0) {
            throw new RuntimeException("execution error");
        }
        return "number-" + id;
    }

    // 回调方法参数要和原方法参数一致
    public String queryFallback(Integer id) {
        return "error-" + id;
    }
}
复制代码

测试结果:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ConsumerApplication.class)
public class ProducerServiceTest {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private ProducerService producerService;

    /** * 执行结果: * * INFO 10:49:13 [hystrix-ExamplePool-1] c.l.s.r.h.ProducerService.query 25 :execute query * INFO 10:49:13 [main] c.l.h.ProducerServiceTest.test_query 29 :result: number-1 * INFO 10:49:13 [hystrix-ExamplePool-2] c.l.s.r.h.ProducerService.query 25 :execute query * INFO 10:49:13 [main] c.l.h.ProducerServiceTest.test_query 30 :result: error-2 */
    @Test
    public void test_query() {
        logger.info("result: {}", producerService.query(1));
        logger.info("result: {}", producerService.query(2));
    }
}
复制代码
相关文章
相关标签/搜索