深入解析 Apache BookKeeper 系列:第二篇 — 寫操作原理

語言: CN / TW / HK

上一篇文章中,我們從元件、執行緒、讀寫流程三個方面講解了 bookie 服務端原理。在這篇文章中,我們將詳細介紹寫操作是如何通過各元件和執行緒模型的配合高效寫入和快速落盤的。我們儘量還是在架構層面剖析。

本系列文章基於 Apache Pulsar 中配置的 BookKeeper 4.14 版本。

寫操作中有很多執行緒呼叫 Journal 和 LedgerStorage 的 API。在上一篇文章中,我們已經知道寫操作中 Journal 為同步操作,DbLedgerStorage 為非同步操作。

圖一:各執行緒是如何處理寫操作的

我們知道可以配置多個 Journal 例項和 DbLedgerStorage 例項,每個例項都有自己的執行緒、佇列和快取。因此當講到某些執行緒、快取和佇列的時候,它們可能是並行存在的。

Netty 執行緒

Netty 執行緒處理所有的 TCP 連線和這些連線中的所有請求。並將這些寫請求轉發到寫執行緒池,其中包括要寫入的 entry 請求、處理請求結束時的回撥、傳送響應到客戶端。

寫執行緒池

寫執行緒池要做的事情不多,因此不需要很多的執行緒(預設值是 1)。每個寫請求新增 Entry 到 DbLedgerStorage 的 Write Cache,如果成功,則將寫請求新增到 Journal 的記憶體佇列(BlockingQueue)中。此時寫執行緒的工作就完成了,剩下的工作就交給其他執行緒處理。

每個 DbLedgerStorage 例項有兩個寫快取,一個是活躍的,一個是空閒的,空閒的這個快取可以在後臺將資料刷到磁碟。當 DbLedgerStorage 需要將資料刷到磁碟時(活躍寫快取寫滿後),兩個寫快取就會發生交換。當空閒狀態的寫快取將資料刷到磁碟的同時,可以使用一個空的寫快取繼續提供寫服務。只要在活躍寫快取被寫滿之前,將空閒寫快取中的資料刷到磁碟,就不會出現什麼問題。

DbLedgerStorage 的刷盤操作可以通過同步執行緒(Sync Thread)定時執行檢查點(checkpoint)機制或通過 DbStorage 執行緒(DbStorage Thread,每個 DbLedgerStorage 例項對應一個 DbStorage 執行緒)觸發。

如果寫執行緒嘗試向寫快取中新增 Entry 時,寫快取已經滿了,則寫執行緒將刷盤操作提交到 DbStorage 執行緒;如果換出的寫快取已經完成了刷盤操作,那麼兩個寫快取將立即執行交換操作(swap),然後寫執行緒將這個 Entry 新增到新交換出來的寫快取中,這部分的寫操作也就完成了。

然而,如果活躍狀態的寫快取被寫滿了,同時交換出的寫快取仍然在刷盤,那麼寫執行緒將等待一段時間,最終拒絕寫請求。等待寫快取的時間由配置檔案中的引數 dbStorage_maxThrottleTimeMs 控制,預設值為 10000(10 秒)。

預設情況下,寫執行緒池中只有一個執行緒,如果刷盤操作過長的話這將導致寫執行緒阻塞 10 秒鐘,這將導致寫執行緒池的任務佇列被寫請求迅速填滿,從而拒絕額外的寫請求。這就是 DbLedgerStorage 的背壓機制。一旦重新整理的寫快取再次能寫入之後,寫執行緒池的阻塞狀態才會被解除。

寫快取的大小預設為可用直接記憶體(direct memory)的 25%,可以通過配置檔案中的 dbStorage_writeCacheMaxSizeMb 來進行設定。總的可用記憶體是分配給每個 DbLedgerStorage 例項中的兩個寫快取,每個 ledger 目錄對應一個 DbLedgerStorage 例項。如果有 2 個 ledger 目錄和 1GB 的可用寫快取記憶體的話,每個 DbLedgerStorage 例項將分配 500MB,其中每個寫快取將分配到 250MB。

DbStorage 執行緒

每個 DbLedgerStorage 例項都有自己的 DbStorage 執行緒。當寫快取寫滿後,該執行緒負責將資料刷到磁碟。

Sync 執行緒

這個執行緒是在 Journal 模組和 DbLedgerStorage 模組之外的。它的工作主要是定期執行檢查點,檢查點有如下幾個:

  • • ledger 的刷盤操作(長期儲存)

  • • 標記 Journal 中已經安全的將資料刷到 ledger 盤的位置,通過寫入磁碟的 log mark 檔案實現。

  • • 清理不再需要的、舊的 Journal 檔案

這種同步操作可以防止兩個不同的執行緒同時刷盤。

當 DbLedgerStorage 刷盤時,交換出的寫快取會被寫入到當前 entry 日誌檔案中(這裡也會有日誌切分操作),首先這些 entry 會通過 ledgerId 和 entryId 進行排序,然後將 entry 寫入到 entry 日誌檔案,並將它們的位置寫入到 Entry Locations Index。這種寫 entry 時的排序是為了優化讀操作的效能,我們將在本系列下一篇文章中介紹。

