1. 程式人生 > 其它 >漫談訊息佇列:以 Kafka 和 RocketMQ 為例

漫談訊息佇列:以 Kafka 和 RocketMQ 為例

  前言

  訊息佇列是一種幫助開發人員解決系統間非同步通訊的中介軟體,常用於解決系統解耦和請求的削峰平谷的問題。它是一種中介軟體,意味著它是面向研發人員而非終端使用者的產品,它的存在不能直接的創造價值,但可以有效的簡化研發的開發工作。下面,我試著用一個簡單的例子來展示下 MQ 的開啟方式。

  一個電商系統的演進

  一個最簡單的電商系統,至少包含以下流程:

  使用者下單付款配送

  相信大家都能理解為什麼我們不會在一個系統中實現所有功能。 那好,我們假設三個功能簡單的對應三個系統: 使用者端系統,也就是給使用者使用的前端,可以是 web 也可以是 app 或者小程式,這不重要;支付系統,對接銀行或其他金融機構,完成付款流程;物流系統,負責商品配送的跟蹤。

  顯而易見,使用者如果想購買商品,會在前端系統中選中自己喜歡的商品,然後它需要付款,之後由物流人員進行配送。 那麼問題來了,當用戶選購物品之後,前端系統如何將訂單訊息轉給支付系統? 很簡單,支付系統可以給前端系統提供一個介面,當有訂單生成時,前端系統呼叫支付系統的介面將訊息傳遞給支付系統,完成付款流程。同樣,支付系統也可以把訂單訊息再傳給物流系統,邏輯清晰,架構簡單,一切看起來都很美好,完全不需要訊息佇列。

  但是接下來,隨著業務的發展,又有了新的需求。公司新建了一套風控系統,防止使用者惡意操作。 所以風控系統也需要接受到所有的訂單請求。 按照前面的方式,前端呼叫支付系統後,又多了一步呼叫風控系統介面的操作。之後的之後又有了優惠卷系統,也需要獲取訂單資訊,還有公司財務系統,內控合規,大資料統計,廣告系統,使用者推薦系統。前端系統的研發每天忙於和各個系統進行對接,完全沒有時間優化使用者體驗。更糟糕的是,後來老闆心血來潮,要做一次 69 大促,優惠套路及具欺騙性。屆時訂單量會有百倍的增長,各個系統當前處理能力嚴重不足。理想情況下,各個系統可以橫向無限擴容,簡單說就是增加伺服器數量。但這樣會帶來嚴重的成本支出,尤其是當我們考慮到有些系統,尤其是大資料相關的其實對訊息實時性要求並不高,這些成本會顯得很沒必要。

  還好,God bless 碼農。訊息佇列橫空出世,拯救研發狗於水深火熱中。前端系統只需將訂單資訊傳送的 MQ 中,而不用關心都有誰需要接受訂單資訊。其他所有系統從 MQ 中獲取訊息,而且前端系統也不用關係其他系統收到訊息後是否處理成功,MQ 可以幫助我們處理這些問題,這就是我們所說的系統節藕。大促時,也只需要保證核心系統有充足的處理能力即可,對於處理能力較弱的系統,在流量峰值時 MQ 系統可以將訊息暫時儲存,下游系統可以優哉遊哉的慢慢處理,這就是我們所說的削峰平谷。

  那麼問題來了,MQ 為什麼這麼牛逼呢?它是如何實現這些功能的? 我們一個個慢慢講。

  MQ 的基本概念

  三種常見訊息協議

  1 JMS(Java Message Service): JMS 本質上是 JAVA API。在 JMS 中定義了 Producer,Consumer,Provider 三種角色,Producer 作為訊息的傳送方,Consumer 作為訊息的接收方,Provider 作為服務的提供者,Producer 和 Consumer 統稱為 Client。JMS 定義了點對點和釋出訂閱兩種訊息模型,釋出訂閱模型中,通過 topic 對訊息進行路由,生產者可以將訊息發到指定的 topic,消費者訂閱這個 topic 即可收到生產者傳送的訊息。一個生產者可以向一個或多個 topic 中傳送訊息,一個消費者也可以消費一個或多個 topic 中的訊息,一個 topic 也可以有多個生產者或消費者,生產者和消費者只需要關聯 topic,而不用關心這訊息由誰傳送或者消費。 Provider 為每一個 topic 維護一個或多個 queue 來儲存訊息,訊息在 queue 中是有序的,遵循先進先出的原則,不同 queue 間的訊息是無序的。點對點模式中沒有 topic 的概念,生產者直接將訊息傳送到指定 queue,消費者也指定 queue 進行消費,訊息只能被一個消費者消費,不可以被多個消費者消費。Kafka 和 RocketMQ 都實現了或部分實現了 JMS 協議。

  2 AMQP(Advanced Message Queuing Protocol): 與 JMS 不同,AMQP 是一個應用層的網路傳輸協議,對報文格式進行定義,與開發語言無關。在 AMQP 中同樣有生產者,消費者兩種角色,訊息也是儲存在 queue 中的。 但不同於 JMS 用 topic 對訊息進行路由,AMQP 的路由方式由 exchange 和 binding 決定。 client 可以建立 queue,並在建立 queue 的同時通知 exchange 這個 queue 接受符合什麼條件的訊息,這個條件即為 Bingding key。生產者傳送訊息到 exchange 的時候會指定一個 router key,exchange 收到訊息後會與自己所維護的 Bingding key 做比較,傳送到符合條件的 queue 中。消費者在消費時指定 queue 進行消費。RabbitMQ 實現了 AMQP 協議。

  3 MQTT(Message Queuing Telemetry Transport):MQTT 協議是一種基於釋出訂閱的輕量級協議,支援 TCP 和 UDP 兩種連線方式,主要應用於即時通訊,小型裝置,移動應用等領域。 MQTT 中有釋出者(Publish),訂閱者(Subscribe)和代理伺服器(Broker)三種角色。Broker 是服務的提供者,釋出者和前兩種協議中的生產者相同,將訊息(Message)傳送到 Broker,Subscribe 從 Broker 中獲取訊息並做業務處理。MQTT 的 Message 中固定訊息頭(Fixed header)僅有 2 位元組,開銷極小,除此之外分為可變頭(Variable header)和訊息體(payload)兩部分。固定頭中包含訊息型別,訊息級別,變長頭的大小以及訊息體的總長度等資訊。 變長頭則根據訊息類別,含有不同的標識資訊。 MQTT 允許客戶端動態的建立主題,釋出者與服務端建立會話(session)後,可以通過 Publish 方法傳送資料到服務端的對應主題,訂閱者通過 Subscribe 訂閱主題後,服務端就會將主題中的訊息推送給對應的訂閱者。

  系統解藕

  一般來說,系統間如果需要通訊,除了正常情況下的訊息傳遞外,還要考慮下游系統處理異常,上游系統如何處理?系統宕機的情況下會不會導致資料丟失?當有業務資料異常時,如何去定位是上游系統傳送出了問題還是下游系統的問題?如果需要同時將資訊傳送給多個下游系統,其中一個處理有問題會不會導致其它系統受影響?而 MQ 可以讓這些問題變得簡單。

  推 / 拉兩種模式

  在消費中,一般有推訊息和拉訊息兩種模式。推模式即服務端收到訊息後,主動將訊息推送給消費者,由消費者進行處理,這種模式具有更高的實時性,但是由於服務端不能準確評估消費端的消費效能,所以有可能造成訊息推送過多使客戶端來不及處理收到的訊息; 拉模式則是服務端收到訊息後將訊息儲存在服務端,被動的等待客戶端來拉取訊息,這種模式下客戶端可以根據自己的處理能力來決定拉訊息的頻率,但是缺點就是訊息處理可能有延遲,不過可以通過長輪詢的方式來提高實時性。

  三種訊息級別

  訊息傳遞過程中,會有各種異常導致訊息不能正常傳送,這時候,我們有以下三種選擇:

  下游允許部分訊息丟失,不進行處理,這種方式一般適用於監控資訊和 log 的傳遞,少一兩條影響不大,稱為至多一次(Qos=0);還有一種是訊息必須全部送達,不允許任何訊息丟失,但是可以接受部分訊息重複,這種我們稱為至少一次(Qos=1),此種方式一般適用於訂單,支付等場景(當然,這要求下游系統實現去重或冪等);還有一種最嚴格的要求,就是訊息只能送達一次,不能多也不能少,這種我們稱為正好一次(Qos=2)。

  這三種方式又分別是如何實現的呢?

  至多一次的實現

  要實現至多一次並不難,生產者只需要非同步傳送,在傳送失敗或者消費失敗的時候不做任何處理即可。MQ 在消費者拉走訊息後,就直接將訊息標記為已經消費或者刪除訊息。在監控系統和日誌系統中,丟失部分資訊是可以接受的,但顯然,電商系統,金融系統等大部分業務,是不允許出現訊息丟失這種情況的,需要保證訊息一定會送達到消費者。

  至少一次的實現

  至少一次的實現一般如下:生產者發訊息到 MQ,MQ 收到訊息後返回確認資訊(ACK)給生產者,生產者收到確認資訊後生產過程完成,如果在一定時間內,生產者沒有收到確認資訊,生產者重新發送訊息。 重新發送的過程可以是立即傳送,也可以將處理異常的訊息持久化,比如儲存到資料庫中,然後定時重試知道成功。同樣,消費者從 MQ 獲取到訊息後,當業務邏輯處理完成,向 MQ 返回 ACK 資訊。 但是存在下面一種情況,當 MQ 收到訊息併發送 ACK,或者消費者消費完成傳送 ACK 資訊之後,由於網路,系統故障等問題,ACK 資訊沒有成功送達,就會導致訊息重複傳送。 對於大部分訊息佇列的實現來說(如 kafka,RocketMQ)對於訊息重複的處理方式,就是不處理,交由消費者根據業務邏輯自己實現去重或冪等。消費者根據業務邏輯自己實現去重或冪等。消費者根據業務邏輯自己實現去重或冪等。 重要的事情說三遍。有些人或許會覺得這是常識和基本素養,但也有部分同學過於相信 MQ 系統和網路環境的穩定性,不做去重導致業務出現問題,比如優惠卷系統沒有做去重處理,本來只能領取一張的優惠卷,結果給使用者發了多張。

  正好一次的實現

  如果要實現正好一次的訊息級別,每次訊息傳遞過程正需要四次通訊,過程如下: 傳送端發訊息給接收端,接收端收到訊息後持久化儲存訊息 ID 並返回 REC 資訊給傳送端,通知生產端我已經收到這個訊息了。 這時訊息是一種中間態,接受端不會進行業務邏輯的處理。這個過程中,如果 REC 訊息丟失,服務端重傳了訊息, 接受端接受到訊息後會和本地儲存到訊息 ID 做對比,如果重複,就丟棄訊息不做處理,避免訊息被處理多次,而且訊息 ID 會持久化到硬碟,防止因為斷電記憶體中資料丟失倒是訊息被重複處理。 傳送端收到接收端返回的 rec 訊息後,傳送一個 rel 請求給消費端,告訴消費端我確認收到了你的確認訊息,接收端收到 rel 請求後才會進行具體的業務邏輯處理,並返回 comp 資訊給傳送端,同時在本地刪除儲存的訊息 ID。如果傳送端沒有收到 comp 資訊,會重發 rel 請求而不會重發訊息。

  以上,就是正好一次的實現過程。如果你沒看懂,那就再看一遍,如果還是沒看懂,那也沒關係,至少,你知道這玩意實現起來很複雜就好了。也就明白了為什麼大部分訊息中介軟體一般只保證至少一次,去重的過程交給消費者自己處理了。 畢竟對於大多數場景,吞吐量才是首要指標。 那麼,什麼場景下,需要保證正好一次呢? 答案是物聯網。 而標準的實現協議,是 MQTT。 因為物聯網場景下,大部分終端是嵌入式系統,處理能力會比伺服器低很多,所以服務端需要幫助終端實現去重,簡化終端的業務邏輯。

  資料的可靠性

  生產者將訊息發到訊息佇列的 Broker 之後,如果 Broker 只將訊息儲存在記憶體中,那當伺服器斷電或各種原因導致宕機時, 還沒有被消費的訊息將會丟失。 為了解決這個問題,可以選擇將資料持久化到硬碟,這樣當機器故障恢復後資料還在,消費者可以繼續消費之前沒有消費完的資料。但是,如果僅僅持久化到硬碟,當伺服器發生磁碟故障,Raid 卡故障時,資料依然存在丟失的風險。為了解決這個問題,絕大多數訊息佇列的實現都引入了複製 / 多副本的概念,將每份資料都儲存在多臺伺服器上,而且一般這些伺服器還要儘可能多實現跨機架甚至跨資料中心。 複製可以是同步的也可以是非同步的,可以是一主一從,也可以是一主多從,也可以基於 Raft,Paxos 等演算法實現多副本。

  不管是持久化還是複製,在保證資料可靠性的同時,都必然會帶來一部分效能的損耗,所以不同的訊息佇列實現根據自己的定位,會選擇不同的複製的實現方式以及持久化時的檔案結構。下面,我根據自己的理解聊一聊常見的檔案結構和複製方式的優缺點,只是個人觀點,僅供參考。

  Kafka 和 RocketMQ 的檔案結構對比

  其實訊息佇列的持久化,除了本地寫檔案外,還可以持久化到 K-V 儲存或者關係型資料庫中,但是效能會比較差,我們就不做討論了,我們只聊聊持久化到本地檔案系統中。而最常見的兩種檔案結構,一種是 Kafka 所使用的,一種是 RocketMQ 所使用的。

  Kafka 會在 Broker 上為每一個 topic 建立一個獨立的 partiton 檔案,Broker 接受到訊息後,會按主題在對應的 partition 檔案中順序的追加訊息內容。而 RocketMQ 則會建立一個 commitlog 的檔案來儲存分片上所有主題的訊息。

  Broker 接收到任意主題的訊息後,都會將訊息的 topic 資訊,訊息大小,校驗和等資訊以及訊息體的內容順序追加到 Commitlog 檔案中,Commitlog 檔案一般為固定大小,當前檔案達到限定大小時,會建立一個新的檔案,檔案以起始便宜位置命名。同時,Broker 會為每一個主題維護各自的 ConsumerQueue 檔案,檔案中記錄了該主題訊息的索引,包括在 Commitlog 中的偏移位置,訊息大小及校驗和,以便於在消費時快速的定位到訊息位置。ConsumerQueue 的維護是非同步進行的,不影響訊息生產的主流程,即使 ConsumerQueue 沒有及時更新的 情況下,服務異常終止,下次啟動時也可以根據 Commitlog 檔案中的內容對 ConsumerQueue 進行恢復。

  這樣的檔案結構也就決定了,在同步刷盤的場景下,RocketMQ 是順序寫,而 Kafka 是隨機寫。通常情況下,我們認為順序寫的效能遠高於隨機寫,尤其時對於傳統的機械硬碟來講更是如此。 且當 Broker 上的 topic 數量增多時,RocketMQ 在寫訊息的效能上幾乎不會受到影響,而對 Kafka 的影響則會較大。

  而在消費時,因為可以根據一定的快取策略將熱資料提前快取到記憶體中,所以不管哪種方式對於磁碟的要求都不是太高。不過對於 RocketMQ 來說,記憶體載入時會載入一整個 Commitlog 檔案,如果同一個 Broker 上的兩個主題,一個主題的訊息積壓了很長時間開始才開始消費,而另一個主題在及時消費新發送的訊息時,Broker 可能會頻發的讀取檔案更新到快取中,造成磁碟效能損耗,進而影響到生產時的傳送效能。所以雖然 RocketMQ 支援海量訊息積壓,但如果是在共享的叢集中,還是建議使用者最好能做到及時消費,保證叢集中所有主題都在消費相近時間段的訊息,這樣命中記憶體快取的概率會比較高,消費時不會帶來額外的磁碟開銷。

  需要補充說明的是,在做技術選型時,還需要考慮到硬體的發展。現今固態硬碟雖然價格較機械硬碟還是高出很多,但普及度越來越高。而固態硬碟在亂序寫時,效能表現比機械硬碟會好很多,特別是多執行緒同時進行寫操作時,效能也會比單執行緒順序寫強。對於需要同步刷盤保證資料可靠性的應用,磁碟讀寫效能的重要性一般來講也會遠高於磁碟的空間大小。 成本上來講,如果可以顯著的提高單機效能,雖然單價來看固態硬碟更加昂貴,但是如果可以節省部分 CPU,記憶體和機架位置,還是很划算的。

  服務可用性保障——複製與 failover 機制

  複製的實現,最簡單的方式就是一主一從的結構,開源版本的 RocketMQ 即使用了這種模式。由兩個 Broker 例項組成一組服務,一個作為主節點,提供讀寫服務,一個作為從節點,在正常情況下只從主節點同步資料不提供讀寫服務,且每個 topic 都會分配到多個 Broker 分組上。當某個從節點發生故障時,可以禁止主節點的寫入,依然允許消費者繼續消費該節點中未處理完成的訊息。而生產者有新訊息過來時,由其它主從都健康的分組提供服務, 直到故障機器恢復後主節點重新提供讀寫服務。如果故障機器無法恢復,只需等積壓訊息全部消費完,替換故障機器即可。 如果主節點故障,則可以在從節點進行消費,其它處理方式與從節點故障處理方式一致。 這種方式的優點是邏輯簡單,實現也簡單,簡單意味著穩定,隱藏的 bug 少。且資料只需要一份冗餘,對磁碟空間的開銷相對較少,可以保證大多數情況下的資料可靠性和服務可用性。

  Kafka 的複製策略,使用的是 ISR(可用服務列表)的方式,可以把他看成主從結構的升級版。對於每一個 partiton,可以分配一個或多個 Broker。 其中一個作為主節點,剩餘的作為跟隨者,跟隨者會儲存一個 partition 副本。生產者將訊息傳送到主節點後,主節點會廣播給所有跟隨者,跟隨者收到後返回確認資訊給主節點。 使用者可以自由的配置副本數及當有幾個副本寫成功後,則認為訊息成功儲存。且同時,會在 ZooKeeper 上維護一個可用跟隨者列表,列表中記錄所有資料和主節點完全同步的跟隨者列表。當主節點發生故障時,在列表中選擇一個跟隨者作為新的主節點提供服務。在這種策略下,假設總共有 m 個副本,要求至少有 n 個(0<n<m+1)副本寫成功,則系統可以在最多 m-n 個機器故障的情況下保證可用性。

  還有一種實現是基於 Raft 演算法實現的多副本機制,具體細節可以參考官方的 paper。Raft 叢集一般由奇數節點構成,如果要保證叢集在 n 個節點故障的情況下可用,則至少需要有 2n+1 個節點。 與 ISR 方式相比,Raft 需要耗費更多的資源,但是整個複製和選舉過程都是叢集中的節點自主完成,不需要依賴 ZooKeeper 等第三者。 理論上 Raft 叢集規模可以無限擴充套件而 ISR 模式下叢集規模會受限於 ZooKeeper 叢集的處理能力。

  訊息佇列的高階特性

  順序訊息

  一般情況下,因為訊息分佈在不同的 Broker 上,且有多個客戶端同時消費,各例項間的網路狀態和處理能力都是不一定的,所以分散式訊息系統是沒有辦法保證訊息的處理順序的。但如果你瞭解了一般訊息佇列的檔案結構,你就會發現不管是 Kafka 的 partition 那種方式,還是 RocketMQ 的方式,都可以保證同一個 partition 或者同一個 ConsumerQueue 內的訊息是可以保證順序的。剩下的,我們需要做的就是將需要保證順序的訊息放入到同一個 partiton 或者 queue 中就好了, 最簡單的方式是我們只為主題分配一個 partition 或者 queue,這樣就可以保證嚴格的順序,但是這樣就不能體現分散式系統的效能優勢了,叢集的處理能力沒有辦法橫向擴充套件。

  在實際的生產中,大多數情況下我們其實並不需要所有的訊息都順序處理,更多時候只要求具有相同特徵的訊息保證順序,如電商系統中,一般要求具有相同訂單號的訊息需要保證順序,不同的訂單之間可以亂序,也就是說我們只要保證有辦法將具有相同訂單編號的訊息放入到同一個佇列或者 partition 中即可。

  Kafka 提供了指定 partition 傳送的功能,使用者可以在客戶端根據業務邏輯自行處理,還有的訊息佇列支援根據某個欄位的值,將訊息 hash 到訊息指定訊息佇列中。 指定 partition 和 hash 兩種方式的主要區別,就是當有某個分片故障時,指定 partition 的方式會導致部分訊息傳送失敗,而 hash 的方式有可能造成少量訊息的亂序。

  事物訊息

  事務訊息主要指訊息生產過程中,需要確保傳送操作和其它業務邏輯處理結果的一致性,要麼都成功要麼都失敗。 比如要同時執行寫入 MySQL 資料庫和傳送訊息兩種操作,要保證寫庫成功同時傳送訊息也成功,如果寫庫失敗,訊息也要取消傳送。事務訊息的實現一般是依賴兩步提交策略。 已寫庫併發訊息為例,首先客戶端將訊息傳送到 Broker,Broker 收到訊息後,給客戶端返回一個確認資訊。 這時訊息在服務端是處於一種中間狀態,消費者不可以消費這種狀態的訊息。 客戶端收到確認訊息後,執行寫資料庫的操作,寫庫成功後,向 Broker 再發送一個提交資訊。 服務端收到提交資訊後將訊息更改就緒狀態,允許消費者正常消費。 同時,生產者客戶端還要提供一個回撥方法,當 Broker 收到訊息後,長時間沒有收到確認資訊時,呼叫客戶端提供的回撥方法進行回滾,如重置資料庫。

  訊息回放

  有時,消費者可能會因為系統問題或其它原因,需要重新消費已經消費完的訊息。大部分訊息佇列都可以實現這個功能,一般將次功能稱為訊息回放。要實現訊息回放的功能,需要保證訊息不會在消費成功之後立刻刪除,而是儲存一段時間後,根據一定策略,如一週後刪除。同時,還需要對消費者當前消費的消費位置進行記錄,RocketMQ 和 Kafka 都會通過一個 Offset 檔案來記錄消費者的消費位置,當消費者消費完成功,更新並提交 Offset。一般來說,Offset 檔案中還需要記錄最大消費位置,即已經入隊的最新一條訊息所在的位置和最小消費位置,即還沒刪除的最老的訊息所在的位置。Offset 檔案可以儲存在服務端,也可以儲存在客戶端,也可以儲存在 ZooKeeper 中,或者其它如 Redis 之類的第三方儲存。 早期版本(0.9.0 之前)的 Kafka 是將訊息儲存在 ZooKeeper 中,之後為了減輕 ZooKeeper 的負擔,將 Offset 儲存到 Broker 對應的 topic 中。 RocketMQ 則支援有兩種模式,預設是叢集模式,topic 中的每條訊息只會叢集中的一個例項消費,這種模式由服務端管理 Offset,還有一種是廣播模式,叢集中的所有例項都會消費一份全量訊息,這種模式由客戶端管理 Offset。

  影響單機效能的因素

  Kafka 和 RocketMQ 都是優秀的分散式訊息系統,當需要服務於有較高高吞吐量要求的服務時,都可以通過擴容來解決需求。雖然如此,我們也不應放棄對單機吞吐量的追求,畢竟單機處理能力越高,意味著可以節省更多資源。而決定單機效能的因素,我能想到的主要有下面幾個方面:

  硬體層面

  硬碟:一般來說訊息佇列的瓶頸主要在磁碟 IO,更好的硬碟會帶來更高的效能,正常情況效能由高到低排序為 NVMe > 傳統 SSD(Non-Volatile Memory express) >SAS >SATA。 對於 SAS 盤和 SATA 盤這種機械硬碟來說,還要看具體硬碟的轉速。Raid 卡: Raid 卡的型號和效能,以及是否帶有 Raid 卡快取,Raid 卡的鞋策略是 WriteThough 還是 WriteBack 也會影響到服務的 I/O 效能進而影響到吞吐量。

  系統層面

  Raid 級別:當有 4 塊盤時,Raid0,Raid5 和 Raid10 三種形式的 Raid 會對 I/O 效能造成不同的影響。Raid0 因為不需要任何其它操作,速度是最快的,幾乎等於單盤寫速度的四倍。Raid10 需要寫一份資料和一份映象,寫效能是略小於單盤寫速度的兩倍的;Raid5 可以有三個盤提供寫服務,另一個盤來存放校驗和,由於計算校驗和存在一定的效能損耗,寫速度略小於單盤寫速度的三倍,而且隨著硬碟數量的增多,Raid5 計算校驗和造成的開銷會隨之增大,如果沒有 Raid 卡快取支撐的話,在磁碟數量超過一定值後,效能是低於 Raid10 的。Linux I/O 排程演算法:Linux 核心包含 Noop,Deadline,CFG, Anticipatory 四種 I/O 排程演算法,需要結合應用特性和硬體選擇合適的排程演算法。 據說 SSD 硬碟更適合使用 Noop 演算法。檔案系統的 block size:調整合適的檔案系統的 block size 也會提高吞吐量。SWAP 的使用: SWAP 空間使用過程中會造成一定的 I/O 開銷,如果記憶體充足的情況下,可以關閉 SWAP 功能。

  應用層面

  檔案讀寫的方式:一般來說,順序讀寫速度遠高於隨機讀寫,且一次性讀寫的檔案越大相對來說效率越高。應用可以據此來對檔案結構和讀寫方式做一定優化。快取策略: 應用可以通過一定的快取策略,提前將可能用到的資料讀到記憶體中,當收到請求時,如果能命中快取中的資料,在快取中直接讀取效率遠高於讀寫磁碟。同樣,寫操作時也可以通過快取將零散的寫操作進行彙集,提高寫操作的效率。 所有適合的快取策略將顯著提高 Broker 的處理能力。