Kafka消息隊列原理總結

最近在測試kafka的讀寫性能,所以借這個機會了解了kafka的一些設計原理,既然作爲分佈式系統,我們還是按照分佈式的套路進行分析。

 

Kafka的邏輯數據模型:

生產者發送數據給服務端時,構造的是ProducerRecord<Integer, String>(String topic, Integer key,String value)對象併發送,從這個構造函數可以看到,kafka的表面邏輯數據模型是key-value。當然api再發送前還會在這個基礎上加入若干校驗信息,不過這個對用戶而言是透明的。

 

Kafka的分發策略:

跟很多分佈式多備份系統類似,kafka的基本網絡結構如下:

一個節點(Broker)中存有不同partition的備份,一個parittion存在多份備份保存在不同節點上並且選舉出一個作爲leader跟客戶端交互,一個topic擁有多個parittion。

默認的kafka分發算法是hash(key)%numPartitions,簡單來就是哈希再取模。當然這個算法可以自定義,只要重寫相關接口。如上圖在一個四臺主機上創建了一個有兩個備份,四個分區partion的話題topic,但生產者需要發送某個key-value對象到消息隊列裏面時,創建連接時通過訪問zookeeper,獲取到一份leader partion列表(Broker1. Partition-0, Broker2. Partition1, Broker3. Partition-2, Broker4.Partition-3),再根據分發算法計算出這個對象應該要發送到哪個leader partion中。

 

Kafka的物理存儲模型和查找數據的設計:

Kafka的物理存儲模型比較簡單,在kafka的物理持久化的存儲中有分Segment的概念,每個Segment有兩種類型的文件:索引文件***.index和日誌文件(數據文件)***.log。兩者的命名規則都是以這個Segment的第一條的消息邏輯偏移量作爲文件名。索引是稀疏索引,目的在於減少索引文件的數據量,其文件的內容是key-value結構,key是消息的偏移量offeset(就是一個自增的序列號),value是對應的log文件的實際物理磁盤偏移量。

值得一提的是,跟其他正常分佈式不一樣,kafka並不支持根據給定的key查找該key對應的value值的能力,某種意義而言,邏輯數據模型中的key只是用來實現分發計算用的,所以使用kafka查找數據只能以指定消息的偏移量的放鬆實現。

整個查找過程:當要查找offset=888及後續的消息時,kafka先到該節點上找到對應的Segment。通過該Segment的index文件上用二分查找的方法找到最接近offset=888的紀錄,比如886,然後找到886對應的物理磁盤偏移量999,這樣就從log的磁盤偏移量找起,連續遍歷了兩個消息後就能找到888這個消息的數據(log文件中保留了每條消息的邏輯偏移量,長度和數據)。

 

Kafka的持久化策略設計:

Kafka的持久化設計是非常有特色的,和其他分佈式系統不同,它沒有自己維護一套緩存機制,而是直接使用了操作系統的文件系統(操作系統的文件系統自帶pagecache)。這樣的好處是減少了一次內存拷貝的消耗。其他分佈式系統比如cassandra,自己在服務端維護了一份數據緩衝內存塊datacache,當需要持久化時再調用操作系統的文件系統寫入到文件中,這樣就多了一次datacache到pagecache的拷貝消耗。這樣的話,kafka的持久化管理關鍵是管理文件系統的pagecache的刷盤。

由於kafka採用了這種特別的持久化策略,所以在kafka中並沒有其他分佈式系統的重做日誌。所以kafka在出現故障後的數據恢復策略有自己的一套:首先,kafka會通過配置文件配置pagecache定時或者定量刷盤的頻率以保證即使出現故障也能把丟失的數據降低到最少。其次,pageche本身是操作系統管理維護的,跟kafka自身的服務進程沒有關係,如果是kafka本身掛了的話,重啓後還是能訪問到pageche中的數據的。最後如果很不幸是kafka所在的一個節點的主機掛掉的話,那麼重啓主機和kafka後也可以從其他備份節點重新同步丟失的數據。

