深入淺出系列之 -- kafka介紹

前言:

官網解釋:   

    Kafka是最初由Linkedin公司開發,是一個分佈式支持分區的(partition)多副本的(replica)基於zookeeper協調分佈式消息系統,它的最大的特性就是可以實時的處理大量數據以滿足各種需求場景:比如基於hadoop的批處理系統、低延遲的實時系統、storm/Spark流式處理引擎,web/nginx日誌、訪問日誌,消息服務等等,用scala語言編寫,Linkedin於2010年貢獻給了Apache基金會併成爲頂級開源 項目。

個人總結:

  1. Apache Kafka是一個開源消息系統,由Scala寫成。是由Apache軟件基金會開發的一個開源消息系統項目。
  2. Kafka最初是由LinkedIn公司開發,並於2011年初開源。2012年10月從Apache Incubator畢業。該項目的目標是爲處理實時數據提供一個統一、高通量、低等待的平臺。
  3. Kafka是一個分佈式消息隊列。Kafka對消息保存時根據Topic進行歸類,發送消息者稱爲Producer,消息接受者稱爲Consumer,此外kafka集羣有多個kafka實例組成,每個實例(server)稱爲broker。
  4. 無論是kafka集羣,還是consumer都依賴於zookeeper集羣保存一些meta信息,來保證系統可用性。

 

 Kafka的特性:

  1. 高吞吐量、低延遲:kafka每秒可以處理幾十萬條消息,它的延遲最低只有幾毫秒,每個topic可以分多個partition, consumer group 對partition進行consume操作。
  2. 可擴展性:kafka集羣支持熱擴展,因爲kafka解耦了整個處理消息的過程,所以增大消息入隊和處理的頻率是很容易的
  3. 持久性、可靠性:消息被持久化到本地磁盤,並且支持數據備份防止數據丟失,有leader和follow之分
  4. 容錯性:允許集羣中節點失敗(若副本數量爲n,則允許n-1個節點失敗)
  5. 高併發:支持數千個客戶端同時讀寫
  6. 順序保證:在大多使用場景下,數據處理的順序都很重要。大部分消息隊列本來就是排序的,並且能保證數據會按照特定的順序來處理。(Kafka保證一個Partition內的消息的有序性)

 

 Kafka的使用場景:

  1. 日誌收集:一個公司可以用Kafka可以收集各種服務的log,通過kafka以統一接口服務的方式開放給各種consumer,例如hadoop、Hbase、Solr等。
  2. 消息系統:解耦和生產者和消費者、緩存消息等。
  3. 用戶活動跟蹤:Kafka經常被用來記錄web用戶或者app用戶的各種活動,如瀏覽網頁、搜索、點擊等活動,這些活動信息被各個服務器發佈到kafka的topic中,然後訂閱者通過訂閱這些topic來做實時的監控分析,或者裝載到hadoop、數據倉庫中做離線分析和挖掘。
  4. 運營指標:Kafka也經常用來記錄運營監控數據。包括收集各種分佈式應用的數據,生產各種操作的集中反饋,比如報警和報告。
  5. 流式處理:比如spark streaming和storm,及當前流行的flink。

 

Kafka架構思想:

1)Producer :消息生產者,就是向kafka broker發消息的客戶端;

2)Consumer :消息消費者,向kafka broker取消息的客戶端;

3)Topic :可以理解爲一個隊列;

4) Consumer Group (CG):這是kafka用來實現一個topic消息的廣播(發給所有的consumer)和單播(發給任意一個consumer)的手段。一個topic可以有多個CG。topic的消息會複製(不是真的複製,是概念上的)到所有的CG,但每個partion只會把消息發給該CG中的一個consumer。如果需要實現廣播,只要每個consumer有一個獨立的CG就可以了。要實現單播只要所有的consumer在同一個CG。用CG還可以將consumer進行自由的分組而不需要多次發送消息到不同的topic;

5)Broker :一臺kafka服務器就是一個broker。一個集羣由多個broker組成。一個broker可以容納多個topic;

6)Partition:爲了實現擴展性,一個非常大的topic可以分佈到多個broker(即服務器)上,一個topic可以分爲多個partition,每個partition是一個有序的隊列。partition中的每條消息都會被分配一個有序的id(offset)。kafka只保證按一個partition中的順序將消息發給consumer,不保證一個topic的整體(多個partition間)的順序

