日本黄色一级经典视频|伊人久久精品视频|亚洲黄色色周成人视频九九九|av免费网址黄色小短片|黄色Av无码亚洲成年人|亚洲1区2区3区无码|真人黄片免费观看|无码一级小说欧美日免费三级|日韩中文字幕91在线看|精品久久久无码中文字幕边打电话

當前位置:首頁 > 單片機 > 架構師社區(qū)
[導讀]大家好,我是yes。 我們都知道 RocketMQ 和 Kafka 消息都是存在磁盤中的,那為什么消息存磁盤讀寫還可以這么快?有沒有做了什么優(yōu)化?都是存磁盤它們兩者的實現(xiàn)之間有什么區(qū)別么?各自有什么優(yōu)缺點? 今天我們就來一探究竟。 存儲介質(zhì)-磁盤 一般而言消息中間件


大家好,我是yes。

我們都知道 RocketMQ 和 Kafka 消息都是存在磁盤中的,那為什么消息存磁盤讀寫還可以這么快?有沒有做了什么優(yōu)化?都是存磁盤它們兩者的實現(xiàn)之間有什么區(qū)別么?各自有什么優(yōu)缺點?

今天我們就來一探究竟。

存儲介質(zhì)-磁盤

一般而言消息中間件的消息都存儲在本地文件中,因為從效率來看直接放本地文件是最快的,并且穩(wěn)定性最高。畢竟要是放類似數(shù)據(jù)庫等第三方存儲中的話,就多一個依賴少一份安全,并且還有網(wǎng)絡的開銷。

那對于將消息存入磁盤文件來說一個流程的瓶頸就是磁盤的寫入和讀取。我們知道磁盤相對而言讀寫速度較慢,那通過磁盤作為存儲介質(zhì)如何實現(xiàn)高吞吐呢?

順序讀寫

答案就是順序讀寫。

首先了解一下頁緩存,頁緩存是操作系統(tǒng)用來作為磁盤的一種緩存,減少磁盤的I/O操作。

在寫入磁盤的時候其實是寫入頁緩存中,使得對磁盤的寫入變成對內(nèi)存的寫入。寫入的頁變成臟頁,然后操作系統(tǒng)會在合適的時候?qū)⑴K頁寫入磁盤中。

在讀取的時候如果頁緩存命中則直接返回,如果頁緩存 miss 則產(chǎn)生缺頁中斷,從磁盤加載數(shù)據(jù)至頁緩存中,然后返回數(shù)據(jù)。

并且在讀的時候會預讀,根據(jù)局部性原理當讀取的時候會把相鄰的磁盤塊讀入頁緩存中。在寫入的時候會后寫,寫入的也是頁緩存,這樣存著可以將一些小的寫入操作合并成大的寫入,然后再刷盤。

而且根據(jù)磁盤的構造,順序 I/O 的時候,磁頭幾乎不用換道,或者換道的時間很短。

根據(jù)網(wǎng)上的一些測試結果,順序?qū)懕P的速度比隨機寫內(nèi)存還要快。

當然這樣的寫入存在數(shù)據(jù)丟失的風險,例如機器突然斷電,那些還未刷盤的臟頁就丟失了。不過可以調(diào)用 fsync強制刷盤,但是這樣對于性能的損耗較大。

因此一般建議通過多副本機制來保證消息的可靠,而不是同步刷盤。

可以看到順序 I/O 適應磁盤的構造,并且還有預讀和后寫。RocketMQ 和 Kafka 都是順序?qū)懭牒徒祈樞蜃x取。它們都采用文件追加的方式來寫入消息,只能在日志文件尾部寫入新的消息,老的消息無法更改。

mmap-文件內(nèi)存映射

從上面可知訪問磁盤文件會將數(shù)據(jù)加載到頁緩存中,但是頁緩存屬于內(nèi)核空間,用戶空間訪問不了,因此數(shù)據(jù)還需要拷貝到用戶空間緩沖區(qū)。

可以看到數(shù)據(jù)需要從頁緩存再經(jīng)過一次拷貝程序才能訪問的到,因此還可以通過mmap來做一波優(yōu)化,利用內(nèi)存映射文件來避免拷貝。

