Kafka:用於日誌處理的分佈式消息系統

摘要

日誌處理已成爲消費者互聯網公司數據管道中的重要組成部分。我們介紹Kafka,這是一個分佈式消息系統,我們開發該系統是爲了以低延遲收集和發送大量日誌數據。我們的系統結合了現有日誌聚合器和消息傳遞系統的思想,同時適用於離線和在線消息消費使用。我們在Kafka中做出了許多非常規但實用的設計選擇,以使我們的系統高效且可擴展。我們的實驗結果表明,與兩種流行的消息傳遞系統相比,Kafka具有卓越的性能。我們已經在生產中使用Kafka有一段時間了,它每天要處理數百GB的新數據。

常用術語

管理,性能,設計,實驗。

關鍵詞

消息傳遞,分佈式,日誌處理,吞吐量,在線。

1. 簡介

在任何規模較大的互聯網公司中都會生成大量“日誌”數據。這類數據通常包括(1) 與登錄、瀏覽量、點擊、“喜歡”、分享、評論和搜索查詢相對應的用戶活動事件;(2) 操作指標,例如服務調用堆棧、調用延遲、錯誤,以及系統指標,例如每臺計算機上的CPU、內存、網絡或磁盤利用率。日誌數據一直是用於跟蹤用戶參與度、系統利用率和其他指標的分析的組成部分。但是,網絡應用的最新趨勢已使生產數據管道的一部分的活動數據被直接用於站點功能。這些用途包括(1) 搜索相關性,(2) 可能由活動流中的項目受歡迎程度或併發所驅動的推薦,(3) 廣告定位和報表,(4) 防止濫用行爲(例如垃圾郵件或未經授權的數據抓取)的安全應用,以及(5) 新聞摘要功能,這些功能彙總了用戶狀態更新或以供其“朋友”或“連接”讀取的操作。

日誌數據的這種實時生產使用給數據系統帶來了新的挑戰,因爲其數據量比“實際”數據大幾個數量級。例如,搜索、推薦和廣告通常需要計算細粒度的點擊率,這不僅會爲每個用戶點擊生成日誌記錄,還會爲每個頁面上數十個未被點擊的項目生成日誌記錄。每天,中國移動收集5–8 TB的電話記錄[11],而Facebook收集近6 TB的各種用戶活動事件[12]。

許多用於處理此類數據的早期系統都依靠物理地將日誌文件從生產服務器上抓取下來進行分析。近年來,已經建立了幾種專門的分佈式日誌聚合器,包括Facebook的Scribe[6],Yahoo的Data Highway[4]和Cloudera的Flume[3]。這些系統主要用於收集日誌數據並將其加載到數據倉庫或Hadoop[8]中以供離線使用。在LinkedIn(一個社交網站),我們發現,除了傳統的離線分析之外,我們還需要以不超過幾秒鐘的時延來支持上述大多數實時應用。

我們已經建立了一種用於日誌處理的新型消息傳遞系統,叫做Kafka[18],該系統結合了傳統日誌聚合器和消息傳遞系統的優勢。一方面,Kafka是分佈式且可擴展的,並提供高吞吐量。另一方面,Kafka提供類似於消息傳遞系統的API,並允許應用實時使用日誌事件。Kafka已開源,並已在LinkedIn中成功用於生產中超過6個月。因爲我們可以利用單個軟件來同時在線和離線使用所有類型的日誌數據,所以它極大地簡化了我們的基礎架構。本文的其餘部分安排如下。我們將在第2節中回顧傳統的消息傳遞系統和日誌聚合器。在第3節中,我們描述Kafka的體系架構及其關鍵設計原則。我們將在第4節中介紹在LinkedIn中Kafka的部署情況,並在第5節中介紹Kafka的性能結果。在第6節中,我們討論未來的工作並得出結論。

2. 相關工作

