轉自:http://www.oschina.net/translate/kafka-design
我們爲什麼要搭建該系統
Kafka是一個消息系統,原本開發自LinkedIn,用作LinkedIn的活動流(activity stream)和運營數據處理管道(pipeline)的基礎。現在它已爲多家不同類型的公司 作爲多種類型的數據管道(data pipeline)和消息系統使用。
活動流數據是所有站點在對其網站使用情況做報表時要用到的數據中最常規的部分。活動數據包括頁面訪問量(page view)、被查看內容方面的信息以及搜索情況等內容。這種數據通常的處理方式是先把各種活動以日誌的形式寫入某種文件,然後週期性地對這些文件進行統計分析。運營數據指的是服務器的性能數據(CPU、IO使用率、請求時間、服務日誌等等數據)。運營數據的統計方法種類繁多。
近年來,活動和運營數據處理已經成爲了網站軟件產品特性中一個至關重要的組成部分,這就需要一套稍微更加複雜的基礎設施對其提供支持。
活動流和運營數據的若干用例
- "動態彙總(News feed)"功能。將你朋友的各種活動信息廣播給你
- 相關性以及排序。通過使用計數評級(count rating)、投票(votes)或者點擊率( click-through)判定一組給定的條目中那一項是最相關的.
- 安全:網站需要屏蔽行爲不端的網絡爬蟲(crawler),對API的使用進行速率限制,探測出擴散垃圾信息的企圖,並支撐其它的行爲探測和預防體系,以切斷網站的某些不正常活動。
- 運營監控:大多數網站都需要某種形式的實時且隨機應變的方式,對網站運行效率進行監控並在有問題出現的情況下能觸發警告。
- 報表和批處理: 將數據裝載到數據倉庫或者Hadoop系統中進行離線分析,然後針對業務行爲做出相應的報表,這種做法很普遍。
活動流數據的特點
這種由不可變(immutable)的活動數據組成的高吞吐量數據流代表了對計算能力的一種真正的挑戰,因其數據量很容易就可能會比網站中位於第二位的數據源的數據量大10到100倍。
傳統的日誌文件統計分析對報表和批處理這種離線處理的情況來說,是一種很不錯且很有伸縮性的方法;但是這種方法對於實時處理來說其時延太大,而且還具有較高的運營複雜度。另一方面,現有的消息隊列系統(messaging and queuing system)卻很適合於在實時或近實時(near-real-time)的情況下使用,但它們對很長的未被處理的消息隊列的處理很不給力,往往並不將數據持久化作爲首要的事情考慮。這樣就會造成一種情況,就是當把大量數據傳送給Hadoop這樣的離線系統後, 這些離線系統每個小時或每天僅能處理掉部分源數據。Kafka的目的就是要成爲一個隊列平臺,僅僅使用它就能夠既支持離線又支持在線使用這兩種情況。
Kafka支持非常通用的消息語義(messaging semantics)。儘管我們這篇文章主要是想把它用於活動處理,但並沒有任何限制性條件使得它僅僅適用於此目的。
部署
下面的示意圖所示是在LinkedIn中部署後各系統形成的拓撲結構。
要注意的是,一個單個的Kafka集羣系統用於處理來自各種不同來源的所有活動數據。它同時爲在線和離線的數據使用者提供了一個單個的數據管道,在線活動和異步處理之間形成了一個緩衝區層。我們還使用kafka,把所有數據複製(replicate)到另外一個不同的數據中心去做離線處理。
我們並不想讓一個單個的Kafka集羣系統跨越多個數據中心,而是想讓Kafka支持多數據中心的數據流拓撲結構。這是通過在集羣之間進行鏡像或“同步”實現的。這個功能非常簡單,鏡像集羣只是作爲源集羣的數據使用者的角色運行。這意味着,一個單個的集羣就能夠將來自多個數據中心的數據集中到一個位置。下面所示是可用於支持批量裝載(batch loads)的多數據中心拓撲結構的一個例子:
請注意,在圖中上面部分的兩個集羣之間不存在通信連接,兩者可能大小不同,具有不同數量的節點。下面部分中的這個單個的集羣可以鏡像任意數量的源集羣。要了解鏡像功能使用方面的更多細節,請訪問這裏.
主要的設計元素
Kafka之所以和其它絕大多數信息系統不同,是因爲下面這幾個爲數不多的比較重要的設計決策:
- Kafka在設計之時爲就將持久化消息作爲通常的使用情況進行了考慮。
- 主要的設計約束是吞吐量而不是功能。
- 有關哪些數據已經被使用了的狀態信息保存爲數據使用者(consumer)的一部分,而不是保存在服務器之上。
- Kafka是一種顯式的分佈式系統。它假設,數據生產者(producer)、代理(brokers)和數據使用者(consumer)分散於多臺機器之上。
以上這些設計決策將在下文中進行逐條詳述。
消息持久化(Message Persistence)及其緩存
不要害怕文件系統!
在對消息進行存儲和緩存時,Kafka嚴重地依賴於文件系統。 大家普遍認爲“磁盤很慢”,因而人們都對持久化結(persistent structure)構能夠提供說得過去的性能抱有懷疑態度。實際上,同人們的期望值相比,磁盤可以說是既很慢又很快,這取決於磁盤的使用方式。設計的很好的磁盤結構往往可以和網絡一樣快。
磁盤性能方面最關鍵的一個事實是,在過去的十幾年中,硬盤的吞吐量正在變得和磁盤尋道時間嚴重不一致了。結果,在一個由6個7200rpm的SATA硬盤組成的RAID-5磁盤陣列上,線性寫入(linear write)的速度大約是300MB/秒,但隨即寫入卻只有50k/秒,其中的差別接近10000倍。線性讀取和寫入是所有使用模式中最具可預計性的一種方式,因而操作系統採用預讀(read-ahead)和後寫(write-behind)技術對磁盤讀寫進行探測並優化後效果也不錯。預讀就是提前將一個比較大的磁盤塊中內容讀入內存,後寫是將一些較小的邏輯寫入操作合併起來組成比較大的物理寫入操作。關於這個問題更深入的討論請參考這篇文章ACM Queue article;實際上他們發現,在某些情況下,順序磁盤訪問能夠比隨即內存訪問還要快!
爲了抵消這種性能上的波動,現代操作系變得越來越積極地將主內存用作磁盤緩存。所有現代的操作系統都會樂於將所有空閒內存轉做磁盤緩存,即時在需要回收這些內存的情況下會付出一些性能方面的代價。所有的磁盤讀寫操作都需要經過這個統一的緩存。想要捨棄這個特性都不太容易,除非使用直接I/O。因此,對於一個進程而言,即使它在進程內的緩存中保存了一份數據,這份數據也可能在OS的頁面緩存(pagecache)中有重複的一份,結構就成了一份數據保存了兩次。
更進一步講,我們是在JVM的基礎之上開發的系統,只要是瞭解過一些Java中內存使用方法的人都知道這兩點:
- Java對象的內存開銷(overhead)非常大,往往是對象中存儲的數據所佔內存的兩倍(或更糟)。
- Java中的內存垃圾回收會隨着堆內數據不斷增長而變得越來越不明確,回收所花費的代價也會越來越大。由於這些因素,使用文件系統並依賴於頁面緩存要優於自己在內存中維護一個緩存或者什麼別的結構 —— 通過對所有空閒內存自動擁有訪問權,我們至少將可用的緩存大小翻了一倍,然後通過保存壓縮後的字節結構而非單個對象,緩存可用大小接着可能又翻了一倍。這麼做下來,在GC性能不受損失的情況下,我們可在一臺擁有32G內存的機器上獲得高達28到30G的緩存。而且,這種緩存即使在服務重啓之後會仍然保持有效,而不象進程內緩存,進程重啓後還需要在內存中進行緩存重建(10G的緩存重建時間可能需要10分鐘),否則就需要以一個全空的緩存開始運行(這麼做它的初始性能會非常糟糕)。這還大大簡化了代碼,因爲對緩存和文件系統之間的一致性進行維護的所有邏輯現在都是在OS中實現的,這事OS做起來要比我們在進程中做那種一次性的緩存更加高效,準確性也更高。如果你使用磁盤的方式更傾向於線性讀取操作,那麼隨着每次磁盤讀取操作,預讀就能非常高效使用隨後準能用得着的數據填充緩存。
這就讓人聯想到一個非常簡單的設計方案:不是要在內存中保存儘可能多的數據並在需要時將這些數據刷新(flush)到文件系統,而是我們要做完全相反的事情。所有數據都要立即寫入文件系統中持久化的日誌中但不進行刷新數據的任何調用。實際中這麼做意味着,數據被傳輸到OS內核的頁面緩存中了,OS隨後會將這些數據刷新到磁盤的。此外我們添加了一條基於配置的刷新策略,允許用戶對把數據刷新到物理磁盤的頻率進行控制(每當接收到N條消息或者每過M秒),從而可以爲系統硬件崩潰時“處於危險之中”的數據在量上加個上限。
這種以頁面緩存爲中心的設計風格在一篇講解Varnish的設計思想的文章中有詳細的描述(文風略帶有助於身心健康的傲氣)。(從轉的連接裏看吧)