從12個方面詳細解讀Pulsar的主題與訂閱

張哥說技術發表於2023-03-14

1、主題(Topics)

Pulsar中主題類似一個URL,格式如下所示:

{persistent|non-persistent}://tenant/namespace/topic

主題的每一個部分說明如下:

  • persistent|non-persistent 表示持久化或非持久化
  • tenant 表示租戶
  • namespace 表示名稱空間
  • topic 主題的名稱

在Pulsar中你不需要顯示的建立主題,如果當客戶端向一個不存在的主題傳送訊息或訂閱訊息時,Pulsar會自動建立主題。

1.1 名稱空間(Namespaces)

名稱空間是一個租戶下的邏輯概念,名稱空間可以為應用程式管理主題的目錄層次結構。

1.2 訂閱型別(Subscription Types)

在Pulsar中,有四種訂閱型別,它們分別是exclusive、shared、failover和key_shared。四種訂閱模式的圖解如下所示:

從12個方面詳細解讀Pulsar的主題與訂閱

在Pulsar中,訂閱模式並不是建立消費者時指定的,而是在消費者啟動時指定的,並可以透過重啟改變訂閱型別,當一個消費組(訂閱)並沒有啟動真實的消費者,這個消費組的訂閱型別是未定義。

接下來對上述四種訂閱模式展開介紹。

1.2.1 獨佔模式(Exclusive)

Exclusive獨佔模式,一個訂閱(對標Kafka/RocketMQ消費組)只允許一個消費者訂閱,如果多個消費者嘗試使用該訂閱去消費訊息會丟擲異常,也就是說一個消費者處理主題的所有分割槽,如下圖所示:


從12個方面詳細解讀Pulsar的主題與訂閱

這個是Consumer B啟動時會報錯,只有ConsumerA能收到訊息。

注意:Pulsar預設的訂閱型別為 Exclusive。

1.2.2 主備/故障轉移模式(Failover)

Failover訂閱模式,可以允許多個消費者附加到一個訂閱上。消費者的佇列分配策略根據主題型別有所區別:

  • 如果是分割槽主題(一個主題擁有多個佇列的主題),broker服務端會根據消費者優先順序和消費者名稱字典順序進行排序,然後Broker會將主題中的分割槽平均分配給高優先順序的消費者,低優先順序消費者會成為分割槽的備消費者。
  • 如果是非分割槽主題,Broker按照消費者訂閱主題的順序選擇主消費者,其他的成為備消費者。

非分割槽主題的訂閱示例說明如下:

從12個方面詳細解讀Pulsar的主題與訂閱

對於分割槽主題的訂閱圖解說明如下:

從12個方面詳細解讀Pulsar的主題與訂閱

溫馨提示:Pulsar的Failover訂閱模式可以類比RocketMQ中的叢集消費模式下的平均分配演算法。

1.2.3 共享/輪詢模式(Shared)

在Shared訂閱模式下,多個消費者可以附加到同一個訂閱,訊息以迴圈分發的方式輪流傳送給各個消費者,並且任何給定的訊息只會傳遞給一個消費者,當一個消費者斷開連線時,所有傳送給它並未被確認的訊息將重新排程,再傳送給其他消費者。

Shared模式的訂閱圖解如下所示:

從12個方面詳細解讀Pulsar的主題與訂閱

上圖中的ConsumerA、ConsumerB、ConsumerC都會參與訊息消費。

Shared模式與Failover模式的主要差別是Shared模式並不和消費者繫結佇列,即Shared模式將所有分割槽的訊息當成一個整體來看,

使用Shared模式需要的幾個注意事項:

  • 無法保證訊息的順序性
  • Shared模式不能使用累積確認機制。

這種模式的最大缺點是不能啟用累積確認機制,訊息確認效率會降低,但其優勢也比較明顯,在解決單個佇列積壓方面,能充分所有消費者的處理能力。

1.2.4 基於Key的共享模式(Key_Shared)