傳統的企業消息傳遞系統[1][7][15][17]已經存在了很長時間,並且經常作爲處理異步數據流的事件總線發揮關鍵作用。但是,由於某些原因,它們往往不適合日誌處理。首先,企業系統提供的功能不匹配。這些系統通常專注於提供豐富的交付保證。例如,IBM Websphere MQ[7]具有事務支持,允許應用原子地將消息插入多個隊列。JMS[14]規範允許在消費後確認每個單獨的消息(可能會導致亂序)。這樣的交付保證對於收集日誌數據通常是過份行爲。例如,偶爾丟失一些綜合瀏覽量事件肯定不是世界末日。這些不需要的功能往往會增加API和這些系統的基礎實現的複雜性。其次,許多系統沒有像其主要設計約束那樣專注於吞吐量。例如,JMS沒有API允許生產者將多個消息顯式批處理爲單個請求。這意味着每個消息都需要一個完整的TCP/IP往返,這對於我們域的吞吐量要求是不可行的。第三,這些系統在分佈式支持方面薄弱。沒有簡單的方法可以在多臺計算機上分區和存儲消息。最後,許多消息發送系統假定消息將立即消費掉,因此未消費的消息隊列總是很小。如果允許累積消息,則它們的性能將大大降低,對於離線使用方(例如,數據倉庫應用,它們會定期進行大負載地而不是連續地使用),情況就是如此。

在過去的幾年中,已經建立了許多專業的日誌聚合器。Facebook使用名爲Scribe的系統。每個前端計算機都可以通過套接字將日誌數據發送到一組Scribe機器。每臺Scribe機器都會彙總日誌條目,並定期將它們轉儲到HDFS[9]或NFS設備。雅虎的數據高速公路項目具有類似的數據流。一組計算機聚集來自客戶端的事件並推出“分鐘”文件,然後將其添加到HDFS。Flume是由Cloudera開發的相對較新的日誌聚合器。它支持可擴展的“管道”和“接收器”,並使流式日誌數據非常靈活。它還具有更多集成的分佈式支持。但是,大多數這些系統是爲離線使用日誌數據而構建的,並且經常不必要地向消費者暴露實現細節(例如“分鐘文件”)。此外,它們中的大多數使用“推送”模式,其代理將數據轉發給消費者。在LinkedIn,我們發現“拉取”模式更適合我們的應用,因爲每個消費者都可以以其可以維持的最大速率檢索消息,並避免被以超出其處理能力的推送速度的消息淹沒。拉取模式還可以輕鬆地回退消費者,我們將在第3.2節的末尾討論這種好處。

最近,Yahoo!Research開發了一種新的分佈式發佈/訂閱系統,稱爲HedWig[13]。HedWig具有高度的可擴展性和可用性,並提供強大的持久性保證。但是,它主要用於保存數據存儲的提交日誌。

3. Kafka架構和設計原則

由於現有系統的限制,我們開發了一種新的基於消息的日誌聚合器Kafka。我們首先介紹Kafka中的基本概念。由主題定義的特定類型的消息流。生產者可以將消息發佈到主題。 然後,已發佈的消息將存儲在一組稱爲代理的服務器上。消費者可以訂閱來自代理的一個或多個主題,並通過從代理拉取數據來消費訂閱的消息。

消息從概念上講很簡單,我們試圖使Kafka API同樣簡單以反映這一點。我們沒有展示確切的API,而是提供了一些示例代碼來說明如何使用API。生產者的示例代碼如下。一條消息被定義爲僅包含字節的有效負載。用戶可以選擇她喜歡的序列化方法來編碼消息。爲了提高效率,生產者可以在單個發佈請求中發送一組消息。

生產者代碼示例:

producer = new Producer(…);
message = new Message(“test message str”.getBytes());
set = new MessageSet(message);
producer.send(“topic1”, set);

