流數據並行處理性能比較:Kafka vs Pulsar vs Pravega
作者 | Raúl Gracia,王鐘樂,周煜敏,滕昱
審校 | 蔡芳芳
1 引言
並行化在處理流數據時也很重要。當應用程序分析流中的數據時,它們通常依賴並行處理來降低延遲和提高吞吐量。爲了在讀取流式數據時支持並行性,流存儲系統允許在數據寫入時,根據事件負載進行分區。這通常基於路由鍵 (routing keys) 的支持。通過分區,應用程序可以保留以應用本身概念(如標識符)的順序。在每個分區內,數據是有序的。在 Pravega 中,Stream 的並行單位被叫做 segment,而在基於 topic 的系統中 (如 Apache Kafka 和 Apache Pulsar),它被稱爲 partitions。Pravega 的 stream 可以自動根據負載的變化改變 segment 的數量。
在本文中,我們關注的是 Pravega 在多個寫客戶端同時寫入到一個多 segment 的 stream 時的表現。我們需要特別關注下數據的添加路徑 (append path),因爲它對於有效地讀取數據流至關重要。我們已經在之前的博客中分析了在只有一個寫入端和最多 16 個 segment 的基本 IO 性能。這一次,我們使用高度並行的負載,每個流最多有 100 個寫入端和 5000 個 segment。這樣的設置參考了當今雲原生應用程序的需求,例如對於高度並行的工作負載,它們對於擴展和維持高性能的需求。我們將 Pravega 與 Kafka 和 Pulsar 進行比較,以瞭解這些系統因爲不同的設計而帶來的影響。我們注意到的兩個關鍵點是:
-
在最多有 100 個寫入端和 5000 個 segment,數據流量在 250MBps 時,Pravega 可以在所有情況下都維持 250MBps 的速率。而 Kafka 在 5000 個 partition 時只有不到 100MBps。Pulsar 則在大多數情況下會直接崩潰。
-
Pravega 可以保證在 95% 中位數時,延遲在 10 毫秒以下,而 Kafka 的延遲卻高達幾十毫秒。對於 Pulsar,它有高達幾秒的延遲,並且是在我們僅僅能成功的那次。
爲了公平起見,我們已經測試了 Pulsar 的其他配置,以瞭解在哪些條件下它表現出良好的性能結果。因此,我們另外展示了一個對 Pulsar 更有利的配置,並且不會導致它經常崩潰。但這個配置對於我們的測試系統來說沒有那麼大的挑戰,如我們在下面進一步解釋的那樣。
在下面的章節中,我們將解釋是什麼能夠讓 Pravega 在這種情況下表現得更好,並詳細介紹我們的環境設置、實驗過程和結果。
2 爲什麼 Pravega 性能更好?
我們介紹一些關於 Pravega 添加路徑 (append path) 的設計特點,這些特點對於理解結果很重要。我們還討論了一些有關設計的權衡,並闡述了我們爲什麼在 Pravega 上選擇這種。
Pravega 的添加路徑 (append path)
Pravega 的添加路徑 (append path) 包括三個相關部分:
-
添加數據的客戶端
-
Segment Store,用以接收數據添加的請求,記錄其日誌並持久化存儲
-
持久化的日誌存儲,由 Apache BookKeeper 實現
下圖闡釋了 Pravega 的添加路徑 (append path):
客戶端添加由程序源生成的數據,並儘可能對這些數據進行批處理。在服務端收集客戶端的批處理數據,這樣做的好處時可以避免緩衝數據,但要注意是由客戶端來控制批處理何時開始和結束。客戶端使用了一種批處理跟蹤的啓發式算法,這個算法通過輸入速率和響應反饋來估計批處理的大小。有了這樣的估計,客戶端就可以決定何時關閉批處理 (代碼在 AppendBatchSizeTrackerImpl.java)。
由於客戶端批處理的大小最終取決於應用程序源可以生成多少數據,因此很有可能單個客戶端自己無法生成足夠大的批處理。因此,當有多個寫入端時,我們有機會聚合來自多個客戶端的批處理,以形成更大的批處理。實際上,這就是 segment store 的關鍵作用之一: 在將數據寫入持久性日誌之前對其進行聚合,其中的細節讀者有興趣可以延申閱讀本篇博客。
segment store 在一個稱爲 segment 容器 (segment container) 的組件中執行這第二級批處理。一個 segment store 可以同時運行多個 segment 容器,並且每個容器都有自己的持久化日誌 (durable log) 並追加到其中。這些 segment container 在單個 segment store 實例中可以並行地進行數據的 append 寫入。
每個 segment 容器負責在 Pravega 集羣中所有 stream 中一部分 segment 上的操作。在全局上,集羣會協調哪些 segment 容器對應哪些 segment store 實例。當增加 segment store 實例時 (例如擴大系統規模) 或減少 segment store 實例時 (如縮減系統規模或局部宕機),segment 容器集合將跨現有 segment store 實例重新平衡。
持久性日誌目前是通過 Apache BookKeeper ledgers 實現的。Bookie(BookKeeper 的存儲服務器) 將數據添加請求的日誌記錄到 ledgers 中,並在將數據添加加到 journals 之前執行另一層合併。這第三層的合併又是一個批量處理來自不同 segment 容器數據的機會。在我們用於 Pravega 的配置中,爲了保證持久性,bookie 只對將數據寫入 journal 的分 segment 容器做出響應。BookKeeper 還維護其他數據結構,但它們與本文的討論無關。
低延遲、高吞吐量和持久性
低延遲對於許多流式應用程序是至關重要的。這些應用要求數據在生成後不久就可以進行處理。高吞吐量同樣適用於需要從許多來源獲取大量數據的應用程序。如果系統不能夠維持高吞吐量的輸入,應用程序在數據峯值時可能會面臨不得不需要局部減載的危險。最後,在分析並處理這些流時,數據的丟失可能導致不正確的結果,因此,持久性對於企業應用程序也是至關重要。
然而,在一套系統裏同時實現這三個特性是具有挑戰性的。存儲設備通常通過更大塊的寫入來提高吞吐量,迫使系統緩存更多的數據並且犧牲了延遲。如果系統偏向更低的延遲,那麼每次寫入時的數據就會減少,從而降低了吞吐量。當然,如果我們不在數據刷新到磁盤後確認,那麼我們就可以不關注延遲和吞吐量之間的權衡,但是這種選擇犧牲了持久性。
在我們評估的所有三個系統中,Pravega 在這三個方面總體上提供了最好的結果。與 Kafka 和 Pulsar 相比,它在保持高吞吐量和低延遲的同時保證了持久性。Kafka 則在這三個方面做出了不同的選擇。與其他兩種配置相比,默認情況下它可以獲得更高的吞吐量和更低的延遲,因爲它不會等待磁盤的寫入成功,但這種選擇犧牲了持久性。Pulsar 能夠像 Pravega 一樣保證持久性,因爲它建立在 BookKeeper 的基礎上 ; 儘管如此,它似乎並沒有實現一個寫路徑 (write path),能夠保證在多寫入客戶端和多 partition 的情況下依然足夠高效,正如之後與其他兩個系統相比的實驗結果所展示的那樣。
3 評測與配置一覽
我們在 AWS 上進行了實驗。我們在 Pravega 中使用的方法非常接近我們在前一篇博文中描述的方法,可以參考那篇博文了解更多細節。與我們之前的博客不同的是,公平起見,對於 Pulsar,類似於對 Kafka 做的,我們使用了 StreamNative 之前的性能分析博客中使用的相同的 OpenMessaging 測試工具代碼。在下面,我們爲這篇博文提供了主要的配置設置:
在我們之前的博客中,我們對 Pravega、 Apache Pulsar 和 Apache Kafka 使用了相同的副本方案: 3 個副本,每次收到 2 個確認進行寫入。對於數據持久性,Pravega 和 Pulsar 在默認情況下保證每次寫入的數據持久性,並且我們保留這種行爲。對於 Kafka,我們測試了兩種配置: 1) 默認配置 no flush,這種配置下數據不會顯式地刷到磁盤中,那麼在有些相關的故障發生時可能導致數據丟失 ; 2) 磁盤刷新配置,其通過每次寫入刷入磁盤的方式保證數據的持久性。日誌在所有的系統都寫入了一個 NVMe 硬盤,這樣我們可以理解這三個系統如何在併發度上升的時候使用它。
我們部署的硬件參數與之前的博客不同。這次的實驗使用了 AWS 更大的服務器實例 (對於 Pulsar Broker 和 Pravega segment store 我們使用了 i3.16xlarge 實例)。更改實例的原因是,我們觀察到所有這些系統在有更多的 partition/segment 時,會使用更多的 CPU。通過增加 CPU 資源,我們才能保證這些系統不會被讓 CPU 成爲性能瓶頸。我們還使用多個測試虛擬機。通過模擬一個分佈式並行數據源 (Pravega、 Kafka 和 Pulsar 的 terraform 配置文件) ,這個部署符合我們在高負載下測試這些系統的目標。還要注意的是,在這個評估中,Pravega 是唯一將數據轉移到長期存儲(AWS EFS)的系統。
我們使用 OpenMessaging 基準測試來運行我們的實驗 (請參閱這裏的部署說明並在 Pravega 實驗中使用此版本)。我們將輸入數據速率固定在 250MBps(每個事件 1KB),而不是探索和評估系統的最大吞吐量。我們的目標是在客戶端數量和 segment/partition 的數量改變時,比較的不同系統的行爲。這樣的設置使得實驗可以保證數據注入的固定以減小其他因素對性能測試的影響。爲了完整起見,我們仍然在博客的末尾討論了這些系統的最大吞吐量。
測試中的生產者和消費者線程分佈在很多虛擬機上 (詳情請參見上表中的 Producers/Consumers 行)。每個生產線程和消費線程都使用一個專用的 Kafka、 Pulsar 或 Pravega 客戶端實例。基準測試的生產者線程使用 Kafka 和 Pulsar 中的 producer 或 Pravega 中的 writer ,而基準測試的消費者線程使用 Kafka 和 Pulsar 中的 consumer 或 Pravega 中的 reader。
在 Pravega 的具體案例中,寫入端使用了連接池技術: 這個特性允許應用程序使用一個公共的網絡連接池 (默認情況下每個 segment store10 個連接) 來處理大量的 segment 連接。
數據輸入和並行性
我們要評估的第一個方面是增加 segment 和客戶端數量對吞吐量的影響。下面的圖表顯示了 Pravega、 Kafka 和 Pulsar 的吞吐量,包括不同數量的 segment 和生產者。每一條線對應於在同一個工作負載下,向單一一個 stream/topic 追加不同數量的生產者。對於 Kafka 和 Pulsar,我們也繪製了其他配置下的圖線。這些配置以犧牲功能性(持久性或者是 no key)爲代價,爲這兩個系統提供了更好的結果。
該實驗可以通過 P3 測試程序復現,它使用以下的工作負載和配置文件作爲輸入。他們分別是 Pravega(工作負載,配置),Kafka(工作負載,配置)和 Pulsar(工作負載,配置)。這些系統的原始基準測試輸出可在此處獲得:Pravega,Kafka(不 flush),Kafka(flush),Pulsar,Pulsar(有利配置)。
通過研究上面的實驗圖表,我們觀察到以下關於吞吐量和並行性的關係:
-
Pravega 是這些系統中唯一可以在 250MBps 數據流,5000 個 segment 和 100 個生產者的負載下穩定工作的。這表明 Pravega 添加路徑 (append path) 的設計可以有效地處理高並行下的工作負載,特別是當許多寫入端的小數據的追加寫在 segment 容器中進行批量化處理的設計。
-
在我們增加 partitions 數量後,Kafka 的吞吐量降低了。更多的寫入者不會線性地增加吞吐量,而是存在一個性能上限。換句話來說,10 個和 50 個生產者在吞吐量上會有顯著的區別,但是 50 個和 100 個生產者的差距對於吞吐量的影響不大。這個結果證明了很多 Kafka 用戶共同的擔憂,即增加 partition 數量後,Kafka 的性能會下降。
-
值得一提的是,當我們在 Kafka 的配置中開啓了 flush 強制刷新到磁盤以確保數據持久性,吞吐量有着顯著的下降 (例如對於 100 個生產者和 500 個分區時,有 80% 的下降)。雖然強制刷新到磁盤會對系統造成一定的性能損失,但是這個實驗表明了在超過十個分區且保證持久性的情況下,吞吐量會有非常明顯的損失。
Pulsar 在我們試驗過的大多數配置情況下都會崩潰。爲了瞭解 Pulsar 穩定性問題的根本原因,我們換了一個更有利的配置:
-
等待所有來自 Bookies 的確認請求,這樣可以解決 out-of-memory 內存不足的錯誤 (更多細節見這個 issue)
-
不使用路由鍵來寫入數據 (這樣會犧牲數據的有序性並且降低實際的寫入並行性)。如果啓動的話,我們會在 Pulsar 的 broker 中發現關於 Bookkeeper 的 DBLedgerStorage 不能夠跟上寫入的速度的錯誤。
通過這種配置,Pulsar 可以得到比基本情景 (如 10 個生產者) 更好的結果。然而,當實驗中有大量的生產者和分區時,它仍然顯示出性能下降和最終的不穩定性。注意,在寫操作中不使用路由鍵是 Pulsar 性能提升的主要原因。在內部,Pulsar 客戶端通過創建更大的批處理並以 round-robin 的方式使用 segment 來優化沒有鍵的情況 (參見文檔)。
總之,Kafka 和 Pulsar 在增加分區和生產者數量時都會顯著降低性能。需要高度並行性的應用程序可能無法滿足所需的性能要求,或者不得不在這個問題上投入更多資源。Pravega 是經過測試下,唯一一個可以在大量的生產者和 segment 的規模下依然保持持續高吞吐量的系統,同時在此配置下數據的持久性也能得到保證。
關注寫入延遲
在寫入流數據時,延遲也和吞吐量一樣重要。接下來,我們展示了 95% 中位數時寫入延遲和 segment/ 生產者數的關係。請注意,在本節中,我們展現了所有系統的延遲數據,而不考慮它們是否達到了要求的高吞吐量。
通過研究上面的實驗圖表,我們重點總結出了以下結論:
-
Pravega 在 95% 中位數的情況下提供 < 10 毫秒的延遲,而 Kafka,即使修改配置使其不刷新到磁盤,也有更高的延遲。回想一下上一節,Pravega 在有很多 segment 和生產者的時候也能達到目標的高吞吐量,而 Kafka 沒有。
-
對於 5000 個 segment,與 100 個生產者的情況相比,Pravega 在 10 個生產者下能獲得更低的延遲。這樣的結果是由於批量化處理的設計。隨着生產者的增加,Pravega 的添加路徑 (append path) 使得單個生產者的每一批次的變小,這導致了數據在服務器端排隊和需要更多的計算量。
-
Kafka 在保證數據持久性 (即打開 flush 開關) 的模式下,延遲比默認配置更高了 (95% 中位數的延遲在 100 個生產者和 500 個 segment 的情況下達到了 13.6 倍的延遲)。在高並行的需求下,應用程序可能不得不在數據持久性和高並行的性能下二選一。
-
基本配置下的 Pulsar 只能在 10 個 partition + 10 個生產者的情況下保持低延遲。任何提升 partition 數量或者寫客戶端數量都會導致系統不穩定。
-
當使用更有利的配置時,Pulsar 在 10 個生產者的情況下可以獲得 < 10 毫秒的延遲。對於 100 個生產者,延遲會隨着 partition 數量的增加而迅速上升。
與 Kafka 相比,Pravega 擁有更低的延遲,甚至對於默認的不等待磁盤返回寫入確認的 Kafka 配置來說也是如此。對於 Pulsar,系統的不穩定性不允許我們對建立的配置進行乾淨的比較。對於更有利的 no key + 等待所有 bookie 返回的配置下,Pulsar 能在 10 個生產者下保持了 < 10 毫秒延遲,但是對於 100 個生產者的話延遲會增加的很快。
關於最大吞吐量的附註
雖然我們在上面的實驗中使用了一個固定的目標速率,但是我們還想了解這些系統在我們的場景中能夠達到的最大吞吐量。爲了縮小分析範圍,我們固定使用 10 個生產者,對比了在 10 和 500 個 segment/partition 時的情況。
從基準測試的角度來看,Pravega 可以在 10 和 500 個 segment 下達到 720MBps 的最大吞吐量,換算在磁盤級別則大約是 780MBps。這個差異是由於 Pravega 和 BookKeeper 添加了額外的元數據開銷 (例如 Pravega 的 segment 屬性)。這個成績非常接近同步寫入磁盤的最大速度(約 803MBps)。實驗如下:
[ec2-user@ip-10-0-0-100 ~]$ sudo dd if=/dev/zero of=/mnt/journal/test-500K bs=500K count=100000 oflag=direct100000+0 records in100000+0 records out51200000000 bytes (51 GB) copied, 63.7856 s, 803 MB/s
對於自定義配置的 Pulsar,我們可以在基準測試上可靠地獲得近 400MBps 的吞吐量。我們還測試了將客戶端的批處理時間增加到 10 毫秒,這使得吞吐量略有提高 (515MBps)。儘管如此,我們注意到這遠遠不是磁盤寫入的最大速率,我們懷疑這是由於使用了路由鍵,因爲它減少了 Pulsar 客戶端批處理的機會。更糟糕的是,隨着分區數量的增加,Pulsar 的吞吐量很快受到了限制。這個結果表明,主要依賴客戶端來聚合數據成批具有很大的侷限性。
對於有 10 個 partition 的情況,我們觀察到,當 Kafka 保證持久性 (“flush” 模式) 時,它可以在等待寫返回時達到 700MBps 和不等待寫返回時達到 900MBps。請注意,這隻發生在有 10 個 partition 的情況下,因爲當有 500 個 partition 時,吞吐量分別降至 22MBps 和 140MBps。
爲了獲得更深入的瞭解,我們在執行實驗時使用 iostat 對服務器端實例進行了檢測。根據從 iostat 每秒收集的信息,Kafka 使用 no flush 的設置允許操作系統緩衝更多的數據,再寫到硬盤,從而導致更高的吞吐量。下面的圖表從操作系統的角度展示了 Kafka 寫入的行爲: 寫往往是 250KB 的大小,或者因爲緩衝根本沒有寫入 (0 大小)。相反,Pravega 顯示了一個更小但是一致的寫大小,因爲每一次寫都被刷新到硬盤,並且 Pravega 添加路徑 (append path) 定義了它們的大小。注意,即使犧牲持久性,Kafka 也只能在更少的分區上達到這樣的吞吐率。
在不同 segment 或寫入者的情況下,iostat 監測的平均磁盤寫入的大小的累積分佈函數
4 總結
隨着越來越多的需要讀寫並行化的實際使用情況,流存儲有效地、高效地適配這些工作負載變得至關重要。許多這樣的應用程序是雲原生的,並且它們需要有效地伸縮和並行化這些工作負載的能力。
這篇博客展示了,Pravega 能夠在保持低延遲和數據持久性的同時,維持數千個 segment 和數十個併發寫入者的高吞吐量。同時提供高吞吐量、低延遲和數據持久性是一個具有挑戰性的問題。我們比較的 Kafka 和 Pulsar 這些消息傳遞系統在同樣的測試數據集上還不夠優秀。Pravega 的添加路徑 (append path) 包括了多個批處理步驟,在保證數據持久性的同時,還能應對高並行度工作負載的挑戰。今後還會有更多的性能測試博客。請保持關注。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/exOs0zrZ2x8Mxi6aB1x-QQ