kafka文檔閱讀記錄

kafka文檔閱讀記錄

持久化

  • Kafka 對消息的存儲和緩存嚴重依賴於文件系統,kafka的持久化隊列是建立在簡單的文件讀取和文件追加兩種操作之上,這和日誌解決方案相同,這種架構的優點在於所有操作的複雜度爲O(1)即常數,而且讀操作不會影響寫操作,讀操作之間也不會相互影響,這在性能上有明顯的優勢
  • 因爲性能和數據大小完全分離開來,因此可以充分利用大量廉價的容量大的硬盤。這樣在性能上幾乎不會有損失,而且能夠儘可能的擴展硬盤空間,使得可以讓消息保留相對較長的一段時間(如一週),而不是試圖在被消費後立即刪除。

效率

  • 前提:追加日誌文件和簡單讀取的方式已經消除了磁盤性能的問題
  • 問題:系統性能低下的另外兩個原因:大量的小型I/O操作,過多的字節拷貝
  • 分析:
        發生場景:小型I/O操作主要是在客戶端和服務端之間以及服務端自身的持久化操作中,
  • 解決方式:
    • 大量小型I/O操作:使用“消息塊”方式傳輸消息,使得網絡請求將多個消息打成一組,而不是每次只發送一條消息,分擔網絡往返中的開銷
    • 過多的字節拷貝:pagecache+sendfile的組合使用,使得在kafka集羣中,大多數 consumer 消費時,將看不到磁盤上的讀取活動,數據將完全由緩存提供。
    • 使用sendfile方法,可以允許操作系統將數據從pagecache直接發送到網絡,這樣避免重新複製數據。所以這種優化方式,只需要最後一步的copy操作,將數據複製到NIC緩衝區。
    • broker 維護的消息日誌本身就是一個文件目錄,每個文件都由一系列以相同格式寫入到磁盤的消息集合組成,這種寫入格式被 producer 和 consumer 共用。保持這種通用格式可以對一些很重要的操作進行優化: 持久化日誌塊的網絡傳輸。

生產者

  • 負載均衡
    客戶端可以控制消息發送數據到哪個分區,實現基本的負載均衡方式,也提供了一些特定語義的分區函數(如根據指定的鍵值進行hash分區,也可以自定義分區函數),
    每個partition(分區)對應一個目錄,kafka相關的存儲機制保證
  • 異步發送

消費者

    consumer通過向broker發送“fetch”請求來獲取想要消費的partition,在每個請求中都指定了對應的offset和消費消息數量,因此是由consumer來控制該offset,並且可以在需要的時候通過回退該位置進行重新消費相應的數據。

  • Push or pull:策略:Producer將數據push到broker,consumer從broker中pull數據,因此對於消息(數據)來說都是由生產者和消費者主動push或pull消息,kafka本身不主動進行。
  • push與pull方式的優缺點:
    pull-based好處:1.consumer可以控制接收消息的速度和時間,可以在適當的時間趕上落後於producer的速度;2.可以發送批量消息給consumer,而push-based則必須選擇立即發送請求或累計更多的數據,並不知道下游的consumer能否立即處理這些數據,如果改爲低延遲狀態,會導致一次只發送一條消息以致於傳輸的數據不再被緩衝,這種方式是極度浪費。
    pull-based不足之處:consumer需要一直輪詢來接收穫取broker的消息,如果broker中消息都被消費了,則consumer可能會一直處於輪詢中直到有數據發送過來。–這個可以通過在consumer連接時設置阻塞等待。
  • offset理解:爲了使得broker與consumer針對被消費的數據保持一致性,通過完全不同的一種方式解決消息丟失問題(保持消息狀態一致性),每個partition在任意給定時間內只能被訂閱了該topic的consumer group中的一個consumer消費(每個consumer group共享一個offset),通過offset來記錄消費的位置(具體參見存儲機制),即下一條要消費的消息的offset。這樣使得需要記錄的被消費的消息的狀態信息相當少,使得以非常低的代價實現和消息確認機制等同的效果(參考傳統消息確認機制)。
    另外通過offset的設置,使得consumer對於消費消息來說更加靈活,可以通過回退offset來再次消費之前的消息,這在實際應用中是一個很好的特性。

消息傳遞語義

  • Kafka可以提供的消息交付語義保證有多種:
    1. at most once:最多一次,消息可能被丟失但不能重傳
    2. at least once:至少一次,消息可以被重傳但不能丟失
    3. exactly once:只有一次,每條消息只被傳遞一次,需要保證不丟失也不會重傳
  • 在0.11.0版本之前,kafka採用的是at least once語義,如果 producer 沒有收到表明消息已經被提交的響應, 那麼 producer 除了將消息重傳之外別無選擇。
  • 在0.11.0版本開始,kafka producer增加了冪等性的機制,保證重傳不會在log中產生重複的消息保存,實現原理:broker給每個producer分配唯一ID,並且producer給每條被髮送的消息分配來序列號作爲標識,並且producer新增了類似事務性的語義(消息事務),發送到多個topic partition的功能,保證消息要麼全部發送並保存成功,要麼一條都沒寫入進去。
  • two-phase commit
  • 消息事務

