降级熔断框架 Hystrix 源码解析:滑动窗口统计

本文深入解析开源降级熔断框架Hystrix的滑动窗口统计源码,介绍了滑动窗口在Hystrix中的作用,以及如何使用RxJava实现统计功能,包括窗口长度、滚动桶长度等配置的解释。
摘要由CSDN通过智能技术生成

降级熔断框架 Hystrix 源码解析:滑动窗口统计

概述

Hystrix 是一个开源的降级熔断框架,用于提高服务可靠性,适用于依赖大量外部服务的业务系统。什么是降级熔断呢?

降级

业务降级,是指牺牲非核心的业务功能,保证核心功能的稳定运行。简单来说,要实现优雅的业务降级,需要将功能实现拆分到相对独立的不同代码单元,分优先级进行隔离。在后台通过开关控制,降级部分非主流程的业务功能,减轻系统依赖和性能损耗,从而提升集群的整体吞吐率。

降级的重点是:业务之间有优先级之分。降级的典型应用是:电商活动期间关闭非核心服务,保证核心买买买业务的正常运行。

熔断

老式电闸都安装了保险丝,一旦有人使用超大功率的设备,保险丝就会烧断以保护各个电器不被强电流给烧坏。同理我们的接口也需要安装上“保险丝”,以防止非预期的请求对系统压力过大而引起的系统瘫痪,当流量过大时,可以采取拒绝或者引流等机制。

同样在分布式系统中,当被调用的远程服务无法使用时,如果没有过载保护,就会导致请求的资源阻塞在远程服务器上耗尽资源。很多时候,刚开始可能只是出现了局部小规模的故障,然而由于种种原因,故障影响范围越来越大,最终导致全局性的后果。这种过载保护,就是熔断器。

在 hystrix 中,熔断相关的配置有以下几个:

滑动窗口长度,单位毫秒
hystrix.command.HystrixCommandKey.circuitBreaker.sleepWindowInMilliseconds
滑动窗口滚动桶的长度,单位毫秒
hystrix.command.HystrixCommandKey.metrics.rollingPercentile.bucketSize
触发熔断的失败率阈值
hystrix.command.HystrixCommandKey.circuitBreaker.errorThresholdPercentage
触发熔断的请求量阈值
hystrix.command.HystrixCommandKey.circuitBreaker.requestVolumeThreshold

从配置信息里可以看出来,熔断逻辑判断里使用了滑动窗口来统计服务调用的成功、失败量。那么这里的滑动窗口是如何实现的呢?下面我们深入源码来研究一下。

注:使用的源码版本是 2017-09-13 GitHub 上 master 分支最新代码。

滑动窗口

在 hystrix 里,大量使用了 RxJava 这个响应式函数编程框架,滑动窗口的实现也是使用了 RxJava 框架。

RxJava 介绍可以查看我所理解的RxJava — 上手其实很简单

源码分析

一个滑动窗口有两个关键要素组成:窗口时长、窗口滚动时间间隔。通常一个窗口会划分为若干个桶 bucket,每个桶的大小等于窗口滚动时间间隔。也就是说,滑动窗口统计数据时,分两步:
1. 统计一个 bucket 内的数据;
2. 统计一个窗口,即若干个 bucket 的数据。

bucket 统计的代码位于 BucketedCounterStream 类中,其关键的代码如下所示:

// 这里的代码并非全部,只展示了和 bucket 统计相关的关键代码
public abstract class BucketedCounterStream< Event extends HystrixEvent, Bucket, Output> {
   
    protected final int numBuckets;
    protected final Observable< Bucket> bucketedStream;
    protected final AtomicReference< Subscription> subscription = new AtomicReference< Subscription>(null);

    private final Func1< Observable< Event>, Observable< Bucket>> reduceBucketToSummary;

    protected BucketedCounterStream(final HystrixEventStream< Event> inputEventStream, final int numBuckets, final int bucketSizeInMs,
                                    final Func2< Bucket, Event, Bucket> appendRawEventToBucket) {
        this.numBuckets = numBuckets;
        this.reduceBucketToSummary = new Func1< Observable< Event>, Observable< Bucket>>() {
            @Override
            public Observable< Bucket> call(Observable< Event> eventBucket) {
                return eventBucket.reduce(getEmptyBucketSummary(), appendRawEventToBucket);
            }
        };

        final List< Bucket> emptyEventCountsToStart = new ArrayList< Bucket>();
        for (int i = 0; i <  numBuckets; i++) {
            emptyEventCountsToStart.add(getEmptyBucketSummary());
        }

        this.bucketedStream = Observable.defer(new Func0< Observable< Bucket>>() {
            @Override
            public Observable< Bucket> call() {
                return inputEventStream
                        .observe()
                        .window(bucketSizeInMs, TimeUnit.MILLISECONDS) //bucket it by the counter window so we can emit to the next operator in time chunks, not on every OnNext
                        .flatMap(reduceBucketToSummary)                //for a given bucket, turn it into a long array containing counts of event types
                        .startWith(emptyEventCountsToStart);           //start it with empty arrays to make consumer logic as generic as possible (windows are always full)
            }
        });
    }

    abstract Bucket getEmptyBucketSummary();
}

首先我们看这几行代码,这几行代码功能是:将服务调用级别的输入数据流 inputEventStream 以 bucketSizeInMs 毫秒为一个桶进行了汇总,汇总的结果输入到桶级别数据流 bucketedStream。

        this.bucketedStream = Observable.defer(new Func0<Observable<Bucket>>() {
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值