Java 在 JDK 1.4 引入了 ByteBuffer 等 NIO 相關的類,使得 Java 程序員能夠拋棄基於 Stream ,從而使用基於 Block 的方式讀寫文件,另外,JDK 還引入了 IO 性能優化之王—— 零拷貝 sendFile 和 mmap。但他們的性能究竟怎麼樣? 和 RandomAccessFile 比起來,快多少? 什麼狀況下快?究竟是 FileChannel 快仍是 MappedByteBuffer 快......linux
(零拷貝參考 Zero Copy I: User-Mode Perspective)程序員
天啊,問題太多了!!!!!!算法
讓咱們慢慢分析。緩存
咱們知道,Java 世界有不少 MQ:ActiveMQ,kafka,RocketMQ,去哪兒 MQ,而他們則是 Java 世界使用 NIO 零拷貝的大戶。安全
然而,他們的性能卻大相同,拋開其餘的因素,例如網絡傳輸方式,數據結構設計,文件存儲方式,咱們僅僅討論 Broker 端對文件的讀寫,看看他們有什麼不一樣。性能優化
下圖是樓主查看源碼總結的各個 MQ 使用的文件讀寫方式。網絡
那麼,究竟是 MMAP 強,仍是 FileChannel 強?數據結構
MMAP 衆所周知,基於 OS 的 mmap 的內存映射技術,經過 MMU 映射文件,使隨機讀寫文件和讀寫內存類似的速度。架構
那 FileChannel 呢?是零拷貝嗎?很遺憾,不是。FileChannel 快,只是由於他是基於 block 的。app
接下來,benchmark everything —— 徐媽.
如何 Benchmark? Benchmark 哪些?
既然是讀寫文件,天然就要看讀寫性能,這是最基本的。但,注意,一般 MQ 會使用定時刷盤,防止數據丟失,MMAP 和 FileChannel 都有 force 方法,用於將 pageCache 的數據刷到硬盤上。force 會影響性能嗎? 答案是會。影響到什麼程度呢? 不知道。每次寫入的數據大小會影響性能嗎,毫無疑問會,但規則是什麼呢?FileOutputStream 真的一無可取嗎?答案是不必定。
一直以來,文件調優都是藝術,由於影響性能的因素太多,首先,SSD 的出現,已經讓傳統基於 B+ tree 的樹形結構產生了自我疑問,第二,每一個文件系統的性能不一樣,Linux ext3 和 ext4 性能天壤之別(刪除文件的性能差距在 20 倍左右)。而 Max OS 的 HFS+ 系統被 Linus 稱之爲「有史以來最垃圾的文件系統」,幸運的是,蘋果終於在 2017 年推送了 macOS High Sierra 和 iOS 10.3 系統,這個兩個系統都拋棄了 HFS+,換成了性能更高的 APFS。而每一個文件系統又能夠設置不一樣的調度算法,另外,還有虛擬內存缺頁中斷帶來的性能毛刺.......
(tips:良心的 RocketMQ 提供了 Linux IO 調優的腳本,這點作的不錯 :)
跑題了。
樓主寫了一個小項目,用於測試 Java MappedByteBuffer & FileChannel & RandomAccessFile & FileXXXputStream 的讀寫性能。你們也能夠在本身的機器上跑跑看。
CPU:intel i7 4核8線程 4.2GHz
內存:40GB DDR4
磁盤:SSD 讀寫 2GB/s 左右
JDK1.8
OS:Mac OS 10.13.6
虛擬內存: 未關閉,大小 9GB
測試注意點:
1GB 文件:
測試 MappedByteBuffer & FileChannel & RandomAccessFile & FileInputStream.
從這張圖裏,咱們看到,mmap 性能完勝,特別是在小數據量的狀況下。其餘的流,只有在4kb 的狀況下,纔開始反殺 mmap。所以,讀 4kb 如下的數據,請使用 mmap。
再放大看看 mmap 和 FileChannel 的比較:
根據上圖,咱們看到,在寫入數據包大於 4kb 以上的狀況下,FileChannel 等一衆非零拷貝,基本完勝 mmap,除了那個一次讀 1G 文件的 BT 測試。
所以,若是你的數據包大於 4kb,請使用 FileChannel。
1GB 文件:
測試 MappedByteBuffer & FileChannel & RandomAccessFile & FileInputStream.
從上圖,咱們能夠看出,mmap 性能仍是同樣的穩定。FileChannel 也不差,可是在 32 字節數據量的狀況下,還差點意思。
再看縮略圖:
咱們看到,64字節 是 FileChannel 和 mmap 性能的分水嶺,從 64字節開始,FileChannel 一路反殺,直到 BT 1GB 文件稍稍輸了一丟丟。
所以,咱們建議:若是你的數據包大小在 64 字節以上,請使用 FileChannel 寫入。
咱們知道,RocketMQ 使用異步刷盤,那麼異步 force 對性能有沒有影響呢?benchmark everything。咱們使用異步線程,每 16kb 刷盤一次,看看性能如何。
mmap 一直落後,且性能不好,除了在 2048 字節那裏有一點點抖動,基本維持 在 4000 左右,而沒有 force 的狀況下,則在 1500 左右。而 FileChannel 則徹底不受 force 的影響。在個人測試中,1GB 的文件,一次 force 須要 800 毫秒左右。buffer 越大,時間越多,反之則越小。
說個題外話,Kafka 一直不建議使用 force,大概也有這個緣由。固然,Kafka 還有本身的多副本策略保證數據安全。
這裏,咱們得出結論,若是你須要常常執行 force,即便是異步的,也請必定不要使用 mmap,請使用 FileChannel。
基於以上測試,咱們得出一張圖表:
假設,咱們的系統的數據包在 1024 - 2048 左右,咱們應該使用什麼策略?
答:讀使用 mmap,僅僅寫使用 FileChannel。
再回過頭看看 MQ 的實現者們,彷佛只有 QMQ 是 這麼作的。固然,RocketMQ 也提供了 FileChannel 的寫選項。但默認 mmap 寫加異步刷盤,應該是 broker busy 的元兇吧。
而 Kafka,由於默認不 force,也是使用 FileChannel 進行寫入的,爲何使用 FileChannel 讀呢?大概是由於消息的大小在 4kb 以上吧。
這樣一揣測,這些 MQ 的設計彷佛都很是合理。
最後,能不用 force 就別用 force。若是要用 force ,就請使用 FileChannel。