爲了支持事務,Kafka 0.11.0版本引入以下概念:
1.事務協調者:類似於消費組負載均衡的協調者,每一個實現事務的生產端都被分配到一個事務協調者(Transaction Coordinator)。
2.引入一個內部Kafka Topic作爲事務Log:類似於消費管理Offset的Topic,事務Topic本身也是持久化的,日誌信息記錄事務狀態信息,由事務協調者寫入。
3.***控制消息(Control Messages)***:這些消息是客戶端產生的並寫入到主題的特殊消息,但對於使用者來說不可見。它們是用來讓broker告知消費者之前拉取的消息是否被原子性提交。
4.TransactionId:不同生產實例使用同一個TransactionId表示是同一個事務,可以跨Session的數據冪等發送。當具有相同Transaction ID的新的Producer實例被創建且工作時,舊的且擁有相同Transaction ID的Producer將不再工作,避免事務僵死。
5.Producer ID:每個新的Producer在初始化的時候會被分配一個唯一的PID,這個PID對用戶是不可見的。主要是爲提供冪等性時引入的。
6.Sequence Numbler:(對於每個PID,該Producer發送數據的每個<Topic, Partition>都對應一個從0開始單調遞增的Sequence Number。
7.每個生產者增加一個epoch:用於標識同一個事務Id在一次事務中的epoch,每次初始化事務時會遞增,從而讓服務端可以知道生產者請求是否舊的請求。
8.冪等性:保證發送單個分區的消息只會發送一次,不會出現重複消息。增加一個冪等性的開關enable.idempotence,可以獨立與事務使用,即可以只開啓冪等但不開啓事務。

存儲機制

日誌存儲方式

  1. kafka對消息隊列數據的存儲形式是以日誌文件的形式,同一個Topic下有多個parition,每個partition對應一個目錄,名稱規則爲:topic名稱+有序序列號,有序序列號從0開始到partition數量-1,partition爲實際物理的概念,而topic則爲邏輯概念,實現上都是以每個Partition爲基本實現單元的。
  2. 每個partition又細分爲多個segement,每個segement文件由兩部分組成,分別爲.index文件和.log文件,分別表示segement的索引文件和數據文件(元數據),也稱爲消息索引和消息實體。索引文件中的元數據指向對應數據文件中message的"物理偏移地址"。其命名規則爲:partition全局的第一個segement從0開始,後續每個segement文件名爲上一個segement文件最後一條消息的offset值,長度爲20爲數值,缺位用0填充。
  3. 從.index中通過某條索引在.log中對應的位置
  4. 從partition中通過offset查找message
  5. 上述保存了對應消息的物理偏移位地址,但並未保存消息的結束地址,這個涉及到消息的物理結構,每個消息都具有固定的物理結構,根據物理結構可以確定消息的大小和結束位置。包括offset(8 Bytes)、消息體的大小(4 Bytes)、crc32(4 Bytes)、magic(1 Byte)、attributes(1 Byte)、key length(4 Bytes)、key(K Bytes)、payload(N Bytes)等等字段。
    kafka建立了基本的索引結構,大部分消息都會被順序讀取,當然也會存在少量的隨機讀取消息(如處理的時候這條消息處理失敗,需要重新處理),所以建立索引以支持少量隨機查詢。所以在索引的實現上,基本上就是爲了支持針對某個Offset進行二分查找而存在的索引。

複製原理和同步方式

複製原理,通過副本策略實現,kafka的每個topic的partition有N個副本(replicas),其中N是topic的複製因子的個數,通過多副本機制實現故障自動轉移,在kafka集羣中的一個Broker失效的情況下仍保證服務可用。kafka發生複製時,爲了確保partition的日誌能夠有序的寫到其他節點,N個replicas中,其中一個爲leader,其他都爲follower,leader處理partition的所有讀寫請求,follower會被動定期的去複製leader上的數據(批量,並不是每有一條數據就去複製)。
kafka提供了數據複製算法保證,如果leader發生故障或掛掉,會有一個新的leader被選舉並接收客戶端的消息寫入。leader主要負責維護和跟蹤ISR中所有follower滯後的狀態,kafka允許follower與leader之間的數據具有滯後,但不能落後太多,當producer發送一條消息到broker後,leader寫入消息並複製到所有follower,消息提交只有才被成功複製到所有副本中,如果follower落後太多或者失效,leader將會把它從ISR中刪除。
副本策略:ISR機制
ISR(In-Sync Replicas):指副本同步隊列,副本數對kafka的吞吐率是有影響的,但可以增強可用性,默認的replica數量爲1,及每個partition都有一個唯一的leader,爲了確保消息的可靠性,通常應用中會將設置爲大於1的值。所有的副本統稱爲Assigned Replicas(AR),ISR是AR的一個子集,每個partition都有各自的leader和follower,由leader維護對應的ISR列表,follower從leader同步數據有一些延遲,有兩個衡量延遲標準的維度:延遲時間(replica.lag.max.ms)和延遲消息條數(replica.lag.max.messages),任意一個超過閾值都會被提出ISR,存儲OSR(outof-Sync Replicas)列表,新加入的follower也會先放在OSR,AR=ISR+OSR。

0.10.x版本後,kafka移除了replica.lag.max.messages參數,因爲在實際過程中該參數沒有起到應有的作用,該參數表示當前某個副本落後leader的消息數量超過該參數的值,那麼leader就會把follower從OSR中刪除。假設設置該參數值爲4,那麼如果producer一次傳送至broker的消息數量都小於4條時,因爲在leader接收到producer發送的消息之後且在follower副本開始拉取這些消息之前,follower落後leader的消息數量都不會超過4條,因此不會有follower會被移出ISR列表,但當producer發起瞬時高峯流量時,即一次發送超過4條,此時follower都會被認爲與leader不同步了,從而被移出ISR,但實際上follower都是正常且沒有性能問題,那麼之後追上leader後被重新加入到ISR中,於是他們不斷的在被移出和迴歸ISR之間循環,這無疑增加了不必要的性能損耗。另外這個參數的值不好控制,無法給定合適的值,設置太大了,影響真正"落後"的follower的移出;設置太小了,會導致follower頻繁的移出和加入。

ISR的管理是保存在ZK節點中的,具體位置爲: /brokers/topics/[topic]/partitions/[partition]/state,目前會有兩個地方進行維護:

  • Controller:kafka集羣中的一個Broker會被選舉爲Controller,主要負責Partition管理和副本狀態管理,也會執行類似重分配partition之類的管理任務。Controller會選舉新的leader,ISR和新的leader_epoch,controller_epoch寫入ZK額相關節點中,同時發起LeaderANdIsrRequest通知所有的replicas。
  • leader:leader有單獨的線程定期檢查ISR中的follower是否脫離ISR,如果發現ISR發生變化,則會將新的ISR信息返回到ZK的相關節點中。

leader選舉

  • 當leader宕機了,kafka是如何在follower中選舉出新的leader的?因爲follower可能落後很多或者直接crash了,所以必須確保選擇“最新”的follower作爲新的leader。一個基本的原則就是,如果leader不在了,新的leader必讀擁有原來的leader commit的所有消息。這就需要做一個折中,如果leader在表明一個消息被commit前等待更多的follower確認,那麼在它掛掉之後就有更多的follower可以成爲新的leader,但這也會造成吞吐率下降。

  • leader選舉的算法有很多,如Zookeeper的Zab、Raft以及ViewsStamped Replication。而Kafka使用的算法更像是微軟的PacificA算法。

  • kafka在zookeeper中爲每個partition動態的維護了一個ISR,這個ISR中所有的replica都跟上了leader(沒有落後),只有ISR裏面的成員纔能有被選舉爲leader的可能(unclean.leader.election.enable=false),這種模式下,對於f+1個副本,一個kafka topic 能在保證不丟失已經 commit 消息的前提下容忍f個副本的失敗,在大多是場景下比較有利。事實上爲了容忍f個副本的失敗, “少數服從多數”的方式和ISR在commit前 需要等待的副本的數量時一樣的,但是ISR需要的總的副本個數幾乎是“少數服從多數”的方式的一半。

  • 在ISR中至少有一個follower時,kafka可以確保已經commit的數據不丟失,但如果所有的replica都掛了,就無法保證數據不丟失了,這種情況下有兩種可行的方案:

    • 等待ISR中任意一個replica“活”過來,並且選它作爲leader
    • 選擇第一個“活”過來的replica(並不一定是在ISR中)的作爲leader
  • 這就需要在可用性和一致性之間做一個抉擇,如果一定要等待ISR中的replica“活”過來,那不可用的時間就可能會相對較長,而且如果ISR中所有的replica都無法“活”過來了,或者數據丟失了,這個partition將永遠不可用了。而第二種方式時,即使它並不保證已經包含了所有已commit的消息,它也會成爲leader而作爲consumer的數據源。默認情況下,kafka採用的是第二種策略(即unclean.leader.election.enable=true)。

可靠性和持久性保證

  • Kafka的高可靠性的保障來源於其健壯的副本(replication)策略。通過調節其副本相關參數,可以使得Kafka在性能和可靠性之間運轉的遊刃有餘。Kafka從0.8.x版本開始提供partition級別的複製,replication的數量可以在$KAFKA_HOME/config/server.properties中配置(default.replication.refactor)。
  • 順序寫+無狀態,不維護消息的狀態,也不維護消息的存儲策略

未完待續…

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