7)Offset:kafka的存儲文件都是按照offset.kafka來命名,用offset做名字的好處是方便查找。例如你想找位於2049的位置,只要找到2048.kafka的文件即可。當然the first offset就是00000000000.kafka。

 

Kafka文件存儲機制

註解:這部分可能比較枯燥,深入理解後會得到很多,尤其是kafka底層是如何複製的,及其Partition是如何保證消息有序的。

  •  kafka一些原理概念

1.持久化

    kafka使用文件存儲消息(append only log),這就直接決定kafka在性能上嚴重依賴文件系統的本身特性.且無論任何OS下,對文件系統本身的優化是非常艱難的.文件緩存/直接內存映射等是常用的手段.因爲kafka是對日誌文件進行append操作,因此磁盤檢索的開支是較小的;同時爲了減少磁盤寫入的次數,broker會將消息暫時buffer起來,當消息的個數(或尺寸)達到一定閥值時,再flush到磁盤,這樣減少了磁盤IO調用的次數.對於kafka而言,較高性能的磁盤,將會帶來更加直接的性能提升.

2.性能

    除磁盤IO之外,我們還需要考慮網絡IO,這直接關係到kafka的吞吐量問題.kafka並沒有提供太多高超的技巧;對於producer端,可以將消息buffer起來,當消息的條數達到一定閥值時,批量發送給broker;對於consumer端也是一樣,批量fetch多條消息.不過消息量的大小可以通過配置文件來指定。具體在broker配置文件中配置message.max.bytes參數,默認值爲1000000,需要注意的是此參數要和consumer的maximum.message.size大小一致,否則會因爲生產者生產的消息太大導致消費者無法消費對於kafka broker端,似乎有個sendfile系統調用可以潛在的提升網絡IO的性能:將文件的數據映射到系統內存中,socket直接讀取相應的內存區域即可,而無需進程再次copy和交換(這裏涉及到"磁盤IO數據"/"內核內存"/"進程內存"/"網絡緩衝區",多者之間的數據copy).  

    其實對於producer/consumer/broker三者而言,CPU的開支應該都不大,因此啓用消息壓縮機制是一個良好的策略;壓縮需要消耗少量的CPU資源,不過對於kafka而言,網絡IO更應該需要考慮.可以將任何在網絡上傳輸的消息都經過壓縮.kafka支持gzip/snappy等多種壓縮方式.

3.負載均衡

    kafka集羣中的任何一個broker,都可以向producer提供metadata信息,這些metadata中包含"集羣中存活的servers列表"/"partitions leader列表"等信息(請參看zookeeper中的節點信息). 當producer獲取到metadata信息之後, producer將會和Topic下所有partition leader保持socket連接;消息由producer直接通過socket發送到broker,中間不會經過任何"路由層".

    異步發送將多條消息暫且在客戶端buffer起來,並將他們批量發送到broker;小數據IO太多,會拖慢整體的網絡延遲,批量延遲發送事實上提升了網絡效率;不過這也有一定的隱患,比如當producer失效時,那些尚未發送的消息將會丟失。這就需要消息語義模型的知識了,後面會介紹。

4.Topic模型

    其他JMS實現,消息消費的位置是有prodiver保留,以便避免重複發送消息或者將沒有消費成功的消息重發等,同時還要控制消息的狀態.這就要求JMS broker需要太多額外的工作.在kafka中,partition中的消息只有一個consumer在消費,且不存在消息狀態的控制,也沒有複雜的消息確認機制,可見kafka broker端是相當輕量級的.當消息被consumer接收之後,consumer可以在本地保存最後消息的offset,並間歇性的向zookeeper註冊offset.由此可見,consumer客戶端也很輕量級。

    kafka中consumer負責維護消息的消費記錄,而broker則不關心這些,這種設計不僅提高了consumer端的靈活性,也適度的減輕了broker端設計的複雜度,也就是我們常說的解耦;這是和衆多JMS prodiver的區別.此外,kafka中消息ACK的設計也和JMS有很大不同,kafka中的消息是批量(通常以消息的條數或者chunk的尺寸爲單位)發送給consumer,當消息消費成功後,向zookeeper提交消息的offset,而不會向broker交付ACK.或許你已經意識到,這種"寬鬆"的設計,將會有"丟失"消息/"消息重發"的危險.

5.消息傳輸一致

Kafka提供3種消息傳輸一致性語義:最多1次最少1次恰好1次

最少1次:可能會重傳數據,有可能出現數據被重複處理的情況;