Kafka高性能的和持久化策略關係非常密切,這部分內容,也是整個kafka設計的精髓所在:

傳統的觀念認爲磁盤的讀寫是非常低效的,所以一般系統都會自己管理一塊內存datacache充當磁盤的緩存,只有需要的時候纔去和磁盤交互。但是實際上,磁盤的低效的原因不在於磁盤io,而在於磁頭的隨機尋址。如果數據是順序讀寫的話(也就是一次磁頭尋址,連續io),其實速度是非常快的((Raid-5,7200rpm):順序 I/O: 600MB/s)。而在傳統的設計中雖然加入了內存作爲緩存,但是爲了保證數據的安全性還是得提供一份重做日誌(每次的修改操作都要記錄在重做日誌redo.log中,以保證內存丟失後能根據重做日誌進行恢復),並且當datacache裏面的數據達到一定容量時刷新到磁盤的data文件中。但是kafka並沒有使用這套常規設計,並沒有自己維護一套datacache而是另闢蹊徑,直接使用操作系統中的文件系統,並利用文件系統原有的pagecache作爲數據緩存。減少了datacache到pagecache的拷貝消耗。並且順序地進行磁盤io,這樣大大提高了kafka寫數據時持久化的效率。對於kafka的讀數據這塊,kafka也使用了Sendfile技術來提高讀的效率,傳統的讀方案是讀取磁盤的數據到pagecache中,然後從pagecache拷貝一份到用戶進程的datacache中,datacache再拷貝到內核的socket緩存區中,最後從socket緩存區拷貝數據到網卡中發送。而Sendfile技術跳過了用戶進程的datacache這一環節,直接讀取磁盤的數據到pagecache中,然後從pagecache拷貝一份到socket緩存區中,最後從socket緩存區拷貝數據到網卡中發送。整個過程減少了兩次拷貝消耗。


 

 

Kafka的節點間的數據一致性策略設計:

對於任何多節點多備份的分佈式系統而言,數據的一致性問題都是繞不開的難點,一般的選擇是要麼優先考慮效率,這樣可能就造成數據不一致甚至是數據丟失,要麼選擇保障數據一致性和數據安全性犧牲效率。在kafka的身上也存在這樣的矛盾。

Kafka是一種分partion,多節點多備份的分佈式系統,每個partion都可以存在多份備份,每個備份在不同的節點上。多個備份中會根據zookpeer的註冊信息通過算法選舉出其中一份作爲leader,這個leader負責和客戶端的讀寫訪問進行交互。其他備份不參與跟客戶端的交互。而是去跟leader partion交互同步數據。這樣一來就可能出現主備之間數據不一致的情況。Kafka在客戶端提供了一個配置選項props.put("acks", "all");--其中all表示生產者等待確認所有的備份數據都寫入pagecache後再返回。可以設置爲0(不等待任何確認),1(leader確認)或者其他小於備份數的數字。其他備份節點會異步去同步leader partion的數據,保持一致,當然如果在同步的過程中,leader partion出現數據丟失,那麼這部分數據將永遠丟失。

 

Kafka的備份和負載均衡:

Kafka的備份很明顯,上文已經說過是通過討論一致性問題已經交待清楚,至於Kafka的負載均衡,個人發現是嚴重依賴於zookeeper上的註冊信息,通過一套算法來選取leader partion來實現kafka多節點的負載均衡。Zookeeper中保存了kafka幾乎一切的重要信息,比如topic,每個topic下面的多個partion信息,主機節點信息(包括ip和端口),每個節點下的多個partion信息,每個partion的主備份信息,消費客戶端的group_id分組信息,每個消費者信息等。通過這一堆信息進行算法計算最後得出負載均衡的方案,主要體現是選出讓kafka效率性能達到最好的每個partion的leader。並且在zookeeper中註冊監視器,一旦發現上述信息有變動則更新負載均衡方案。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章