爲了訂閱某主題,消費者首先爲該主題創建一個或多個消息流。發佈給該主題的消息將平均分配到這些子流中。有關Kafka如何分發消息的詳細信息,將在第3.2節中介紹。每個消息流在正在生成的連續消息流上提供迭代器接口。然後,消費者遍歷流中的每個消息並處理消息的有效負載。與傳統的迭代器不同,消息流迭代器永遠不會終止。如果當前沒有更多消息可消費,則迭代器將阻塞,直到有新消息發佈到該主題爲止。我們既支持多個消費者共同使用一個主題中所有消息的單個副本的點對點傳遞模型,也支持多個消費者各自拉取其自己的主題副本的發佈/訂閱模型。

消費者代碼示例:

streams[] = Consumer.createMessageStreams(“topic1”, 1)
for (message : streams[0]) {
  bytes = message.payload();
  // do something with the bytes
}

Kafka的總體架構如圖1所示。由於Kafka實際上是分佈式的,因此Kafka集羣通常由多個代理組成。爲了平衡負載,一個主題分爲多個分區,每個代理存儲一個或多個這些分區。多個生產者和消費者可以同時發佈和拉取消息。在3.1節中,我們描述了代理上單個分區的佈局以及我們選擇的一些設計選擇,以使訪問分區更加有效。在3.2節中,我們描述了生產者和消費者如何在分佈式環境中與多個代理進行交互。我們將在第3.3節中討論Kafka的交付保證。

圖1. Kafka架構

3.1 單分區的效率

我們在Kafka中做出了一些選擇,以提高系統效率。

3.1.1 簡單的存儲

Kafka的存儲佈局非常簡單。主題的每個分區都對應一個邏輯日誌。在物理上,日誌是由一組大小近似相同(例如1GB)的段文件實現的。生產者每次將消息發佈到分區時,代理都將消息簡單地附加到最後一個段文件。爲了獲得更好的性能,我們僅在發佈了可配置數量的消息或經過一定時間後纔將分段文件刷新到磁盤。消息僅在刷新後才暴露給消費者。

與典型的消息系統不同,存儲在Kafka中的消息沒有明確的消息ID。相反,每個消息都通過其在日誌中的邏輯偏移量來尋址。這樣避免了維護將消息ID映射到實際消息位置的輔助的、查找密集型隨機訪問索引結構的開銷。請注意,我們的消息ID正在增加,但不是連續的。要計算下一條消息的ID,我們必須將當前消息的長度添加到其ID中。從現在開始,我們將交替使用消息ID和偏移量。

消費者始終按順序使用來自特定分區的消息。如果消費者確認特定的消息偏移量,則表示消費者已經接收了在分區中的該偏移量之前的所有消息。在後臺,消費者正在向代理髮出異步請求,以使數據緩衝區可供應用消費。每個拉取請求都包含從中開始消費的消息的偏移量和要提取的可接受的字節數。每個代理在內存中保存一個偏移量的排序列表,包括每個段文件中第一條消息的偏移量。代理通過搜索偏移量列表來定位所請求消息所位於的段文件,並將數據發送回消費者。消費者接收到一條消息後,它將計算下一條要消費的消息的偏移量,並在下一個拉取請求中使用它。Kafka日誌和內存索引的佈局如圖2所示。每個框顯示一條消息的偏移量。

圖2. Kafka日誌

3.1.2 高效的傳輸

我們非常關注將數據傳入和傳出Kafka。之前,我們已經展示了生產者可以在單個發送請求中提交一組消息。儘管最終消費者API一次迭代一條消息,但在後臺,來自消費者的每個拉取請求還可以檢索到一定大小(通常爲數百KB)的多條消息。

我們做出的另一個非常規選擇是避免在Kafka層上將消息顯式緩存在內存中。相反,我們依賴於底層文件系統頁面緩存。這具有避免雙重緩衝的主要好處 - 消息僅被緩存在頁面高速緩存中。這樣做還有一個好處,即使重新啓動代理進程,也可以保留熱緩存。由於Kafka根本不緩存進程中的消息,因此在垃圾回收內存方面幾乎沒有開銷,從而可以使用基於VM的語言進行高效實現。最後,由於生產者和消費者都按順序訪問段文件,因此消費者經常落後於生產者一小部分,因此正常的操作系統緩存試探法非常有效(特別是直寫式緩存和預讀)。我們已經發現,生產和消耗都具有與數據大小成線性關係的一致性能,最大可達數TB的數據。