最多1次:可能會出現數據丟失情況;

恰好1次:並不是指真正只傳輸1次,只不過有一個機制。確保不會出現“數據被重複處理”和“數據丟失”的情況。

at most once: 消費者fetch消息,然後保存offset,然後處理消息;當client保存offset之後,但是在消息處理過程中consumer進程失效(crash),導致部分消息未能繼續處理.那麼此後可能其他consumer會接管,但是因爲offset已經提前保存,那麼新的consumer將不能fetch到offset之前的消息(儘管它們尚沒有被處理),這就是"at most once".

at least once: 消費者fetch消息,然後處理消息,然後保存offset.如果消息處理成功之後,但是在保存offset階段zookeeper異常或者consumer失效,導致保存offset操作未能執行成功,這就導致接下來再次fetch時可能獲得上次已經處理過的消息,這就是"at least once".

"Kafka Cluster"到消費者的場景中可以採取以下方案來得到“恰好1次”的一致性語義:

最少1次+消費者的輸出中額外增加已處理消息最大編號:由於已處理消息最大編號的存在,不會出現重複處理消息的情況。

6.副本

    在kafka中,replication策略是基於partition,而不是topic;kafka將每個partition數據複製到多個server上,任何一個partition有一個leader和多個follower(可以沒有);備份的個數可以通過broker配置文件來設定leader處理所有的read-write請求,follower需要和leader保持同步.Follower就像一個"consumer",消費消息並保存在本地日誌中;leader負責跟蹤所有的follower狀態,如果follower"落後"太多或者失效,leader將會把它從replicas同步列表中刪除.當所有的follower都將一條消息保存成功,此消息才被認爲是"committed",那麼此時consumer才能消費它。這種同步策略,就要求follower和leader之間必須具有良好的網絡環境.即使只有一個replicas實例存活,仍然可以保證消息的正常發送和接收,只要zookeeper集羣存活即可.

    選擇follower時需要兼顧一個問題,就是新leader server上所已經承載的partition leader的個數,如果一個server上有過多的partition leader,意味着此server將承受着更多的IO壓力.在選舉新leader,需要考慮到"負載均衡",partition leader較少的broker將會更有可能成爲新的leader.

    關於leader和follow的圖,後期我會補上。

7.log

    每個log entry格式爲"4個字節的數字N表示消息的長度" + "N個字節的消息內容";每個日誌都有一個offset來唯一的標記一條消息,offset的值爲8個字節的數字,表示此消息在此partition中所處的起始位置..每個partition在物理存儲層面,有多個log file組成(稱爲segment).segment file的命名爲"最小offset".kafka.例如"00000000000.kafka";其中"最小offset"表示此segment中起始消息的offset.

    獲取消息時,需要指定offset和最大chunk尺寸,offset用來表示消息的起始位置,chunk size用來表示最大獲取消息的總長度(間接的表示消息的條數).根據offset,可以找到此消息所在segment文件,然後根據segment的最小offset取差值,得到它在file中的相對位置,直接讀取輸出即可.

 

8.分佈式

    kafka使用zookeeper來存儲一些meta信息,並使用了zookeeper watch機制來發現meta信息的變更並作出相應的動作(比如consumer失效,觸發負載均衡等)

  1. Broker node registry: 當一個kafka broker啓動後,首先會向zookeeper註冊自己的節點信息(臨時znode),同時當broker和zookeeper斷開連接時,此znode也會被刪除.
  2. Broker Topic Registry: 當一個broker啓動時,會向zookeeper註冊自己持有的topic和partitions信息,仍然是一個臨時znode.
  3. Consumer and Consumer group: 每個consumer客戶端被創建時,會向zookeeper註冊自己的信息;此作用主要是爲了"負載均衡".一個group中的多個consumer可以交錯的消費一個topic的所有partitions;簡而言之,保證此topic的所有partitions都能被此group所消費,且消費時爲了性能考慮,讓partition相對均衡的分散到每個consumer上.
  4. Consumer id Registry: 每個consumer都有一個唯一的ID(host:uuid,可以通過配置文件指定,也可以由系統生成),此id用來標記消費者信息.
  5. Consumer offset Tracking: 用來跟蹤每個consumer目前所消費的partition中最大的offset.此znode爲持久節點,可以看出offset跟group_id有關,以表明當group中一個消費者失效,其他consumer可以繼續消費.
  6. Partition Owner registry: 用來標記partition正在被哪個consumer消費.臨時znode。此節點表達了"一個partition"只能被group下一個consumer消費,同時當group下某個consumer失效,那麼將會觸發負載均衡(即:讓partitions在多個consumer間均衡消費,接管那些"遊離"的partitions)