簡單的說文件映射就是將程序虛擬頁面直接映射到頁緩存上,這樣就無需有內(nèi)核態(tài)再往用戶態(tài)的拷貝,而且也避免了重復數(shù)據(jù)的產(chǎn)生。并且也不必再通過調(diào)用read或write方法對文件進行讀寫,可以通過映射地址加偏移量的方式直接操作

sendfile-零拷貝

既然消息是存在磁盤中的,那消費者來拉消息的時候就得從磁盤拿。我們先來看看一般發(fā)送文件的流程是如何的。

簡單說下DMA是什么,全稱 Direct Memory Access ,它可以獨立地直接讀寫系統(tǒng)內(nèi)存,不需要 CPU 介入,像顯卡、網(wǎng)卡之類都會用DMA。

可以看到數(shù)據(jù)其實是冗余的,那我們來看看mmap之后的發(fā)送文件流程是怎樣的。

可以看到上下文切換的次數(shù)沒有變化,但是數(shù)據(jù)少拷貝一份,這和我們上文提到的mmap能達到的效果是一樣的。

但是數(shù)據(jù)還是冗余了一份,這不是可以直接把數(shù)據(jù)從頁緩存拷貝到網(wǎng)卡不就好了嘛?sendfile就有這個功效。我們先來看看Linux2.1版本中的sendfile。

因為就一個系統(tǒng)調(diào)用就滿足了發(fā)送的需求,相比 read + write或者 mmap + write上下文切換肯定是少了的,但是好像數(shù)據(jù)還是有冗余啊。是的,因此 Linux2.4 版本的 sendfile  + 帶 「分散-收集(Scatter-gather)」的DMA。實現(xiàn)了真正的無冗余。

這就是我們常說的零拷貝,在 Java 中FileChannal.transferTo()底層用的就是sendfile。

接下來我們看看以上說的幾點在 RocketMQ 和 Kafka中是如何應用的。

RocketMQ 和 Kafka 的應用

RocketMQ

采用Topic混合追加方式,即一個 CommitLog 文件中會包含分給此 Broker 的所有消息,不論消息屬于哪個 Topic 的哪個 Queue 。

Kafka和RocketMQ底層存儲之那些你不知道的事

所以所有的消息過來都是順序追加寫入到 CommitLog 中,并且建立消息對應的 CosumerQueue ,然后消費者是通過 CosumerQueue 得到消息的真實物理地址再去 CommitLog 獲取消息的??梢詫?CosumerQueue 理解為消息的索引。

在 RocketMQ 中不論是 CommitLog 還是 CosumerQueue 都采用了 mmap。

在發(fā)消息的時候默認用的是將數(shù)據(jù)拷貝到堆內(nèi)存中,然后再發(fā)送。我們來看下代碼。

Kafka和RocketMQ底層存儲之那些你不知道的事

可以看到這個配置 transferMsgByHeap默認是 true ,那我們再看消費者拉消息時候的代碼。

可以看到 RocketMQ 默認把消息拷貝到堆內(nèi) Buffer 中,再塞到響應體里面發(fā)送。但是可以通過參數(shù)配置不經(jīng)過堆,不過也并沒有用到真正的零拷貝,而是通過mapedBuffer 發(fā)送到 SocketBuffer 。

所以 RocketMQ 用了順序?qū)懕P、mmap。并沒有用到 sendfile ,還有一步頁緩存到 SocketBuffer 的拷貝。

然后拉消息的時候嚴格的說對于 CommitLog 來說讀取是隨機的,因為 CommitLog 的消息是混合的存儲的,但是從整體上看,消息還是從 CommitLog 順序讀的,都是從舊數(shù)據(jù)到新數(shù)據(jù)有序的讀取。并且一般而言消息存進去馬上就會被消費,因此消息這時候應該還在頁緩存中,所以不需要讀盤。

而且我們在上面提到,頁緩存會定時刷盤,這刷盤不可控,并且內(nèi)存是有限的,會有swap等情況。

而且mmap其實只是做了映射,當真正讀取頁面的時候產(chǎn)生缺頁中斷,才會將數(shù)據(jù)真正加載到內(nèi)存中,這對于消息隊列來說可能會產(chǎn)生監(jiān)控上的毛刺。

因此 RocketMQ 做了一些優(yōu)化,有:文件預分配和文件預熱

文件預分配

CommitLog 的大小默認是1G,當超過大小限制的時候需要準備新的文件,而 RocketMQ 就起了一個后臺線程 AllocateMappedFileService,不斷的處理 AllocateRequest,AllocateRequest其實就是預分配的請求,會提前準備好下一個文件的分配,防止在消息寫入的過程中分配文件,產(chǎn)生抖動。

