熔断Hystrix使用尝鲜

熔断Hystrix使用尝鲜

当服务有较多外部依赖时,如果其中某个服务的不可用,导致整个集群会受到影响(比如超时,导致大量的请求被阻塞,从而导致外部请求无法进来),这种情况下采用hystrix就很有用了

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

I. 原理探究

通过官网和相关博文,可以简单的说一下这个工作机制,大致流程如下

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

下面是主流程图

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(结束)

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

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

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

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

II. 使用尝鲜

1. 引入依赖

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

2. 简单使用

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

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或者有更好的建议,随时欢迎批评指正

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏阿杜的世界

聊聊单元测试

单元测试(unit testing):是指对软件中的最小可测试单元进行检查和验证。

1071
来自专栏JAVA高级架构

Java面试分享(题目+答案)

2473
来自专栏Java技术栈

2018年不能错过的 14 个 Java 库!

3541
来自专栏吴伟祥

Settings -> Plugins 原

Free Mybatis Plugins    (*mapper.java-- *mapper.xml)

782
来自专栏从零开始学自动化测试

appium+python自动化60-appium命令行参数

许多Appium 1.5服务器参数已被弃用,以支持—default-capabilities标志。

2141
来自专栏JAVA同学会

Zookeeper应用之——栅栏(barrier)

barrier的作用是所有的线程等待,知道某一时刻,锁释放,所有的线程同时执行。举一个生动的例子,比如跑步比赛,所有

963
来自专栏Java Web

Java 面试知识点解析(七)——Web篇

在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Jav...

36614
来自专栏邹立巍的专栏

Linux进程间通信:共享内存 (下)

使用文件或管道进行进程间通信会有很多局限性,比如效率问题以及数据处理使用文件描述符而不如内存地址访问方便,于是多个进程以共享内存的方式进行通信就成了很自然要实现...

8420
来自专栏Java学习网

Java开发技术之Spring依赖注入知识学习

不管是构造器、Setter方法还是其他的方法,Spring都会尝试满足方法参数上所声明的依赖。假如有且只有一个bean匹配依赖需求的话,那么这个bean将会被装...

1042
来自专栏ImportSource

刨根问底synchronized | 锁系列-Java中的锁

铺垫 在Java SE 1.5之前,多线程并发中,synchronized一直都是一个元老级关键字,而且给人的一贯印象就是一个比较重的锁。 为此,在Java ...

4305

扫码关注云+社区

领取腾讯云代金券