前言
Java 在 JDK 1.4 引入了 ByteBuffer 等 NIO 相關(guān)的類峭梳,使得 Java 程序員可以拋棄基于 Stream 持偏,從而使用基于 Block 的方式讀寫文件砍的,另外痹筛,JDK 還引入了 IO 性能優(yōu)化之王—— 零拷貝 sendFile 和 mmap。但他們的性能究竟怎么樣? 和 RandomAccessFile 比起來帚稠,快多少谣旁? 什么情況下快?到底是 FileChannel 快還是 MappedByteBuffer 快......
(零拷貝參考 Zero Copy I: User-Mode Perspective)
天啊翁锡,問題太多了B凇!9菹巍N僚小!角溃!
讓我們慢慢分析拷获。
看看善于利用 IO 零拷貝的 MQ 們
我們知道,Java 世界有很多 MQ:ActiveMQ减细,kafka匆瓜,RocketMQ,去哪兒 MQ未蝌,而他們則是 Java 世界使用 NIO 零拷貝的大戶驮吱。
然而,他們的性能卻大相同萧吠,拋開其他的因素左冬,例如網(wǎng)絡(luò)傳輸方式,數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)纸型,文件存儲方式拇砰,我們僅僅討論 Broker 端對文件的讀寫,看看他們有什么不同狰腌。
下圖是樓主查看源碼總結(jié)的各個 MQ 使用的文件讀寫方式除破。
- kafka:record 的讀寫都是基于 FileChannel。index 讀寫基于 MMAP(廝大提示)琼腔。
- RocketMQ:讀盤基于 MMAP瑰枫,寫盤默認(rèn)使用 MMAP,可通過修改配置展姐,配置成 FileChannel躁垛,原因是作者想避免 PageCache 的鎖競爭,通過兩層架構(gòu)實(shí)現(xiàn)讀寫分離圾笨。
- QMQ: 去哪兒 MQ教馆,讀盤使用 MMAP,寫盤使用 FileChannel擂达。
- ActiveMQ 5.15: 讀寫全部都是基于 RandomAccessFile土铺,這也是我們拋棄 ActiveMQ 的原因。
那么,到底是 MMAP 強(qiáng)悲敷,還是 FileChannel 強(qiáng)究恤?
MMAP 眾所周知,基于 OS 的 mmap 的內(nèi)存映射技術(shù)后德,通過 MMU 映射文件部宿,使隨機(jī)讀寫文件和讀寫內(nèi)存相似的速度。
那 FileChannel 呢瓢湃?是零拷貝嗎理张?很遺憾,不是绵患。FileChannel 快雾叭,只是因?yàn)樗腔?block 的。
接下來落蝙,benchmark everything —— 徐媽.
Benchmark 织狐?
如何 Benchmark? Benchmark 哪些筏勒?
既然是讀寫文件移迫,自然就要看讀寫性能,這是最基本的管行。但起意,注意,通常 MQ 會使用定時(shí)刷盤病瞳,防止數(shù)據(jù)丟失,MMAP 和 FileChannel 都有 force 方法悲酷,用于將 pageCache 的數(shù)據(jù)刷到硬盤上套菜。force 會影響性能嗎? 答案是會设易。影響到什么程度呢逗柴? 不知道。每次寫入的數(shù)據(jù)大小會影響性能嗎顿肺,毫無疑問會戏溺,但規(guī)則是什么呢?FileOutputStream 真的一無是處嗎屠尊?答案是不一定旷祸。
一直以來,文件調(diào)優(yōu)都是藝術(shù)讼昆,因?yàn)橛绊懶阅艿囊蛩靥嗤邢恚紫龋琒SD 的出現(xiàn),已經(jīng)讓傳統(tǒng)基于 B+ tree 的樹形結(jié)構(gòu)產(chǎn)生了自我疑問闰围,第二赃绊,每個文件系統(tǒng)的性能不同,Linux ext3 和 ext4 性能天壤之別(刪除文件的性能差距在 20 倍左右)羡榴。而 Max OS 的 HFS+ 系統(tǒng)被 Linus 稱之為“有史以來最垃圾的文件系統(tǒng)”碧查,幸運(yùn)的是,蘋果終于在 2017 年推送了 macOS High Sierra 和 iOS 10.3 系統(tǒng)校仑,這個兩個系統(tǒng)都拋棄了 HFS+忠售,換成了性能更高的 APFS。而每個文件系統(tǒng)又可以設(shè)置不同的調(diào)度算法肤视,另外档痪,還有虛擬內(nèi)存缺頁中斷帶來的性能毛刺.......
(tips:良心的 RocketMQ 提供了 Linux IO 調(diào)優(yōu)的腳本,這點(diǎn)做的不錯 :)
跑題了邢滑。
樓主寫了一個小項(xiàng)目腐螟,用于測試 Java MappedByteBuffer & FileChannel & RandomAccessFile & FileXXXputStream 的讀寫性能。大家也可以在自己的機(jī)器上跑跑看困后。
測試環(huán)境
CPU:intel i7 4核8線程 4.2GHz
內(nèi)存:40GB DDR4
磁盤:SSD 讀寫 2GB/s 左右
JDK1.8
OS:Mac OS 10.13.6
虛擬內(nèi)存: 未關(guān)閉乐纸,大小 9GB
測試注意點(diǎn):
- 為了防止 PageCache 緩存的影響,每次都生成一個新的文件進(jìn)行讀取摇予。
- 為了測試不同數(shù)據(jù)包對性能的影響汽绢,需要使用不同大小的數(shù)據(jù)包進(jìn)行多次測試。
- force 對性能影響很大侧戴,應(yīng)該單獨(dú)測試宁昭。
- 使用 1GB 文件進(jìn)行測試(小文件沒有參考意義,大文件 mmap 無法映射)
純粹讀測試
1GB 文件:
測試 MappedByteBuffer & FileChannel & RandomAccessFile & FileInputStream.
從這張圖里酗宋,我們看到积仗,mmap 性能完勝,特別是在小數(shù)據(jù)量的情況下蜕猫。其他的流寂曹,只有在4kb 的情況下,才開始反殺 mmap回右。因此隆圆,讀 4kb 以下的數(shù)據(jù),請使用 mmap翔烁。
再放大看看 mmap 和 FileChannel 的比較:
根據(jù)上圖渺氧,我們看到,在寫入數(shù)據(jù)包大于 4kb 以上的情況下租漂,F(xiàn)ileChannel 等一眾非零拷貝阶女,基本完勝 mmap颊糜,除了那個一次讀 1G 文件的 BT 測試。
因此秃踩,如果你的數(shù)據(jù)包大于 4kb衬鱼,請使用 FileChannel。
純粹寫測試
1GB 文件:
測試 MappedByteBuffer & FileChannel & RandomAccessFile & FileInputStream.
從上圖憔杨,我們可以看出鸟赫,mmap 性能還是一樣的穩(wěn)定。FileChannel 也不差消别,但是在 32 字節(jié)數(shù)據(jù)量的情況下抛蚤,還差點(diǎn)意思。
再看縮略圖:
我們看到寻狂,64字節(jié) 是 FileChannel 和 mmap 性能的分水嶺岁经,從 64字節(jié)開始,F(xiàn)ileChannel 一路反殺蛇券,直到 BT 1GB 文件稍稍輸了一丟丟缀壤。
因此,我們建議:如果你的數(shù)據(jù)包大小在 64 字節(jié)以上纠亚,請使用 FileChannel 寫入塘慕。
異步 force 測試
我們知道,RocketMQ 使用異步刷盤蒂胞,那么異步 force 對性能有沒有影響呢图呢?benchmark everything。我們使用異步線程骗随,每 16kb 刷盤一次蛤织,看看性能如何。
mmap 一直落后鸿染,且性能很差瞳筏,除了在 2048 字節(jié)那里有一點(diǎn)點(diǎn)抖動,基本維持 在 4000 左右牡昆,而沒有 force 的情況下,則在 1500 左右摊欠。而 FileChannel 則完全不受 force 的影響丢烘。在我的測試中,1GB 的文件些椒,一次 force 需要 800 毫秒左右播瞳。buffer 越大,時(shí)間越多免糕,反之則越小赢乓。
說個題外話忧侧,Kafka 一直不建議使用 force,大概也有這個原因牌芋。當(dāng)然蚓炬,Kafka 還有自己的多副本策略保證數(shù)據(jù)安全。
這里躺屁,我們得出結(jié)論肯夏,如果你需要經(jīng)常執(zhí)行 force,即使是異步的犀暑,也請一定不要使用 mmap驯击,請使用 FileChannel。
總結(jié)耐亏。
基于以上測試徊都,我們得出一張圖表:
假設(shè),我們的系統(tǒng)的數(shù)據(jù)包在 1024 - 2048 左右广辰,我們應(yīng)該使用什么策略暇矫?
答:讀使用 mmap,僅僅寫使用 FileChannel轨域。
再回過頭看看 MQ 的實(shí)現(xiàn)者們袱耽,似乎只有 QMQ 是 這么做的。當(dāng)然干发,RocketMQ 也提供了 FileChannel 的寫選項(xiàng)朱巨。但默認(rèn) mmap 寫加異步刷盤,應(yīng)該是 broker busy 的元兇吧枉长。
而 Kafka冀续,因?yàn)槟J(rèn)不 force,也是使用 FileChannel 進(jìn)行寫入的必峰,為什么使用 FileChannel 讀呢洪唐?大概是因?yàn)橄⒌拇笮≡?4kb 以上吧。
這樣一揣測吼蚁,這些 MQ 的設(shè)計(jì)似乎都非常合理凭需。
最后,能不用 force 就別用 force肝匆。如果要用 force 粒蜈,就請使用 FileChannel。