千億級、大規模:騰訊超大 Apache Pulsar 叢集效能調優實踐

語言: CN / TW / HK

關於作者

鮑明宇

騰訊高階軟體工程師,目前就職於騰訊 TEG 資料平臺部,負責 Apache Pulsar、Apache Inlong、DB 資料採集等專案的開發工作。目前專注於大資料領域,訊息中介軟體、大資料資料接入等方向,擁有 10 年 Java 相關開發經驗。

張大偉

騰訊高階軟體工程師,Apache Pulsar Committer,目前就職於騰訊 TEG 資料平臺部,主要負責 Apache Pulsar 專案相關工作。目前專注於 MQ 和資料實時處理等領域,擁有 6 年大資料平臺相關開發經驗。

關於 Apache Pulsar

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

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

導讀

近期,騰訊 TEG 資料平部 MQ 團隊開發部署了一套底層運維指標效能分析系統(本文簡稱 Data 專案) ,目前作為通用基礎設施服務整個騰訊集團。該系統旨在收集效能指標、上報資料以用於業務的運維監控,後續也將延用至前後端實時分析場景。

騰訊 Data 專案選用 Apache Pulsar 作為訊息系統,其服務端採用 CVM 伺服器(Cloud Virtual Machine,CVM)部署,並將生產者和消費者部署在 Kubernetes 上,該專案 Pulsar 叢集是騰訊資料平臺部 MQ 團隊接入的訊息量最大的 Pulsar 叢集。在整個專案中,我們在 Apache Pulsar 大規模叢集運維過程中遇到了一些問題和挑戰。本文將對這些問題展開描述分析,並分享對應處理方案,同時也會解析涉及到的相關 Apache Pulsar 設計原理。

希望本文能夠對面臨同類場景的使用者與開發者提供參考。

業務訊息量大,對生產與消費耗時指標敏感

Data 專案的業務場景,具有非常明顯的特點。首先,業務系統執行過程中,訊息的生產、消費量都非常大,而且生產訊息的 QPS(每秒查詢率)波動性不明顯,即業務會在近乎固定的 QPS 生產和消費資料。

其次,業務方對系統的可靠性、生產耗時、消費耗時這幾個指標項比較敏感,需要在比較低的延遲基礎上完成業務處理流程。像 Data 專案這樣的業務場景和需求,對叢集的部署、運營和系統穩定性都提出了非常高的要求。

Data 專案叢集最大的特點是訊息量大、節點多,一個訂閱裡可高達數千消費者。雖然 Data 專案當前 Topic 總量並不多,但單個 Topic 對應的客戶端比較多,每個分割槽要對應 100+ 個生產者和 10000+個消費者。在對訊息系統選型時,團隊將訊息系統的低延遲、高吞吐設為關鍵指標。經過綜合對比市面上常見的訊息系統,Apache Pulsar 憑藉其功能和效能勝出。

Apache Pulsar 提供了諸多消費模型如獨佔、故障轉移、共享(Shared)和鍵共享(Key_Shared),其中在 Key_Shared 和 Shared 訂閱下可以支撐大量消費者節點。其他訊息流系統如 Kafka,因為消費者節點受限於分割槽個數,導致其在多分割槽時效能相對較低。(編者注: Pulsar 和 Kafka 的最新效能測評,敬請期待 StreamNative 即將釋出的 2022 版報告)

超大 Pulsar 叢集:單分割槽最大消費者數量超8K

