結合最近Disruptor的學習,和之前一直思考解決的大文件拆分問題,想到是否可以使用Disruptor作為生產者/消費者傳遞數據的通道呢?借助其高效的傳遞,理論上應當可以提升性能。此文便是此想法的落地實現。
問題描述
將大文件按照指定大小拆分為若干小文件。具體可參考:大文件拆分方案的java實踐(附源碼)。
方案設計
設計簡圖
如下:
核心組件
- FileReadTask —— Disruptor的生產者線程,負責讀取源文件,;
- Disruptor —— FileReadTask和FileLineEventHandler線程之間傳遞數據的通道,無阻塞;
- RingBuffer —— Disruptor的核心組件,負責暫存被傳遞的消息,同時負責協調生產者和消費者之間的工作節奏;
- FileLineEventHandler —— 不斷從Disruptor中讀取FileLine,並直接扔給FileWriteTask的queue,是Disruptor的消費者,同時也是queue的生產者;
- FileWriteTask —— 從queue中讀取FileLine,並寫入到子文件,是queue的消費者。
設計思路
使用Disruptor作為生產者和消費者之間傳遞數據的通道,利用Disruptor高效傳遞數據的特性提升性能;
FileLineEventHandler作為Disruptor的消費者,只負責從中讀取數據,但是不負責耗時長的子文件操作;
FIleWriteTask服務耗時長的文件寫入工作,且每個task獨享queue,減少資源競爭。
性能表現
實測下來,和之前的‘生產者/消費者+nio’方案性能相當,最佳性能點為:
方案 |
-Xms |
-Xmx |
readTaskNum |
writeTaskNum |
queueSize |
Durition |
jvm_ |
jvm_ |
Physics |
生產者/消費者+nio |
512m |
512m |
24 |
8 |
4096 |
8158 |
80 |
100M |
4.6G |
Disruptor+生產者/消費者+nio |
512m |
512m |
2 |
2 |
1024 |
6191 |
80 |
500m |
4.2G |
相對與不使用Disruptor的方案,時延有所下降,但是並不明顯,兩個方案主要瓶頸都在於FileReadTask中對文件進行拆分的邏輯處理太費時,需要逐個字節讀取並比對是否為換行符/回車符。所以性能提升並不是很明顯。且性能表現並不穩定。
心得
這個示例或許沒有達到想要的效果,但是通過這個實例,將Disruptor用到了生產者和消費者模式中,體會Disruptor的設計初衷,提升生產者與消費者之間數據傳遞的效率,尤其是在純粹地快速交換數據的場景非常有用。
Disruptor持有的entry對象不宜直接傳遞給后續消費者使用,鑒於Disruptor會對RingBuffer的entries做內存預加載,且會循環使用對應entries,所以如果供消費者直接使用,會出現數據覆蓋的問題。可以參考實例代碼中FileLineEventHandler對寫入queue的FileLine的處理。
代碼示例
github地址:https://github.com/daoqidelv/filespilt-demo
包路徑:com.daoqidlv.filespilt.disruptor