當consumer啓動時,所觸發的操作:

A) 首先進行"Consumer id Registry";

B) 然後在"Consumer id Registry"節點下注冊一個watch用來監聽當前group中其他consumer的"leave"和"join";只要此znode path下節點列表變更,都會觸發此group下consumer的負載均衡.(比如一個consumer失效,那麼其他consumer接管partitions).

C) 在"Broker id registry"節點下,註冊一個watch用來監聽broker的存活情況;如果broker列表變更,將會觸發所有的groups下的consumer重新balance.

 

總結:

1) Producer端使用zookeeper用來"發現"broker列表,以及和Topic下每個partition leader建立socket連接併發送消息.

2) Broker端使用zookeeper用來註冊broker信息,已經監測partition leader存活性.

3) Consumer端使用zookeeper用來註冊consumer信息,其中包括consumer消費的partition列表等,同時也用來發現broker列表,並和partition leader建立socket連接,並獲取消息。

9.Leader的選擇

    Kafka的核心是日誌文件,日誌文件在集羣中的同步是分佈式數據系統最基礎的要素。

     如果leaders永遠不會down的話我們就不需要followers了!一旦leader down掉了,需要在followers中選擇一個新的leader.但是followers本身有可能延時太久或者crash,所以必須選擇高質量的follower作爲leader.必須保證,一旦一個消息被提交了,但是leader down掉了,新選出的leader必須可以提供這條消息。大部分的分佈式系統採用了多數投票法則選擇新的leader,對於多數投票法則,就是根據所有副本節點的狀況動態的選擇最適合的作爲leader.Kafka並不是使用這種方法。

    Kafka動態維護了一個同步狀態的副本的集合(a set of in-sync replicas),簡稱ISR,在這個集合中的節點都是和leader保持高度一致的,任何一條消息必須被這個集合中的每個節點讀取並追加到日誌中了,纔回通知外部這個消息已經被提交了。因此這個集合中的任何一個節點隨時都可以被選爲leader.ISR在ZooKeeper中維護。ISR中有f+1個節點,就可以允許在f個節點down掉的情況下不會丟失消息並正常提供服。ISR的成員是動態的,如果一個節點被淘汰了,當它重新達到“同步中”的狀態時,他可以重新加入ISR.這種leader的選擇方式是非常快速的,適合kafka的應用場景。

一個邪惡的想法:如果所有節點都down掉了怎麼辦?Kafka對於數據不會丟失的保證,是基於至少一個節點是存活的,一旦所有節點都down了,這個就不能保證了。

實際應用中,當所有的副本都down掉時,必須及時作出反應。可以有以下兩種選擇:

1. 等待ISR中的任何一個節點恢復並擔任leader。

2. 選擇所有節點中(不只是ISR)第一個恢復的節點作爲leader.

    這是一個在可用性和連續性之間的權衡。如果等待ISR中的節點恢復,一旦ISR中的節點起不起來或者數據都是了,那集羣就永遠恢復不了了。如果等待ISR意外的節點恢復,這個節點的數據就會被作爲線上數據,有可能和真實的數據有所出入,因爲有些數據它可能還沒同步到。Kafka目前選擇了第二種策略,在未來的版本中將使這個策略的選擇可配置,可以根據場景靈活的選擇。

這種窘境不只Kafka會遇到,幾乎所有的分佈式數據系統都會遇到。

10.副本管理

    以上僅僅以一個topic一個分區爲例子進行了討論,但實際上一個Kafka將會管理成千上萬的topic分區.Kafka儘量的使所有分區均勻的分佈到集羣所有的節點上而不是集中在某些節點上,另外主從關係也儘量均衡這樣每個幾點都會擔任一定比例的分區的leader.

    優化leader的選擇過程也是很重要的,它決定了系統發生故障時的空窗期有多久。Kafka選擇一個節點作爲“controller”,當發現有節點down掉的時候它負責在游泳分區的所有節點中選擇新的leader,這使得Kafka可以批量的高效的管理所有分區節點的主從關係。如果controller down掉了,活着的節點中的一個會備切換爲新的controller.

