博文推薦|使用 Apache Pulsar 和 Scala 進行事件流處理

ApachePulsar發表於2022-03-30
本文翻譯自《Event Streaming with Apache Pulsar and Scala》,作者 Giannis 。

譯者資訊:姚餘錢@深圳覺行科技有限公司,致力於醫療大資料領域。熱衷開源,活躍於 Apache Pulsar 社群。

本文作者 Giannis Polyzos,StreamNative 高階工程師,主攻 Apache Pulsar 方向。Apache Pulsar 是雲原生訊息流平臺,擁有廣闊前景。在本文中,他將介紹 Pulsar 是什麼以及它出色的效能,然後通過快速教程以幫助讀者入門 Scala 語言執行 Pulsar。

文章摘要

在現代資料時代,對儘可能快地提供資料洞察的需求不斷增加。“當前正在”發生的事情在幾分鐘甚至幾秒鐘後就可能變得無關緊要,因此越來越需要儘可能快地接收和處理事件——無論是為了改善業務使其在要求苛刻的市場中更具競爭力,還是為了使一個系統能根據其所受到的環境刺激而自我成長和適應。

隨著容器和雲基礎設施的發展,公司在尋求利用和採用雲原生的辦法。遷移到雲端並在系統中採用容器意味著我們很可能會利用 Kubernetes 等技術來實現其所有驚人的功能。將基礎架構放置雲端並採用雲原生解決方案意味著很多使用者也希望其訊息傳遞和流解決方案符合這些原則。

在這篇文章中,我們將介紹如何使用 Apache Pulsar 和 Scala 實現雲原生事件流處理。我們將回顧 Apache Pulsar 在這個現代資料時代須具備的能力,是什麼讓它脫穎而出,以及如何通過使用 Scala 和 pulsar4s 庫建立一些簡單的生產者和消費者來執行它。

1. 什麼是 Apache Pulsar

如文件中所述,

Apache Pulsar 是一個雲原生、分散式訊息和流平臺,每天管理數千億個事件。

它最初是在 2013 年由 Yahoo 建立的,以滿足其巨大的擴充套件需求 - 工程團隊當時也審查了類似 Apache Kafka 等解決方案(儘管這些系統此後有了很大的發展),但並沒有完全滿足他們的需求。

其它系統缺少跨地域複製、多租戶和偏移量管理等特性,以及處理訊息積壓情況下的效能,因此 Apache Pulsar 誕生了。

讓我們仔細看看是什麼讓它脫穎而出:

  1. 統一訊息和流兩種場景:關於 Apache Pulsar,您應該注意的第一件事是,它是訊息和流的統一平臺。訊息和流這兩個術語經常被混為一談,實際存在根本差異。例如在訊息傳遞的場景中,使用者希望訊息一到就立刻消費它,然後將該訊息刪除;然而,對於流處理的場景,使用者可能希望保留訊息並能夠重現它們。
  2. 多租戶:Apache Pulsar 從一開始就被設計成一個多租戶系統。您可以將多租戶視為不同的使用者組,每個使用者組都在自己的隔離環境中執行。Pulsar 的邏輯架構由租戶、名稱空間和主題組成。名稱空間是租戶內主題的邏輯分組。您可以使用定義的層次結構輕鬆對映組織的需求,並提供隔離、身份驗證、授權、配額以及在名稱空間和主題級別應用不同的策略。電子商務業務的多租戶示例如下,將 WebBanking 和 Marketing 等不同部門作為租戶,然後這些部門成員可以在租戶內進行操作。

  1. 跨地域複製:跨地域複製就是通過在跨地域上分佈的不同叢集的資料中心提供資料副本來保證容災能力。Apache Pulsar 提供開箱即用的跨地域複製,無需外部工具。類似 Apache Kafka 的替代方案依賴於第三方解決方案 —— 即 MirrorMaker - 來解決此類已知存在問題的場景。使用 Pulsar,您可以通過強大的內建跨地域複製克服這些問題,並設計滿足您需求的災難恢復解決方案。
  2. 水平擴充套件:Apache Pulsar 的架構由三個元件組成。Pulsar Broker 是無狀態服務層,Apache BookKeeper(bookie 伺服器)作為儲存層,最後 Apache ZooKeeper 作為後設資料層 —— 儘管 2.8.0 版本引入了後設資料層作為替代(參考 PIP 45)。所有層都彼此分離,這意味著您可以根據需要獨立地擴充套件每個元件。Apache BookKeeper 使用分散式 ledger 的概念而不是基於日誌的抽象,這使得它非常容易擴充套件,無需重新平衡。這也使得 Apache Pulsar 非常適合雲原生環境。
  3. 分層儲存:當您處理大量資料時,主題可能會無限制地變大,隨著時間的推移,(儲存成本)可能會變得非常昂貴。Apache Pulsar 提供了分層儲存,因此隨著主題的增長,您可以將舊資料解除安裝到一些更便宜的儲存中(例如 Amazon S3),而您的客戶端仍然可以訪問資料並繼續提供服務,就好像什麼都沒有發生過一樣。
  4. Pulsar Functions:Pulsar Functions 是一個輕量級的無伺服器計算框架,允許您以非常簡單的方式部署自己的流處理邏輯。輕量級也使其成為物聯網邊緣分析用例的絕佳選擇。

