譯文|如何將 Pulsar 用作訊息佇列

ApachePulsar發表於2020-09-22
原文作者為 Luk Perkins,來自 Splunk 團隊。
文章翻譯已獲得原作者授權。

訊息佇列是大多數大規模資料架構的主要元件。如果必須對資料進行實時處理,那麼使用訊息佇列是很好的選擇。

image

資料處理管道會發生各種故障,資料 consumer 可能會受到延遲或完全不能工作,網路分割槽可能會暫時切斷整個 consumer 組與資料管道的連線等。

有些情況必須使用訊息佇列,例如:

  • 開發拼車應用程式,不考慮高峰時段的使用峰值,需要確保每個乘車請求最終只匹配到一位司機
  • 金融級事務交易管道需要同步請求處理,以防止資料丟失
  • 搭建基於微服務的處理管道,前端為具有多個寫入端點的 REST API(每秒進行數千次運算),需要確保即使後端微服務出現故障,所有的工作物件都保留在系統中

訊息佇列如何工作

下圖為訊息佇列常見工作方式(並對故障做出響應)的示意圖:

image

在上圖中,producer 1、2、3 和 4 通過訊息 broker 將訊息傳送到管道,而 consumer 1、2、3 和 4 處理(然後確認)這些訊息。在本示例中,當 consumer 1 出現故障時,會出現非常嚴重的問題。Producer 會繼續將資料傳送到系統中,但 consumer 1 不能繼續處理訊息。Broker 應該開始儲存所有原本將會用於 consumer 1 的訊息資料,直到 consumer 1 能夠繼續處理訊息。

從這個示例可以看出,對於堆疊中任何重要的訊息佇列而言,穩定的儲存元件都必不可少。幸運的是,訊息佇列與支援訊息佇列的儲存系統一樣效能良好。如果儲存元件易發故障、受到損壞,或執行緩慢,因而即便僅有一個元件出現故障,也不能很好地應對,那麼強烈建議大家更換儲存部件。

引入 Apache Pulsar

一般而言,由不同的系統處理訂閱-釋出訊息和訊息佇列。例如,典型的技術棧可能使用 Apache Kafka 處理髮布-訂閱訊息,使用 RabbitMQ 處理訊息佇列。在這種情況下,雖然系統工作良好,但是你需要同時部署、管理多個訊息系統。

我最喜歡 Apache Pulsar 的一點就是,它可以輕鬆連線訂閱-釋出訊息和訊息佇列。Pulsar 是第一個為了同時處理訂閱-釋出訊息和訊息佇列而開源的訊息系統。

因為使用 Apache BookKeeper 分散式日誌儲存資料庫作為儲存元件,Pulsar 可以輕鬆地同時支援訂閱-釋出訊息和訊息佇列。BookKeeper 作為日誌儲存系統,基於訊息 topic 資料結構而構建,支援水平擴充套件(增加 “bookie” 數量即可擴充套件容量),且執行迅速。

Pulsar 支援兩種基本的 topic 型別:持久 topic 與非持久 topic。使用者可以根據名稱辨別 topic 型別,因為型別即為 topic 名稱的“schema”(類似於 https 是 URL https://google.com 的 schema)。
持久 topic 的名稱格式為:persistent://public/default/some-topic,而非持久 topic 的名稱格式為:non-persistent://public/default/some-topic。

使用者使用持久 topic 時,Pulsar 將所有未確認訊息(即未處理訊息)儲存在 BookKeeper 中的多個“bookie”伺服器上。

Pulsar 的確支援非持久 topic,但是我們建議使用者只在可以接受丟失訊息的用例中,使用非持久訊息。對於具有訊息佇列功能的 topic,絕不應該使用非持久 topic。與將訊息資料儲存在記憶體中相比,這種儲存方式具有很多優勢。

如何將 Apache Pulsar 用作訊息佇列

Pulsar 無需特殊配置或調整,即可支援兩種用例,因此在使用方面具有一定的優勢。重點在於如何使用 Pulsar,如下圖所示:

image

釋出-訂閱 producer 和 consumer 通過釋出-訂閱 topic 進行通訊,而佇列 producer 和 consumer 通過佇列 topic 進行通訊。不需要“標記”topic,也不需要預先指定 topic 為實時 topic 或佇列 topic。

訊息佇列 topic 需要 consumer 使用共享訂閱,而不能是獨佔訂閱(exclusive)或災備訂閱(failover)。另外,所有 consumer 必須使用相同的訂閱名稱,否則就不是同一訂閱。當 consumer 在 topic 上建立共享訂閱後,Pulsar 會自動在接收訊息的 consumer 之間進行負載平衡,對於訊息佇列來說,這是最理想的狀態。

以下程式碼展示了五個 Java consumer 使用共享訂閱監聽同一 topic 的場景:

String PULSAR_SERVICE_URL = "pulsar://localhost:6650";
String MQ_TOPIC = "persistent://public/default/message-queue-topic";
String SUBSCRIPTION = "sub-1";
// Pulsar client
PulsarClient client = PulsarClient.builder()
 .serviceUrl(PULSAR_SERVICE_URL)
 .build();
// Base consumer builder for instantiating multiple consumers
ConsumerBuilder<byte[]> consumerBuilder = client.newConsumer()
 .topic(MQ_TOPIC)
 .subscriptionName(SUBSCRIPTION)
 .subscriptionType(SubscriptionType.Shared)
 .messageListener(messageCallback);
// Create five consumers (mq-consumer-0, mq-consumer-1, etc.)
IntStream.range(0, 4).forEach(i -> {
 String name = String.format("mq-consumer-%d", i);
 consumerBuilder
 .consumerName(name)
 .subscribe();
});

控制訊息排程

吞吐量在訊息佇列中尤為重要。如果訊息佇列沒有足夠的吞吐量來處理周圍資料管道所需要的內容,那麼訊息佇列可能不僅效能不夠好,甚至會產生一些負面影響。如果使用 Pulsar 作為訊息佇列,則可以通過調整 consumer 的配置來微調處理吞吐量

預設情況下,Apache Pulsar consumer 有一個接收佇列,用於一次處理多條訊息。使用者可以自行配置單個 consumer 接收佇列的大小(預設值為 1000 條訊息)。

理想情況下,應該根據 consumer 處理訊息的速度來設定接收佇列的大小。如果可以非常快速地處理訊息(只需幾毫秒),那麼建議將接收佇列的大小設定為較大的值,因為這樣有助於最大化 consumer 的處理吞吐量。

但是如果處理訊息需要較長時間,最好將接收佇列的大小設定為較小的值。如果 consumer 正在執行的任務屬於 CPU 密集型,也就是說任務處理需要幾秒鐘甚至更久,則建議將接收佇列的大小設定為個位數或 1,這樣負載平衡器能夠在 consumer 之間合理地分發訊息。

在下面這段程式碼中,consumer 接收佇列比較小(Java):

Consumer<byte[]> consumer = client.newConsumer()
 .topic("slow-processing-topic")
 .subscriptionType(SubscriptionType.Shared)
 .subscriptionName("sub-1")
 .receiverQueueSize(5)
 .messageListener(messageCallback)
 .subscribe();

接收佇列的預設值適用於很多用例。但是建議使用者稍微留意一下接收佇列,以免在後續工作中需要進行調優。

一個訊息平臺,兩種用例場景

如果想在不同用例場景中同時執行多個訊息平臺,大家可以考慮使用 Pulsar。Pulsar 同時支援兩種主要的訊息用例——釋出-訂閱訊息(尤其是持久訊息)和訊息佇列,並且執行速度快、可擴充套件,還可以減輕運維管理負擔。

相關文章