此外,我們爲消費者優化了網絡訪問。Kafka是一個多用戶系統,單個消息可能會被不同的消費者應用多次使用。從本地文件向遠程套接字發送字節的典型方法包括以下步驟:(1) 從存儲介質讀取數據到OS中的頁面緩存,(2) 將頁面緩存中的數據複製到應用程序緩衝區,(3) 將應用程序緩衝區複製到另一個內核緩衝區,(4) 從內核緩衝區發送到套接字。這包括4個數據複製和2個系統調用。在Linux和其他Unix操作系統上,存在一個sendfile API[5],它可以直接將字節從文件通道傳輸到套接字通道。這樣通常可以避免步驟2和步驟3中引入的2個複製和1個系統調用。Kafka利用sendfile API有效地將日誌段文件中的字節從代理傳遞到消費者。

3.1.3 無狀態代理

與大多數其他消息傳遞系統不同,在Kafka中,有關每個消費者已消費多少的信息不是由代理維護的,而是由消費者自己維護的。這樣的設計減少了代理的很多複雜性和開銷。但是,由於代理不知道是否所有訂閱者都已消費該消息,因此刪除消息變得很棘手。Kafka通過將簡單的基於時間的SLA用於保留策略來解決此問題。如果消息在代理中的保留時間超過一定時間(通常爲7天),則會自動刪除該消息。該解決方案在實踐中效果很好。大多數消費者(包括離線消費者)每天、每小時或實時完成消費。Kafka的性能不會隨着數據量的增加而降低,這一事實使得這種長期保留成爲可能。

此設計有一個重要的附帶好處。消費者可以有意地回退到舊的偏移量並重新使用數據。這違反了隊列的通用協議,但是事實證明這是許多消費者的基本功能。例如,當消費者中的應用程序邏輯出現錯誤時,錯誤修復後,應用程序可以重播某些消息。這對於將ETL數據加載到我們的數據倉庫或Hadoop系統中特別重要。作爲另一示例,所消費的數據可以僅週期性地被刷新到持久性存儲(例如,全文本索引器)。如果消費者崩潰,則未刷新的數據將丟失。在這種情況下,消費者可以將未刷新消息的最小偏移量設置檢查點,並在重新啓動後從該偏移量中重新消費。我們注意到,在拉取模式中支持回退消費者要比推送模型容易得多。

3.2 分佈式協調

現在我們描述生產者和消費者在分佈式環境中的行爲。每個生產者可以將消息發佈到隨機選擇的分區或由分區鍵和分區函數語義確定的分區。我們將關注於消費者與代理的互動方式。

Kafka具有消費者組的概念。每個消費者組由一個或多個共同消費一組訂閱主題的消費者組成,即,每個消息僅傳遞給該組中的一個消費者。不同的消費者組各自獨立地消費整個訂閱消息集,並且不需要跨消費者組進行協調。同一組中的消費者可以處於不同的進程中,也可以位於不同的機器上。我們的目標是在消費者之間平均分配存儲在代理中的消息,而不會引入過多的協調開銷。

我們的第一個決定是使主題內的分區成爲並行度的最小單位。這意味着在任何給定時間,來自一個分區的所有消息僅由每個消費者組中的單個消費者消費。如果我們允許多個消費者同時消費一個分區,那麼他們將必須協調誰消費哪些消息,這需要加鎖和狀態維護開銷。相反,在我們的設計中,消費進程僅在消費者重新平衡負載時才需要協調(這種情況很少發生)。爲了使負載真正達到平衡,我們在一個主題中需要的分區要比每組中的消費者多得多。我們可以通過對主題進行過度分區來輕鬆實現這一目標。