在Key_Shared模式中,多個消費者可以附加到同一個訂閱。具有相同Key的訊息會分發給同一個消費者。

Key_Shared模式的訊息分發機制如下所示:

從12個方面詳細解讀Pulsar的主題與訂閱

Pulsar提供了Sticky(粘性)、Auto-split Hash Range(自動分割雜湊範圍)、Auto-split Consistent Hashing(自動分割一致性雜湊)這三種選擇演算法。

選擇消費者的基本過程如下所示:

從12個方面詳細解讀Pulsar的主題與訂閱

  • 將分片Key傳遞到一個雜湊函式,生成一個雜湊值
  • 將Key的雜湊值傳入到對應的分片演算法中,從而選擇出一個消費者。

當一個新的消費者加入或者一個消費者退出時,分配演算法都將會重新計算訊息到消費者的對映(選擇)。

接下來分別介紹這三種分配演算法底層的工作機制。

1.2.4.1 Auto-split Hash Range

分段雜湊取模演算法,設定的總區間為0~65536,再根據消費者個數來分配段,然後用消費者的key進行雜湊演算法得出hash值,再用這個hash值域65535取模得到最終的區間值,然後看該值落在哪個區間,就由哪個消費者來消費,示例如下:

從12個方面詳細解讀Pulsar的主題與訂閱

下面用圖解的方式說明當新增消費者或減少消費者,分段雜湊取模演算法是如何進行重新對映的:

從12個方面詳細解讀Pulsar的主題與訂閱

從12個方面詳細解讀Pulsar的主題與訂閱

1.2.4.2 Auto-split Consistent Hashing

一致性雜湊演算法。如果要開啟一致性hash演算法,需要在broker端subscriptionKeySharedUseConsistentHashing設定為true。

1.2.4.3 Sticky

這個類似Auto-split Hash Range,總的區間也是0~65536,與Auto-split Hash Range的區別是每一個消費者對應的區間是固定的,並且由使用者來保證每一個消費者的區間不會重疊。

從12個方面詳細解讀Pulsar的主題與訂閱

對應消費者C1的初始化程式碼:

KeySharedPolicySticky keySharedPolicy = KeySharedPolicy.stickyHashRange();
keySharedPolicy.ranges(new Range(0,16384), new Range(3276849152));

