熔断Hystrix使用尝鲜

熔断Hystrix使用尝鲜

当服务有较多外部依赖时,若是其中某个服务的不可用,致使整个集群会受到影响(好比超时,致使大量的请求被阻塞,从而致使外部请求没法进来),这种状况下采用hystrix就颇有用了java

出于这个目的,了解了下hystrix框架,下面记录下,框架尝新的历程git

I. 原理探究

经过官网和相关博文,能够简单的说一下这个工做机制,大体流程以下github

首先是请求过来 -> 判断熔断器是否开 -> 服务调用 -> 异常则走fallback,失败计数+1 -> 结束redis

下面是主流程图多线程

流程图

graph LR
    A(请求)-->B{熔断器是否已开}
    B --> | 熔断 | D[fallback逻辑]
    B --> | 未熔断 | E[线程池/Semphore]
    E --> F{线程池满/无可用信号量}
    F --> | yes | D
    F --> | no | G{建立线程执行/本线程运行}
    G --> | yes | I(结束)
    G --> | no | D
    D --> I(结束)

熔断机制主要提供了两种,一个是基于线程池的隔离方式来作;还有一个则是根据信号量的抢占来作hexo

线程池方式 : 支持异步,支持超时设置,支持限流app

信号量方式 : 本线程执行,无异步,无超时,支持限流,消耗更小框架

基本上有上面这个简单的概念以后,开始进入咱们的使用测试流程异步

II. 使用尝鲜

1. 引入依赖

<dependency>
    <groupId>com.netflix.hystrix</groupId>
    <artifactId>hystrix-core</artifactId>
    <version>1.5.12</version>
</dependency>

2. 简单使用

从官方文档来看,支持两种Command方式,一个是基于观察者模式的ObserverCommand, 一个是基本的Command,先用简单的看如下ide

public class HystrixConfigTest extends HystrixCommand<String> {

    private final String name;

    public HystrixConfigTest(String name, boolean ans) {
//        注意的是同一个任务,
        super(Setter.withGroupKey(
//                CommandGroup是每一个命令最少配置的必选参数,在不指定ThreadPoolKey的状况下,字面值用于对不一样依赖的线程池/信号区分
                HystrixCommandGroupKey.Factory.asKey("CircuitBreakerTestGroup"))
//                每一个CommandKey表明一个依赖抽象,相同的依赖要使用相同的CommandKey名称。依赖隔离的根本就是对相同CommandKey的依赖作隔离.
                        .andCommandKey(HystrixCommandKey.Factory.asKey("CircuitBreakerTestKey_" + ans))
//                当对同一业务依赖作隔离时使用CommandGroup作区分,可是对同一依赖的不一样远程调用如(一个是redis 一个是http),可使用HystrixThreadPoolKey作隔离区分
                        .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("CircuitBreakerTest_" + ans))
                        .andThreadPoolPropertiesDefaults(    // 配置线程池
                                HystrixThreadPoolProperties.Setter()
                                        .withCoreSize(12)    // 配置线程池里的线程数,设置足够多线程,以防未熔断却打满threadpool
                        )
                        .andCommandPropertiesDefaults(    // 配置熔断器
                                HystrixCommandProperties.Setter()
                                        .withCircuitBreakerEnabled(true)
                                        .withCircuitBreakerRequestVolumeThreshold(3)
                                        .withCircuitBreakerErrorThresholdPercentage(80)
//                		.withCircuitBreakerForceOpen(true)	// 置为true时,全部请求都将被拒绝,直接到fallback
//                		.withCircuitBreakerForceClosed(true)	// 置为true时,将忽略错误
//                                        .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)    // 信号量隔离
                                        .withExecutionIsolationSemaphoreMaxConcurrentRequests(20)
                                        .withExecutionTimeoutEnabled(true)
                                        .withExecutionTimeoutInMilliseconds(200)
                                .withCircuitBreakerSleepWindowInMilliseconds(1000) //熔断器打开到关闭的时间窗长度
//                		.withExecutionTimeoutInMilliseconds(5000)
                        )
        );
        this.name = name;
    }

    @Override
    protected String run() throws Exception {
        System.out.println("running run():" + name + " thread: " + Thread.currentThread().getName());
        int num = Integer.valueOf(name);
        if (num % 2 == 0 && num < 10) {    // 直接返回
            return name;
        } else if (num < 40) {
            Thread.sleep(300);
            return "sleep+"+ name;
        } else {    // 无限循环模拟超时
            return name;
        }
    }
