下一代分散式訊息系統:Apache Kafka

五柳-先生發表於2015-11-17

簡介

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拉資料,從而消費這些已釋出的訊息。

Apache Kafka:下一代分散式訊息系統

 

圖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叢集通常包括多個代理。為了均衡負載,將話題分成多個分割槽,每個代理儲存一或多個分割槽。多個生產者和消費者能夠同時生產和獲取訊息。

Apache Kafka:下一代分散式訊息系統

圖2:Kafka架構

Kafka儲存

Kafka的儲存佈局非常簡單。話題的每個分割槽對應一個邏輯日誌。物理上,一個日誌為相同大小的一組分段檔案。每次生產者釋出訊息到一個分割槽, 代理就將訊息追加到最後一個段檔案中。當釋出的訊息數量達到設定值或者經過一定的時間後,段檔案真正寫入磁碟中。寫入完成後,訊息公開給消費者。

與傳統的訊息系統不同,Kafka系統中儲存的訊息沒有明確的訊息Id。

訊息通過日誌中的邏輯偏移量來公開。這樣就避免了維護配套密集定址,用於對映訊息ID到實際訊息地址的隨機存取索引結構的開銷。訊息ID是增量的,但不連續。要計算下一訊息的ID,可以在其邏輯偏移的基礎上加上當前訊息的長度。

消費者始終從特定分割槽順序地獲取訊息,如果消費者知道特定訊息的偏移量,也就說明消費者已經消費了之前的所有訊息。消費者向代理髮出非同步拉請求,準備位元組緩衝區用於消費。每個非同步拉請求都包含要消費的訊息偏移量。Kafka利用sendfile API高效地從代理的日誌段檔案中分發位元組給消費者。

Apache Kafka:下一代分散式訊息系統

圖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,以獲得高可用性。每個伺服器都持有分散式檔案系統的記憶體複本,為客戶端的讀取請求提供服務。

Apache Kafka:下一代分散式訊息系統

圖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所示。

Apache Kafka:下一代分散式訊息系統

圖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所示:

Apache Kafka:下一代分散式訊息系統

圖6:LinkedIn的生產者效能實驗結果

Kafka效能要好很多的主要原因包括:

  • Kafka不等待代理的確認,以代理能處理的最快速度傳送訊息。
  • Kafka有更高效的儲存格式。平均而言,Kafka每條訊息有9位元組的開銷,而ActiveMQ有144位元組。其原因是JMS所需的沉重 訊息頭,以及維護各種索引結構的開銷。LinkedIn注意到ActiveMQ一個最忙的執行緒大部分時間都在存取B-Tree以維護訊息後設資料和狀態。

消費者測試

為了做消費者測試,LinkedIn使用一個消費者獲取總共1000萬條訊息。LinkedIn讓所有系統每次拉請求都預獲取大約相同數量的數 據,最多1000條訊息或者200KB。對ActiveMQ和RabbitMQ,LinkedIn設定消費者確認模型為自動。結果如圖7所示。

Apache Kafka:下一代分散式訊息系統

圖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的特性非常適用於我們專案的需求。

當前專案具備的特性:

  1. 使用Fetchmail獲取遠端郵件訊息,然後由Procmail過濾並處理,例如單獨分發基於附件的訊息。
  2. 每條訊息從單獨的檔案獲取,該檔案被處理(讀取和刪除)為一條訊息插入到訊息伺服器中。
  3. 訊息內容從訊息服務佇列中獲取,用於解析和提取資訊。

示例應用

這個示例應用是基於我在專案中使用的原始應用修改後的版本。我已經刪除日誌的使用和多執行緒特性,使示例應用的工件儘量簡單。示例應用的目的是展示如何使用Kafka生產者和消費者的API。應用包括一個 生產者示例(簡單的生產者程式碼,演示Kafka生產者API用法併發布特定話題的訊息), 消費者示例(簡單的消費者程式碼,用於演示Kafka消費者API的用法)以及 訊息內容生成API(在特定路徑下生成訊息內容到檔案的API)。下圖展示了各元件以及它們與系統中其它元件間的關係。

Apache Kafka:下一代分散式訊息系統

圖8:示例應用元件架構

示例應用的結構與Kafka原始碼中的例子程式相似。應用的原始碼包含Java源程式資料夾‘src’和'config'資料夾,後者包括幾個配置檔案和一些Shell指令碼,用於執行示例應用。要執行示例應用,請參照 ReadMe.md檔案或GitHub網站 Wiki頁面的說明。

程式構建可以使用 Apache Maven,定製也很容易。如果有人想修改或定製示例應用的程式碼,有幾個Kafka構建指令碼已經過修改,可用於重新構建示例應用程式碼。關於如何定製示例應用的詳細描述已經放在專案GitHub的 Wiki頁面

現在,讓我們看看示例應用的核心工件。

Kafka生產者程式碼示例

?
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代理。

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基於拉的消費模型讓消費者以自己的速度處理訊息。如果處理訊息時出現了異常,消費者始終可以選擇再消費該訊息。

 

http://www.infoq.com/cn/articles/apache-kafka/

相關文章