一旦將所有寫請求的資料刷到磁碟,則交換出的寫快取就會被清空,以便再次與活躍的寫快取進行交換。

Journal 執行緒

Journal 執行緒是一個迴圈,它從記憶體佇列(BlockingQueue)中獲取 entry,並將 entry 寫到磁碟,並且週期性的向強制寫佇列(Force Write queue)新增強制寫請求,這會觸發 fsync 操作。

Journal 不會為佇列中獲取的每個 entry 執行 write 系統呼叫,它會對 entry 進行累計,然後批量的寫入磁碟(這就是 BookKeeper 的刷盤方式),這也稱為組提交(group commit)。以下幾個條件會觸發刷盤操作:

  • • 達到最大等待時間(通過 journalMaxGroupWaitMSec 配置,預設值為 2ms)

  • • 達到最大累計位元組數(通過 journalBufferedWritesThreshold 配置,預設值為 512Kb)

  • • entry 累計的數量達到最大值(通過 journalBufferedEntriesThreshold 配置,預設值為 0,0 表示不使用該配置)

  • • 當佇列中最後一個 entry 被取出時,也就是佇列由非空變為空(通過 journalFlushWhenQueueEmpty 配置,預設值為 false

每次刷盤都會建立一個強制寫請求(Force Write Request),其中包含要刷盤的 entry。

強制寫執行緒

強制寫執行緒是一個迴圈,迴圈從強制寫佇列中獲取強制寫請求,並對 journal 檔案執行 fsync 操作。強制寫請求包括要寫入的 entry 和這些 entry 請求的回撥,以便在持久化到磁碟後,這些 entry 寫入請求的回撥能被提交到回撥執行緒執行。

Journal 回撥執行緒

這個執行緒執行寫請求的回撥,並將響應傳送到客戶端。

常見問題梳理

  • • 寫操作的瓶頸通常在 Journal 或 DbLedgerStorage 中的磁碟 IO 上。如果寫 Journal 或同步操作(Fsync)太慢的話,那麼 Journal 執行緒和強制寫執行緒(就不能快速地從各自的佇列中獲取 entry。同樣,DbLedgerStorage 刷磁碟太慢,那麼 Write Cache 就無法清空,也無法快速的進行互換。

  • • 如果 Journal 遇到瓶頸,將導致寫執行緒池的任務佇列的任務數量達到容量上限,entry 將阻塞在 Journal 佇列中,寫執行緒也將被阻塞。一旦執行緒池任務佇列滿了,寫操作就會在 Netty 層被拒絕,因為 Netty 執行緒將無法向寫執行緒池提交更多的寫請求。如果你使用了火焰圖,你會發現寫執行緒池中的寫執行緒都很繁忙。如果瓶頸在於 DbLedgerStorage,那麼 DbLedgerStorage 自身就可以拒絕寫操作,在 10 秒(預設情況下)之後,寫執行緒池的資源很快就會被佔滿,然後導致 Netty 執行緒拒絕寫請求。

  • • 如果磁碟 IO 不是瓶頸,而是 CPU 利用率非常高的話,很有可能是因為使用了高效能磁碟,但是 CPU 效能比較低,導致 Netty 執行緒和其他各種執行緒處理效率降低。這種情況通過系統的監控指標就能很容易地定位。

總結

本文在 Journal 和 DBLedgerStorage 層面講解了寫操作流程,以及涉及到寫操作的執行緒是如何工作的。在下一篇文章中,我們將介紹讀操作。

相關閱讀

博文推薦|深入解析 Apache BookKeeper 系列:第一篇 — 架構原理

本文翻譯自《Apache BookKeeper Internals — Part 2 — Writes》[1] ,作者 Jack Vanlightly。

譯者簡介

邱峰 @360 技術中臺基礎架構部中介軟體產品線成員,主要負責 Pulsar、Kafka 及周邊配套服務的開發與維護工作。

引用連結

[1] 《Apache BookKeeper Internals — Part 2 — Writes》: https://medium.com/splunk-maas/apache-bookkeeper-internals-part-2-writes-359ffc17c497




轉發本文章到朋友圈集贊 30 個,掃碼新增 Pulsar Bot 👇🏻👇🏻👇🏻微信憑藉朋友圈截圖領取👆🏻👆🏻👆🏻技術書籍《深入解析 Apache Pulsar》一本。

限量 5 本!

先到先得,送完即止!

雲原生時代訊息佇列和流融合系統,提供統一的消費模型,支援訊息佇列和流兩種場景,既能為佇列場景提供企業級讀寫服務質量和強一致性保障,又能為流場景提供高吞吐、低延遲;採用儲存計算分離架構,支援大叢集、多租戶、百萬級 Topic、跨地域資料複製、持久化儲存、分層儲存、高可擴充套件性等企業級和金融級功能。 


GitHub 地址:http://github.com/apache/pulsar/


場景關鍵詞

非同步解耦 削峰填谷 跨城同步 訊息匯流排 

流儲存  批流融合  實時數倉  金融風控

本文分享自微信公眾號 - ApachePulsar(ApachePulsar)。
如有侵權,請聯絡 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

「其他文章」