細說雙Buffer緩衝池

前言

緩衝機制是對數據持久化的延遲,減小沒必要要的IO,提升數據落盤的效率。本文將會詳細探討擁有雙Buffer的緩衝池(下文統稱TwinsBufferPool)是如何實現的,讀者能夠依此推廣,獲得N-Buffer的實現原理。java

在此篇文章中,緩衝區(Buffer)和緩衝池(BufferPool)是兩個重要的概念,很明顯,二者構成了一個包含與被包含的關係,一個緩衝池內能夠有一個或者多個緩衝區協同工做,緩衝池中的全部緩衝區被組織成了一個環形隊列,一前一後的兩個緩衝區能夠互相替換角色。算法

固然,在整個過程當中,還會有其餘輔助工具的出現,在下文都會逐一闡述。編程

1、設計要點

一、可擴展性。毫無疑問,可擴展性是對一個設計良好的軟件的一項基本要求,而一個軟件的可擴展的地方一般是有不少處的,這在某種程度上會依賴於編程者的經驗,若是僅僅侷限於產品需求,可能會嚴重限制了軟件的可擴展性。緩衝池是一種相對通用的中間件,擴展點相對比較多,好比:緩衝區數量可指定,線程安全與否,緩衝區閾值調配等等。安全

二、易用性。設計出來的中間件應該是對用戶友好的,使用過程當中不會有繁瑣的配置,奇形怪狀的API,更不能有諸多沒必要要的Dependencies,若是能作到代碼無侵入性,那就很是完美了。基於這個要求,TwinsBufferPool作成了一個Spring Boot Starter的形式,加入到項目裏的dependencies中便可開啓使用。微信

三、穩定性。這就是衡量一箇中間件好壞的重要KPI之一,從外觀上看,一樣是一艘船,破了一個洞和無缺完好將會是一個致命的區別,用戶指望本身搭上了一艘完整的船,以便能航行萬里而無憂。數據結構

四、高效性。說到穩定性,那就不得不說高效了,若是能幫助用戶又好又快的解決問題,無疑是最完美的結果。關於TwinsBufferPool的穩定性和高效性兩個指標,會在文中附上jemeter的壓測結果,並加以說明。架構

2、設計方案

這一小節將會給出TwinsBufferPool完整的設計方案,咱們先從配置提及。併發

每一個參數都會提供默認值,因此不作任何配置也是容許的。以下是目前TwinsBufferPool能提供的配置參數(yml):負載均衡

buffer:
 capacity: 2000
 threshold: 0.5
 allow-duplicate: true
 pool:
 enable-temporary-storage: true
 buffer-time-in-seconds: 120
複製代碼

下面附上參數說明表: 工具

TwinsBufferPool參數表
以上參數比較淺顯易懂,這裏重點解釋enable-temporary-storage和buffer-time-in-seconds這兩個參數。

根據參數說明,很明顯能夠感覺到,這兩個參數是爲了預防突發狀況,致使數據丟失。由於緩衝區都是基於內存的設計的,這就意味着緩衝的數據隨時處於一種服務重啓,或者服務宕機的高風險環境中,所以,纔會有這兩個參數的誕生。

由於TwinsBufferPool良好的接口設計,對於以上兩個參數的實現機制也是高度可擴展的。TwinsBufferPool默認的是基於Redis的實現,用戶也能夠用MongoDB,MySQL,FileSystem等方式實現。由此又會衍生出另一個問題,因爲各類異常狀況,致使臨時存儲層遺留了必定量的數據,須要在下次啓動的時候,恢復這一部分的數據。

總而言之,數據都是經過flush動做最終持久化到磁盤上。

在這裏插入圖片描述
由於大多數實際業務場景對於緩衝池的併發量是有必定要求的,因此默認就採用了線程安全的實現策略,受到JDK中ThreadPool的啓發,緩衝池也具有了自身狀態管理的機制。以下列出了緩衝池全部可能存在的狀態,以及各個狀態的流轉。

/** * 緩衝池暫未就緒 */
private static final int ST_NOT_READY = 1;

/** * 緩衝池初始化完畢,處於啓動狀態 */
private static final int ST_STARTED = 2;

/** * 若是安全關閉緩衝池,會當即進入此狀態 */
private static final int ST_SHUTTING_DOWN = 3;

/** * 緩衝池已關閉 */
private static final int ST_SHUTDOWN = 4;

/** * 正在進行數據恢復 */
private static final int ST_RECOVERING = 5;
複製代碼

TwinsBufferPool狀態機

經過上述的一番分析,設計的方案也呼之欲出了,下面給出主要的接口設計與實現。