我們做出的第二個決定是沒有中央的“主”節點,而是讓消費者以分散的方式在彼此之間進行協調。增加主服務器會使系統複雜化,因爲我們必須進一步考慮主服務器故障。爲了促進協調,我們使用了高度可用的一致性服務Zookeeper[10]。Zookeeper有一個非常簡單的類文件系統API。可以創建路徑、設置路徑的值、讀取路徑的值、刪除路徑並列出路徑的子路徑。它做了一些更有趣的事情:(a) 可以在路徑上註冊觀察者,並在路徑的子路徑或路徑的值發生更改時得到通知;(b) 可以臨時創建一個路徑(與持久性相反),這意味着如果創建的客戶端不存在了,則該路徑將由Zookeeper服務器自動刪除;© Zookeeper將其數據複製到多個服務器,這使數據高度可靠且可用。

Kafka使用Zookeeper執行以下任務:(1) 檢測代理和消費者的添加和刪除,(2) 在上述事件發生時,在每個消費者中觸發重新平衡過程,以及(3) 維護消費關係並跟蹤每個分區的消費的偏移量。具體來說,每個代理或消費者啓動時,會將其信息存儲在Zookeeper中的代理或消費者的註冊表中。代理註冊表包含代理的主機名和端口,以及存儲在其中的主題和分區集合。消費者註冊表包括消費者所屬的消費者組及其訂閱的主題集。每個消費者組都與Zookeeper中的所有權註冊表和偏移量註冊表關聯。所有權註冊表對每個訂閱的分區都有一個路徑,路徑值是當前從該分區消費的消費者的ID(我們使用的消費者擁有該分區的術語)。偏移量註冊表爲每個訂閱的分區存儲該分區中最後消費的消息的偏移量。

在Zookeeper中創建的路徑對於代理註冊表、消費者註冊表和所有權註冊表是臨時的,對於偏移量註冊表是持久的。如果代理失敗,則該代理上的所有分區都會自動從代理註冊表中刪除。消費者的故障導致它丟失在消費者註冊表中的條目以及在所有者註冊表中擁有的所有分區。每個消費者都在代理註冊表和消費者註冊表上都註冊了Zookeeper觀察者,並且每當代理集合或消費者組發生更改時,都將收到通知。

在消費者的初始啓動過程中,或者通過觀察者通知消費者有關代理/消費者更改的通知時,消費者將啓動重新平衡過程以確定應從中消費的新分區子集。算法1中描述了該過程。通過從Zookeeper中讀取代理和消費者註冊表,消費者首先計算可用於每個已訂閱主題T的分區集(PT)和訂閱T的消費者集(CT)。然後它將PT範圍分區爲|CT|塊並確定地選擇擁有其中一個塊。對於消費者選擇的每個分區,它都會在所有權註冊表中將自己寫爲該分區的新所有者。最後,消費者啓動一個線程從每個擁有的分區中拉取數據(從偏移註冊表中存儲的偏移處開始)。隨着從分區中拉取消息,消費者將定期更新偏移量註冊表中的最新的消費偏移量。

Algorithm 1: rebalance process for consumer Ci in group G
For each topic T that Ci subscribes to {
  remove partitions owned by Ci from the ownership registry
  read the broker and the consumer registries from Zookeeper
  compute PT = partitions available in all brokers under topic T
  compute CT = all consumers in G that subscribe to topic T
  sort PT and CT
  let j be the index position of Ci in CT and let N = |PT|/|CT|
  assign partitions from j*N to (j+1)*N - 1 in PT to consumer Ci
  for each assigned partition p {
    set the owner of p to Ci in the ownership registry
    let Op = the offset of partition p stored in the offset registry
    invoke a thread to pull data in partition p from offset Op
  }
}

當一個組中有多個消費者時,將通知每個代理或消費者變更。但是,在消費者處通知的到達時間可能略有不同。因此,一個消費者有可能嘗試獲得仍由另一個消費者擁有的分區的所有權。發生這種情況時,第一個消費者只需釋放其當前擁有的所有分區,稍等片刻,然後重試重新平衡過程。實際上,重新平衡過程通常只需重試幾次即可穩定下來。