文件預熱

有一個warmMappedFile方法,它會把當前映射的文件,每一頁遍歷多去,寫入一個0字節(jié),然后再調(diào)用mlock和 madvise(MADV_WILLNEED)。

我們再來看下this.mlock,內(nèi)部其實就是調(diào)用了mlock和 madvise(MADV_WILLNEED)。

mlock:可以將進程使用的部分或者全部的地址空間鎖定在物理內(nèi)存中,防止其被交換到swap空間。

madvise:給操作系統(tǒng)建議,說這文件在不久的將來要訪問的,因此,提前讀幾頁可能是個好主意。

RocketMQ 小結

順序?qū)懕P,整體來看是順序讀盤,并且使用了 mmap,不是真正的零拷貝。又因為頁緩存的不確定性和 mmap 惰性加載(訪問時缺頁中斷才會真正加載數(shù)據(jù)),用了文件預先分配和文件預熱即每頁寫入一個0字節(jié),然后再調(diào)用mlock和 madvise(MADV_WILLNEED)。

Kafka

Kafka 的日志存儲和 RocketMQ 不一樣,它是一個分區(qū)一個文件。

Kafka 的消息寫入對于單分區(qū)來說也是順序?qū)?,如果分區(qū)不多的話從整體上看也算順序?qū)懀娜罩疚募]有用到 mmap,而索引文件用了 mmap。但發(fā)消息 Kafka 用到了零拷貝。

對于消息的寫入來說 mmap 其實沒什么用,因為消息是從網(wǎng)絡中來。而對于發(fā)消息來說 sendfile 對比 mmap+write 我覺得效率更高,因為少了一次頁緩存到 SocketBuffer 中的拷貝。

來看下Kafka發(fā)消息的源碼,最終調(diào)用的是 FileChannel.transferTo,底層就是 sendfile。

從 Kafka 源碼中我沒看到有類似于 RocketMQ的 mlock 等操作,我覺得原因是首先日志也沒用到 mmap,然后 swap 其實可以通過 Linux 系統(tǒng)參數(shù) vm.swappiness來調(diào)節(jié),這里建議設置為1,而不是0。

假設內(nèi)存真的不足,設置為 0 的話,在內(nèi)存耗盡的情況下,又不能 swap,則會突然中止某些進程。設置個 1,起碼還能拖一下,如果有良好的監(jiān)控手段,還能給個機會發(fā)現(xiàn)一下,不至于突然中止。

RocketMQ & Kafka 對比

首先都是順序?qū)懭耄贿^ RocketMQ 是把消息都存一個文件中,而 Kafka 是一個分區(qū)一個文件。

每個分區(qū)一個文件在遷移或者數(shù)據(jù)復制層面上來說更加得靈活。

但是分區(qū)多了的話,寫入需要頻繁的在多個文件之間來回切換,對于每個文件來說是順序?qū)懭氲?,但是從全局看其實算隨機寫入,并且讀取的時候也是一樣,算隨機讀。而就一個文件的 RocketMQ 就沒這個問題。

從發(fā)送消息來說 RocketMQ 用到了 mmap + write 的方式,并且通過預熱來減少大文件 mmap 因為缺頁中斷產(chǎn)生的性能問題。而 Kafka 則用了 sendfile,相對而言我覺得 kafka 發(fā)送的效率更高,因為少了一次頁緩存到 SocketBuffer 中的拷貝。

并且 swap 問題也可以通過系統(tǒng)參數(shù)來設置。


特別推薦一個分享架構+算法的優(yōu)質(zhì)內(nèi)容,還沒關注的小伙伴,可以長按關注一下:

Kafka和RocketMQ底層存儲之那些你不知道的事

Kafka和RocketMQ底層存儲之那些你不知道的事

Kafka和RocketMQ底層存儲之那些你不知道的事

長按訂閱更多精彩▼

Kafka和RocketMQ底層存儲之那些你不知道的事

如有收獲,點個在看,誠摯感謝

免責聲明:本文內(nèi)容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

ckquote>
本站聲明: 本文章由作者或相關機構授權發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權益,請及時聯(lián)系本站刪除( 郵箱:macysun@21ic.com )。
換一批
延伸閱讀
關閉