編輯| Debra
更多幹貨內容請關注微信公眾號“AI 前線”,(ID:ai-front)
本文整理自 Streamlio 核心創始人翟佳在 QCon2018 北京站的演講,在本次演講中,翟佳介紹了 Apache Pulsar 的架構、特性和其生態系統的組成,並展示了 Apache Pulsar 在訊息、計算和儲存三個方面進行的協調、抽象和統一。
-
Messaging:Pulsar 對 pub/sub 和 queue 兩種模式提供統一的支援,同時保證了一致性,高效能和易擴充套件性。
-
Computing:Pulsar 內部的 Pulsar-Functions 提供了 Stream-native 的輕量級計算框架,保證了資料的即時流式處理。
-
Storage:Pulsar 藉助 Apache BookKeeper 提供了以 segment 為中心的儲存架構,保證了儲存的效能,永續性和彈性。
實時資料處理在剛剛興起的時候,一般企業會採用λ架構,維護兩套系統:一套用來處理實時的資料;另一套用 batch 的方式處理歷史資料。兩套系統帶來了資源的冗餘佔用和維護的不便。
為了消除冗餘,逐漸演化出κ架構,使用一套系統來滿足實時資料處理和歷史資料處理的需求。
不管是λ架構還是κ架構,在實時處理的系統中,系統的核心由訊息、計算和儲存三個子系統組成,比如訊息系統有 Kafka、RabbitMQ、Flume 等;計算系統有 Spark Streaming、Flink、Heron 等;儲存系統有各種分散式的檔案系統,DB、K/V store 等。 由於三個部分中,每個部分都有相應的不同產品,三個部分之間也相互分隔和獨立很少關聯,這帶來了一些問題,比如需要更多人力維護,部署複雜,調優難度大,監管難,資料丟失風險大等等。
面對訊息,儲存和計算三個部分分隔的現狀,Apache Pulsar 在這三個方面進行了很好的協調、抽象和統一。 具體到 Apache Pulsar 內部,訊息部分由 Pulsar Broker 來負責;儲存部分使用了 Apache BookKeeper,計算部分由 Pulsar Functions 來負責。
Apache Pulsar 是 2016 年 yahoo 開源的下一代大規模分散式訊息系統,目前在 Apache 基金會下孵化。在 Yahoo 的生產環境中大規模部署並使用了近 4 年,服務於 Mail、Finance、Sports、 Flickr、 the Gemini Ads platform、 Sherpa 以及 Yahoo 的 KV 儲存等,在 Yahoo 全球 8 個資料中心之間維護了全聯通的複製,幷包含了 200 多萬個 Topics。
Apache Pulsar 有幾個明顯區別於其他訊息系統的特點:
-
優秀的資料永續性和順序性。每一條訊息都提供了全域性唯一的 ID,多副本,並都是在實時刷盤後再返回給使用者。
-
統一的消費模型: 支援 Stream(如 Kafka)和 Queue(如 RabbitMQ)兩種消費模型, 支援 exclusive、failover 和 shared 三種消費模式。
-
靈活的擴充套件性: 節點擴充套件的線性和瞬時完成,在擴充套件中不會有資料的拷貝和遷移。
-
高吞吐低延遲,在實時刷盤的前提下,依然提供了高頻寬(180 萬 messages/ 秒)和低延遲(5ms at 99%)。
除了這些特性,Apache Pulsar 也具備了優秀的企業級特性,比如多機房互聯互備(Geo-replication),多租戶等。
Apache Pulsar 在架構上最明顯的優勢是採用了訊息服務和訊息儲存分層的策略。它包括了無狀態的訊息服務層(broker 節點)和訊息儲存層(BookKeeper 中 Bookie 是基本的儲存節點)。這為系統帶來了極好的擴充套件性和健壯性。
在訊息服務層和儲存層,系統所關注的內容是不一樣的: 在服務層更多的是對 Producer 和 Consumer 的支援,更關注使用者介面和訊息的服務質量,需要更好的 CPU 和網路頻寬來支援訊息的扇入扇出。儲存層更關注磁碟 IOPS 和儲存容量,負責資料的持久化等。
分層的架構帶為服務和儲存兩層都帶來了線性、瞬時的擴充套件性。如果需要增加和支援更多的 Producer 和 Consumer,只用對 broker 進行 Scale。如果儲存空間緊張,或者想要訊息的時間保持的時間更長,可以單獨增加儲存節點 Bookie。
在服務層中,broker 不會有相關的資料被持久化儲存,是無狀態的。對 Topic 的服務可以很容易地遷移。如果 broker 失效,可以很容易地將 topic 遷移到健康的 broker。
在儲存層(Bookie)也是一樣。每個 topic 的資料被打散並均勻 partition 到多個 segment,每個 segment 的資料又被分散儲存在 Bookie 叢集中。當想增加容量的時候,只需要新增新的 Bookie,資料會優先選擇剛加入的 Bookie。
同樣當 broker 被 overloaded,新增新的 broker 之後,負載會被均衡地分配到新新增的 broker 之上。
介紹完 Apache Pulsar 的總體架構和特性,下面會從訊息、儲存和計算三個方面分別介紹 Apache Pulsar 的設計理念,各層內部以及各層之間的協調、抽象和統一。
Apache Pulsar 面向使用者的也是最簡單的三個概念: 主題 Topic、生產者 Producer 和消費者 Consumer。 Topic 是訊息的一個通道和載體; Producer 產生資料並向 Topic 這個通道中傳送資料; Consumer 從 Topic 中獲取並消費資料。
在 Apache Pulsar 中提供了對 Namespace 的支援。Namespace 是 ApachePulsar 的多租戶機制中重要的組成部分。在一個 Topic 的名字中,包含了:租戶 (Tenant) ,名稱空間(namespace)和 Topic 名字,這樣就可以對所有的 topic 提供層級化的管理。
Tenant 代表系統裡的租戶。假設有一個 Pulsar 叢集被多個組織共享,叢集裡的每個 Tenant 可以代表一個組織的團隊、一個核心的功能或一個產品線。一個 Tenant 可以包含多個 namespace,一個 namespace 可以包含多個主題。
Tenant 是資源的隔離的單位。namespace 是資源使用和許可權設定的單位,我們可以設定許可權、調整複製選項、管理跨叢集的資料複製、控制訊息的過期時間等。namespace 下的 Topic 會繼承 namespace 的配置。如果使用者獲取了 namespace 的寫入許可權就可以往 namespace 寫入資料,如果要寫入的 topic 不存在,就會建立該 topic。
為了支援異地多備,namespace 又分為兩種,一種是本地的,只在叢集內可見;一種是全域性的,對多個叢集可見。可以在不同的資料中心之間進行資料的互動和互備。
Apache Pulsar 的每個 namespace 可以包含多個 topic,而每個 topic 可以有多個生產者和訂閱者。每個訂閱者可以接受 topic 的所有的訊息。為了給應用程式提供更大的靈活性,Apache Pulsar 通過增加一層 subscription 的抽象,提供了統一的消費模式。 訊息的傳遞路徑是 producer-topic-subscription-consumer。subscription 類似 Kafka 中 consumer group 的概念。
Apache Pulsar 支援 exclusive、failover 和 shared 三種訂閱型別,它們可以共存在同一個 topic 上。資料雖然只寫了一次,但是可以通過三種的消費方式被多次消費。
前兩種 exclusive 和 failover,都是 Streaming 的模型,只有一個 consumer 來消費一個 topic partition 中的所有資料,都能保證嚴格的順序。Kafka 和 Kinesis 也是這種消費模型(一個 consumer 消費一個 partition)。
Exclusive 是隻能有一個 consumer 來消費一個 topic 中的資料,不允許其他的 consumer 加入;failover 是允許多個 consumer 和一個 subscription 關聯,當 master consumer 失效後,可以有另外的 consumer 來接管成為新的 master。
第三種是 shared 的消費模式,它屬於 Queue 的模式,常見的 RabbitMQ、ActiveMQ 均屬於這種模式。如果三個 consumer 共同訂閱同一個 subscription,每個 consumer 大概會消費這個 topic 中的三分之一的資料,如果想ß增加消費的頻寬,只用單獨增加 consumer 的數量而不需要改變 topic 和 partition,非常實用於一些 consumer 處理複雜度比較高的場景,比如視訊,圖片處理等。
除了這三種消費模式,Apache Pulsar 還提供了 reader 的 API 來讀取訊息,讓使用者可以更加靈活的控制和消費訊息。
Apache Pulsar 提供了兩種 ack 的機制: 累積(cumulative)模式和單條(individual)模式。
Ack 機制在在訊息系統中是非常重要的。訊息系統中的 broker 和 consumer 可能會出錯或當機,當有錯誤發生的時候,如果能夠獲取上次消費者消費的位置,然後從這個消費的位置再接著消費,這是非常有用的,這樣可以避免丟失資料,避免把所有的處理過的資料再處理一遍。
一般通過 message acknowledgement、committing offset 來標記訊息的消費情況。
Kafka 中通過 offset 來簡單的管理 ack,記錄一個 partition 的消費位置。
Pulsar 通過維護一個專門的資料結構 ManagedCursor 來管理 ack 的資訊,每次 ack 的改變都會被持久化到硬碟中。
對於 cumulative 的 ack,在標記的訊息之前,所有的資料都被消費過了;遇到出錯的情況會從標記的位置再開始消費。
對於 individual 的消費模式,會單獨標記已經被消費過的訊息;遇到出錯的情況,所有的未被標記 ack 的訊息都會被重新傳送。Individual 的 ack 模式主要支援 share 的消費模式。它是很有必要的,因為對一般的 share 的消費模式,都是單個的訊息消費處理比較慢,所以才增加 consumer。單獨的標記,能在出錯的時候減少不必要的昂貴的處理。
訊息的 retention 策略,管理著訊息什麼時候被刪除。 其他的系統大多是通過時間來控制。有可能時間到了,但訊息沒有被消費,也被刪除了。
Apache Pulsar 中,提供了比較全面的 retention 策略。一般情況下,藉助 ack 的資訊,當所有 subscription 都消費了訊息之後,訊息才會刪除。資料還可以額外的設定 retention period,即使都消費了也能再將訊息儲存一段時間。另外也支援 TTL 的模式。
對於留在 backlog 中的訊息,Apache Pulsar 也提供了多種策略,包括 producer-request-hold、producer-exception、consumer-backlog-eviction 等。在 backlog 的 quota 達到時,供使用者選擇怎麼處理新的訊息和在 backlog 中的訊息。
接下來我們來看一下 Apache Pulsar 的儲存層,也就是 Apache BookKeeper。Apache BookKeeper 在 2011 年開源,並隨後加入 Apache,成為 Apache 的頂級專案。BookKeeper 是分散式的是一個可擴充套件的、高可用、低延遲的專門為實時系統優化過的儲存系統。更多系統可以參考 BookKeeper 的網站 https://bookkeeper.apache.org/ 和 github:https://github.com/apache/bookkeeper。
Apache BookKeeper 為 Pulsar 系統提供了一個以 Segment(BookKeeper ledger)為儲存單元的儲存服務。BookKeeper 的儲存節點稱作一個 Bookie。
-
BookKeeper 為 append-only 的寫入模式提供了優化,通過獨特的設計提供了高頻寬和低延遲。
-
BookKeeper 提供了強一致性和順序性。通過實時刷盤和多備份保證資料的永續性。順序性通過記錄本身攜帶的全域性唯一順序 ID 來保證的。這樣對很多對順序要求比較高的應用場景。
-
高可用是說資料會同時寫入多個 bookie 上,如果 bookie 發生錯誤,即使只有一臺包含資料的 bookie 可用,仍能為應用提供服務,在其他 bookie 恢復或有新的 bookie 加入後,會自動檢查並補全所需要的資料備份。
-
IO 隔離,對於 Bookie 的讀和寫是分別發生在不同的磁碟上的。這樣不依賴於檔案系統和 pagecache 的設計,能保證即使有大量的讀的同時,也能保證寫的高頻寬和低延遲;在大量的寫入的同時,讀請求的服務質量也能得到保證。這也是能保證多租戶的一個關鍵。
一個 BookKeeper 的叢集由多個 Bookie 節點構成。每個 Bookie 負責具體的資料儲存。當使用者的 application 要使用 bk 的時候,會設定三個引數,ensemble size(使用者要使用幾臺 bookie)、write quorum(寫入的資料要保留幾個備份)和 ack quorum(每次的寫入操作,有幾個成功後就返回)。Bookie 採用 quorum-vote 的模式,當寫一條資料時,資料同時併發的寫到所有的 write quorum 的 bookie 中,當指定的 ack quorum 返回後,bookie 認為寫成功,返回。
當 ensemble 中有 bookie 出錯,會從 cluster 中尋找其他可用的 bookie,進行替換。然後後臺有 autorecovery 做資料的自動恢復,對使用者透明。
BookKeeper 的一個特性是儲存是以 Segment(在 BookKeeper 內部被稱作 ledger)為儲存的基本單元。每個 Segment 甚至到每個訊息的粒度,都會被均勻分散到 BookKeeper 的叢集中。保證了資料和服務在多個 Bookie 上的均勻性。通過這張圖,我們通過簡單對比 Pulsar 和 Kafka 中的 partition 的儲存過程,對 Pulsar 有一個更好的理解。
Pulsar 和 Kafka 都是基於 partition 的邏輯概念來做做 topic 的儲存。最根本的不同是,Kafka 的物理儲存也是以 partition 為單位的,每個 partition 必須作為一個整體(一個目錄)被儲存在某一個 broker 上。 而 Pulsar 的每個 partition 是以 segment 作為物理儲存的單位,Pulsar 中的每個 partition 會再被打散並均勻分散到多個 bookie 節點中。
這樣的一個直接的影響是,Kafka 的 partition 的大小,受制於單臺 broker 的儲存;而 Pulsar 的一個 partition 則可以利用整個叢集的儲存容量。
當 partition 的容量上限達到後,需要擴容的時候,如果現有的單臺機器不能滿足,Kafka 可能需要新增新的儲存節點,將 partition 的資料搬移到更大的節點上。但是 Pulsar 只用新增新的 Bookie 儲存節點,新加入的節點由於剩餘的空間大,會被優先使用,更多的接收新的資料;而且其中不會涉及到任何的老的資料的拷貝和搬移。
Pulsar 在單個節點失敗時也會體現同樣的優勢。如果 Pulsar 的服務節點 broker 失效,由於 broker 是無狀態的,其他的 broker 可以很快的接管 topic,不會涉及 topic 資料的拷貝;如果儲存節點 Bookie 失效,叢集中其他的 Bookie 會從多個 Bookie 節點中併發讀取資料,並對失效節點的資料自動進行資料的恢復,不會對前端的服務有影響。
Apache BookKeeper 內部除了基礎的的 Segment(ledger), 還提供了 Stream 和 Table 兩種服務。 Segment 可以簡單理解為一段複製日誌。Stream 服務是通過一定的方式,將一組 Segment 按照順序共同管理起來,這樣就可以組成一個源源不斷的流。進而,如果我們用 Stream 來作為一個 Table 的 change log,實現了一個簡單的 K/V Store,也就是這裡說的 Table 的服務。在實時處理的過程中,比如 Pulsar Functions 的處理過程中,需要使用 K/V 的 Table 來存取計算的中間狀態。
通過在 BookKeeper 內部提供 Stream 和 Table 兩種服務,可以很方便的滿足在實時資料處理中的絕大部分的儲存需求。
介紹完 Pulsar 中的訊息和儲存,下面我們來了解一下 Pulsar 中的計算部分 – Pulsar Functions。介紹一下 Pulsar Functions 的設計和實現。看看 Pulsar Functions 和其他的計算引擎不同的地方。
首先我們看一個計算引擎最本質的是要解決什麼問題。 首先使用者定了了一個計算的需求,也就是處理過程: f(x),一組輸入資料通過 f(x)的計算,得到一組輸出的結果。
基於本質問題,計算引擎經過了長期的發展。第一代的計算引擎,以 Storm 為代表的通過一個有向無環圖(DAG)來完成一組計算,通常需要大量的程式碼編寫工作。現在大部分的計算引擎都提供第二代的 API,即通過 DSL 的方式。第二代的 API 相比第一代更加的緊湊和方便,但是還是有些複雜,比如包含著大量的 map、flatmap 等。
我們發現,在實時資料的處理中,有大部分(60%——80%)的計算過程,本質上都是一些很簡單的資料轉換,比如 ETL/Reactive Services/Classification/Real-time Aggregation/Event Routing/Microservices 等等。
另外,雲的興起,帶動了 serverless 的出現和興盛,Serverless 為我們提供了一個很好的思路。serverless 提供的是 function 的 API,每一個事件觸發一次 function,多個 function 可以通過組合的方式,完成比較複雜的邏輯。
基於這些原因,我們決定設計基於 Serverless 的,由訊息來驅動的“Stream-native”的 Pulsar Functions。Pulsar Function 的一個特點是簡單:給使用者的介面簡單;每個 Function 的實現也十分容易理解;提供多語言的介面(目前支援 Java 和 Python)。
另一個特點是 Stream-native: Pulsar Functions 的輸入,輸出和中間的 log 都以 Topic 和訊息為中心。
Pulsar Functions 提供兩種 API,第一種是 SDK less 的 API,使用者不用依賴 Pulsar 的 sdk,只用實現 java.util.function.Function 的介面。第二種藉助 Pulsar SDK 的 API,通過 Context 來和 Pulsar 互動和定製。
和 Pulsar 的管理一樣,Pulsar Functions 也提供命令列和 Rest 兩種方式。執行的引數包括輸入的 topic,輸出的 topic 和要執行的 Function 的名字。
我們可以舉例說明一下 Pulsar Functions 適用的典型應用場景。
在邊緣計算(Edge Computing)中,感測器會產生大量資料,而且資料會在邊緣的本地節點上進行很多簡單的處理,比如 Simple filtering, threshold detection, regex matching 等,另外邊緣節點的計算資源有限。 Pulsar Functions 對這樣的場景十分匹配。另外是在機器學習中。最開始的基礎模型通過離線進行計算和訓練。當訓練完,上線後,每一個輸入,都會匹配和應用模型,並對模型進行調整。這十分匹配 Pulsar Functions 的訊息驅動的模式。另外模型本身也可以使用 BookKeeper 做儲存,簡化系統的部署。
這裡 Pulsar Functions 的特性做一個總結。
首先,Pulsar Function 可以簡單執行在 Pulsar 的 broker 裡面,簡化系統的部署。輸入的 Topic 中的每一個訊息都會觸發對 Function 的執行。可以支援多個 Topic 作為輸入。使用者可以控制 Function 執行的各種語義:AtMostOnce 是當 Function 收到訊息後就進行 ACK;AtLeastOnce 是在 Function 對訊息處理完成後才進行 ACK;ExactlyOnce 是通過 Pulsar 內部實現的 deDup 的策略來實現。 Pulsar Functions 可以使用 BookKeeper 提供的 Stream 服務來做 Topic 的儲存,使用提供的 Table 服務來做中間狀態的儲存,實現儲存的統一,不需要部署其他的系統。這為系統的開發、測試、整合和運維帶來了更多的便利。
通過介紹 Pulsar 的訊息,儲存和計算三個部分,希望能讓大家對 Pulsar 有更進一步的瞭解。在 Pulsar 的訊息系統中,提供了基於 Stream 和 Queue 的統一的消費模式,提供了無狀態的 Broker 來提升系統的擴充套件性和容錯性。在儲存系統 BookKeeper 中,提供了對 Stream 的儲存和對 K/V Table 的儲存的統一,滿足了實時處理系統中對 topic 和狀態的儲存需求。 在計算部分,Pulsar Functions 中基於訊息驅動(stream-native),可以計算和訊息一種統一。
另外對於 Pulsar 系統和外部系統的互聯(connector),可以看作是一種特殊的 Pulsar Functions。
這裡的 Benchmark(https://github.com/openmessaging/openmessaging-benchmark)是我們和阿里一起起草的 openMessaging 專案的一部分。如果有時間和機器,歡迎大家自己驗證一下。
這個 Benchmark 通過相同的配置,對 Apache Pulsar 和 Kafka 的頻寬和延遲進行了簡單的測試。
這個結果是分別測試了 Pulsar 和 Kafka 在一般模式和 Exactly-once 模式下的 Publish 頻寬。
在 1KB 訊息大小下,Pulsar 的一般模式和 Exactly-once 模式下的頻寬都在 21 萬條 / 秒左右;Kafka 在一般模式和 Exactly-once 模式下的頻寬分別是 7 萬多條 / 秒和 5 萬多條 / 秒。
除了頻寬數值的區別,另一方面是對 ExactlyOnce 的處理,Pulsar 通過自身的機制,幾乎相對於一般的 模式在效能上沒有區別。但是 Kafka 的兩種模式會有較大的差別。
這個結果是 Pulsar 和 Kafka 在固定的 Public 頻寬(50K/ 秒)下,各個百分位訊息的釋出時延。可以看出 Kafka 在不到 99% 的百分位,時延就開始大幅上升,但是 Pulsar 在 99.9% 的百分位以後,時延才開始上升。
這個結果是從時間軸的角度來看 Pulsar 和 Kafka 的時延。先不關注時延的絕對數值,直觀的感覺是 Pulsar 的時延更加穩定;Kafka 的時延會有很大的波動。 這和 Pulsar 中的記憶體和對 GC 的優化有直接的關係。Apache Pulsar 是一個新興的下一代的訊息系統,由於 Pulsar Functions 的加入,和底層 Apache BookKeeper 提供的 Table 服務的完善,現在可以認為 Apache Pulsar 是一個在訊息、儲存和計算三方面的統一的實時資料處理平臺。
Apache Pulsar 有很多先進的理念、設計和抽象在裡面。由於時間關係有很多的部分沒能展開細講。
Apache Pulsar 和 Apache BookKeeper 中也有越來越多的有意思的 feature 和功能正在進行,公司和社群也都期待大家的關注和加入。如果大家有更多的關於 Meetup 和 POC 等需求,或者在使用其他訊息系統中遇到問題,可以通過 Slack Channel 和微信聯絡我們。
作者介紹
翟佳,Streamlio核心創始成員之一,畢業於中科院計算所,目前就職於一家下一代實時處理初創公司 Streamlio,是 Streamlio的核心創始成員之一。在此之前任職於 EMC,是北京 EMC實時處理平臺的技術負責人。主要從事實時計算和分散式儲存系統的相關開發,是開源專案 Apache BookKeeper PMC Member和 Committer,也在 Apache Pulsar, Distributedlog等專案中持續貢獻程式碼。