Pulsar 介紹
Apache Pulsar 作為 Apache 軟體基金會頂級專案,是下一代雲原生分散式訊息流平臺,集訊息、儲存、輕量化函式計算為一體,採用計算與儲存分離架構設計,支援多租戶、持久化儲存、跨區域複製、具有強一致性、高吞吐、低延遲及高可擴充套件性等流資料儲存特性。
Pulsar 誕生於 2012 年,最初的目的是為在 Yahoo 內部,整合其他訊息系統,構建統一邏輯、支撐大叢集和跨區域的訊息平臺。當時的其他訊息系統(包括 Kafka),都不能滿足 Yahoo 的需求,比如大叢集多租戶、穩定可靠的 IO 服務質量、百萬級 Topic、跨地域複製等,因此 Pulsar 應運而生。
Pulsar 的關鍵特性如下:
- Pulsar 的單個例項原生支援多個叢集,可跨機房在叢集間無縫地完成訊息複製。
- 極低的釋出延遲和端到端的延遲。
- 可無縫擴充套件到超過一百萬個 Topic。
- 簡單的客戶端 API,支援 Java、Go、Python 和 C++.
- 支援多種 Topic 訂閱模式 (獨佔訂閱、共享訂閱、故障轉移訂閱)。
- 通過 Apache BookKeeper 提供的持久化訊息儲存機制保證訊息傳遞。
- 由輕量級的 Serverless 計算框架 Pulsar Functions 實現流原生的資料處理。
- 基於 Pulsar Functions 的 serverless connector 框架 Pulsar IO 使得資料更容易移入、移出 Apache Pulsar。
- 分層儲存可在資料陳舊時,將資料從熱儲存解除安裝到冷/長期儲存(如S3、GCS)中。
社群:
目前 Apache Pulsar 在 Github 的 star 數量是10K+,共有470+個 contributor。並且正在持續更新,社群的活躍度比較好。
概念
Producer
訊息的源頭,也是訊息的釋出者,負責將訊息傳送到 topic。
Consumer
訊息的消費者,負責從 topic 訂閱並消費訊息。
Topic
訊息資料的載體,在 Pulsar 中 Topic 可以指定分為多個 partition,如果不設定預設只有一個 partition。
Broker
Broker 是一個無狀態元件,主要負責接收 Producer 傳送過來的訊息,並交付給 Consumer。
BookKeeper
分散式的預寫日誌系統,為訊息系統比 Pulsar 提供儲存服務,為多個資料中心提供跨機器複製。
Bookie
Bookie 是為訊息提供持久化的 Apache BookKeeper 的服務端。
Cluster
Apache Pulsar 例項叢集,由一個或多個例項組成。
雲原生架構
Apache Pulsar 採用計算儲存分離的一個架構,不與計算邏輯耦合在一起,可以做到資料獨立擴充套件和快速恢復。計算儲存分離式的架構隨著雲原生的發展,在各個系統中出現的頻次也是越來越多。Pulsar 的 Broker 層就是一層無狀態的計算邏輯層,主要負責接收和分發訊息,而儲存層由 Bookie 節點組成,負責訊息的儲存和讀取。
Pulsar 的這種計算儲存分離式的架構,可以做到水平擴容不受限制,如果系統的 Producer、Consumer 比較多,那麼就可以直接擴容計算邏輯層 Broker,不受資料一致性的影響。如果不是這種架構,我們在擴容的時候,計算邏輯和儲存都是實時變化的,就很容易受到資料一致性的限制。同時計算層的邏輯本身就很複雜容易出錯,而儲存層的邏輯相對簡單,出錯的概率也比較小,在這種架構下,如果計算層出現錯誤,可以進行單方面恢復而不影響儲存層。
Pulsar 還支援資料分層儲存,可以將舊訊息移到便宜的儲存方案中,而最新的訊息可以存到 SSD 中。這樣可以節約成本、最大化利用資源。
叢集架構
Pulsar的叢集由多個 Pulsar 例項組成的,其中包括
- 多個 Broker 例項,負責接收和分發訊息
- 一個 ZooKeeper 服務,用來協調叢集配置
- BookKeeper 服務端叢集 Bookie,用來做訊息的持久化
- 叢集之間通過跨地域複製進行訊息同步
設計原理
pulsar 採用釋出-訂閱的設計模式(pub-sub),在該設計模式中 producer 釋出訊息到 topic ,consumer 訂閱 topic 中的訊息並在處理完成之後傳送 ack 確認。
Producer
傳送模式
Producer 傳送訊息有兩種模式,可以同步(sync)或非同步(async)的方式釋出訊息到 broker。
同步傳送訊息是 Producer 傳送訊息以後要等到 broker 的確認以後才會認為訊息傳送成功,如果沒有收到確認就認為訊息傳送失敗。
MessageId messageId = producer.send("同步傳送的訊息".getBytes(StandardCharsets.UTF_8));
非同步傳送訊息是 Producer 傳送訊息,將訊息放到阻塞佇列中並立即返回。不需要等待 broker 的確認。
CompletableFuture<MessageId> messageIdCompletableFuture = producer.sendAsync( "非同步傳送的訊息".getBytes(StandardCharsets.UTF_8));
訪問方式
Pulsar 為 Producer 提供多種不同型別的 Topic 訪問模式:
Shared
預設情況下多個生產者可以釋出訊息到同一個 Topic 。
Exclusive
要求生產者以獨佔模式訪問 Topic,在此模式下 如果 Topic 已經有了生產者,那麼其他生產者在連線就會失敗報錯。
"Topic has an existing exclusive producer: standalone-0-12"
WaitForExclusive
如果主題已經連線了生產者,則將當前生產者掛起,直到生產者獲得了 Exclusive 訪問許可權。
可以通過以下方式來設定訪問模式:
Producer<byte[]> producer = pulsarClient.newProducer().accessMode(ProducerAccessMode.Shared).topic("test-topic-1").create();
壓縮
Pulsar 支援對 Producer 傳送的訊息進行壓縮,Pulsar 支援以下型別的壓縮:
LZ4
LZ4 是無失真壓縮演算法,提供每核 > 500 MB/s 的壓縮速度,可通過多核 CPU 進行擴充套件。它具有極快的解碼器,每個核心的速度達數 GB/s,通常達到多核系統上的 RAM 速度限制。
ZLIB
zlib旨在成為一個免費的、通用的、不受法律約束的——也就是說,不受任何專利保護——無損資料壓縮庫,幾乎可以在任何計算機硬體和作業系統上使用。zlib 資料格式本身可以跨平臺移植。
ZSTD
Zstandard 是一種快速壓縮演算法,提供高壓縮率。它還為小資料提供了一種特殊模式,稱為字典壓縮。參考庫提供了非常廣泛的速度/壓縮權衡,並由極快的解碼器提供支援。
snappy
Snappy是一個壓縮/解壓庫。它不以最大壓縮為目標,也不與任何其他壓縮庫相容;相反,它的目標是非常高的速度和合理的壓縮。
批處理
Producer 支援在單次請求傳送批量訊息,可以通過( acknowledgmentAtBatchIndexLevelEnabled = true ) 來開啟批處理。當一個批次的所有訊息都被 Consumer 消費確認後,這個批次的訊息才會被確認傳送成功。如果出現了意外故障,可能會導致這個批次的所有訊息重新投遞,包括已經被確認消費過得訊息。
為了避免這個問題,Pulsar 2.6.0 以後開始引入了批量索引確認,由 broker 來維護每個索引的確認狀態,避免將已確認的訊息傳送給 Consumer。當這個批次的所有訊息索引都被確認後,將會刪除批訊息。
訊息分塊
Producer 支援將訊息分塊,可以使用 chunkingEnabled=true 啟動分塊,啟用分塊時,要注意一下幾點:
- 不能同時啟動批處理和分塊,要啟動分塊,必須提前禁用批處理。
- 僅持久化主題支援分塊。
- 僅對獨佔和故障轉移訂閱型別支援分塊。
當分塊被啟用的時候,如果 Producer 傳送的訊息超過最大負載大小,則 Producer 會將原始訊息拆分成多個塊訊息,然後將每個塊傳送給 broker,分塊訊息在broker內的儲存和正常的訊息是一樣的。只是在 Consumer 消費的時候,發現這是一個分塊訊息,就需要在快取分塊訊息,當收集完一個訊息的所有分塊組合成原始放到接收者佇列裡面給客戶端消費。如果 Producer 未能傳送所有的分塊訊息, Consumer 有過期處理機制,預設的過期時間為一個小時。
一個生產者和一個有序的消費者的分塊訊息模型:
多個生產者和一個有序消費者的分塊訊息模型:
Consumer
Consumer 是訊息的消費者,通過訂閱指定的 Topic 從 broker中獲取訊息。
Consumer 向 broker 傳送流請求以獲取訊息。在 Consumer 端有一個佇列來接收從 broker 推送的訊息。您可以使用receiverQueueSize引數配置佇列大小。預設大小為1000)。每次consumer.receive()呼叫時,都會從緩衝區中取出一條訊息。
接收方式
訊息可以同步(sync)或非同步(async)的方式從 broker 接收,還可以通過MessageListener返回訊息:接收訊息後回撥使用者的MessageListener。
同步接收訊息將被阻塞,直到有訊息可用。
Message<byte[]> message = consumer.receive(); System.out.println("接收訊息內容: " + new String(message.getData())); consumer.acknowledge(message); // 確認消費訊息
非同步接收訊息將會立即返回一個未來值。使用CompletableFuture。如果CompletableFuture完成訊息接收,應該隨後呼叫receiveAsync(),否則,它會在應用程式中建立接收請求的積壓。
可以通過呼叫.cancel(false) ( CompletableFuture.cancel(boolean) )在完成之前取消返回的
future
,以將其從接收請求的積壓中刪除。CompletableFuture<Message<byte[]>> messageCompletableFuture = consumer.receiveAsync(); Message<byte[]> message = messageCompletableFuture.get(); System.out.println("接收訊息內容: " + new String(message.getData())); consumer.acknowledge(message); // 確認消費訊息
客戶端庫為消費者提供偵聽器實現。例如,Java 客戶端提供了一個MesssageListener 介面,每當收到新訊息時都會呼叫received方法。
pulsarClient.newConsumer().topic("test-topic-1").messageListener((MessageListener<byte[]>) (consumer, msg) -> { System.out.println("接受訊息內容: " + new String(msg.getData())); try { consumer.acknowledge(msg); //確認消費訊息 } catch (PulsarClientException e) { consumer.negativeAcknowledge(msg); // 訊息消費失敗 } }).subscriptionName("test-subscription-1").subscribe();
消費確認
成功確認:
當 Consumer 成功消費一條訊息以後,需要向 broker 傳送訊息確認。只有在所有的訂閱都確認後 ,訊息才會被刪除。如果需要儲存已經確認消費成功的訊息,需要設定訊息儲存策略。否則 Pulsar 會立即刪除所有確認消費成功的訊息。
對於批處理的訊息,可以通過以下兩種方式確認訊息:
- 訊息被單獨確認。通過單獨確認,消費者需要確認每條訊息並向代理髮送確認請求。
- 訊息被累積確認。通過累積確認,消費者只需要確認它收到的最後一條訊息。流中直到提供的訊息的所有訊息都不會重新傳遞給該消費者。
失敗確認:
當 Consumer 消費失敗一條訊息,想要再次消費該訊息時,需要向 broker 傳送否定確認。說明訊息沒有消費成功,這樣 broker 會重新投遞這個訊息。訊息會被單獨或累積地否定確認,具體取決於消費訂閱型別:
- 在獨佔和故障轉移訂閱型別中,消費者僅否定他們收到的最後一條訊息。
- 在 shared 和 Key_Shared 訂閱型別中,您可以單獨否定確認訊息。
確認超時:
如果一條訊息沒有消費成功,想要觸發 broker 自動重發訊息,可以採用未確認訊息自動重發機制。建議優先使用失敗確認,可以更加精準的控制單個訊息的重新投遞。
死信佇列
Apache Pulsar 內建死信佇列特性,當訊息處理失敗收到否認 Ack 時,Apache Pulsar 可以自動重試。超過重試次數可以將訊息存放至死信佇列,以確保新訊息可以得到處理。
Consumer<byte[]> consumer = pulsarClient.newConsumer(Schema.BYTES)
.topic(topic)
.subscriptionName("my-subscription")
.subscriptionType(SubscriptionType.Shared)
.deadLetterPolicy(DeadLetterPolicy.builder()
.maxRedeliverCount(maxRedeliveryCount)
.build())
.subscribe();
消費模型
Apache Pulsar 提供了佇列模型和流模型的統一,在 Topic 級別只需要儲存一份資料,同一份資料可多次消費,以流、佇列等方式計算不同的訂閱模型,大大提升了靈活度。Apache Pulsar 中有四種訂閱型別:exclusive、shared、failover和key_shared。這些型別如下圖所示。
Topic
Topic 命名
在 Pulsar 中 Topic 負責將訊息從生產者傳遞到消費者,主題名稱是具有明確定義結構的 URL:
{persistent|non-persistent}://tenant/namespace/topic
persistent / non-persistent
表示主題的型別,主題分為持久化和非持久化主題,預設是持久化的型別。持久化的主題會將訊息儲存到磁碟上,而非持久化的主題就不會將訊息儲存到磁碟。
tenant
Pulsar 例項中主題的租戶,租戶對於 Pulsar 中的多租戶至關重要,並且分佈在叢集中。
namespace
將相關聯的 Topic 作為一個組來管理,是管理 Topic 的基本單元。每個租戶可以有一個或多個名稱空間。
topic
Pulsar 中的主題被命名為通道,用於將訊息從生產者傳輸到消費者。
在 Pulsar 中不需要顯示的建立 Topic,如果嘗試向不存在的 Topic 傳送或接收訊息,會在預設租戶和名稱空間中建立 Topic。
Topic 分割槽
普通的 Topic 被儲存在單個 broker 中,而 Topic 可以分為多個 partition,分別儲存到不同的 broker 中,由多個 broker 來處理,這大大的提升了 Topic 的吞吐量。
如上圖,Topic1 被分為了5個分割槽,分別是 P0、P1、P2、P3、P4。這5個分割槽被分到了3個 Broker(Broker1、Broker2、Broker3) 中,由於分割槽的數量多於代理的數量,前兩個代理分別處理兩個分割槽,第三個代理只處理一個,Pulsar 會自動處理這種分割槽分佈。
釋出到分割槽主題時,必須指定路由模式。路由模式決定了每條訊息應該釋出到哪個分割槽。
共有三種MessageRoutingMode 可用:
RoundRobinPartition
如果沒有提供金鑰,生產者將以迴圈方式跨所有分割槽釋出訊息,以實現最大吞吐量。 請注意,迴圈不是針對單個訊息進行的,而是設定為與批處理延遲相同的邊界,以確保批處理有效。而如果在訊息上指定了一個鍵,分割槽的生產者將雜湊鍵並將訊息分配給特定的分割槽。
SinglePartition
如果沒有提供金鑰,分割槽生產者將隨機選擇一個分割槽並將所有訊息釋出到該分割槽中。 如果在訊息上提供了金鑰,則分割槽生產者將雜湊金鑰並將訊息分配給特定分割槽。
CustomPartition
使用將被呼叫的自定義訊息路由器實現來確定特定訊息的分割槽。
多租戶
Apache Pulsar 的多租戶特性可以滿足企業的管理需求,租戶、名稱空間是 Apache Pulsar 支援多租戶的 2 個核心概念。
- 在租戶級別,Pulsar 為特定的租戶預留合適的儲存空間、應用授權與認證機制。
- 在名稱空間級別,Pulsar 提供一些列的配置策略,包括儲存配額、流控、訊息過期策略和名稱空間之間的隔離策略。
跨地域複製
跨地域複製機制為大型分散式系統多資料中心提供了冗餘,防止服務無法正常執行。也為跨地域生產、跨地域消費提供了基礎。
分層儲存
Pulsar 的分層儲存功能允許將較舊的積壓資料從 BookKeeper 移動到長期且更便宜的儲存,降低儲存成本,同時仍然允許客戶端訪問積壓,就好像沒有任何變化一樣。管理員可配置名稱空間大小閾值策略,實現資料自動遷移到長期儲存。
元件
Pulsar Schema Registry
Schema registry 使 Producer 和 Consumer 通過 broker 即可溝通 Topic 的資料結構,而不需要藉助外部協調機制,從而避免各種潛在的問題如序列化、反序列化問題。
Pulsar Functions
Pulsar Functions 是一個輕量化計算框架,它給使用者提供一個部署簡單、運維簡單、API 簡單的 FAAS(Function as a service)平臺,目標是幫助使用者輕鬆建立各種級別的複雜處理邏輯,而無需部署單獨的計算系統。
Pulsar IO
Pulsar IO 支援 Apache Pulsar 與外部系統如資料庫、其他訊息系統進行互動,如 Apache Cassandra 等系統,使用者無需關注實現細節,用一個命令就可以快速執行。
Source 將資料從外部系統匯入 Apache Pulsar,Sink 將資料從 Apache Pulsar 匯出到外部系統。
Pulsar SQL
Pulsar SQL 是構建在 Apache Pulsar 之上的查詢層,支援使用者動態查詢儲存在 Pulsar 內部的所有舊資料流。使用者可以在同一系統上注入資料的同時進行資料流的清理、轉化和查詢,極大簡化了資料管道。
快速上手
二進位制安裝
這裡就簡單介紹一個 Pulsar Demo 的案例,只安裝一個 Pulsar 的服務端,首先通過以下命令下載 Pulsar:
wget https://archive.apache.org/dist/pulsar/pulsar-2.8.1/apache-pulsar-2.8.1-bin.tar.gz
下載到本地以後,通過以下命令解壓 apache-pulsar-2.8.1-bin.tar.gz 壓縮包:
tar xvfz apache-pulsar-2.8.1-bin.tar.gz
然後 cd 到 apache-pulsar-2.8.1 資料夾下,包含以下目錄 :
- bin :Pulsar 的命令列工具,例如
[pulsar](https://pulsar.apache.org/docs/en/reference-cli-tools#pulsar)
和[pulsar-admin](https://pulsar.apache.org/tools/pulsar-admin/)
。 - conf : Pulsar 的配置檔案,包括broker 配置、ZooKeeper 配置等。
- examples : 包含Pulsar 函式示例的Java JAR 檔案。
- lib : Pulsar 使用的JAR)檔案。
- licenses :
.txt
以 Pulsar程式碼庫的各種元件的形式存在的許可證檔案。
獨立啟動 Pulsar
當您在本地安裝好 Pulsar 以後,您就可以使用[pulsar](https://pulsar.apache.org/docs/en/reference-cli-tools#pulsar)
儲存在bin
目錄中的命令啟動本地叢集,並指定您要以獨立模式啟動 Pulsar。
$ bin/pulsar standalone
如果你已經成功啟動了 Pulsar,你會看到這樣的INFO
level 日誌訊息:
2017-06-01 14:46:29,192 - INFO - [main:WebSocketService@95] - Configuration Store cache started
2017-06-01 14:46:29,192 - INFO - [main:AuthenticationService@61] - Authentication is disabled
2017-06-01 14:46:29,192 - INFO - [main:WebSocketService@108] - Pulsar WebSocket Service started
一個傳送/接收訊息案例
public class PulsarDemo {
private static PulsarClient PULSAR_CLIENT = null;
static {
try {
// 建立pulsar客戶端
PULSAR_CLIENT = PulsarClient.builder().serviceUrl("pulsar://127.0.0.1:6650").build();
} catch (PulsarClientException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws PulsarClientException {
// 建立生產者
Producer<byte[]> producer = PULSAR_CLIENT.newProducer().topic("test-topic-1").create();
// 同步傳送訊息
MessageId messageId = producer.send("同步傳送的訊息".getBytes(StandardCharsets.UTF_8));
System.out.println("訊息傳送成功,訊息id: " + messageId);
// 建立消費者
Consumer<byte[]> consumer = PULSAR_CLIENT.newConsumer().topic("test-topic-1")
.subscriptionName("test-subscription-1").subscribe();
//獲取一個訊息內容
Message<byte[]> message = consumer.receive();
System.out.println("接收的訊息內容: " + new String(message.getData()));
// 確認消費成功,以便pulsar刪除消費成功的訊息
consumer.acknowledge(message);
//關閉客戶端
producer.close();
consumer.close();
PULSAR_CLIENT.close();
}
}
輸出:
訊息傳送成功,訊息id: 66655:0:-1:0
接收的訊息內容: 同步傳送的訊息