Spring Cloud高可用之熔断器 - Hystrix

一、Hystrix高层示意图

1、调用“Main”方法

2、业务验证

1)判断有没有缓存

2)判断熔断有没有开启

3)判断限流有没有触发

4)判断业务执行有没有失败

5)判断业务执行有没有超时

6)所有的失败都会触发fallback

3、业务直接返回

二、Hystrix简介

1、Hystrix是用于处理延迟和容错的开源库

2、Hystrix主要用于避免级联故障,提高系统弹性

3、Hystrix解决了由于扇出导致的“雪崩效应”

4、Hystrix的核心是“隔离术”和“熔断机制”

三、Hystrix主要作用

1、服务隔离和服务熔断

2、服务降级、限流和快速失败

3、请求合并和请求缓存

4、自带单体和集群监控

四、Hystrix两种命令模式区别

1、HystrixCommand会以隔离的形式完成run方法调用(默认线程池隔离)

2、HystrixObservableCommand使用当前线程进行调用(默认信号量隔离)

五、Hystrix四种执行方式区别

Command:

1、单次处理

1)execute:同步执行

2)queue:异步执行

2、多次处理

1)observe(Hot处理):

执行Command的run方法;

加载/注册Subscriber对象;

将run方法结果注入到Subscriber对象的onNext方法

2)toObserve(Cold处理:每次订阅都需要一个新的Command对象)

加载/注册Subscriber对象;

执行Command的run方法;

将run方法结果注入到Subscriber对象的onNext方法

六、GroupKey和CommandKey

Hystrix完整配置列表:Hystrix完整配置列表 - throwable - 博客园

配置文件中配置

#全局配置示例
hystrix:
  threadpool:
    default:
      coreSize: 1000 ##并发执行的最大线程数,默认10
      maxQueueSize: 1000 ##BlockingQueue的最大队列数
      queueSizeRejectionThreshold: 500 ##即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 10000

编程式配置:可以继承HystrixCommand或者HystrixObservableCommand对Hystrix进行配置(如果不做修改使用的是默认配置):

public Command() {
    super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("Group Key"))
                .andCommandKey(HystrixCommandKey.Factory.asKey("Command Key")));
}

1、Hystrix配置之GroupKey

例如同一个请求,请求多次,可能需要统计和监控,所以需要做一个手动分组,这个分组指的就是GroupKey,指定同一个GroupKey就是指定同一个分组

如果配置了GroupKey,那么具有相同的GroupKey的使用同一个线程池

1)Hystrix中GroupKey是唯一必填项

2)GroupKey可以起到分组监控和报警的作用

3)GroupKey将作为线程池的默认名称

2、Hystrix配置之CommandKey

1)Hystrix可以不填写CommandKey

2)默认Hystrix会通过反射类名命名CommandKey

3)在Setting中加入andCommandKey进行命名

七、Hystrix请求特性

1、请求缓存

1)Hystrix支持将请求结果进行本地缓存

2)通过重写实现getCacheKey方法来判断是否取出缓存

3)请求缓存要求请求必须在同一个上下文

4)可以用RequestCacheEnabled开启请求缓存

2、请求合并

优化点:多个服务调用的多次HTTP请求合并

缺点:很少有机会对同一个服务进行多次HTTP调用,同时还要足够的“近”

1)Hystrix支持将多个请求合并成一次请求

2)Hystrix请求合并要求两次请求必须足够“近”

3)请求合并分为局部合并和全局合并两种

4)Collapser可以设置相关参数

八、Hystrix隔离(具体配置见第六点)

1、隔离介绍

1)Hystrix提供了信号量和线程两种隔离手段

2)线程隔离会在单独的线程中执行业务逻辑

3)信号量隔离在调用线程上执行

4)官方推荐优先线程隔离

2、线程隔离

1)应用自身完全受保护,不会受其他依赖影响

2)有效降低接入新服务的风险

3)依赖服务出现问题,应用自身可以快速反应问题

4)可以通过实时刷新动态属性减少依赖问题影响

3、信号量隔离

1)信号量隔离是轻量级的隔离术

2)无网络开销的情况推荐使用信号量隔离

3)信号量是通过计数器与请求线程比对进行限流的

4、Hystrix隔离之ThreadPoolKey

1)Hystrix可以不填写ThreadPoolKey

2)默认Hystrix会使用GroupKey命名线程池

3)在Setting中加入andThreadPoolKey进行命名