創建新的消費者組時,偏移量註冊表中沒有可用的偏移量。在這種情況下,使用我們在代理上提供的API,消費者將從每個訂閱分區上可用的最小或最大偏移量(取決於配置)開始。

3.3 交付保證

通常,Kafka僅保證至少一次交付。恰好一次交付通常需要兩階段提交,而對於我們的應用不是必需的。在大多數情況下,一條消息恰好一次發送給每個消費者組。但是,如果消費者進程崩潰而沒有完全關閉,則接管有故障的消費者擁有的那些分區的消費者進程可能會得到一些重複的消息,這些消息是在最後一次偏移量成功提交給Zookeeper之後的。如果應用關心重複,則它必須使用我們返回給使用者的偏移量或消息中的某些唯一鍵來添加自己的重複數據刪除邏輯。與使用兩階段提交相比,這通常是一種更經濟有效的方法。

Kafka保證將來自單個分區的消息按順序傳遞給消費者。但是,不能保證來自不同分區的消息的順序。

爲了避免日誌損壞,Kafka將每個消息的CRC存儲在日誌中。如果代理上存在任何I/O錯誤,Kafka將運行恢復過程以刪除帶有不一致CRC的消息。在消息級別使用CRC還可以使我們在產生或消費消息之後檢查網絡錯誤。

如果代理髮生故障,則存儲在其上尚未消費的任何消息將變得不可用。如果代理上的存儲系統被永久損壞,則所有未使用的消息將永遠丟失。將來,我們計劃在Kafka中添加內置複製,以將每個消息冗餘地存儲在多個代理上。

4. Kafka在LinkedIn中的使用

在本節中,我們描述瞭如何在LinkedIn使用Kafka。圖3顯示了我們部署的簡化版本。我們有一個Kafka集羣與運行我們的用戶界面服務的每個數據中心共置一處。前端服務生成各種日誌數據,並將其批量發佈到本地Kafka代理。我們依靠硬件負載平衡器將發佈請求平均分配給Kafka代理集。Kafka的在線消費者在同一數據中心內的服務中運行。

圖3. Kafka的部署

我們還在單獨的數據中心中部署了一個Kafka集羣,以進行離線分析,該集羣的地理位置靠近我們的Hadoop集羣和其他數據倉庫基礎設施。該Kafka實例運行一組嵌入式消費者,以從實時數據中心的Kafka實例中拉取數據。然後,我們運行數據加載作業,以將數據從Kafka的副本集羣中拉取到Hadoop和我們的數據倉庫,在此我們對數據運行各種報表作業和分析過程。我們還使用此Kafka集羣進行原型設計,並能夠針對原始事件流運行簡單腳本以進行特定查詢。無需太多調整,整個管道的端到端延遲平均約爲10秒,足以滿足我們的要求。

目前,Kafka每天存儲數百GB的數據和近10億條消息,隨着我們完成對舊系統的切換以利用Kafka的優勢,我們預計這一數量將顯著增長。將來會添加更多類型的消息。當操作人員啓動或停止代理進行軟件或硬件維護時,重新平衡過程能夠自動重定向消費。

我們的跟蹤還包括一個審覈系統,以驗證整個管道中沒有數據丟失。爲方便起見,每條消息在生成時均帶有時間戳和服務器名稱。我們對每個生產者進行監測,使其定期生成監視事件,該事件記錄該生產者在固定時間窗口內針對每個主題發佈的消息數。生產者在單獨的主題中將監視事件發佈到Kafka。然後,消費者可以計算他們從給定主題中收到的消息數,並使用監視事件來驗證這些計數以驗證數據的正確性。

加載到Hadoop集羣中是通過實現一種特殊的Kafka輸入格式來完成的,該格式允許MapReduce作業直接從Kafka讀取數據。MapReduce作業將加載原始數據,然後對其進行分組和壓縮,以在將來進行有效處理。消息偏移的無狀態代理和客戶端存儲再次在這裏發揮作用,允許MapReduce任務管理(允許任務失敗並重新啓動)以自然方式處理數據加載,而不會在任務重新啓動時重複或丟失消息。僅在成功完成作業時,數據和偏移量才被存儲到HDFS中。