Apache Pulsar 還有很多特性,比如內建的 Schema Registry、對事務和 Pulsar SQL 的支援,但現在讓我們看看如何真正啟動和執行 Pulsar,並用 Scala 建立我們的第一個生產者和消費者。

2. 場景和叢集設定示例

以簡單的場景作為例子,我們建立一個讀取模擬感測器事件的生產者,將事件傳送到一個主題,然後在另一端建立訂閱該主題並僅讀取傳入事件的消費者。我們將使用 pulsar4s client 庫進行實現,同時使用 docker 執行 Pulsar 叢集。為了以單機模式啟動 Pulsar 叢集,請在終端中執行以下命令:

docker run -it \ 
    -p 6650:6650 \ 
    -p 8080:8080 \ 
    --name pulsar \ 
    apachepulsar/pulsar:2.8.0 \ 
    bin/pulsar standalone

該命令將啟動 Pulsar 並將必要的埠繫結到本地計算機。隨著叢集的啟動執行,可以開始建立生產者和消費者。

3. Apache Pulsar 生產者

首先,需要有 pulsar4s-core 和 pulsar4s-circe 依賴——所以我們需要將以下內容新增到 build.sbt 檔案中:

val pulsar4sVersion = "2.7.3"

lazy val pulsar4s       = "com.sksamuel.pulsar4s" %% "pulsar4s-core"  % pulsar4sVersion
lazy val pulsar4sCirce  = "com.sksamuel.pulsar4s" %% "pulsar4s-circe" % pulsar4sVersion

libraryDependencies ++= Seq(
  pulsar4s, pulsar4sCirce
)
``
Then we will define the message payload for a sensor event as follows:

然後我們為感測器事件定義訊息負載,如下所示:


case class SensorEvent(sensorId: String,
                         status: String,
                         startupTime: Long,
                         eventTime: Long,
                         reading: Double)

我們還需要引入以下範圍內容:

import com.sksamuel.pulsar4s.{DefaultProducerMessage, EventTime, ProducerConfig, PulsarClient, Topic}
import io.ipolyzos.models.SensorDomain
import io.ipolyzos.models.SensorDomain.SensorEvent
import io.circe.generic.auto._
import com.sksamuel.pulsar4s.circe._
import scala.concurrent.ExecutionContext.Implicits.global

所有應用程式的生產和消費的主要入口點是 Pulsar Client,它處理與 broker 的連線。在 Pulsar 客戶端中,您還可以設定叢集的身份驗證或者調整其他重要的配置,例如超時設定和連線池。您可以通過提供要連線的 service url 來簡單地例項化客戶端。

val pulsarClient = PulsarClient("pulsar://localhost:6650")

有了客戶端,讓我們看看生產者的初始化和迴圈。

val topic = Topic("sensor-events")

// create the producer
val eventProducer = pulsarClient.producer[SensorEvent](ProducerConfig(
  topic, 
  producerName = Some("sensor-producer"), 
  enableBatching = Some(true),
  blockIfQueueFull = Some(true))
)

// sent 100 messages 
(0 until 100) foreach { _ =>
   val sensorEvent = SensorDomain.generate()
   val message = DefaultProducerMessage(
      Some(sensorEvent.sensorId), 
      sensorEvent, 
      eventTime = Some(EventTime(sensorEvent.eventTime)))
  
   eventProducer.sendAsync(message) // use the async method to sent the message
}

這裡有幾點需要注意:

  • 我們通過提供必要的配置來建立生產者 —— 生產者和消費者都是高度可配的,可以根據所需場景進行配置。
  • 我們在此處提供生產者的主題名稱,啟用批處理並使生產者在佇列已滿時阻塞操作。
  • 通過啟用批處理,Pulsar 將使用內部佇列來儲存訊息(預設值為 1000),並在佇列滿後將批次傳送訊息給 broker。
  • 正如您在示例程式碼中看到的,我們使用 .sendAsync() 方法將訊息傳送到 Pulsar。這是在不等待確認的情況下傳送訊息,同時由於我們將訊息緩衝到佇列中,這可能會使客戶端不堪重負。
  • 通過選項 blockIfQueueFull 使用反壓機制並通知生產者在傳送更多訊息之前等待。
  • 最後,我們建立要傳送的訊息。這裡我們指定 sensorId 作為訊息的鍵、sensorEvent 的值,我們還提供 eventTime,即事件產生的時間。