目前 Data 專案業務資料接入兩套 Pulsar 叢集,分為 T-1 和 T-2。其中,T-1 對接的業務的客戶端 Pod(分為生產者和消費者,且不在同一個 Pod 上,部署在騰訊雲容器化平臺 (STKE) ,與 Pulsar 叢集在相同機房;T-2 對接業務的客戶端 Pod 與 Pulsar 叢集不在相同的機房(注:機房之間的資料時延相比同機房內部略高)。

伺服器側相關引數

| 序號 | 引數 | 詳情 | 備註 | | -- | ----- | ---- | ---- | | 1 | 叢集 數| 2 | T-1/ T-2 | | 2 | 機器數量 | 100+臺/叢集 | CVM IT5(非物理機/64 核/256G 記憶體/ 4 SSD) | | 3 | Broker 數量 | 100+ | 2.8.1.2(內部版本),部署三臺 Discovery 服務 | | 4 | Bookie 數量 | 100+ | | | 5 | Discovery 服務 | 3/叢集 | 與 Bookie 混合部署在同一臺機器上面 | | 6 | ZooKeeper 數量 | 5/每叢集 | 3.6.3 版本,單獨部署,使用 SA2.4XLARGE32 機型 | | 7 | 部署方式 | Broker+Bookie 混合部署在同一臺機器上面 | | | 8 | Topic 數 | 3/叢集 | | | 9 | 分割槽數 | 100+/Topic | | | 10 | 訊息副本數 | 2 | 副本寫入策略:E=5, W=2, A=2 | | 11 | 訊息儲存時長 | 1 天 | Retention\TTL 均配置為 1 天 | | 12 | namespace 數 | 3 | 每個 namespace下一個 Topic | | 13 | 當前訊息量/天(按照每條訊息大小 4k 均值評估) | 千億 /天/叢集 | | | 14 | 當前訊息量/分鐘 | 千萬/分鐘 | |

業務側相關引數

Data 專案業務側使用 Go 語言開發,接入 Pulsar 叢集使用 Pulsar Go Client 社群 Master 分支的最新版本。使用雲梯 STKE 容器方式部署。

| 序號 | 引數 | 描述 | 備註 | | -- | ------ | ------ | ----------------------- | | 1 | 單分割槽最大生產者數量 | 150左右 | | | 2 | 單分割槽最大消費者數量 | 1w個左右 | 單分割槽這個量,伺服器端需要維護大量的元資料資訊 | | 3 | 客戶端接入方式 | Go SDK | 目前使用 Master 分支最新程式碼 | | 4 | 生產者 Pod 數量 | 150左右 | | | 5 | 消費者 Pod 數量 | 1w個左右 | | | 6 | 客戶端部署平臺 | STKE | 騰訊內部的騰訊雲容器服務平臺 |

本文接下來將介紹 Pulsar 客戶端在多種場景下的效能調優,分別針對專案在使用 Pulsar 的過程中遇到的客戶端生產超時、客戶端頻繁斷開等情況進行原因解析,並提供我們的解決方案,供大家參考。

客戶端效能調優:問題與方案

調優一:客戶端生產超時,伺服器端排查

在大叢集下,導致客戶端生產訊息耗時較長或生產超時的原因有很多,我們先來看幾個伺服器端的原因,包括:

  • 訊息確認資訊過大(確認空洞)
  • Pulsar-io 執行緒卡死
  • Ledger 切換耗時過長
  • BookKeeper-io 單執行緒耗時過長
  • DEBUG 級別日誌影響
  • Topic 分割槽資料分佈不均\

接下來,針對每個可能的伺服器端原因,我們逐個進行解析。

解析 1:消費確認資訊過大(確認空洞)

與 Kafka、RocketMQ、TubeMQ 等不同,Apache Pulsar 不僅僅會針對每個訂閱的消費進度儲存一個最小的確認位置(即這個位置之前的訊息都已經被確認已消費),也會針對這個位置之後且已經收到確認響應的訊息,用 range 區間段的方式儲存確認資訊。

如下圖所示:

1.png

另外,由於 Pulsar 的每個分割槽都會對應一個訂閱組下的所有消費者。Broker 向客戶端推送訊息的時候,通過輪詢的方式(此處指 Shared 共享訂閱;Key_Shared 訂閱是通過 key 與一個消費者做關聯來進行推送)給每個消費者推送一部分訊息。每個消費者分別確認一部分訊息後,Broker 端可能會儲存很多這種確認區段資訊。

如下圖所示:

2.png

確認空洞是兩個連續區間之間的點,用於表示確認資訊的區間段的個數。確認空洞受相同訂閱組下消費者個數的多少、消費者消費進度的快慢等因素的影響。空洞較多或特別多即表示消費確認的資訊非常大。

Pulsar 會週期性地將每個消費組的確認資訊組成一個 Entry,寫入到 Bookie 中進行儲存,寫入流程與普通訊息寫入流程一樣。因此當消費組的消費確認空洞比較多、消費確認資訊比較大、寫入比較頻繁的時候,會對系統的整體響應機制產生壓力,在客戶端體現為生產耗時增長、生產超時增多、耗時毛刺明顯等現象。

在此情況下,可以通過減少消費者個數、提高消費者消費速率、調整儲存確認資訊的頻率和儲存的 range 段的個數等方式處理確認空洞。

解析 2:Pulsar-io 執行緒卡死

Pulsar-io 執行緒池是 Pulsar Broker 端用於處理客戶端請求的執行緒池。當這裡的執行緒處理慢或卡住的時候,會導致客戶端生產超時、連線斷連等。Pulsar-io 執行緒池的問題,可以通過 jstack 資訊進行分析,在 Broker 端體現為存在大量的 CLOSE_WAIT 狀態的連線, 如下圖所示:

3.png

Pulsar-io 執行緒池卡住的現象,一般為伺服器端程式碼 bug 導致,目前處理過的有: - 部分併發場景產生的死鎖; - 非同步程式設計 Future 異常分支未處理結束等。

除了程式自身的 bug 外,配置也可能引起執行緒池卡住。如果 Pulsar-io 執行緒池的執行緒長時間處於執行狀態,在機器 CPU 資源足夠的情況下,可以通過變更 broker.conf 中的 numioThreads 引數來調整 Pulsar-io 執行緒池中的工作執行緒個數,來提高程式的並行處理效能。

注意:Pulsar-io 執行緒池繁忙,本身並不會導致問題。 但是,Broker 端有一個後臺執行緒,會週期的判斷每一個 Channel(連線)有沒有在閾值時間內收到客戶端的請求資訊。如果沒有收到,Broker 會主動的關閉這個連線(相反,客戶端 SDK 中也有類似的邏輯)。因此,當 Pulsar-io 執行緒池被卡住或者處理慢的時候,客戶端會出現頻繁的斷連-重聯的現象。

解析 3:Ledger 切換耗時過長

Ledger 作為 Pulsar 單個分割槽訊息的一個邏輯組織單位,每個 Ledger 下包含一定大小和數量的 Entry,而每個 Entry 下會儲存至少一條訊息( batch 引數開啟後,可能是多條)。每個 Ledger 在滿足一定的條件時,如包含的 Entry 數量、總的訊息大小、存活的時間三個維度中的任何一個超過配置限制,都會觸發 Ledger 的切換。

Ledger 切換時間耗時比較長的現象如下:

4.png

當 Ledger 發生切換時,Broker 端新接收到或還未處理完的訊息會放在 appendingQueue 佇列中,當新的 Ledger 建立完成後,會繼續處理這個佇列中的資料,保證訊息不丟失。

因此,當 Ledger 切換過程比較慢時會導致訊息生產的耗時比較長甚至超時。這個場景下,一般需要關注下 ZooKeeper 的效能,排查下 ZooKeeper 所在機器的效能和 ZooKeeper 程序的 GC 狀況。

解析 4:BookKeeper-io 單執行緒耗時過長

目前 Pulsar 叢集中,BookKeeper 的版本要相對比較穩定,一般通過調整相應的客戶端執行緒個數、儲存資料時的 E、QW、QA 等引數可以達到預期的效能。

如果通過 Broker 的 jstack 資訊發現 BookKeeper 客戶端的 Bookkeeper-io 執行緒池比較繁忙時或執行緒池中的單個執行緒比較繁忙時,首先要排查 ZooKeeper、Bookie 程序的 Full GC 情況。如果沒有問題,可以考慮調整 Bookkeeper-io 執行緒池的執行緒個數和 Topic 的分割槽數。

解析 5:Debug 級別日誌影響

在日誌級別下,影響生產耗時的場景一般出現在 Java 客戶端。如客戶端業務引入的是 Log4j,使用的是 Log4j 的日誌輸出方式,同時開啟了 Debug 級別的日誌則會對 Pulsar Client SDK 的效能有一定的影響。建議使用 Pulsar Java 程式引入 Log4j 或 Log4j + SLF4J 的方式輸出日誌。同時,針對 Pulsar 包調整日誌級別至少到 INFO 或 ERROR 級別。

在比較誇張的情況下,Debug 日誌影響生產耗時的執行緒能將生產耗時拉長到秒級別,調整後降低到正常水平(毫秒級)。具體現象如下:

5.png

在訊息量特別大的場景下,伺服器端的 Pulsar 叢集需要關閉 Broker、Bookie 端的 Debug 級別的日誌列印(建議線網環境),直接將日誌調整至 INFO 或 ERROR 級別。

解析 6:Topic 分割槽分佈不均

Pulsar 會在每個 Namespace 級別配置 bundles ,預設 4 個,如下圖所示。每個 bundle range 範圍會與一個 Broker 關聯,而每個 Topic 的每個分割槽會經過 hash 運算,落到對應的 Broker 進行生產和消費。當過多的 Topic 分割槽落入到相同的 Broker 上面,會導致這個 Broker 上面的負載過高,影響訊息的生產和消費效率。

6.png

中度可信度描述已自動生成]()Data 專案在開始部署的時候,每個 Topic 的分割槽數和每個 Namespace 的 bundle 數都比較少。通過調整 Topic 的分割槽個數和 bundle 的分割個數,使得 Topic 的分割槽在 Broker 上面達到逐步均衡分佈的目的。