//
//    @Override
//    protected String getFallback() {
//        Throwable t = this.getExecutionException();
//        if(t instanceof HystrixRuntimeException) {
//            System.out.println(Thread.currentThread() + " --> " + ((HystrixRuntimeException) t).getFailureType());
//        } else if (t instanceof HystrixTimeoutException) {
//            System.out.println(t.getCause());
//        } else {
//            t.printStackTrace();
//        }
//        System.out.println(Thread.currentThread() + " --> ----------over------------");
//        return "CircuitBreaker fallback: " + name;
//    }

    public static class UnitTest {

        @Test
        public void testSynchronous() throws IOException, InterruptedException {
            for (int i = 0; i < 50; i++) {
                if (i == 41) {
                    Thread.sleep(2000);
                }
                try {
                    System.out.println("===========" + new HystrixConfigTest(String.valueOf(i), i % 2 == 0).execute());
                } catch (HystrixRuntimeException e) {
                    System.out.println(i + " : " + e.getFailureType() + " >>>> " + e.getCause() + " <<<<<");
                } catch (Exception e) {
                    System.out.println("run()抛出HystrixBadRequestException时,被捕获到这里" + e.getCause());
                }
            }

            System.out.println("------开始打印现有线程---------");
            Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
            for (Thread thread : map.keySet()) {
                System.out.println("--->name-->" + thread.getName());
            }
            System.out.println("thread num: " + map.size());

            System.in.read();
        }
    }
}

使用起来仍是比较简单的,通常步骤以下:

  • 继承 HsytrixCommand
  • 重载构造方法,内部须要指定各类配置
  • 实现run方法,这个里面主要执行熔断监控的方法

写上面的代码比较简单,可是有几个地方不太好处理

  1. 配置项的具体含义,又是怎么生效的?
  2. 某些异常不进入熔断逻辑怎么办?
  3. 监控数据如何获取?
  4. 如何模拟各类不一样的case(超时?服务异常?熔断已开启?线程池满?无可用信号量?半熔断的重试?)

3. 实测理解

根据上面那一段代码的删删改改,貌似理解了如下几个点,不知道对误

a. 配置相关

  • groupKey 用于区分线程池和信号量,即一个group对应一个
  • commandKey 很重要,这个是用于区分业务
    • 简单来说,group相似提供服务的app,command则对应app提供的service,一个app能够有多个service,这里就是将一个app的全部请求都放在一个线程池(or共享一个信号量)
  • 开启熔断机制,指定触发熔断的最小请求数(10s内),指定打开熔断的条件(失败率)
  • 设置熔断策略(线程池or信号量)
  • 设置重试时间(默认熔断开启后5s,放几个请求进去,看服务是否恢复)
  • 设置线程池大小,设置信号量大小,设置队列大小
  • 设置超时时间,设置容许超时设置

b. 使用相关

run方法是核心执行服务调用,若是须要某些服务不统计到熔断的失败率(好比由于调用姿式不对致使服务内部的异常抛上来了,可是服务自己是正常的),这个时候,就须要包装下调用逻辑,将不须要的异常包装到 HystrixBadRequestException 类里

@Override
protected String run() {
    try {
        return func.apply(route, parameterDescs);
    } catch (Exception e) {
        if (exceptionExcept(e)) {
            // 若是是不关注的异常case, 不进入熔断逻辑
            throw new HystrixBadRequestException("unexpected exception!", e);
        } else {
            throw e;
        }
    }
}

c. 如何获取失败的缘由

当发生失败时,hystrix会把原生的异常包装到 HystrixRuntimeException 这个类里,因此咱们能够在调用的地方以下处理

try {
    System.out.println("===========" + new HystrixConfigTest(String.valueOf(i), i % 2 == 0).execute());
} catch (HystrixRuntimeException e) {
    System.out.println(i + " : " + e.getFailureType() + " >>>> " + e.getCause() + " <<<<<");
} catch (Exception e) {
    System.out.println("run()抛出HystrixBadRequestException时,被捕获到这里" + e.getCause());
}

当定义了fallback逻辑时,异常则不会抛到具体的调用方,因此在 fallback 方法内,则有必要获取对应的异常信息

// 获取异常信息
Throwable t = this.getExecutionException();

而后下一步就是须要获取对应的异常缘由了,经过FailureType来代表失败的根源

((HystrixRuntimeException) t).getFailureType()

d.如何获取统计信息

hystrix本身提供了一套监控插件,基本上你们都会有本身的监控统计信息,所以须要对这个数据进行和自定义,目前还没想到能够如何优雅的处理这些统计信息

4. 小结

主要是看了下这个东西能够怎么玩,整个用下来的感受就是,设计的比较有意思,可是配置参数太多,不少都没有彻底摸透

其次就是一些特殊的case(如监控,报警,特殊状况过滤)须要处理时,用起来并非很顺手,主要问题仍是没有理解清楚这个框架的内部工做机制的问题

III. 其余

我的博客: Z+|blog

基于hexo + github pages搭建的我的博客,记录全部学习和工做中的博文,欢迎你们前去逛逛

声明

尽信书则不如,以上内容,纯属一家之言,因本人能力通常,见识有限,如发现bug或者有更好的建议,随时欢迎批评指正

扫描关注

QrCode

相关文章
相关标签/搜索