11.Leader與副本同步

    對於某個分區來說,保存正分區的"broker"爲該分區的"leader",保存備份分區的"broker"爲該分區的"follower"。備份分區會完全複製正分區的消息,包括消息的編號等附加屬性值。爲了保持正分區和備份分區的內容一致,Kafka採取的方案是在保存備份分區的"broker"上開啓一個消費者進程進行消費,從而使得正分區的內容與備份分區的內容保持一致。一般情況下,一個分區有一個“正分區”和零到多個“備份分區”。可以配置“正分區+備份分區”的總數量,關於這個配置,不同主題可以有不同的配置值。注意,生產者,消費者只與保存正分區的"leader"進行通信

    Kafka允許topic的分區擁有若干副本,這個數量是可以配置的,你可以爲每個topic配置副本的數量。Kafka會自動在每個副本上備份數據,所以當一個節點down掉時數據依然是可用的。

    Kafka的副本功能不是必須的,你可以配置只有一個副本,這樣其實就相當於只有一份數據。

創建副本的單位是topic的分區,每個分區都有一個leader和零或多個followers.所有的讀寫操作都由leader處理一般分區的數量都比broker的數量多的多,各分區的leader均勻的分佈在brokers中所有的followers都複製leader的日誌,日誌中的消息和順序都和leader中的一致followers向普通的consumer那樣從leader那裏拉取消息並保存在自己的日誌文件中

許多分佈式的消息系統自動的處理失敗的請求,它們對一個節點是否着(alive)”有着清晰的定義。Kafka判斷一個節點是否活着有兩個條件:

  • 1. 節點必須可以維護和ZooKeeper的連接,Zookeeper通過心跳機制檢查每個節點的連接。
  • 2. 如果節點是個follower,他必須能及時的同步leader的寫操作,延時不能太久。

符合以上條件的節點準確的說應該是“同步中的(in sync)”,而不是模糊的說是“活着的”或是“失敗的”。Leader會追蹤所有“同步中”的節點,一旦一個down掉了,或是卡住了,或是延時太久,leader就會把它移除。至於延時多久算是“太久”,是由參數replica.lag.max.messages決定的,怎樣算是卡住了,怎是由參數replica.lag.time.max.ms決定的。

  只有當消息被所有的副本加入到日誌中時,纔算是“committed”,只有committed的消息纔會發送給consumer,這樣就不用擔心一旦leader down掉了消息會丟失。Producer也可以選擇是否等待消息被提交的通知,這個是由參數acks決定的。

Kafka保證只要有一個“同步中”的節點,“committed”的消息就不會丟失。

 

簡單說下ack機制:

當producer向leader發送數據時,可以通過request.required.acks參數來設置數據可靠性的級別:

  • 0:這意味着producer無需等待來自broker的確認而繼續發送下一批消息。這種情況下數據傳輸效率最高,但是數據可靠性確是最低的。
  • 1(默認):這意味着producer在ISR中的leader已成功收到的數據並得到確認後發送下一條message。如果leader宕機了,則會丟失數據。
  • -1(或者是all):producer需要等待ISR中的所有follower都確認接收到數據後纔算一次發送完成,可靠性最高。但是這樣也不能保證數據不丟失,比如當ISR中只有leader時(前面ISR那一節講到,ISR中的成員由於某些情況會增加也會減少,最少就只剩一個leader),這樣就變成了acks=1的情況。

如果要提高數據的可靠性,在設置request.required.acks=-1的同時,也要min.insync.replicas這個參數(可以在broker或者topic層面進行設置)的配合,這樣才能發揮最大的功效。min.insync.replicas這個參數設定ISR中的最小副本數是多少,默認值爲1,當且僅當request.required.acks參數設置爲-1時,此參數才生效。如果ISR中的副本數少於min.insync.replicas配置的數量時,客戶端會返回異常:
org.apache.kafka.common.errors.NotEnoughReplicasExceptoin: Messages are rejected since there are fewer in-sync replicas than required

 

可能有很多網友看到這,感覺腦子暈暈的了,這是正常現象,需要結合官網慢慢理解。後面會繼續有相關博客更新kafka的知識點。

 

------------------------------------------------------------------------------------------------------------------------------------------------------------------------

      考慮一千次,不如去做一次;猶豫一萬次,不如實踐一次;華麗的跌倒,勝過無謂的彷徨,將來的你,一定會感謝現在奮鬥的你。歡迎大家加入大數據交流羣:725967421     一起交流,一起進步!!

------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 

 

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