九、Hystrix降级处理

1、降级介绍

1)降级是一种“无奈”的选择

2)Command降级需要实现fallback方法

3)ObservableCommand降级实现resumeWithFallback方法

2、降级触发原则

1)HystrixBadRequestException以外的异常

2)运行超时或熔断器处于开启状态

3)线程池或信号量已满

3、快速失败

1)Hystrix提供了快速失败的机制

2)当不实现fallback方法时会将异常直接抛出

3、fallback

1)fallback方法与run方法是一一对应的关系

2)fallback中一般都是定义自己的业务异常

十、Hystrix熔断机制

hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: true    ##开启熔断器
        isolation:
          strategy: THREAD
          thread:
            timeoutInMilliseconds: 60000    ##超时时间,毫秒

1、熔断器介绍

熔断器是一种开关,用来控制流量是否执行业务逻辑

2、熔断器核心指标

1)快照时间窗(一个时间段)

2)请求总数阈值(核心时间窗内有多少请求总数)

3)错误百分比阈值(当达到请求总数阈值,同时错误内容达到一定比例,触发熔断开关)

3、熔断器状态

1)熔断器开启:所有请求都会进入fallback方法

2)熔断器半开启:间歇性让请求触发run方法

3)熔断器关闭:正常处理业务请求

默认情况瞎熔断器开启5秒后进入半开启状态

熔断器的计算是有耗时的,等待熔断健康检查

4、强制开启和关闭

如何设置线程池?

例:30rps * 0.2seconds = 6 + breathing room(为额外开销增加冗余量) = 10 threads

Threadpool Queue size:5 – 10(线程个数的1.5 – 2倍)

十一、关于Hystrix配置

Command基础配置:

配置

配置描述

execution.isolation.strategy

隔离类型:THREAD-线程隔离,SEMAPHORE-信号量隔离

execution.timeout.enabled

超时检查是否开启

fallback.enabled

是否开启降级处理

请求上下文配置:

配置

配置描述

requestCache.enabled

是否开启请求缓存,默认为true

requestLog.enabled

是否开启请求日志,默认为true

maxRequestsInBatch

设置批处理中允许的最大请求数

timerDelayInMilliseconds

设置批处理创建到执行之间的毫秒数

线程池相关配置:

配置

配置描述

coreSize

配置线程池大小,默认为10

keepAliveTimeMinutes

配置核心线程数空闲时keepAlived时长,默认1分钟

maxQueueSize

配置线程池任务队列大小,默认为-1

maximumSize

线程池中线程的最大数量,默认值是 10

queueSizeRejectionThreshold

任务队列的请求上线,默认值是10

allowMaximumSizeToDivergeFromCoreSize

是否开启最大线程数

execution.isolation.thread.timeoutInMilliseconds

设置超时时间

execution.isolation.thread.interruptOnTimeout

请求超时是否中断任务

execution.isolation.thread.interruptOnCancel

请求取消是否终端任务

信号量隔离配置:

配置

配置描述

execution.isolation.semaphore.maxConcurrentRequests

任务执行信号量最大数

fallback.isolation.semaphore.maxConcurrentRequests

失败任务执行信号量最大数

熔断机制相关配置:

配置

配置描述

circuitBreaker.enabled

是否开启熔断器

circuitBreaker.requestVolumeThreshold

启用熔断器功能窗口时间内的最小请求数

circuitBreaker.sleepWindowInMilliseconds

半熔断开启时间

circuitBreaker.errorThresholdPercentage

开启熔断的失败率阈值

circuitBreaker.forceOpen

强制开启熔断器

circuitBreaker.forceClosed

强制关闭熔断器

metrics相关配置:

配置

配置描述

metrics.rollingStats.timeInMilliseconds

此配置项指定了窗口的大小,单位是 ms,默认值是 1000

metrics.rollingStats.numBuckets

生成统计数据流时滑动窗口应该拆分的桶数

metrics.rollingPercentile.enabled

是否统计方法响应时间百分比,默认为 true

metrics.rollingPercentile.timeInMilliseconds

统计响应时间百分比时的窗口大小

metrics.rollingPercentile.numBuckets

统计响应时间百分比时滑动窗口要划分的桶用,默认为6

metrics.rollingPercentile.bucketSize

统计响应时间百分比时,每个滑动窗口的桶内要保留的请求数

metrics.healthSnapshot.intervalInMilliseconds