此時,我們的生產者就位,開始向 Pulsar 傳送訊息。完整的實現詳見此處

4. Apache Pulsar 消費者

現在讓我們把注意力轉移到消費方面。就像我們對生產端所做的那樣,消費端需要配置一個 Pulsar Client。

val consumerConfig = ConsumerConfig(
  Subscription("sensor-event-subscription"), 
  Seq(Topic("sensor-events")), 
  consumerName = Some("sensor-event-consumer"), 
  subscriptionInitialPosition = Some(SubscriptionInitialPosition.Earliest), 
  subscriptionType = Some(SubscriptionType.Exclusive)
)

val consumerFn = pulsarClient.consumer[SensorEvent](consumerConfig) 

var totalMessageCount = 0 
while (true) {   
  consumerFn.receive match {
     case Success(message) => 
         consumerFn.acknowledge(message.messageId)
         totalMessageCount += 1
         println(s"Total Messages '$totalMessageCount - Acked Message: ${message.messageId}")
      case Failure(exception) => 
         println(s"Failed to receive message: ${exception.getMessage}")
  }
}

步驟依次如下:

  • 同樣的,首先建立消費者配置。在這裡我們指定一個訂閱名稱,要訂閱的主題、消費者的名稱,以及我們想要消費者開始消費訊息的位置 —— 在這裡我們指定 Earliest - 這意味著訂閱將在其確認的最後一條訊息之後開始讀取。
  • 最後,我們指定 SubscriptionType - 在這裡它是 Exclusive (獨佔)型別,這也是預設的訂閱型別(稍後會詳細介紹訂閱型別)。
  • 配置就緒後,使用建立的配置設定一個新的消費者,然後我們就有了一個簡單的消費迴圈 —— 我們要做的就是使用接收方法讀取一條新訊息,該方法會阻塞直到有訊息可用,然後我們確認訊息,最後獲取到目前為止收到的訊息總數以及已確認的 messageId。
  • 請注意:當收到新訊息時,如果一切順利,您需要向客戶端確認,否則需要使用 negativeAcknowledge() 方法否定確認訊息。
  • 有了消費實現方式,我們就有了一個在執行的釋出-訂閱應用程式,它為 Pulsar 主題生產感測器事件,以及一個訂閱並消費這些訊息的消費者。
  • 消費者的完整實現詳見此處

5. Apache Pulsar 訂閱型別

正如文章中提到的,Apache Pulsar 通過提供不同的訂閱型別做到了提供訊息和流模式的統一。

Pulsar 有以下訂閱型別:

  • 獨佔訂閱:任何時間點都只允許一個消費者使用訂閱讀取訊息
  • 災備訂閱:在任何時間點只允許一個消費者使用訂閱讀取訊息,但您可以有多個備用消費者來接手工作,以防活躍的消費者失敗
  • 共享訂閱:可以將多個消費者附加到訂閱上,並以輪詢方式在它們之間共享工作。
  • 鍵共享訂閱:可以將多個消費者附加到訂閱上,並且每個消費者都分配有一組唯一的鍵。該消費者負責處理分配給它的一組鍵。如果失敗,將為另一個消費者分配該組鍵。

不同的訂閱型別用於不同的場景。例如,為了實現典型的扇出訊息傳遞模式,您可以使用獨佔或災備訂閱型別。對於訊息佇列和工作佇列,可選擇共享訂閱,以便在多個消費者之間共享工作;而對於流場景或基於鍵的流處理,災備和鍵共享訂閱不失為很好的選擇,它們可以允許有序消費或基於某些鍵擴充套件您的處理。

6. 總結與閱讀延伸

在這篇文章中,我們簡要介紹了 Apache Pulsar 是什麼、它作為統一的訊息流平臺與脫穎而出的能力、如何建立一些簡單的生產和消費應用程式,最後我們重點介紹了 Pulsar 是如何通過不同的訂閱模式統一訊息和流。

延伸閱讀:

  • Pulsar IO:輕鬆地將資料移入和移出 Pulsar
  • Pulsar Functions(Pulsar 的無伺服器和輕量級的計算框架):使用者可以使用它們在 Pulsar 主題上處理邏輯,減少生產和消費應用程式所需的所有樣板程式碼
  • Function Mesh:通過利用 Kubernetes 原生功能(如部署和自動縮放)使您的事件流真正成為雲原生。

關注公眾號「Apache Pulsar」,獲取更多技術乾貨

加入 Apache Pulsar 中文交流群??

點選連結閱讀英文原文

相關文章