入一行,先別惦記着掙錢,而是要先讓自己值錢
–> 返回專欄總目錄 <–
代碼下載地址:https://github.com/f641385712/netflix-learning
前言
上篇文章介紹了Hystrix
指標數據收集的數據源HystrixEvent
和數據流HystrixEventStream
,通過示例瞭解了Hystrix數據收集的基本模型,但實際上並不精確。
我們已經知道Hystrix
它是通過滑動窗口的數據結構/算法來統計調用的指標數據的,但若直接使用HystrixEventStream
作爲管道傳播數據的話,是點對點的,並無時間區間、時間窗口等概念。因此本文將以以這爲目的,深入瞭解與時間窗口相關的數據傳輸、收集處理的核心API:BucketedCounterStream
。
正文
Hystrix
從1.5
版本(2016.12)開始,它全面擁抱RxJava
把這塊代碼重寫設計爲了基於數據流Stream的形式,通過消費數據流的形式利用滑動窗口,並對數據流進行變換後進行後續的操作,可以讓開發者更加靈活地去使用。
滑動窗口本質就是不斷變換的數據流,滑動窗口中每個桶的數據都來自於源源不斷的事件,因此滑動窗口非常適合用觀察者模式和響應式編程思想的 RxJava 實現。
說明數據流Stream的實現強依賴與RxJava思想,推薦若對此還不太熟悉,請翻閱前幾篇文章or其它文章先了解RxJava的思想以及使用。
使用 RxJava可以通過它的一系列操作符來實現滑動窗口,從而可以依賴 RxJava 的線程模型來保證數據寫入和聚合的線程安全,將這一系列的機制交給 RxJava來得以保證。所有的操作都是在 RxJava 的後臺線程上進行的,這也大大降低了對業務線程的延遲性的影響。
Hystrix裏的滑動窗口
Hystrix
通過滑動窗口來對數據進行“平滑”統計,默認情況下,一個滑動窗口包含10個桶(Bucket),每個桶時間寬度是1秒,負責1秒的數據統計。滑動窗口包含的總時間以及其中的桶數量都是可以配置的,來張官方的截圖認識下滑動窗口:
上圖的每個小矩形代表一個桶,可以看到,每個桶都記錄着1秒內的四個指標數據:成功量、失敗量、超時量和拒絕量,這裏的拒絕量指的就是上面流程圖中【信號量/線程池資源檢查】中被拒絕的流量。10個桶合起來是一個完整的滑動窗口,所以計算一個滑動窗口的總數據需要將10個桶的數據加起來。
BucketedCounterStream
滑動窗口所有的數據流實現均位於com.netflix.hystrix.metric.consumer
這個包下,這裏先挑最頂層的類BucketedCounterStream
進行說明。
BucketedCounterStream
它是抽象類,提供了基本的桶計數器(BucketedCounter)實現:按配置的時間間隔將所有事件聚合成桶。
該抽象類定義最爲基本的概念:桶、窗口
// Event:需要匯聚到桶裏面的原始事件類型(HystrixEvent是原始的,HystrixRollingNumberEvent是直接的)
// Hystrix 中的調用事件,如命令開始執行、命令執行完成等
// Bucket:每個桶中包含的數據類型
// Output:最終輸出類型:發送給流訂閱者的數據類型(通常與Bucket相同,但不必相同)
public abstract class BucketedCounterStream<Event extends HystrixEvent, Bucket, Output> {
protected final int numBuckets;
protected final Observable<Bucket> bucketedStream;
// 訂閱信息:允許訂閱or取消訂閱
protected final AtomicReference<Subscription> subscription = new AtomicReference<Subscription>(null);
// 它是一個函數。用於把Observable<Event>轉爲Observable<Bucket>
private final Func1<Observable<Event>, Observable<Bucket>> reduceBucketToSummary;
// 它是個Subject:既能發射數據,也能監聽數據
// 用於計數
private final BehaviorSubject<Output> counterSubject = BehaviorSubject.create(getEmptyOutputValue());
// inputEventStream:事件流,input輸入。比如command執行開始、結束時都會有輸入
// numBuckets:用戶不配置的話,默認它是10
// bucketSizeInMs:窗口毫秒值。若不配置回事1秒
// appendRawEventToBucket:它是一個函數 R call(T1 t1, T2 t2) 輸入Bucket, Event返回Bucket類型
protected BucketedCounterStream(final HystrixEventStream<Event> inputEventStream, final int numBuckets, final int bucketSizeInMs,
final Func2<Bucket, Event, Bucket> appendRawEventToBucket) {
this.numBuckets = numBuckets;
// getEmptyBucketSummary是否抽象方法:獲取空桶
this.reduceBucketToSummary = eventBucket -> eventBucket.reduce(getEmptyBucketSummary(), appendRawEventToBucket);
final List<Bucket> emptyEventCountsToStart = new ArrayList<>();
for (int i = 0; i < numBuckets; i++) {
emptyEventCountsToStart.add(getEmptyBucketSummary());
}
this.bucketedStream = Observable.defer(() -> {
return inputEventStream
.observe()
// 利用RxJava進行窗口滑動
// bucketSizeInMs默認值是1000,表示1s表示一個窗口
.window(bucketSizeInMs, TimeUnit.MILLISECONDS)
.flatMap(reduceBucketToSummary)
.startWith(emptyEventCountsToStart);
});
}
}
用戶在使用 Hystrix 的時候一般都要配兩個值(當然,大多數情況下默認值即可):timeInMilliseconds
和numBuckets
,前者代表滑動窗口的長度(時間間隔),後者代表滑動窗口中桶的個數,那麼每個桶對應的窗口長度就是 bucketSizeInMs = timeInMilliseconds / numBuckets
(記爲一個單元窗口週期)。BucketedCounterStream
每隔一個單元窗口週期(bucketSizeInMs
)就把這段時間內的所有調用事件聚合到一個桶內(使用的便是reduceBucketToSummary
函數完成)。
共享的事件流HystrixEventStream
BucketedCounterStream 核心代碼在構造函數裏,裏面最核心的邏輯就是如何將一個一個的事件按一段時間(RxJava的window方法)聚合成一個桶(flatMap方法)。我們可以看到 bucketedStream 是經事件源 inputEventStream 變換而成的,事件源的類型爲 HystrixEventStream<Event>
,關於此事件流你可參考上篇文章,電梯直達:[享學Netflix] 二十二、Netflix Hystrix事件源與事件流:HystrixEvent和HystrixEventStream
此處說明一點:發送事件/數據的順序性、
write()
數據時的線程安全性均由RxJava以及Hystrix使用ThreadLocal
提供保證的,使用者放心使用即可
事件聚合 -> 桶(Event -> Bucket)
事件流通過HystrixEventStream
源源不斷的傳遞過來,某一時段甚至某一時刻進來的事件會有N個,但是這個時候需要把它聚合成Bucket
桶,以方便後續的統計(因爲桶纔是窗口的最小單位),這部分核心邏輯在這:
this.reduceBucketToSummary = eventBucket -> eventBucket.reduce(getEmptyBucketSummary(), appendRawEventToBucket);
this.bucketedStream = Observable.defer(() -> { // defer 的意思是 lazy 創建
return inputEventStream
.observe()
.window(bucketSizeInMs, TimeUnit.MILLISECONDS) // 按單元窗口長度來將某個時間段內的調用事件聚集起來
.flatMap(reduceBucketToSummary) // 將每個單元窗口內聚集起來的事件集合聚合成桶
.startWith(emptyEventCountsToStart); // 爲了保證窗口的完整性,開始的時候先產生一串空的桶
});
這裏最爲核心是 window 操作符:它可以按單元窗口長度來將某個時間段內的調用事件聚集起來。
此時數據流裏每個對象都是一個集合:Observable<Event>
,所以需要將其聚集成桶類型以將其扁平化。Hystrix 通過 RxJava 的 reduce 操作符進行“歸納”操作,將一串事件歸納成一個桶:
this.reduceBucketToSummary = eventBucket -> eventBucket.reduce(getEmptyBucketSummary(), appendRawEventToBucket);
這個reduce函數的初始值爲:getEmptyBucketSummary()
也就是空桶,它是抽象方法由子類實現。appendRawEventToBucket
負責具體的reduce聚合邏輯,這是由構造函數傳進來的函數:Bucket + Event -> Bucket
,表示:對於每個 Event,都將其聚合到 Bucket 中,並返回聚合後的 Bucket。
說明:不同的實現對歸約
appendRawEventToBucket
函數的實現是不同的,比如熔斷器依賴的HealthCountsStream
它就是以long[]
作爲每個桶的。
Tips:window(timespan, unit)
操作符屬於計算型操作符,默認會在 Schedulers.computation() 調度器下執行(CPU 密集型,關於Schedulers前文有過詳細解釋),其底層本質是線程數爲 CPU 核數的線程池。RxJava 會確保其線程安全。
其它方法
BucketedCounterStream:
// 抽象方法:訪問權限是Default哦~~~
abstract Bucket getEmptyBucketSummary(); // 空桶
abstract Output getEmptyOutputValue(); // 空的輸出值。作爲BehaviorSubject的默認值
// 注意:這個泛型是output,並不是輸入哦。返回的是處理後的輸出流,所以一般是桶
// 它是public的
public abstract Observable<Output> observe();
// 取消subscription的訂閱(它的設值方法見下)
public void unsubscribe() {
Subscription s = subscription.get();
if (s != null) {
s.unsubscribe();
subscription.compareAndSet(s, null);
}
}
// 若subscription還爲null(還未開始),那就讓counterSubject去監聽着
// observe().subscribe(counterSubject);
public void startCachingStreamValuesIfUnstarted() { ... }
// 這是一個同步調用。以檢索最後一個計算的桶,而不需要等待任何發射
// 該方法會在很多地方被調用
public Output getLatest() {
startCachingStreamValuesIfUnstarted();
if (counterSubject.hasValue()) {
return counterSubject.getValue();
} else {
return getEmptyOutputValue();
}
}
總結
BucketedCounterStream
提供的能力可描述爲:桶計數器,它負責把一段時間窗口內的事件歸約到一個桶裏,並且對外提供Stream
的訪問方式,讓外部可以訂閱、處理。
如果說HystrixEventStream
是點對點的建立了通道,那麼BucketedCounterStream
就是定期的去通道了收集數據,統計裝到桶裏,以便後續使用。至於桶是什麼結構?裝了哪些數據,以及具體的歸約、計算邏輯均在子類實現,下面文章將繼續分享這方面的內容,敬請關注。
聲明
原創不易,碼字不易,多謝你的點贊、收藏、關注。把本文分享到你的朋友圈是被允許的,但拒絕抄襲
。你也可【左邊掃碼/或加wx:fsx641385712】邀請你加入我的 Java高工、架構師 系列羣大家庭學習和交流。
- [享學Netflix] 一、Apache Commons Configuration:你身邊的配置管理專家
- [享學Netflix] 二、Apache Commons Configuration事件監聽機制及使用ReloadingStrategy實現熱更新
- [享學Netflix] 三、Apache Commons Configuration2.x全新的事件-監聽機制
- [享學Netflix] 四、Apache Commons Configuration2.x文件定位系統FileLocator和FileHandler
- [享學Netflix] 五、Apache Commons Configuration2.x別樣的Builder模式:ConfigurationBuilder
- [享學Netflix] 六、Apache Commons Configuration2.x快速構建工具Parameters和Configurations
- [享學Netflix] 七、Apache Commons Configuration2.x如何實現文件熱加載/熱更新?
- [享學Netflix] 八、Apache Commons Configuration2.x相較於1.x使用上帶來哪些差異?
- [享學Netflix] 九、Netflix Archaius配置管理庫:初體驗及基礎API詳解
- [享學Netflix] 十、Netflix Archaius對Commons Configuration核心API Configuration的擴展實現
- [享學Netflix] 十一、Netflix Archaius配置管理器ConfigurationManager和動態屬性支持DynamicPropertySupport
- [享學Netflix] 十二、Netflix Archaius動態屬性DynamicProperty原理詳解(重要)
- [享學Netflix] 十三、Netflix Archaius屬性抽象Property和PropertyWrapper詳解
- [享學Netflix] 十四、Netflix Archaius如何對多環境、多區域、多雲部署提供配置支持?
- [享學Netflix] 十五、Netflix Archaius和Spring Cloud的集成:spring-cloud-starter-netflix-archaius
- [享學Netflix] 十六、Netflix Hystrix斷路器:初體驗及RxJava簡介
- [享學Netflix] 十七、Netflix Hystrix屬性抽象以及和Archaius整合實現配置外部化、動態化
- [享學Netflix] 十八、Netflix Hystrix配置之:全局配置和實例配置
- [享學Netflix] 十九、Netflix Hystrix插件機制:SPI接口介紹和HystrixPlugins詳解
- [享學Netflix] 二十、Netflix Hystrix跨線程傳遞數據解決方案:HystrixRequestContext
- [享學Netflix] 二十一、Netflix Hystrix指標數據收集(預熱):滑動窗口算法(附代碼示例)
- [享學Netflix] 二十二、Netflix Hystrix事件源與事件流:HystrixEvent和HystrixEventStream