它指定了健康数据统计器中每个桶的大小,默认是 500ms

十二、Hystrix监控

1、添加hystrix-dashboard依赖

2、在配置文件里添加配置,暴露正常的可访问的安全的端口:

management:
    endpoints:
	    web:
		    exposure:
			    include:"*"

3、访问ip:port/hystrix

附CommandDemo和CommandTest

 1、CommandDemo:

@Data
public class CommandDemo extends HystrixCommand<String> {
    private String name;

    public CommandDemo(String name){
        super(Setter
                .withGroupKey(HystrixCommandGroupKey.Factory.asKey("CommandHelloWorld"))
                .andCommandPropertiesDefaults(
                    HystrixCommandProperties.defaultSetter()
                        .withRequestCacheEnabled(false) // 请求缓存开关)
// 切换线程池隔离还是信号量隔离
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)
                        .withExecutionIsolationSemaphoreMaxConcurrentRequests(2)
//.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)
                        // .withCircuitBreakerForceOpen(true) // 强制开启熔断器
                        // 单位时间内的请求阈值
                        .withCircuitBreakerRequestVolumeThreshold(2) 
                        // 当满足请求阈值时,超过50%则开启熔断
                        .withCircuitBreakerErrorThresholdPercentage(50) 
        ).andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("MyThreadPool"))
//         .andThreadPoolPropertiesDefaults(
//                 HystrixThreadPoolProperties.defaultSetter()
//                    .withCoreSize(2)
//                    .withMaximumSize(3).withAllowMaximumSizeToDivergeFromCoreSize(true)
//                    .withMaxQueueSize(2)
//         )
        );

        this.name = name;
    }

    // 单次请求调用的业务方法
    @Override
    protected String run() throws Exception {
        String result = "CommandHelloWorld name : "+ name;

//        Thread.sleep(800l);

        if(name.startsWith("zhenghaorui")){
            int i = 6/0;
        }

        System.err.println(result+" , currentThread-"+Thread.currentThread().getName());

        return result;
    }

    @Override
    protected String getCacheKey() {
        return String.valueOf(name);
    }
}

2、CommandTest:

public class CommandTest {
    @Test
    public void executeTest(){
        long beginTime = System.currentTimeMillis();

        CommandDemo commandDemo = new CommandDemo("execute");

        // 同步执行Command
        String result = commandDemo.execute();

        long endTime = System.currentTimeMillis();
        System.out.println("result="+result+" , speeding="+(endTime-beginTime));
    }

    @Test
    public void queueTest() throws ExecutionException, InterruptedException {
        long beginTime = System.currentTimeMillis();

        CommandDemo commandDemo = new CommandDemo("queue");

        Future<String> queue = commandDemo.queue();

        long endTime = System.currentTimeMillis();

        System.out.println("future end , speeding="+(endTime-beginTime));

        long endTime2 = System.currentTimeMillis();

        System.out.println("result="+queue.get()+" , speeding="+(endTime2-beginTime));
    }

    @Test
    public void observeTest(){
        long beginTime = System.currentTimeMillis();

        CommandDemo commandDemo = new CommandDemo("observe");

        Observable<String> observe = commandDemo.observe();

        // 阻塞式调用
        String result = observe.toBlocking().single();

        long endTime = System.currentTimeMillis();
        System.out.println("result="+result+" , speeding="+(endTime-beginTime));


        // 非阻塞式调用
        observe.subscribe(new Subscriber<String>() {
            @Override
            public void onCompleted() {
                System.err.println("observe , onCompleted");
            }

            @Override
            public void onError(Throwable throwable) {
                System.err.println("observe , onError - throwable="+throwable);
            }

            @Override
            public void onNext(String result) {
                long endTime = System.currentTimeMillis();
                System.err.println("observe , onNext result="+result+" speend:"+(endTime - beginTime));
            }
        });
    }

    @Test
    public void toObserveTest() throws InterruptedException {
        long beginTime = System.currentTimeMillis();

        CommandDemo commandDemo1 = new CommandDemo("toObservable1");

        Observable<String> toObservable1 = commandDemo1.toObservable();

        // 阻塞式调用
        String result = toObservable1.toBlocking().single();

        long endTime = System.currentTimeMillis();
        System.out.println("result="+result+" , speeding="+(endTime-beginTime));

        CommandDemo commandDemo2 = new CommandDemo("toObservable2");
        Observable<String> toObservable2 = commandDemo2.toObservable();
        // 非阻塞式调用
        toObservable2.subscribe(new Subscriber<String>() {
                @Override
                public void onCompleted() {
                System.err.println("toObservable , onCompleted");
            }

            @Override
            public void onError(Throwable throwable) {
                System.err.println("toObservable , onError - throwable="+throwable);
            }

            @Override
            public void onNext(String result) {
                long endTime = System.currentTimeMillis();
                System.err.println("toObservable , onNext result="+result+" speend:"+(endTime - beginTime));
            }
        });

        Thread.sleep(2000l);
    }


