下一代分散式訊息系統:Apache Kafka
簡介
Apache Kafka是分散式釋出-訂閱訊息系統。它最初由LinkedIn公司開發,之後成為Apache專案的一部分。Kafka是一種快速、可擴充套件的、設計內在就是分散式的,分割槽的和可複製的提交日誌服務。
Apache Kafka與傳統訊息系統相比,有以下不同:
- 它被設計為一個分散式系統,易於向外擴充套件;
- 它同時為釋出和訂閱提供高吞吐量;
- 它支援多訂閱者,當失敗時能自動平衡消費者;
- 它將訊息持久化到磁碟,因此可用於批量消費,例如 ETL,以及實時應用程式。
本文我將重點介紹Apache Kafka的架構、特性和特點,幫助我們理解Kafka為何比傳統訊息服務更好。
我將比較Kafak和傳統訊息服務 RabbitMQ、Apache ActiveMQ的特點,討論一些Kafka優於傳統訊息服務的場景。在最後一節,我們將探討一個進行中的示例應用,展示Kafka作為訊息伺服器的用途。這個示例應用的完整原始碼在 GitHub。關於它的詳細討論在本文的最後一節。
架構
首先,我介紹一下Kafka的基本概念。它的架構包括以下元件:
- 話題(Topic)是特定型別的 訊息流。 訊息是位元組的有效負載(Payload),話題是訊息的分類名或種子(Feed)名。
- 生產者(Producer)是能夠釋出訊息到話題的任何物件。
- 已釋出的訊息儲存在一組伺服器中,它們被稱為 代理(Broker)或Kafka叢集。
- 消費者可以訂閱一個或多個話題,並從Broker拉資料,從而消費這些已釋出的訊息。
圖1:Kafka生產者、消費者和代理環境
生產者可以選擇自己喜歡的序列化方法對訊息內容編碼。為了提高效率,生產者可以在一個釋出請求中傳送一組訊息。下面的程式碼演示瞭如何建立生產者併傳送訊息。
生產者示例程式碼:
1
2
3
4
|
producer
= new
Producer(…); message
= new
Message(“test message str”.getBytes()); set
= new
MessageSet(message); producer.send(“topic1”,
set); |
為了訂閱話題,消費者首先為話題建立一個或多個訊息流。釋出到該話題的訊息將被均衡地分發到這些流。每個訊息流為不斷產生的訊息提供了迭代接 口。然後消費者迭代流中的每一條訊息,處理訊息的有效負載。與傳統迭代器不同,訊息流迭代器永不停止。如果當前沒有訊息,迭代器將阻塞,直到有新的訊息發 布到該話題。Kafka同時支援點到點分發模型(Point-to-point delivery model),即多個消費者共同消費佇列中某個訊息的單個副本,以及釋出-訂閱模型(Publish-subscribe model),即多個消費者接收自己的訊息副本。下面的程式碼演示了消費者如何使用訊息。
消費者示例程式碼:
1
2
3
4
5
|
streams[]
= Consumer.createMessageStreams(“topic1”, 1 ) for
(message : streams[ 0 ])
{ bytes
= message.payload(); //
do something with the bytes } |
Kafka的整體架構如圖2所示。因為Kafka內在就是分散式的,一個Kafka叢集通常包括多個代理。為了均衡負載,將話題分成多個分割槽,每個代理儲存一或多個分割槽。多個生產者和消費者能夠同時生產和獲取訊息。
圖2:Kafka架構
Kafka儲存
Kafka的儲存佈局非常簡單。話題的每個分割槽對應一個邏輯日誌。物理上,一個日誌為相同大小的一組分段檔案。每次生產者釋出訊息到一個分割槽, 代理就將訊息追加到最後一個段檔案中。當釋出的訊息數量達到設定值或者經過一定的時間後,段檔案真正寫入磁碟中。寫入完成後,訊息公開給消費者。
與傳統的訊息系統不同,Kafka系統中儲存的訊息沒有明確的訊息Id。
訊息通過日誌中的邏輯偏移量來公開。這樣就避免了維護配套密集定址,用於對映訊息ID到實際訊息地址的隨機存取索引結構的開銷。訊息ID是增量的,但不連續。要計算下一訊息的ID,可以在其邏輯偏移的基礎上加上當前訊息的長度。
消費者始終從特定分割槽順序地獲取訊息,如果消費者知道特定訊息的偏移量,也就說明消費者已經消費了之前的所有訊息。消費者向代理髮出非同步拉請求,準備位元組緩衝區用於消費。每個非同步拉請求都包含要消費的訊息偏移量。Kafka利用sendfile API高效地從代理的日誌段檔案中分發位元組給消費者。
圖3:Kafka儲存架構
Kafka代理
與其它訊息系統不同,Kafka代理是無狀態的。這意味著消費者必須維護已消費的狀態資訊。這些資訊由消費者自己維護,代理完全不管。這種設計非常微妙,它本身包含了創新。
- 從代理刪除訊息變得很棘手,因為代理並不知道消費者是否已經使用了該訊息。Kafka創新性地解決了這個問題,它將一個簡單的基於時間的SLA應用於保留策略。當訊息在代理中超過一定時間後,將會被自動刪除。
- 這種創新設計有很大的好處,消費者可以故意倒回到老的偏移量再次消費資料。這違反了佇列的常見約定,但被證明是許多消費者的基本特徵。
ZooKeeper與Kafka
考慮一下有多個伺服器的分散式系統,每臺伺服器都負責儲存資料,在資料上執行操作。這樣的潛在例子包括分散式搜尋引擎、分散式構建系統或者已知的系統如 Apache Hadoop。 所有這些分散式系統的一個常見問題是,你如何在任一時間點確定哪些伺服器活著並且在工作中。最重要的是,當面對這些分散式計算的難題,例如網路失敗、頻寬 限制、可變延遲連線、安全問題以及任何網路環境,甚至跨多個資料中心時可能發生的錯誤時,你如何可靠地做這些事。這些正是 Apache ZooKeeper所 關注的問題,它是一個快速、高可用、容錯、分散式的協調服務。你可以使用ZooKeeper構建可靠的、分散式的資料結構,用於群組成員、領導人選舉、協 同工作流和配置服務,以及廣義的分散式資料結構如鎖、佇列、屏障(Barrier)和鎖存器(Latch)。許多知名且成功的專案依賴於 ZooKeeper,其中包括HBase、Hadoop 2.0、Solr Cloud、Neo4J、 Apache Blur(Incubating)和Accumulo。
ZooKeeper是一個分散式的、分層級的檔案系統,能促進客戶端間的鬆耦合,並提供最終一致的,類似於傳統檔案系統中檔案和目錄的 Znode檢視。它提供了基本的操作,例如建立、刪除和檢查Znode是否存在。它提供了事件驅動模型,客戶端能觀察特定Znode的變化,例如現有 Znode增加了一個新的子節點。ZooKeeper執行多個ZooKeeper伺服器,稱為Ensemble,以獲得高可用性。每個伺服器都持有分散式檔案系統的記憶體複本,為客戶端的讀取請求提供服務。
圖4:ZooKeeper Ensemble架構
上圖4展示了典型的ZooKeeper ensemble,一臺伺服器作為Leader,其它作為Follower。當Ensemble啟動時,先選出Leader,然後所有Follower復 制Leader的狀態。所有寫請求都通過Leader路由,變更會廣播給所有Follower。變更廣播被稱為 原子廣播。
Kafka中ZooKeeper的用途:正如ZooKeeper用於分散式系統的協調和促 進,Kafka使用ZooKeeper也是基於相同的原因。ZooKeeper用於管理、協調Kafka代理。每個Kafka代理都通過 ZooKeeper協調其它Kafka代理。當Kafka系統中新增了代理或者某個代理故障失效時,ZooKeeper服務將通知生產者和消費者。生產者 和消費者據此開始與其它代理協調工作。Kafka整體系統架構如圖5所示。
圖5:Kafka分散式系統的總體架構
Apache Kafka對比其它訊息服務
讓我們瞭解一下使用Apache Kafka的兩個專案,以對比其它訊息服務。這兩個專案分別是LinkedIn和我的專案:
LinkedIn的研究
LinkedIn團隊做了個 實驗研究,對比Kafka與Apache ActiveMQ V5.4和RabbitMQ V2.4的效能。他們使用ActiveMQ預設的訊息持久化庫 Kahadb。LinkedIn在兩臺Linux機器上執行他們的實驗,每臺機器的配置為8核2GHz、16GB記憶體,6個磁碟使用RAID10。兩臺機器通過1GB網路連線。一臺機器作為代理,另一臺作為生產者或者消費者。
生產者測試
LinkedIn團隊在所有系統中配置代理,非同步將訊息刷入其持久化庫。對每個系統,執行一個生產者,總共釋出1000萬條訊息,每條訊息 200位元組。Kafka生產者以1和50批量方式傳送訊息。ActiveMQ和RabbitMQ似乎沒有簡單的辦法來批量傳送訊息,LinkedIn假定 它的批量值為1。結果如下面的圖6所示:
圖6:LinkedIn的生產者效能實驗結果
Kafka效能要好很多的主要原因包括:
- Kafka不等待代理的確認,以代理能處理的最快速度傳送訊息。
- Kafka有更高效的儲存格式。平均而言,Kafka每條訊息有9位元組的開銷,而ActiveMQ有144位元組。其原因是JMS所需的沉重 訊息頭,以及維護各種索引結構的開銷。LinkedIn注意到ActiveMQ一個最忙的執行緒大部分時間都在存取B-Tree以維護訊息後設資料和狀態。
消費者測試
為了做消費者測試,LinkedIn使用一個消費者獲取總共1000萬條訊息。LinkedIn讓所有系統每次拉請求都預獲取大約相同數量的數 據,最多1000條訊息或者200KB。對ActiveMQ和RabbitMQ,LinkedIn設定消費者確認模型為自動。結果如圖7所示。
圖7:LinkedIn的消費者效能實驗結果
Kafka效能要好很多的主要原因包括:
- Kafka有更高效的儲存格式;在Kafka中,從代理傳輸到消費者的位元組更少。
- ActiveMQ和RabbitMQ兩個容器中的代理必須維護每個訊息的傳輸狀態。LinkedIn團隊注意到其中一個ActiveMQ線 程在測試過程中,一直在將KahaDB頁寫入磁碟。與此相反,Kafka代理沒有磁碟寫入動作。最後,Kafka通過使用sendfile API降低了傳輸開銷。
目前,我正在工作的一個專案提供實時服務,從訊息中快速並準確地提取場外交易市場(OTC)定價內容。這是一個非常重要的專案,處理近25種資 產類別的財務資訊,包括債券、貸款和ABS(資產擔保證券)。專案的原始資訊來源涵蓋了歐洲、北美、加拿大和拉丁美洲的主要金融市場領域。下面是這個專案 的一些統計,說明了解決方案中包括高效的分散式訊息服務是多麼重要:
- 每天處理的訊息數量超過 1,300,000;
- 每天解析的OTC價格數量超過 12,000,000;
- 支援超過25種資產類別;
- 每天解析的獨立票據超過 70,000。
訊息包含PDF、Word文件、Excel及其它格式。OTC定價也可能要從附件中提取。
由於傳統訊息伺服器的效能限制,當處理大附件時,訊息佇列變得非常大,我們的專案面臨嚴重的問題,JMSqueue一天需要啟動2-3次。重啟 JMS佇列可能丟失佇列中的全部訊息。專案需要一個框架,不論解析器(消費者)的行為如何,都能夠保住訊息。Kafka的特性非常適用於我們專案的需求。
當前專案具備的特性:
- 使用Fetchmail獲取遠端郵件訊息,然後由Procmail過濾並處理,例如單獨分發基於附件的訊息。
- 每條訊息從單獨的檔案獲取,該檔案被處理(讀取和刪除)為一條訊息插入到訊息伺服器中。
- 訊息內容從訊息服務佇列中獲取,用於解析和提取資訊。
示例應用
這個示例應用是基於我在專案中使用的原始應用修改後的版本。我已經刪除日誌的使用和多執行緒特性,使示例應用的工件儘量簡單。示例應用的目的是展示如何使用Kafka生產者和消費者的API。應用包括一個 生產者示例(簡單的生產者程式碼,演示Kafka生產者API用法併發布特定話題的訊息), 消費者示例(簡單的消費者程式碼,用於演示Kafka消費者API的用法)以及 訊息內容生成API(在特定路徑下生成訊息內容到檔案的API)。下圖展示了各元件以及它們與系統中其它元件間的關係。
圖8:示例應用元件架構
示例應用的結構與Kafka原始碼中的例子程式相似。應用的原始碼包含Java源程式資料夾‘src’和'config'資料夾,後者包括幾個配置檔案和一些Shell指令碼,用於執行示例應用。要執行示例應用,請參照 ReadMe.md檔案或GitHub網站 Wiki頁面的說明。
程式構建可以使用 Apache Maven,定製也很容易。如果有人想修改或定製示例應用的程式碼,有幾個Kafka構建指令碼已經過修改,可用於重新構建示例應用程式碼。關於如何定製示例應用的詳細描述已經放在專案GitHub的 Wiki頁面。
現在,讓我們看看示例應用的核心工件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
/** *
Instantiates a new Kafka producer. * *
@param topic the topic *
@param directoryPath the directory path */ public
KafkaMailProducer(String topic, String directoryPath) { props.put( "serializer.class" , "kafka.serializer.StringEncoder" ); props.put( "metadata.broker.list" , "localhost:9092" ); producer
= new
kafka.javaapi.producer.Producer<Integer, String>( new
ProducerConfig(props)); this .topic
= topic; this .directoryPath
= directoryPath; } public
void
run() { Path
dir = Paths.get(directoryPath); try
{ new
WatchDir(dir).start(); new
ReadDir(dir).start(); } catch
(IOException e) { e.printStackTrace(); } } |
上面的程式碼片斷展示了Kafka生產者API的基本用法,例如設定生產者的屬性,包括髮布哪個話題的訊息,可以使用哪個序列化類以及代理的相關 資訊。這個類的基本功能是從郵件目錄讀取郵件訊息檔案,然後作為訊息釋出到Kafka代理。目錄通過java.nio.WatchService類監視, 一旦新的郵件訊息Dump到該目錄,就會被立即讀取並作為訊息釋出到Kafka代理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
public
KafkaMailConsumer(String topic) { consumer
= Kafka.consumer.Consumer.createJavaConsumerConnector(createConsumerConfig()); this .topic
= topic; } /** *
Creates the consumer config. * *
@return the consumer config */ private
static
ConsumerConfig createConsumerConfig() { Properties
props = new
Properties(); props.put( "zookeeper.connect" ,
KafkaMailProperties.zkConnect); props.put( "group.id" ,
KafkaMailProperties.groupId); props.put( "zookeeper.session.timeout.ms" , "400" ); props.put( "zookeeper.sync.time.ms" , "200" ); props.put( "auto.commit.interval.ms" , "1000" ); return
new
ConsumerConfig(props); } public
void
run() { Map<String,
Integer> topicCountMap = new
HashMap<String, Integer>(); topicCountMap.put(topic, new
Integer( 1 )); Map<String,
List<KafkaStream< byte [], byte []>>>
consumerMap = consumer.createMessageStreams(topicCountMap); KafkaStream< byte [], byte []>
stream = consumerMap.get(topic).get( 0 ); ConsumerIterator< byte [], byte []>
it = stream.iterator(); while
(it.hasNext()) System.out.println( new
String(it.next().message())); } |
上面的程式碼演示了基本的消費者API。正如我們前面提到的,消費者需要設定消費的訊息流。在Run方法中,我們進行了設定,並在控制檯列印收到的訊息。在我的專案中,我們將其輸入到解析系統以提取OTC定價。
在當前的質量保證系統中,我們使用Kafka作為訊息伺服器用於概念驗證(Proof of Concept,POC)專案,它的整體效能優於JMS訊息服務。其中一個我們感到非常興奮的特性是訊息的再消費(re-consumption),這讓 我們的解析系統可以按照業務需求重新解析某些訊息。基於Kafka這些很好的效果,我們正計劃使用它,而不是用Nagios系統,去做日誌聚合與分析。
總結
Kafka是一種處理大量資料的新型系統。Kafka基於拉的消費模型讓消費者以自己的速度處理訊息。如果處理訊息時出現了異常,消費者始終可以選擇再消費該訊息。
相關文章
- Apache Kafka分散式訊息系統ApacheKafka分散式
- Kafka 分散式訊息系統Kafka分散式
- 分散式訊息系統:Kafka分散式Kafka
- 分散式訊息系統Kafka初步分散式Kafka
- 分散式訊息Kafka分散式Kafka
- 分散式訊息系統之Kafka叢集部署分散式Kafka
- 分散式訊息系統Kafka Java客戶端程式碼分散式KafkaJava客戶端
- 分散式訊息通訊Kafka(二) - 原理分析分散式Kafka
- Kafka(分散式釋出-訂閱訊息系統)工作流程說明Kafka分散式
- 分散式高效能訊息系統(Kafka MQ)的原理與實踐分散式KafkaMQ
- 快速理解Kafka分散式訊息佇列框架Kafka分散式佇列框架
- Apache Kafka訊息傳遞策略ApacheKafka
- 下一代分散式訊息佇列Apache Pulsar從入門到實現分散式佇列Apache
- 高吞吐量訊息系統—kafkaKafka
- 釋出於訂閱訊息系統-KafkaKafka
- Kafka訊息系統基礎知識索引Kafka索引
- Kafka真正定位並不是訊息系統Kafka
- 分散式訊息流平臺:不要只想著Kafka,還有Pulsar分散式Kafka
- Kafka無法消費?!我的分散式訊息服務Kafka卻穩如泰山!Kafka分散式
- 分散式訊息系統如何解決訊息的順序&重複兩大硬傷?分散式
- 分散式訊息佇列分散式佇列
- 從訊息中介軟體看分散式系統的多種套路分散式
- 分散式系統訊息中介軟體——RabbitMQ的使用進階篇分散式MQ
- RocketMQ 分散式事務訊息MQ分散式
- 分散式訊息中介軟體分散式
- 分散式系統:程序間通訊分散式
- KMQ:基於Apache Kafka的可靠性訊息佇列MQApacheKafka佇列
- Kafka 訊息監控 - Kafka EagleKafka
- 訊息佇列在大型分散式系統中的實戰要點分析!佇列分散式
- 大資料技術 - 分散式訊息流平臺:Kafka與Pulsar的介紹大資料分散式Kafka
- Kafka訊息佇列Kafka佇列
- kafka 訊息佇列Kafka佇列
- Kafka的訊息格式Kafka
- Kafka入門(構建TB級非同步訊息系統)及Spring整合KafkaKafka非同步Spring
- 【分散式】 07 系統通訊初識分散式
- 從 Kafka 到 Pulsar,BIGO 打造實時訊息系統之路KafkaGo
- 分散式訊息佇列RocketMQ--事務訊息--解決分散式事務的最佳實踐分散式佇列MQ
- 分散式事務:訊息可靠傳送分散式