我們選擇使用Avro[2]作爲序列化協議,因爲它高效且支持模式演變。對於每條消息,我們將其Avro模式的ID和序列化的字節存儲在有效負載中。這種模式使我們可以強制協議確保數據生產者和消費者之間的兼容性。我們使用輕量級的模式註冊表服務將模式ID映射到實際模式。消費者收到消息時,它將在模式註冊表中進行查詢以檢索模式,該模式用於將字節解碼爲對象(由於值是不可變的,因此每個模式只需要執行一次該查詢)。

5. 實驗結果

我們進行了一項實驗研究,將Kafka與Apache ActiveMQ v5.4[1](一種流行的JMS開源實現)和RabbitMQ v2.4[16](一種以其性能著稱的消息系統)的性能進行了比較。我們使用了ActiveMQ的默認持久消息存儲KahaDB。儘管此處未介紹,但我們還測試了替代的AMQ消息存儲,發現其性能與KahaDB非常相似。只要可能,我們都會嘗試在所有系統中使用可比較的設置。

我們在2臺Linux機器上進行了實驗,每臺機器具有8個2GHz核、16GB內存、6個RAID 10磁盤。這兩臺機器通過1Gb網絡鏈路連接。其中一臺機器用作代理,另一臺機器用作生產者或消費者。

5.1 生產者測試

我們在所有系統中將代理配置爲異步將消息刷新到其持久性存儲。對於每個系統,我們運行一個生產者來發布總共1000萬條消息,每條消息200個字節。我們配置了Kafka生產者以1和50的大小批量發送消息。ActiveMQ和RabbitMQ似乎沒有簡單的方法來批量發送消息,並且我們假定它使用的批量大小爲1。結果如圖4所示。x軸表示隨時間推移發送給代理的數據量(以MB爲單位),y軸表示生產者吞吐量(以每秒消息數爲單位)。平均而言,Kafka可以以每秒50000和400000條消息的速度發佈消息,對應的批量大小分別爲1和50。這些數字比ActiveMQ高几個數量級,比RabbitMQ高至少2倍。

圖4. 生產性能

Kafka表現出色很多的原因有幾個。首先,Kafka生產者當前不等待代理的確認,而是以代理可以處理的儘可能快的速度發送消息。這大大提高了發佈者的吞吐量。當批量大小爲50時,單個Kafka生產者幾乎飽和了生產者和代理之間的1Gb鏈路。對於日誌聚合情況,這是一種有效的優化,因爲必須異步發送數據,以避免將任何延時引入流量的實時服務中。我們注意到,在生產者沒有確認的情況下,不能保證代理實際上收到了每個已發佈的消息。對於許多類型的日誌數據,希望以持久性換取吞吐量,只要所丟棄消息的數量相對較小即可。但是,我們確實計劃在將來解決持久性問題,以獲取更多關鍵數據。

其次,Kafka具有更有效的存儲格式。平均而言,每條消息在Kafka中的開銷爲9字節,而在ActiveMQ中爲144字節。這意味着ActiveMQ使用的存儲空間比Kafka多70%,用於存儲相同的1000萬條消息。ActiveMQ的一項開銷來自JMS所需的繁重消息頭。另一個開銷是維護各種索引結構的成本。我們觀察到ActiveMQ中最繁忙的線程之一花費了大部分時間來訪問B樹以維護消息元數據和狀態。最後,批處理通過平攤RPC開銷大大提高了吞吐量。在Kafka中,每批50條消息將吞吐量提高了近一個數量級。

5.2 消費者測試

在第二個實驗中,我們測試了消費者的性能。同樣,對於所有系統,我們僅使用一個消費者就可以檢索總共1000萬條消息。我們配置了所有系統,以便每個拉取請求會預拉取大約相同數量的數據 - 最多1000條消息或大約200KB。我們將消費者確認模式設置爲自動。由於所有消息都在內存中,因此所有系統都從底層文件系統的頁面緩存或某些內存緩衝區中提供數據。結果如圖5所示。