在 bundle 的動態分割和 Topic 的分佈調整上,Pulsar 還是有很大的提升空間,需要在 bundle 的分割演算法(目前支援 range_equally_dividetopic_count_equally_divide,預設目前支援 range_equally_divide,建議使用 topic_count_equally_divide)、Topic 分割槽的分佈層面,在保證系統穩定、負載均衡的情況下做進一步的提升。

調優二:客戶端頻繁斷開與重連

客戶端斷連/重連的原因有很多,結合騰訊 Data 專案場景,我們總結出客戶端 SDK 導致斷連的主要有如下幾個原因,主要包括: - 客戶端超時斷連-重連機制 - Go SDK 的異常處理 - Go SDK 生產者 sequence id 處理 - 消費者大量、頻繁的建立和銷燬

下面依次為大家解析這些問題的原因與解決方案。

解析 1:客戶端超時斷連-重連機制

Pulsar 客戶端 SDK 中有與 Broker 端類似邏輯(可參考#解析2部分內容),週期判斷是否在閾值的時間內收到伺服器端的資料,如果沒有收到則會斷開連線。

這種現象,排除伺服器端問題的前提下,一般問題出現在客戶端的機器資源比較少,且使用率比較高的情況,導致應用程式沒有足夠的 CPU 能力處理伺服器端的資料。此種情況,可以調整客戶端的業務邏輯或部署方式,進行規避處理。

解析 2:Go SDK 的異常處理

Pulsar 社群提供多語言的客戶端的接入能力,如支援 Java、Go、C++、Python 等。但是除了 Java 和 Go 語言客戶端外,其他的語言實現相對要弱一些。Go 語言的 SDK 相對於 Java 語言 SDK 還有很多地方需要完善和細化,比方說在細節處理上與 Java 語言 SDK 相比還不夠細膩。

如收到伺服器端的異常時,Java SDK 能夠區分哪些異常需要銷燬連線重連、哪些異常不用銷燬連線(如 ServerError_TooManyRequests),但 Go 客戶端會直接銷燬 Channel ,重新建立。

解析 3:Go SDK 生產者 Sequence id 處理

傳送訊息後,低版本的 Go SDK 生產者會收到 Broker 的響應。如果響應訊息中的 sequenceID 與本端維護的佇列頭部的 sequenceID 不相等時會直接斷開連線——這在部分場景下,會導致誤斷,需要區分小於和大於等於兩種場景。

這裡描述的場景和解析 1-客戶端超時中的部分異常場景,已經在高版本 Go SDK 中做了細化和處理,建議大家在選用 Go SDK 時儘量選用新的版本使用。目前,Pulsar Go SDK 也在快速的迭代中,歡迎感興趣的同學一起參與和貢獻。

解析 4:消費者大量且頻繁地建立和銷燬

叢集運維過程中在更新 Topic 的分割槽數後,消費者會大量且頻繁地建立和銷燬。針對這個場景,我們已排查到是 SDK  bug 導致,該問題會在 Java 2.6.2 版本出現。運維期間,消費者與 Broker 端已經建立了穩定的連線;運維過程中,因業務訊息量的增長需求,需要調整 Topic 的分割槽數。客戶端在不需要重啟的前提下,感知到了伺服器端的調整,開始建立新增分割槽的消費者,這是因為處理邏輯的 bug,會導致客戶端大量且頻繁地反覆建立消費者。如果你也遇到類似問題,建議升級 Java 客戶端的版本。 除了上面的斷連-重連場景外,在我們騰訊 Data 專案的客戶端還遇到過 Pod 頻繁重啟的問題。經過排查和分析,我們確定是客戶端出現異常時,丟擲 Panic 錯誤導致。建議在業務實現時,要考慮相關的容錯場景,在實現邏輯層面進行一定程度的規避。

調優三:升級 ZooKeeper

由於我們騰訊 Data 專案中使用的 Pulsar 叢集訊息量比較大,機器的負載也相對較高。涉及到 ZooKeeper,我們開始使用的是 ZooKeeper 3.4.6 版本。在日常運維過程中,整個叢集多次觸發 ZooKeeper 的一個 bug,現象如下截圖所示:

7.png

當前,ZooKeeper 專案已經修復該 Bug,感興趣的小夥伴可以點選該連線檢視詳情: http://issues.apache.org/jira/browse/ZOOKEEPER-2044。因此,在 Pulsar 叢集部署的時候建議打上相應的補丁或升級 ZooKeeper 版本。在騰訊 Data 專案中,我們則是選擇將 ZooKeeper 版本升級到 3.6.3 進行了對應處理。

小結:Pulsar 叢集運維排查指南

不同的業務有不同的場景和要求,接入和運維 Apache Pulsar 叢集時遇到的問題,可能也不太一樣,但我們還是能從問題排查角度方面做個梳理,以便找到相應規律提升效率。

針對 Apache Pulsar 叢集運維過程中遇到的問題,如生產耗時長、生產超時(timeout)、訊息推送慢、消費堆積等,如果日誌中沒有什麼明顯的或有價值的異常(Exception)、錯誤(Error) 之類的資訊時,可以從如下幾個方面進行排查: - 叢集資源配置及使用現狀 - 客戶端消費狀況 - 訊息確認資訊 - 執行緒狀態 - 日誌分析\ 下面針對每個方面做個簡要說明。

1. 叢集資源配置及使用現狀

首先,需要排查程序的資源配置是否能夠滿足當前系統負載狀況。可以通過 Pulsar 叢集監控儀表盤平臺檢視 Broker、Bookie、ZooKeeper 的 CPU、記憶體及磁碟 IO 的狀態。

其次,檢視 Java 程序的 GC 情況(特別是 Full GC 情況),處理 Full GC 頻繁的程序。如果是資源配置方面的問題,需要叢集管理人員或者運維人員調整叢集的資源配置。

2. 客戶端消費狀況

可以排查消費能力不足引起的反壓(背壓)。出現背壓的現象一般是存在消費者程序,但是收不到訊息或緩慢收到訊息。 可以通過 Pulsar 管理命令,檢視受影響 Topic 的統計資訊(stats),可重點關注未確認訊息數量數量(unackedMessages)backlog 數量消費訂閱型別,以及處理未確認訊息(unackmessage)比較大的訂閱/消費者。如果未確認訊息(unackmessage)數量過多,會影響 Broker 向客戶端的訊息分發推送。這類問題一般是業務側的程式碼處理有問題,需要業務側排查是否有異常分支,沒有進行訊息的 ack 處理。

3. 訊息確認資訊

如果叢集生產耗時比較長或生產耗時毛刺比較多,除了系統資源配置方面排查外,還需要檢視是否有過大的訊息確認資訊。

檢視是否有過大的確認空洞資訊,可以通過管理命令針對單個 Topic 使用 stats-internal 資訊,檢視訂閱組中的 individuallyDeletedMessages 欄位儲存的資訊大小。

訊息確認資訊的儲存過程與訊息的儲存過程是一樣的,過大的確認資訊如果頻繁下發儲存,則會對叢集儲存造成壓力,進而影響訊息的生產耗時。

4. 執行緒狀態

如果經過上面的步驟,問題還沒有分析清楚,則需要再排查下 Broker 端的執行緒狀態,主要關注 pulsar-iobookkeeper-iobookkeeper-ml-workers-OrderedExecutor 執行緒池的狀態,檢視是否有執行緒池資源不夠或者執行緒池中某個執行緒被長期佔用。

可以使用 top -p PID(具體pid) H 命令,檢視當前 CPU 佔用比較大的執行緒,結合 jstack 資訊找到具體的執行緒。

5. 日誌分析

如果經過上面所述步驟仍然沒有確認問題來源,就需要進一步檢視日誌,找到含有有價值的資訊,並結合客戶端、Broker 和 Bookie 的日誌及業務的使用特點、問題出現時的場景、最近的操作等進行綜合分析。

回顧與計劃

上面我們花了很大篇幅來介紹客戶端效能調優的內容,給到客戶端生產超時、頻繁斷開與重連、ZooKeeper 等相應的排查思路與解決方案,並彙總了常見 Pulsar 叢集問題排查指南 5 條建議,為在大訊息量、多節點、一個訂閱裡高達數千消費者的 Pulsar 應用場景運維提供參考。

當然,我們對 Pulsar 叢集的調優不會停止,也會繼續深入並參與社群專案共建。

由於單個 Topic 對應的客戶端比較多,每個客戶端所在的 Pod、Client 內部會針對每個 Topic 建立大量的生產者和消費者。由此就對 Pulsar SDK 在斷連、重連、生產等細節的處理方面的要求比較高,對各種細節處理流程都會非常的敏感。SDK 內部處理比較粗糙的地方會導致大面積的重連,進而影響生產和消費。目前,Pulsar Go SDK 在很多細節方面處理不夠細膩,與 Pulsar Java SDK 的處理有很多不一樣的地方,需要持續優化和完善。騰訊 TEG 資料平臺 MQ 團隊也在積極參與到社群,與社群共建逐步完善 Go SDK 版本。

另外,針對騰訊 Data 專案的特大規模和業務特點,Broker 端需要處理大量的元資料資訊,後續 Broker 自身的配置仍需要做部分的配置和持續調整。同時,我們除了擴容機器資源外,還計劃在 Apache Pulsar 的讀/寫執行緒數、Entry 快取大小、Bookie 讀/寫 Cache 配置、Bookie 讀寫執行緒數等配置方面做進一步的調優處理。