BufferPool接口定義
經過以上的講解,也不難理解BufferPool定義的接口。緩衝池的整個生命週期,以及內部的一些運做機制都得以體現。值得注意的是,在設計上,將緩衝池和存儲層作了邏輯分離,使得擴展性進一步獲得加強。

存儲相關的接口包含了一些簡單的CURD,目前默認是用Redis做爲臨時存儲層,MongoDB做爲永久存儲層,用戶能夠根據須要實現其餘的存儲方式。

下圖展示的是TwinsBufferPool的實現方式,DataBuffer是緩衝區,必須依賴的基礎元素。由於設計的是環形隊列,因此依賴了CycleQueue,這個環形隊列的interface也是自定義的,在JDK中沒有找到比較合適的實現。

BufferPool接口實現
值得注意的是,BufferPool接口定義是靈活可擴展的,TwinsBufferPool只是提供了一種基於環形隊列的實現方式,用戶也能夠自行設計,使用另一種數據結構來支撐緩衝池的運做。

3、壓測報告

使用的是我的的PC電腦,機器的配置以下:

處理器:i5-7400 CPU 3.00GHZ 四核

內存:8.00GB

操做系統:Windows10 64位 基於x64的處理器

運行環境以下:

jdk 1.8.0_144

SpringBoot_2.1.0,內置Tomcat9.0

Redis_v4.0.1

MongoDB_v3.4.7

測試工具:

jemeter_v5.1

總共測試了四組參數,每組參數主要是針對最大容量,閾值和最大緩衝時間三個參數來作調整。

第一組:

buffer:
 capacity: 1000
 threshold: 0.8
 pool:
 buffer-time-in-seconds: 60
複製代碼

第二組:

buffer:
 capacity: 5000
 threshold: 0.8
 pool:
 buffer-time-in-seconds: 60
複製代碼

第三組

buffer:
 capacity: 5000
 threshold: 0.8
 pool:
 buffer-time-in-seconds: 300
複製代碼

第四組

buffer:
 capacity: 10000
 threshold: 0.8
 pool:
 buffer-time-in-seconds: 300
複製代碼

總共採集了9個指標:CPU佔用率,堆內存/M,線程數,錯誤率,吞吐量/sec,最長響應時間/ms,最短響應時間/ms,平均響應時間/ms,數據丟失量。

限於篇幅,只展現4個指標:堆內存,數據丟失量,平均響應時間,吞吐量。

堆內存/mb
數據丟失量
平均響應時間/ms
吞吐量/sec
整體來看,隨着每秒併發量的增長,各項指標呈現了不太樂觀的趨勢,其中最不穩定的是第四組參數,波動較爲明顯,綜合表現最佳的是第二組參數,其次是第三組。

數據丟失量是一個比較讓人關心的指標,從圖中能夠得知,在併發量達到4000的時候,開始有數據丟失的現象,而形成這一現象的緣由並不是是TwinsBufferPool實現代碼的Bug,而是請求超時致使的「Connection refused」,由於每一個Servlet運行容器都會有超時機制,若是排隊請求時間過長,就是直接被拒絕了。所以,看數據丟失量和錯誤率曲線,這二者是一致的。若是設置成不超時,那麼將是零丟失量,零錯誤率,所帶來的代價就是平均響應時間會拉長。

由於受限於我的的測試環境,整個測試過程顯得不是很嚴謹,得出來的數據也並非很完美,不過,我這裏提供了一些優化調整的建議:

一、硬件環境。正所謂「巧婦難爲無米之炊」,若是提供的硬件性能自己就是有限的話,那麼,在上面運行的軟件也難以獲得正常的發揮。

二、軟件架構。這個想象的空間很大,其中有一種方案我認爲將來能夠歸入到RoadMap中:多緩衝池的負載均衡。咱們能夠嘗試在一個應用中啓用多個緩衝池,經過調度算法,將緩衝數據均勻的分配給各個緩衝池,不至於出現只有一個緩衝池「疲於奔命」的情況,最起碼系統的吞吐量會有所提高。

三、其餘中間件或者工具的輔助,好比加上消息中間件能夠起到削峯的做用,各項指標也將會有所改善。

四、參數調優。這裏的參數指代的不只僅是緩衝池的參數,還有包括最大鏈接數,最大線程數,超時時間等諸多外部參數。

4、總結

本文詳細闡述了雙Buffer緩衝池的設計原理,以及實現方式,並對TwinsBufferPool實施了壓測,也對測試結果進行了一番分析。


歡迎關注個人微信訂閱號:技術匯

若是想查看完整的測試報告,可在訂閱號內回覆關鍵詞:測試報告,便可獲取到下載連接。

若是想深刻研究TwinsBufferPool源碼的讀者,可在訂閱號內回覆關鍵詞:緩衝池

技術匯
相關文章
相關標籤/搜索