關於 Apache Pulsar
Apache Pulsar 是 Apache 軟體基金會頂級專案,是下一代雲原生分散式訊息流平臺,集訊息、儲存、輕量化函式式計算為一體,採用計算與儲存分離架構設計,支援多租戶、持久化儲存、多機房跨區域資料複製,具有強一致性、高吞吐、低延時及高可擴充套件性等流資料儲存特性。
GitHub 地址:http://github.com/apache/pulsar/
專案背景介紹
拉卡拉支付成立於 2005 年,是國內領先的第三方支付企業,致力於整合資訊科技,服務線下實體,從支付切入,全維度為中小微商戶的經營賦能。2011 年成為首批獲得《支付業務許可證》企業的一員,2019 年上半年服務商戶超過 2100 萬家。2019 年 4 月 25 日,登陸創業板。
功能需求
由於拉卡拉的專案組數量較多,各個專案在建設時,分別根據需要選擇了自己的訊息系統。這就導致一方面很多系統的業務邏輯和具體的訊息系統之間存在耦合,為後續系統維護和升級帶來麻煩;另一方面業務團隊成員對訊息系統的管理和使用水平存在差異,從而使得整體系統服務質量和效能不穩定;此外,同時維護多套系統,物理資源利用率和管理成本都比較高。
因此,我們計劃建設一套分散式基礎訊息平臺,同時為各個團隊提供服務。該平臺需要具備以下特性:高可靠、低耦合、租戶隔離、易於水平擴充套件、易於運營維護、統一管理、按需申請使用,同時支援傳統的訊息佇列和流式佇列。表 1 展示了這兩類服務應該具備的特性。
為什麼選擇 Apache Pulsar
大廠開源背書
現在可供使用者選擇的大廠開源訊息平臺有很多,架構設計大多類似,比如 Kafka 和 RocketMQ 都採用儲存與計算一體的架構,只有 Pulsar 採用儲存與計算分離的多層架構。我們比較選型的訊息系統有三個:Kafka、RocketMQ 和 Pulsar。測試之前,我們通過網上的公開資料,對三者的效能和功能進行了簡單的對比,表 2 為對比結果。從中可以看出 Pulsar 更符合我們的需求。
Pulsar 的架構優勢
Pulsar 是雲原生的分散式訊息流平臺,源於 Yahoo!,支援 Yahoo! 應用,服務 140 萬個 topic,日處理超過 1000 億條訊息。2016 年 Yahoo! 開源 Pulsar 並將其捐贈給 Apache 軟體基金會,2018 年 Pulsar 成為 Apache 軟體基金會的頂級專案。
作為一種高效能解決方案,Pulsar 具有以下特性:支援多租戶,通過多租戶可為每個租戶單獨設定認證機制、儲存配額、隔離策略等;高吞吐、低延遲、高容錯;原生支援多叢集部署,叢集間支援無縫資料複製;高可擴充套件,能夠支撐上百萬個 topic;支援多語言客戶端,如 Java、Go、Python、C++ 等;支援多種訊息訂閱模式(獨佔、共享、災備、Key_Shared)。
架構合理
Kafka 採用計算與儲存一體的架構,當 topic 數量較多時,Kafka 的儲存機制會導致快取汙染,降低效能。Pulsar 採用計算與儲存分離的架構(如圖 1)。無狀態計算層由一組接收和投遞訊息的 broker 組成,broker 負責與業務系統進行通訊,承擔協議轉換,序列化和反序列化、選主等功能。有狀態儲存層由一組 bookie 儲存節點組成,可以持久儲存訊息。
Broker 架構
Broker 主要由四個模組組成。我們可以根據實際需求對相應的功能進行二次開發。
- Dispatcher:排程分發模組,承擔協議轉換、序列化反序列化等。
- Load balancer:負載均衡模組,對訪問流量進行控制管理。
- Global replicator:跨叢集複製模組,承擔非同步的跨叢集訊息同步功能。
- Service discovery:服務發現模組,為每個 topic 選擇無狀態的主節點。
持久層(BookKeeper)架構
圖 3 為 Pulsar 中持久層的架構圖。Bookie 是 BookKeeper 的儲存節點,提供獨立的儲存服務。ZooKeeper 為後設資料儲存系統,提供服務發現以及後設資料管理服務。BookKeeper 架構屬於典型的 slave-slave 架構,所有 bookie 節點的角色都是 slave,負責持久化資料,每個節點的處理邏輯都相同;BookKeeper 客戶端為 leader 角色,承擔協調工作,由於其本身無狀態,所以可以快速實現故障轉移。
隔離架構
保證了 Pulsar 的優良效能,主要體現在以下幾個方面:
- IO 隔離:寫入、追尾讀和追趕讀隔離。
- 利用網路流入頻寬和磁碟順序寫入的特性實現高吞吐寫:傳統磁碟在順序寫入時,頻寬很高,零散讀寫導致磁碟頻寬降低,採取順序寫入方式可以提升效能。
- 利用網路流出頻寬和多個磁碟共同提供的 IOPS 處理能力實現高吞吐讀:收到資料後,寫到效能較好的 SSD 盤裡,進行一級快取,然後再使用非同步執行緒,將資料寫入到傳統的 HDD 硬碟中,降低儲存成本。
- 利用各級快取機制實現低延遲投遞:生產者傳送訊息時,將訊息寫入 broker 快取中;實時消費時(追尾讀),首先從 broker 快取中讀取資料,避免從持久層 bookie 中讀取,從而降低投遞延遲。讀取歷史訊息(追趕讀)場景中,bookie 會將磁碟訊息讀入 bookie 讀快取中,從而避免每次都讀取磁碟資料,降低讀取延時。
對比總結
左側為 Kafka、RabbitMQ 等訊息系統採用的架構設計,broker 節點同時負責計算與儲存,在某些場景中使用這種架構,可以實現高吞吐;但當 topic 數量增加時,快取會受到汙染,影響效能。
右側為 Pulsar 的架構,Pulsar 對 broker 進行了拆分,增加了 BookKeeper 持久層,雖然這樣會增加系統的設計複雜性,但可以降低系統的耦合性,更易實現擴縮容、故障轉移等功能。表 3 總結了分割槽架構和分片架構的主要特性。
基於對 Pulsar 的架構和功能特點,我們對 Pulsar 進行了測試。在作業系統層面使用 NetData 工具進行監控,使用不同大小的資料包和頻率進行壓測,測試的幾個重要指標是磁碟、網路頻寬等的波動情況。
測試結論如下:
- 部署方式:混合部署優於分開部署。broker 和 bookie 可以部署在同一個節點上,也可以分開部署。節點數量較多時,分開部署較好;節點數量較少或對效能要求較高時,將二者部署在同一個節點上較好,可以節省網路頻寬,降低延遲。
- 負載大小:隨著測試負載的增大,tps 降低,吞吐量穩定。
- 刷盤方式:非同步刷盤優於同步刷盤。
- 壓縮演算法:壓縮演算法推薦使用 LZ4 方式。我們分別測試了 Pulsar 自帶的幾種壓縮方式,使用 LZ4 壓縮演算法時,CPU 使用率最低。使用壓縮演算法可以降低網路頻寬使用率,壓縮比率為 82%。
- 分割槽數量:如果單 topic 未達到單節點物理資源上限,建議使用單分割槽;由於 Pulsar 儲存未與分割槽耦合,可以根據業務發展情況,隨時調整分割槽數量。
- 主題數量:壓測過程中,增加 topic 數量,效能不受影響。
- 資源約束:如果網路頻寬為千兆,網路會成為效能瓶頸,網路 IO 可以達到 880 MB/s;在網路頻寬為萬兆時,磁碟會成為瓶頸,磁碟 IO 使用率為 85% 左右。
- 記憶體與執行緒:如果使用物理主機,需注意記憶體與執行緒數目的比例。預設配置引數為 IO 執行緒數等於 CPU 核數的 2 倍。這種情況下,實體機核數為 48 核,如果記憶體設定得較小,比較容易出現 OOM 的問題。
除了上述測試以外,我們還複測了 Jack Vanlightly(RabbitMQ 的測試工程師)的破壞性測試用例,得到如下結論:
- 所有測試場景中,沒有出現訊息丟失與訊息亂序;
- 開啟訊息去重的場景中,沒有出現訊息重複。
支援團隊專業
另外,我們與 Apache Pulsar 專案的核心開發人員交流溝通時間較早,他們在 Yahoo! 和推特有過豐富的實踐經驗,預備成立公司在全世界範圍內推廣使用 Pulsar,並且會將中國作為最重要的基地,這為我們的使用提供了強有力的保障。現在大家也都知道,他們成立了 StreamNative 公司,並且已獲得多輪融資,隊伍也在不斷壯大。
Pulsar 在基礎訊息平臺的實踐
我們基於 Pulsar 構建的基礎訊息平臺架構如下圖,圖中綠色部分為基於 Pulsar 實現的功能或開發的元件。本節將結合實際使用場景,詳細介紹我們如何在實際使用場景中應用 Pulsar 及基於 Pulsar 開發的元件。
場景 1:流式佇列
1. OGG For Pulsar 介面卡
源資料儲存在 Oracle 中,我們希望實時抓取 Oracle 的變更資料,進行實時計算、資料分析、提供給下游業務系統查詢等場景。
我們使用 Oracle 的 OGG(Oracle Golden Gate) 工具進行實時抓取,它包含兩個模組:源端 OGG 和目標 OGG。由於 OGG 官方沒有提供 Sink 到 Pulsar 的元件,我們根據需要開發了 OGG For Pulsar 元件。下圖為資料處理過程圖,OGG 會抓取到表中每條記錄的增刪改操作,並且把每次操作作為一條訊息推送給 OGG For Pulsar 元件。OGG For Pulsar 元件會呼叫 Pulsar 客戶端的 producer 介面,進行訊息投遞。投遞過程中,需要嚴格保證訊息順序。我們使用資料庫表的主鍵作為訊息的 key,資料量大時,可以根據 key 對 topic 進行分割槽,將相同的 key 投遞到同一分割槽,從而保證對資料庫表中主鍵相同的記錄所進行的增刪改操作有序。
2. Pulsar To TiDB 元件
我們通過 Pulsar To TiDB 元件將抓取到的變更訊息儲存到 TiDB 中,對下游系統提供查詢服務。這一元件的處理邏輯為:
- 使用災備訂閱方式,消費 Pulsar 訊息。
- 根據訊息的 key 進行雜湊運算,將相同的 key 雜湊到同一持久化執行緒中。
- 啟用 Pulsar 的訊息去重功能,避免訊息重複投遞。假設 MessageID2 重複投遞,那麼資料一致性將被破壞。
3. Pulsar 的訊息持久化過程分析
Pulsar 的訊息持久化過程包括以下四步:
- OGG For Pulsar 元件呼叫 Pulsar 客戶端的 producer 介面,投遞訊息。
- Pulsar 客戶端根據配置檔案中的 broker 地址列表,獲取其中一個 broker 的地址,然後傳送 topic 歸屬查詢服務,獲取服務該 topic 的 broker 地址(下圖示例中為 broker2)。
- Pulsar 客戶端將訊息投遞給 Broker2。
- Broker2 呼叫 BookKeeper 的客戶端做持久化儲存,儲存策略包括本次儲存可選擇的 bookie 總數、副本數、成功儲存確認回覆數。
4. 資料庫表結構動態傳遞
OGG 使用 AVRO 方式進行序列化操作時,如果將多個表投遞到同一個 topic 中,AVRO Schema 為二級結構:wrapper schema 和 table schema。wrapper schema 結構始終不變,包含 table_name、schema_fingerprint、payload 三部分資訊;OGG 在抓取資料時,會感知資料庫表結構的變化並通知給 OGG For Pulsar,即表結構決定其 table schema,再由 table schema 生成對應的 schema_fingerprint。
我們將獲取到的 table schema 傳送並儲存在指定的 Schema topic 中。Data topic 中的訊息只包含 schema_fingerprint 資訊,這樣可以降低序列化後訊息包的大小。Pulsar To TiDB 啟動時,從 Schema topic 消費資料,使用 schema_fingerprint 為 Key 將 table schema 快取在記憶體中。反序列化 Data Topic 中的訊息時,從快取中根據 schema_fingerprint 提取 table schema,對 payload 進行反序列化操作。
5. 一致性保障
要保證訊息有序和去重,需要從 broker、producer、consumer 三方面進行設定。
Broker
- 在 namespace 級別開啟去重功能:bin/pulsar-admin namespaces set-deduplication namespace --enable
- 修復 / 優化 Pulsar 客戶端死鎖問題。2.7.1 版本已修復,詳細資訊可參考 PR 9552。
Producer
- pulsar.producer.batchingEnabled=false
在 producer 設定中,關閉批量傳送。如果開啟批量傳送訊息,則訊息可能會亂序。
- pulsar.producer.blocklfQueueFull=true
為了提高效率,我們採用非同步傳送訊息,需要開啟阻塞佇列處理,否則可能會出現訊息丟失。
呼叫非同步傳送超時,傳送至異常 topic。如果在非同步超時重發訊息時,出現訊息重複,可以通過開啟自動去重功能進行處理;其它情況下出現的訊息傳送超時,需要單獨處理,我們將這些訊息儲存在異常 topic 中,後續通過對賬程式從源庫直接獲取終態資料。
Consumer
實現攔截器:ConsumerInterceptorlmpl implements ConsumerInterceptor 配置確認超時:pulsarClient.ackTimeout(3000, TimeUnit.MILLISECONDS).ackTimeoutTickTime(500, TimeUnit.MILLISECONDS) 使用累積確認:consumer.acknowledgeCumulative(sendMessageID)
備註:配置確認超時引數,如果沒有在 ackTimeout 時間內進行消費確認的話,訊息將重新投遞。為了嚴格保證一致性,我們需要使用累計確認方式進行確認。
6. 訊息消費的確認方式
假如在 MessageID 為 1 的訊息已確認消費成功,開始採用累積確認方式,此時正在確認 MessageID 為 3 的訊息,則已消費但未確認的 MessageID 為 2 的訊息也會被確認成功。假如在“確認超時”時間內一直未收到確認,則會按照原順序重新投遞 MessageID 為 2、3、4、5 的訊息。
假如採用單條確認方式,圖中 MessageID 為 1、3、4 的訊息確認消費成功,而 MessageID 為 2 的訊息“確認超時”。在這種情況下,如果應用程式處理不當,未按照消費順序逐條確認,則出現訊息“確認超時”時,只有發生超時的訊息(即 MessageID 為 2 的訊息)會被重新投遞,導致消費順序發生錯亂。
總結:佇列消費模式建議使用單條確認方式,流式消費模式建議使用累積確認方式。
7. 訊息確認超時(客戶端)檢測機制
確認超時機制中有兩個引數,超時時間和輪詢間隔。超時檢測機制通過一個雙向佇列 + 多個 HashSet 實現。HashSet 的個數為(超時時間)除以(輪詢間隔)後取整,因此每次輪詢處理一個 HashSet,從而有效規避全域性鎖帶來的效能損耗。
場景 2:訊息佇列:OpenMessaging 協議實現(透明層協議)
我們過去使用的很多業務系統都和訊息系統強耦合,導致後續升級和維護很麻煩,因此我們決定使用 OpenMessaging 協議作為中間層進行解耦。
- 通過 Pulsar 實現 OpenMessaging 協議。
- 開發框架(基於 spring boot)呼叫 OpenMessaging 協議介面,傳送和接收訊息。
場景 3:流式佇列:自定義 Kafka 0.8-Source(Source 開發)
Pulsar IO 可以輕鬆對接到各種資料平臺。我們的部分業務系統使用的是 Kafka 0.8,官方沒有提供對應的 Source,因此我們根據 Pulsar IO 的介面定義,開發了 Kafka 0.8 Source 元件。
場景 4:流式佇列:Function 訊息過濾(訊息過濾)
我們通過 Pulsar Functions 把 Pulsar IDC 叢集訊息中的敏感欄位(比如身份證號,手機號)脫敏後實時同步到雲叢集中,供雲上應用消費。
場景 5:流式佇列:Pulsar Flink Connector 流式計算(流式計算)
商戶經營分析場景中,Flink 通過 Pulsar Flink Connector 連線到 Pulsar,對流水資料根據不同維度,進行實時計算,並且將計算結果再通過 Pulsar 持久化到 TiDB 中。從目前的使用情況來看,Pulsar Flink Connector 的效能和穩定性均表現良好。
場景 6:流式佇列:TiDB CDC 適配(TiDB 適配)
我們需要基於 TiDB 資料變更進行實時抓取,但 TiDB CDC For Pulsar 序列化方式不支援 AVRO 方式,因此我們針對這一使用場景進行了定製化開發,即先封裝從 TiDB 發出的資料,再投遞到 Pulsar 中。TiDB CDC For Pulsar 元件的開發語言為 Go 語言。
未來規劃
我們基於 Pulsar 構建的基礎訊息平臺有效提高了物理資源的使用效率;使用一套訊息平臺簡化了系統維護和升級等操作,整體服務質量也得以提升。我們對 Pulsar 的未來使用規劃主要包括以下兩點:
- 陸續下線其它訊息系統,最終全部接入到 Pulsar 基礎訊息平臺;
- 深度使用 Pulsar 的資源隔離和流控機制。
在實踐過程中,藉助 Pulsar 諸多原生特性和基於 Pulsar 開發的元件,新訊息平臺完美實現了我們預期的功能需求。
作者簡介
姜殿璟,基礎架構部架構師,負責基礎訊息平臺及其生態的建設與運營,團隊合作將 Apache Pulsar 引入到拉卡拉核心架構中,並且在強一致性的流式消費場景和佇列消費場景中取得了成功實踐,目前主要負責 Pulsar 效能調優、新功能開發及 Pulsar 生態整合。
點選連結,獲取 Apache Pulsar 硬核乾貨資料!