try (Consumer<String> consumer = client.newConsumer(Schema.STRING)
    .topic(Constants.testTopic)
    .subscriptionName(Constants.subscriptionName)
        .subscriptionType(Constants.subscriptionType)
        .subscriptionInitialPosition(Constants.subscriptionInitialPosition)
        .keySharedPolicy(keySharedPolicy)
        .subscribe()) {
   ... 省略 ....

Key_Shared訂閱模式要確保同一個Key的訊息在被統一時刻只會傳遞給單一的消費者處理,但當一個消費者新加入時,一些鍵的對映會發生改變,說明如下所示:

從12個方面詳細解讀Pulsar的主題與訂閱

為了避免相同key的消費被轉發給不同的消費者,Plusar的處理方法是新消費者建立時與Broker建立連線後,會將新消費者與當前的讀位置【可以理解已經下發給消費者的最大訊息偏移量】關聯起來,只有在讀位置之前的訊息全部確認後,後續訊息才會繼續推送到新的消費者。

但這樣做也會引發一個新的問題:那就是如果一個現有消費者堵塞了,並且沒有定義消費超時,那麼新的消費者將收不到任何訊息,直到被阻塞的消費者恢復或斷開連線。

當然我們也可以在消費端設定 allowOutOfOrderDelivery 為true,放鬆上面的限制,即新的消費者連線后里面可以接受訊息,這樣會短暫的破壞這一原則,在嚴格順序消費場景,會破壞順序語義。

值得注意的是當消費者使用Key_Shared訂閱型別時,需要在訊息傳送端禁用批處理或者啟用積壓Key的批處理機制。

在訊息傳送端啟用基於Key的批處理機制,Java版本的示例程式碼如下:

Producer<byte[]> producer = client.newProducer()
        .topic("my-topic")
        .batcherBuilder(BatcherBuilder.KEY_BASED)
        .create();

在使用Key_Shared訂閱模式時需注意如下幾點:

  • 在訊息傳送時必須為訊息指定一個Key
  • 無法使用累積確認
  • 當主題中最新訊息的位置為X時,預設新的消費者一上線後不會立馬消費訊息,必須等待X之前的訊息全部確認。

1.3 訂閱模式(Subscription modes)

訂閱模式主要是指的遊標型別。Pulsar在建立一個訂閱時,將建立一個遊標來記錄最後消費的位置。當消費者重新啟動時可以繼續從上一次消費位置繼續消費。

Pulsar中定義了Durable、NonDurable兩種訂閱模式:

  • Durable 遊標持久,如果Broker由於某一個錯誤重啟後,它可以從持久化儲存元件(BookKeeper)中恢復遊標,這樣訊息會從上一次持久的位置開始消費。Durable為預設方式。
  • NonDurable 遊標非持久化,一旦Broker停止,遊標就會丟失,並且永遠無法再恢復。

客戶端在構建消費者時可以透過如下程式碼改變訂閱模式:

 Consumer<byte[]> consumer = pulsarClient.newConsumer()
                .topic("my-topic")
                .subscriptionName("my-sub")
                .subscriptionMode(SubscriptionMode.Durable)
                .subscribe();

1.4 多主題訂閱(Multi-topic subscriptions)

當消費者訂閱Pulsar主題時,預設情況下它訂閱一個特定的主題,例如persistent://public/default/my-topic。然而,從Pulsar 1.23.0-incubating版本開始,Pulsar消費者可以同時訂閱多個主題。你可以用兩種方式定義主題列表:

  • 主題名稱可以使用正規表示式,例如persistent://public/default/finance-.*。
  • 可以顯示指定多個主題

溫馨提示:當使用正規表示式訂閱多個主題時,這些主題會限制在同一個名稱空間(Namespace)中。

當符合正規表示式的主題建立後,消費者能夠自動訂閱。當生產者向多個主題傳送訊息時,不能保證訊息在不同主題直接的順序性。

多主題訂閱的使用示例程式碼如下:

PulsarClient pulsarClient = // Instantiate Pulsar client object
// Subscribe to all topics in a namespace
Pattern allTopicsInNamespace = Pattern.compile("persistent://public/default/.*");
Consumer<byte[]> allTopicsConsumer = pulsarClient.newConsumer()
                .topicsPattern(allTopicsInNamespace)
                .subscriptionName("subscription-1")
                .subscribe();

1.5 分割槽主題(Partitioned topics)

普通主題僅僅由一個Broker提供服務,這限制了主題的最大吞吐量。分割槽主題是由多個Broker共同處理的特殊型別的主題,分割槽的主題的示意圖如下所示:

從12個方面詳細解讀Pulsar的主題與訂閱

正如上圖中那樣,Topic1擁有5個分割槽(P0,p1,p2,p3,p4),分別分在3個Broker中,其中broker1,broker2上分別建立了2個,而Broker3中建立了3個,關於分割槽在Broker的分佈情況由Pulsar內部根據負載決定其分佈。

訊息傳送者在訊息傳送時如何選擇分割槽由**(Routing mode)路由模式**來決定,Broker如何將訊息推送給消費者(訂閱者)則有訂閱型別來決定。

分割槽主題需要透過管理API顯示建立,分割槽的數量可以在建立主題時指定,具體命令如下:

./pulsar-admin topics create-partitioned-topic  persistent://codingw/codingw00/dw_test_03_05_000 --partitions 4

1.6 路由模式(Routing mode)

需要將訊息傳送到分割槽主題時必須指定路由模式(負載均衡演算法),Pulsar中的路由模式由MessageRoutingMode列舉型別定義,其選項說明如下:

  • RoundRobinPartition 輪詢模式,如果訊息不包含Key,則按批次進行輪詢所有分割槽,如果設定了key,則按Key的雜湊值與分割槽數取模。這個是Pulsar的預設行為。
  • SinglePartition 如果訊息沒有指定Key,生產者將隨機選擇一個分割槽,一旦分割槽被選擇後,後續所有訊息都將傳送該分割槽;如果指定了Key,則按key的雜湊進行雜湊。
  • CustomPartition 使用者自定義演算法,需要實現MessageRouter介面。

1.7 順序性保證(ordering guarantee)

訊息的順序性主要取決於訊息的路由模型(MessageRoutingMode)與訊息的key。如果訊息指定了Key,則無論是RoundRobinPartition還是SinglePartition,相同的key的訊息都會傳送到相同的分割槽。

在Pulsar中提供了兩種順序級保證:

  • Per-key-partition 分割槽級 同一個Key的訊息分佈在一個分割槽中,實現訊息在分割槽級順序,使用技巧:訊息附加Key並採取RoundRobinPartition、SinglePartition路由演算法。
  • Per-producer 來自同一個訊息傳送者的所有訊息保持順序,使用技巧:每條訊息不設定key並且採用SinglePartition路由演算法。

在Pulsar中提供了JavaStringHash、Murmur3_32Hash兩種Hash演算法,預設為JavaStringHash,但如果客戶端存在多種語言,則推薦使用Murmur3_32Hash。可以透過如下程式碼指定Hash演算法:

Producer<String> producer = pulsarClient.newProducer(Schema.STRING)
   .enableBatching(false)
   .topic(Constants.testTopic)
   .hashingScheme(HashingScheme.JavaStringHash)
   .create()

1.8 非持久化主題(Non-persistent topics)

預設情況下Pulsar會將所有未確認的訊息儲存在BookKeeper叢集中。因此Broker發生故障,未確認的訊息可以進行故障轉移。與之對應的是非持久化主題,Pulsar將這部分訊息只儲存在Broker的記憶體中,一旦Broker發生故障而重啟,這塊訊息會丟失。

非持久化主題的字首為:non-persistent,如下所示:

non-persistent://tenant/namespace/topic

對於非持久化主題,訊息只存在Broker的記憶體中,沒有額外的快取區,這意外著Broker接受到訊息生產者訊息後,會立即傳遞給所有連線的消費者,一旦Broker出現異常或者無法從記憶體中檢索訊息資料,則可能會導致訊息丟失,因此需要謹慎使用。

預設情況下非持久化主題在Broker上時開啟的,可以在Pulsar  Broker配置檔案中透過修改enableNonPersistentTopics的值為fasle禁用該機制。

non-persistent的主題元資訊不會持久化到Zookeeper,這就意味著如果擁有主題的Broker崩潰,這些非持久化主題的主題無法自動轉移到其他Broker。解決的辦法:Broker的配置檔案中將allowAutoTopicCreation設定為true,並將allowAutoTopicCreationType設定為non-partitioned。

1.9 系統主題(System topic)

系統主題是一個預定義的主題,供Pulsar內部使用。目前Pulsar中的系統主題如下圖所示:

從12個方面詳細解讀Pulsar的主題與訂閱

我們可以透過pulsar運維命令 pulsar-admin topics list 命令中增加 -ist或者--include-system-topic選項,用於顯示系統主題。

1.10 訊息重新投遞(Message redelivery)

Apache Pulsar使用至少消費一次的語義。也就是確保訊息至少被消費一次,要想啟用Broker的訊息重新投遞機制,在消費端可以透過如下機制:

  • Negative Acknowledgment 主動取消確認。
  • Acknowledgment Timeout 確認超時機制。
  • Retry letter topic 重試主題。

1.11 訊息保留與過期機制

預設情況下,Pulsar對訊息採取如下機制:

  • 立即刪除已被消費者確認的所有訊息
  • 將所有未確認的消費持久化儲存在backlog中

但我們可以覆蓋上述預設行為:

  • 訊息保留機制(Message retention)使您能夠儲存已被使用者確認的訊息
  • 訊息過期(Message expiry)使您能夠為尚未被確認的訊息設定生存時間(TTL)

關於訊息保留與過期刪除機制,將在後續文章中以專題方式詳細介紹其實現原理,對應官方文件:Message retention and expiry | Apache Pulsar

1.12 訊息重複刪除(Message deduplication)

如果沒有啟用訊息重複刪除機制,下圖展示了訊息被持久化多次的情況:

從12個方面詳細解讀Pulsar的主題與訂閱

也就是當訊息傳送者由於超時進行重試時,Broker收到了同一個客戶端多條內容相同的訊息,Broker會將多條內容相同的訊息儲存多次,造成訊息在服務端的重複儲存。

Pulsar支援訊息重複刪除機制,如果開啟了訊息重試機制,其工作示意如下所示:

從12個方面詳細解讀Pulsar的主題與訂閱

在Pulsar中,訊息重複刪除機制可以在namespace或主題級別開啟。

預設情況下在Broker、Namespace、Topic都是禁用訊息重複刪除機制的。

我們可以透過如下三種方式開啟訊息重複刪除機制:

  • 在Broker端進行全域性配置

  • 透過pulsar-admin namespaces命令在namespace級別設定

  • 透過pulsar-admin topics 命令在topic級別設定

在broker的配置檔案中我們可以配置如下引數:

  • brokerDeduplicationEnabled 在Broker是否開啟訊息重複刪除機制,預設為false。如果設定為true,則預設在namespace,topic級別開啟,如果設定為false,則可以單獨在namespace、topic級別啟用或禁用。
  • brokerDeduplicationMaxNumberOfProducers 設定識別訊息重複刪除所涉及到的最大生產者數量,預設為10000。
  • brokerDeduplicationEntriesInterval 重複訊息快照中條目數量,預設為1000。
  • brokerDeduplicationSnapshotIntervalSeconds 生成訊息快照的間隔週期,預設為120s。
  • brokerDeduplicationProducerInactivityTimeoutMinutes Broker丟棄不活動生產者快照的等待時間,如果一個生產者在指定時間內不與Broker保持心跳,超過掛職後會刪除對應的快照,單位為分鐘,預設為360,表示6小時。

預設情況下,在所有Pulsar名稱空間/主題上禁用訊息重複刪除。要在所有名稱空間/主題上啟用它,請將brokerDeduplicationEnabled引數設定為true並重新啟動Broker。

如果Broker端將brokerDeduplicationEnabled設定為false,我們也可以pulsar-admin namespaces set-deduplication或者pulsar-admin topics set-deduplication命令在namespace或者主題級別開啟訊息重複刪除。示例如下:

bin/pulsar-admin namespaces set-deduplication public/default --enable 

如果要禁用,命令如下:

bin/pulsar-admin namespaces set-deduplication public/default --disable

如果在Pulsar broker、名稱空間或主題中啟用了訊息去重功能,建議客戶端無限次地重試訊息,直到成功為止,否則可能會破壞順序保證,因為一些請求可能會超時,並且應用程式不知道請求是否成功新增到主題。

如果開啟訊息重複刪除機制,建議客戶端配合做如下兩件事情:

  • 為生產者制定一個全域性唯一的名稱
  • 將訊息傳送超時設定為0,表示無超時時間

討論點:訊息傳送端Pulsar提供的SinglePartition負載均衡演算法對應的應用場景是什麼?歡迎留言或微信dingwpmz,一起交流,一起成長。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024923/viewspace-2939556/,如需轉載,請註明出處,否則將追究法律責任。

相關文章