圖5. 消費者性能

平均而言,Kafka每秒消費22000條消息,是ActiveMQ和RabbitMQ的4倍以上。我們可以想到幾個原因。首先,由於Kafka具有更有效的存儲格式,因此從代理向Kafka中的消費者傳輸的字節數更少。其次,ActiveMQ和RabbitMQ中的代理都必須維護每個消息的發送狀態。我們觀察到,在此測試期間,ActiveMQ線程之一正在忙於將KahaDB頁面寫入磁盤。相反,Kafka的代理上沒有磁盤寫入活動。最後,通過使用sendfile API,Kafka減少了傳輸開銷。

在結束本節時,我們注意到該實驗的目的並不是要表明其他消息傳遞系統不如Kafka。畢竟,ActiveMQ和RabbitMQ都具有比Kafka更多的功能。重點是說明通過專用系統可以實現的潛在性能提升。

6. 結論與未來工作

我們提出了一種稱爲Kafka的新穎系統,用於處理大量日誌數據流。像消息傳遞系統一樣,Kafka使用基於拉取的消費模式,該模式允許應用以自己的速率消費數據並在需要時回退該消費。通過專注於日誌處理應用,Kafka實現了比常規消息傳遞系統更高的吞吐量。它還提供集成的分佈式支持,並且可以擴展。我們已經在LinkedIn成功地將Kafka用於離線和在線應用。

我們將來會探求許多方向。首先,我們計劃在多個代理之間添加消息的內置複製,從而即使在無法恢復的機器故障的情況下,也可以保證持久性和數據可用性。我們希望同時支持異步和同步複製模型,以在生產者延遲和提供的保證強度之間進行權衡。應用可以根據其對持久性、可用性和吞吐量的要求來選擇合適的冗餘級別。其次,我們要在Kafka中添加一些流處理功能。從Kafka檢索消息後,實時應用通常會執行類似的操作,例如基於窗口的統計以及將每個消息與輔助存儲中的記錄或另一個流中的消息關聯在一起。在底層,這可以通過在發佈過程中對消息按聯接鍵進行語義分區來支持,以便使用特定鍵發送的所有消息都到達相同的分區,從而到達單個消費者進程。這爲處理消費者機器集羣中的分佈式流提供了基礎。最重要的是,我們認爲有用的流實用工具庫(例如不同的窗口功能或連接技術)將對此類應用有益。

7. 參考文獻

  1. http://activemq.apache.org/
  2. http://avro.apache.org/
  3. Cloudera’s Flume, https://github.com/cloudera/flume
  4. http://developer.yahoo.com/blogs/hadoop/posts/2010/06/enabling_hadoop_batch_processi_1/
  5. Efficient data transfer through zero copy: https://www.ibm.com/developerworks/linux/library/jzerocopy/
  6. Facebook’s Scribe, http://www.facebook.com/note.php?note_id=32008268919
  7. IBM Websphere MQ: http://www-01.ibm.com/software/integration/wmq/
  8. http://hadoop.apache.org/
  9. http://hadoop.apache.org/hdfs/
  10. http://hadoop.apache.org/zookeeper/
  11. http://www.slideshare.net/cloudera/hw09-hadoop-baseddata-mining-platform-for-the-telecom-industry
  12. http://www.slideshare.net/prasadc/hive-percona-2009
  13. https://issues.apache.org/jira/browse/ZOOKEEPER-775
  14. JAVA Message Service: http://download.oracle.com/javaee/1.3/jms/tutorial/1_3_1-fcs/doc/jms_tutorialTOC.html.
  15. Oracle Enterprise Messaging Service: http://www.oracle.com/technetwork/middleware/ias/index-093455.html
  16. http://www.rabbitmq.com/
  17. TIBCO Enterprise Message Service: http://www.tibco.com/products/soa/messaging/
  18. Kafka, http://sna-projects.com/kafka/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章