    /**
     * @Description: 演示请求缓存
     * @Param: []
     * @return: void
     * @Author: zhenghaorui
     */
    @Test
    public void requestCache(){
        // 开启请求上下文
        HystrixRequestContext requestContext = HystrixRequestContext.initializeContext();
        long beginTime = System.currentTimeMillis();

        CommandDemo c1 = new CommandDemo("c1");
        CommandDemo c2 = new CommandDemo("c2");
        CommandDemo c3 = new CommandDemo("c1");

        // 第一次请求
        String r1 = c1.execute();

        System.out.println("result="+r1+" , speeding="+(System.currentTimeMillis()-beginTime));

        // 第二次请求
        String r2 = c2.execute();

        System.out.println("result="+r2+" , speeding="+(System.currentTimeMillis()-beginTime));

        // 第三次请求
        String r3 = c3.execute();
        System.out.println("result="+r3+" , speeding="+(System.currentTimeMillis()-beginTime));

        // 请求上下文关闭
        requestContext.close();
    }

    /**
    * @Description: 演示线程池内容
    * @Param: []
    * @return: void
    * @Author: zhenghaorui
    */
    @Test
    public void threadTest() throws ExecutionException, InterruptedException {
        CommandDemo c1 = new CommandDemo("c1");
        CommandDemo c2 = new CommandDemo("c2");
        CommandDemo c3 = new CommandDemo("c3");
        CommandDemo c4 = new CommandDemo("c4");
        CommandDemo c5 = new CommandDemo("c5");

        Future<String> q1 = c1.queue();
        Future<String> q2 = c2.queue();
        Future<String> q3 = c3.queue();
        Future<String> q4 = c4.queue();
        Future<String> q5 = c5.queue();

        String r1 = q1.get();
        String r2 = q2.get();
        String r3 = q3.get();
        String r4 = q4.get();
        String r5 = q5.get();

        System.out.println(r1+","+r2+","+r3+","+r4+","+r5);

        // 1,2,3,4,5
        // core 1,2  max 1
        // queue 2
    }

    /**
    * @Description: 信号量隔离测试
    * @Param: []
    * @return: void
    * @Author: zhenghaorui
    */
    @Test
    public void semTest() throws InterruptedException {
        MyThread t1 = new MyThread("t1");
        MyThread t2 = new MyThread("t2");
        MyThread t3 = new MyThread("t3");
        MyThread t4 = new MyThread("t4");
        MyThread t5 = new MyThread("t5");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();

        Thread.sleep(2000l);
    }

    /**
    * @Description: 熔断演示
    * @Param: []
    * @return: void
    * @Author: zhenghaorui
    */
    @Test
    public void CBTest() throws InterruptedException {
        // 正确 - 业务
        CommandDemo c1 = new CommandDemo("helloworld");
        System.out.println(c1.execute());

        // 错误 - 业务
        CommandDemo c2 = new CommandDemo("zhenghaorui-1");
        System.out.println(c2.execute());

        // 正确 - 业务
        Thread.sleep(1000l);
        CommandDemo c3 = new CommandDemo("helloworld");
        System.out.println(c3.execute());

        // 半熔断状态
        Thread.sleep(5000l);
        // 错误 - 业务
//        CommandDemo c4 = new CommandDemo("zhenghaorui-2");
//        System.out.println(c4.execute());

        // 正确 - 业务
//        CommandDemo c5 = new CommandDemo("helloworld");
//        System.out.println(c5.execute());


        // 成功
        CommandDemo c6 = new CommandDemo("helloworld");
        System.out.println(c6.execute());

    }
}

class MyThread extends Thread{
    private String name;

    public MyThread(String name){
        this.name = name;
    }

    @Override
    public void run() {
        CommandDemo commandDemo = new CommandDemo(name);
        System.out.println("commandDemo result="+commandDemo.execute());
    }
}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

z.haoui

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值