Kafka知識點總結

西柚發表於2021-11-16

1、什麼是Kafka?

Kafka是一個多分割槽、多副本、分散式的基於釋出/訂閱模式的訊息佇列。目前 Kafka 已經定位為一個分散式流式處理平臺,它以高吞吐、可持久化、可水平擴充套件、支援流資料處理等多種特性而被廣泛使用。

2、Kafka架構

image.png

Kafak 總體架構圖中包含多個概念:
(1)ZooKeeper: Zookeeper 負責儲存 broker 叢集後設資料,並對控制器進行選舉等操作。
(2)Producer: 訊息生產者,就是向 kafka broker 發訊息的客戶端。
(3)Broker: 一個獨立的 Kafka 伺服器被稱作 broker,一個叢集由多個 broker 組成,一個 broker 可以容納多個 topic。broker負責接收來自生產者的訊息,為訊息設定偏移量,並將訊息儲存在磁碟。broker 為消費者提供服務,對讀取分割槽的請求作出響應,返回已經提交到磁碟上的訊息。
(4)Consumer: 訊息消費者,向 kafka broker 取訊息的客戶端。
(5)Consumer Group:消費者組,一個消費者組可以包含一個或多個 Consumer。消費者組內每個消費者負責消費不同分割槽的資料,一個分割槽只能由一個組內消費者消費。消費者組之間互不影響。所有的消費者都屬於某個消費者組,即消費者組是邏輯上的一個訂閱者。使用多分割槽+多消費者方式可以極大提高資料下游的處理速度,同一消費者組中的消費者不會重複消費訊息,同樣的,不同消費組中的消費者消費訊息時互不影響。Kafka 就是通過消費者組的方式來實現訊息 P2P 模式和廣播模式。
(6)Topic: Kafka 中的訊息以 Topic 為單位進行劃分,可以理解為一個佇列。生產者將訊息傳送到特定的 Topic,而消費者負責訂閱 Topic 的訊息並進行消費。
(7)Partition: 為了實現擴充套件性,一個非常大的 topic 可以分佈到多個 broker(伺服器)上,一個 topic 可以分為多個 partition,每個 partition 是一個有序的佇列。同一個主題下不同分割槽包含的訊息是不同的,分割槽在儲存層面可以看作一個可追加的日誌(Log)檔案,訊息在被
追加到分割槽日誌檔案的時候都會分配一個特定的偏移量(offset)。
(8)Offset: 分割槽中每條訊息都會分配一個有序的id,即偏移量。offset 不跨越分割槽,也就是說 Kafka 保證的是分割槽有序性而不是主題有序性。
(9)Replica: 副本,為保證叢集中的某個節點發生故障時,該節點上的 partition 資料不丟失,且 kafka 仍然能夠繼續工作,kafka 提供了副本機制,一個 topic 的每個分割槽都有若干個副本,一個 leader 和若干個 follower。通常只有 leader 副本對外提供讀寫服務,當主副本所在 broker 崩潰或發生網路異常,Kafka 會在 Controller 的管理下會重新選擇新的 leader 副本對外提供讀寫服務。
(10)Record: 實際寫入 Kafka 中並可以被讀取的訊息記錄。每個 record 包含了key、value 和 timestamp。
(11)Leader: 每個分割槽多個副本的 "主" 副本,生產者傳送資料的物件,以及消費者消費資料的物件都是 leader。
(12)Follower: 每個分割槽多個副本中的"從" 副本,實時從 Leader 中同步資料,保持和 leader 資料的同步。Leader 發生故障時,某個 follow 會成為新的 leader。
(13)ISR(In-Sync Replicas):副本同步佇列,表示和 leader 保持同步的副本的集合(包括leader本身)。如果 follower 長時間不與 leader 同步資料則將該副本踢出ISR佇列。leader發生故障會從ISR中選舉新leader。
(14)OSR(Out-of-Sync Replicas):因同步延遲過高而被踢出ISR的副本存在OSR。
(15)AR(Assigned Replicas):所有副本集合,即 AR = ISR + OSR 。

3、釋出訂閱的訊息系統那麼多,為啥選擇 Kafka?(Kafka的特點)

(1)多個生產者
KafKa 可以無縫地支援多個生產者,不管客戶端使用一個主題,還是多個主題。Kafka 適合從多個前端系統收集資料,並以統一的格式堆外提供資料。

(2)多個消費者
Kafka 支援多個消費者從一個單獨的訊息流中讀取資料,並且消費者之間互不影響。這與其他佇列系統不同,其他佇列系統一旦被客戶端讀取,其他客戶端就不能再讀取它。並且多個消費者可以組成一個消費者組,他們共享一個訊息流,並保證消費者組對每個給定的訊息只消費一次。

(3)基於磁碟的資料儲存(永續性,可靠性)
Kafka 允許消費者非實時地讀取訊息,原因在於 Kafka 將訊息提交到磁碟上,設定了保留規則進行儲存,無需擔心訊息丟失等問題。

(4)伸縮性,可擴充套件性
可擴充套件多臺 broker。使用者可以先使用單個 broker,到後面可以擴充套件到多個 broker 。

(5)高效能(高吞吐,低延遲)
Kafka 可以輕鬆處理百萬千萬級訊息流,同時還能保證亞秒級的訊息延遲。以時間複雜度為O(1)的方式提供訊息持久化能力,即使對TB級以上資料也能保證常數時間複雜度的訪問效能。即使在非常廉價的商用機器上也能做到單機支援每秒100K條以上訊息的傳輸。Kafka 順序寫磁碟,因此效率非常高,經驗證,順序寫磁碟效率比隨機寫記憶體還要高,這是Kafka高吞吐率的一個很重要的保證。

對比如圖:
image.png

4、Kafka 如何做到高吞吐量/高效能的?

Kafka 實現高吞吐量和效能,主要通過以下幾點:

1、頁快取技術
Kafka 是基於 作業系統 的頁快取來實現檔案寫入的。作業系統本身有一層快取,叫做 page cache,是在 記憶體裡的快取,我們也可以稱之為 os cache,意思就是作業系統自己管理的快取。Kafka 在寫入磁碟檔案的時候,可以直接寫入這個 os cache 裡,也就是僅僅寫入記憶體中,接下來由作業系統自己決定什麼時候把 os cache 裡的資料真的刷入磁碟檔案中。通過這一個步驟,就可以將磁碟檔案寫效能提升很多了,因為其實這裡相當於是在寫記憶體,不是在寫磁碟。

2、磁碟順序寫
另一個主要功能是 kafka 寫資料的時候,是以磁碟順序寫的方式來寫的。也就是說,僅僅將資料追加到log檔案的末尾,不是在檔案的隨機位置來修改資料。同樣的磁碟,順序寫能到 600M/s,而隨機寫只有 100K/s。這與磁碟的機械機構有關,順序寫之所以快,是因為其省去了大量磁頭定址的時間。

基於上面兩點,Kafka 就實現了寫入資料的超高效能。

3、零拷貝
大家應該都知道,從 Kafka 裡經常要消費資料,那麼消費的時候實際上就是要從 Kafka 的磁碟檔案裡讀取某條資料然後傳送給下游的消費者,如下圖所示:
image.png
那麼這裡如果頻繁的從磁碟讀資料然後發給消費者,會增加兩次沒必要的拷貝,如下圖:
image.png
一次是從作業系統的 cache 裡拷貝到應用程式的快取裡,接著又從應用程式快取裡拷貝回作業系統的 Socket 快取裡。而且為了進行這兩次拷貝,中間還發生了好幾次上下文切換,一會兒是應用程式在執行,一會兒上下文切換到作業系統來執行。所以這種方式來讀取資料是比較消耗效能的。

Kafka 為了解決這個問題,在讀資料的時候是引入零拷貝技術。

也就是說,直接讓作業系統的 cache 中的資料傳送到網路卡後傳輸給下游的消費者,中間跳過了兩次拷貝資料的步驟,Socket 快取中僅僅會拷貝一個描述符過去,不會拷貝資料到 Socket 快取,如下圖所示:
image.png
通過 零拷貝技術,就不需要把 os cache 裡的資料拷貝到應用快取,再從應用快取拷貝到 Socket 快取了,兩次拷貝都省略了,所以叫做零拷貝。對 Socket 快取僅僅就是拷貝資料的描述符過去,然後資料就直接從 os cache 中傳送到網路卡上去了,這個過程大大的提升了資料消費時讀取檔案資料的效能。Kafka 從磁碟讀資料的時候,會先看看 os cache 記憶體中是否有,如果有的話,其實讀資料都是直接讀記憶體的。Kafka 叢集經過良好的調優,資料直接寫入 os cache 中,然後讀資料的時候也是從os cache 中讀。相當於 Kafka 完全基於記憶體提供資料的寫和讀了,所以這個整體效能會極其的高。

5、 Kafka 和 Zookeeper 之間的關係

Kafka 使用 Zookeeper 來儲存叢集的後設資料資訊和消費者資訊(偏移量),沒有zookeeper,kafka 是工作不起來。 在 zookeeper 上會有一個專門用來進行 Broker伺服器列表記錄的點,節點路徑為/brokers/ids。

每個 Broker 伺服器在啟動時,都會到 Zookeeper 上進行註冊,即建立/brokers/ids/[0-N] 的節點,然後寫入 IP,埠等資訊,Broker 建立的是臨時節點,所以一旦 Broker 上線或者下線,對應 Broker 節點也就被刪除了,因此可以通過 zookeeper 上 Broker 節點的變化來動態表徵 Broker 伺服器的可用性。

6、生產者向 Kafka 傳送訊息的執行流程

如下圖所示:
image.png

(1)生產者要往 Kafka 傳送訊息時,需要建立 ProducerRecoder,程式碼如下:

ProducerRecord<String,String> record 
      = new ProducerRecoder<>("CostomerCountry","Precision Products","France");
      try{
      producer.send(record);
      }catch(Exception e){
        e.printStackTrace();
      }

(2)ProducerRecoder 物件會包含目標 topic,分割槽內容,以及指定的 key 和 value,在傳送 ProducerRecoder 時,生產者會先把鍵和值物件序列化成位元組陣列,然後在網路上傳輸。

(3)生產者在將訊息傳送到某個 Topic ,需要經過攔截器、序列化器和分割槽器(Partitioner)。

(4)如果訊息 ProducerRecord 沒有指定 partition 欄位,那麼就需要依賴分割槽器,根據 key 這個欄位來計算 partition 的值。分割槽器的作用就是為訊息分配分割槽。

若沒有指定分割槽,且訊息的 key 不為空,則使用 murmur 的 Hash 演算法(非加密型 Hash 函式,具備高運算效能及低碰撞率)來計算分割槽分配。

若沒有指定分割槽,且訊息的 key 也是空,則用輪詢的方式選擇一個分割槽。

(5)分割槽選擇好之後,會將訊息新增到一個記錄批次中,這個批次的所有訊息都會被髮送到相同的 Topic 和 partition 上。然後會有一個獨立的執行緒負責把這些記錄批次傳送到相應的 broker 中。

(6)leader 接收到 Msg 後,將訊息寫入本地 log。如果成功寫入 Kafka 中,就返回一個 RecordMetaData 物件,它包含 Topic 和 Partition 資訊,以及記錄在分割槽的 offset。

(7)若寫入失敗,就返回一個錯誤異常,生產者在收到錯誤之後嘗試重新傳送訊息,幾次之後如果還失敗,就返回錯誤資訊。

(8)Followers 從 leader 拉取訊息,寫入本地 log 後向 leader 傳送 ACK。leader 收到所有 ISR 中的 replica 的 ACK 後,增加高水位,並向 producer 傳送 ACK。

7、Kafka 如何保證對應型別的訊息被寫到相同的分割槽?

通過 訊息鍵 和 分割槽器 來實現,分割槽器為鍵生成一個 offset,然後使用 offset 對主題分割槽進行取模,為訊息選取分割槽,這樣就可以保證包含同一個鍵的訊息會被寫到同一個分割槽上。

如果 ProducerRecord 沒有指定分割槽,且訊息的 key 不為空,則使用 Hash 演算法(非加密型 Hash 函式,具備高運算效能及低碰撞率)來計算分割槽分配。

如果 ProducerRecord 沒有指定分割槽,且訊息的 key 也是空,則用 輪詢 的方式選擇一個分割槽。

8、Kafka 檔案儲存機制

image.png

在 Kafka 中,一個 Topic 會被分割成多個 Partition,而 Partition 由多個更小的 Segment 的元素組成。Partition 在伺服器上的表現形式就是一個一個的資料夾,每個 partition 資料夾下面會有多組 segment(邏輯分組,並不是真實存在),每個 segment 對應三個檔案:.log檔案、.index檔案、.timeindex檔案。topic是邏輯上的概念,而 partition是物理上的概念,每個 partition對應於多個 log 檔案,該 log 檔案中儲存的就是 producer生產的資料。Producer生產的資料會被不斷追加到該 log 檔案末端,且每條資料都有自己的 offset。消費者組中的每個消費者,都會實時記錄自己消費到了哪個offset,以便出錯恢復時,從上次的位置繼續消費。

image.png

Kafka 會根據 log.segment.bytes 的配置來決定單個 Segment 檔案(log)的大小,當寫入資料達到這個大小時就會建立新的 Segment。

9、如何根據 offset 找到對應的 Message?

每個索引項佔用 8 個位元組,分為兩個部分:
(1) relativeOffset: 相對偏移量,表示訊息相對於 baseOffset 的偏移量,佔用4個位元組(relativeOffset = offset - baseOffset),當前索引檔案的檔名即為 baseOffset 的值。

例如:一個日誌片段的 baseOffset 為 32,那麼其檔名就是 00000000000000000032.log,offset=35 的訊息在索引檔案中的 relativeOffset 的值為 35-32=3

(2) position: 實體地址,也就是訊息在日誌分段檔案中對應的物理位置,佔用 4 個位元組。

image.png

image.png

(1)先找到 offset=3 的 message 所在的 segment檔案(利用二分法查詢),先判斷.index檔名稱offset(baseOffset)是否小於3;
 若小於,則繼續二分與下一個.inde檔名稱offset比較;
 若大於,則返回上次小於3的.index檔案,這裡找到的就是在第一個segment檔案。

(2)找到的 segment 中的.index檔案,用查詢的offset 減去.index檔名的offset(relativeOffset = offset - baseOffset),也就是00000.index檔案,我們要查詢的offset為3的message在該.index檔案內的索引為3(index採用稀疏儲存的方式,它不會為每一條message都建立索引,而是每隔4k左右,建立一條索引,避免索引檔案佔用過多的空間。缺點是沒有建立索引的offset不能一次定位到message的位置,需要做一次順序掃描,但是掃描的範圍很小)。

(3)根據找到的 relative offset為3的索引,確定message儲存的物理偏移地址為756。

(4)根據物理偏移地址,去.log檔案找相應的Message

同理,我如果想找offset=8對應的Message資料呢?

(1)首先根據二分查詢法找到segment的對應的00000000000000000006.index索引檔案

(2)根據offset=8找到對應的索引檔案中的位置,該位置儲存了一個偏移量326,根據偏移量326在00000000000000000006.log檔案中找到對應的訊息Message-8。

Kafka 的 Message 儲存採用了分割槽,磁碟順序讀寫,分段和稀疏索引等一些手段來達到高效性,在0.9版本之後,offset 已經直接維護在kafka叢集的__consumer_offsets這個topic中。

10、 Producer 傳送的一條 message 中包含哪些資訊?

訊息由可變長度的報頭、可變長度的不透明金鑰位元組陣列和可變長度的不透明值位元組陣列組成。

image.png

RecordBatch 是 Kafka 資料的儲存單元,一個 RecordBatch 中包含多個 Record(即我們通常說的一條訊息)。RecordBatch 中各個欄位的含義如下:

image.png

一個 RecordBatch 中可以包含多條訊息,即上圖中的 Record,而每條訊息又可以包含多個 Header 資訊,Header 是 Key-Value 形式的。

11、kafka 如何實現訊息有序

生產者:通過分割槽的 leader 副本負責資料以先進先出的順序寫入,來保證訊息順序性。

消費者:同一個分割槽內的訊息只能被一個 group 裡的一個消費者消費,保證分割槽內消費有序。

kafka 每個 partition 中的訊息在寫入時都是有序的,消費時, 每個 partition 只能被每一個消費者組中的一個消費者消費,保證了消費時也是有序的。

整個 kafka 不保證有序。如果為了保證 kafka 全域性有序,那麼設定一個生產者,一個分割槽,一個消費者。

12、kafka 有哪些分割槽演算法?

Kafka包含三種分割槽演算法:

(1)輪詢策略

也稱 Round-robin 策略,即順序分配。比如一個 topic 下有 3 個分割槽,那麼第一條訊息被髮送到分割槽 0,第二條被髮送到分割槽 1,第三條被髮送到分割槽 2,以此類推。當生產第四條訊息時又會重新開始。

輪詢策略是 kafka java 生產者 API 預設提供的分割槽策略。輪詢策略有非常優秀的負載均衡表現,它總是能保證訊息最大限度地被平均分配到所有分割槽上,故預設情況下它是最合理的分割槽策略,也是平時最常用的分割槽策略之一。

(2)隨機策略

也稱 Randomness 策略。所謂隨機就是我們隨意地將訊息放置在任意一個分割槽上,如下圖:

(3)按 key 分配策略

kafka 允許為每條訊息定義訊息鍵,簡稱為 key。一旦訊息被定義了 key,那麼你就可以保證同一個 key 的所有訊息都進入到相同的分割槽裡面,由於每個分割槽下的訊息處理都是有順序的,如下圖所示:

13、Kafka 的預設訊息保留策略

broker 預設的訊息保留策略分為兩種:
日誌片段通過 log.segment.bytes 配置(預設是1GB)
日誌片段通過 log.segment.ms 配置 (預設7天)

14、Kafka 如何實現單個叢集間的訊息複製?

Kafka 訊息負責機制只能在單個叢集中進行復制,不能在多個叢集之間進行。

kafka 提供了一個叫做 MirrorMaker 的核心元件,該元件包含一個生產者和一個消費者,兩者之間通過一個佇列進行相連,當消費者從一個叢集讀取訊息,生產者把訊息傳送到另一個叢集。

15、Kafka 訊息確認(ack 應答)機制

為保證 producer 傳送的資料,能可靠的達到指定的 topic ,Producer 提供了訊息確認機制。生產者往 Broker 的 topic 中傳送訊息時,可以通過配置來決定有幾個副本收到這條訊息才算訊息傳送成功。可以在定義 Producer 時通過 acks 引數指定,這個引數支援以下三種值:

(1)acks = 0:producer 不會等待任何來自 broker 的響應。

特點:低延遲,高吞吐,資料可能會丟失。

如果當中出現問題,導致 broker 沒有收到訊息,那麼 producer 無從得知,會造成訊息丟失。

(2)acks = 1(預設值):只要叢集中 partition 的 Leader 節點收到訊息,生產者就會收到一個來自伺服器的成功響應。

如果在 follower 同步之前,leader 出現故障,將會丟失資料。

此時的吞吐量主要取決於使用的是 同步傳送 還是 非同步傳送 ,吞吐量還受到傳送中訊息數量的限制,例如 producer 在收到 broker 響應之前可以傳送多少個訊息。

(3)acks = -1:只有當所有參與複製的節點全部都收到訊息時,生產者才會收到一個來自伺服器的成功響應。

這種模式是最安全的,可以保證不止一個伺服器收到訊息,就算有伺服器發生崩潰,整個叢集依然可以執行。

根據實際的應用場景,選擇設定不同的 acks,以此保證資料的可靠性。

另外,Producer 傳送訊息還可以選擇同步或非同步模式,如果設定成非同步,雖然會極大的提高訊息傳送的效能,但是這樣會增加丟失資料的風險。如果需要確保訊息的可靠性,必須將 producer.type 設定為 sync。

#同步模式
producer.type=sync 
#非同步模式
producer.type=async 

16、說一下什麼是副本?

kafka 為了保證資料不丟失,從 0.8.0 版本開始引入了分割槽副本機制。在建立 topic 的時候指定 replication-factor,預設副本為 3 。

副本是相對 partition 而言的,一個分割槽中包含一個或多個副本,其中一個為leader 副本,其餘為follower 副本,各個副本位於不同的 broker 節點中。

所有的讀寫操作都是經過 Leader 進行的,同時 follower 會定期地去 leader 上覆制資料。當 Leader 掛掉之後,其中一個 follower 會重新成為新的 Leader。通過分割槽副本,引入了資料冗餘,同時也提供了 Kafka 的資料可靠性。

Kafka 的分割槽多副本架構是 Kafka 可靠性保證的核心,把訊息寫入多個副本可以使 Kafka 在發生崩潰時仍能保證訊息的永續性。

17、Kafka 的 ISR 機制

在分割槽中,所有副本統稱為 AR ,Leader 維護了一個動態的 in-sync replica(ISR),ISR 是指與 leader 副本保持同步狀態的副本集合。當然 leader 副本本身也是這個集合中的一員。

當 ISR 中的 follower 完成資料同步之後, leader 就會給 follower 傳送 ack ,如果其中一個 follower 長時間未向 leader 同步資料,該 follower 將會被踢出 ISR 集合,該時間閾值由 replica.log.time.max.ms 引數設定。當 leader 發生故障後,就會從 ISR 集合中重新選舉出新的 leader。

18、LEO、HW、LSO、LW 分別代表什麼?

LEO :是 LogEndOffset 的簡稱,代表當前日誌檔案中下一條。

HW:水位或水印一詞,也可稱為高水位(high watermark),通常被用在流式處理領域(flink、spark),以表徵元素或事件在基於時間層面上的進展。在 kafka 中,水位的概念與時間無關,而是與位置資訊相關。嚴格來說,它表示的就是位置資訊,即位移(offset)。取 partition 對應的ISR中最小的 LEO作為HW,consumer 最多隻能消費到 HW 所在的上一條資訊。

LSO: 是 LastStableOffset 的簡稱,對未完成的事務而言,LSO 的值等於事務中第一條訊息的位置(firstUnstableOffset),對已完成的事務而言,它的值同HW 相同。

LW: Low Watermark 低水位,代表AR 集合中最小的 logStartOffset 值。

image.png

19、如何進行 Leader 副本選舉?

每個分割槽的 leader 會維護一個 ISR 集合,ISR 列表裡面就是 follower 副本的 Borker 編號,只有“跟得上” Leader 的 follower 副本才能加入到 ISR 裡面,這個是通過 replica.lag.time.max.ms 引數配置的。只有 ISR 裡的成員才有被選為 leader 的可能。

所以當 Leader 掛掉了,而且 unclean.leader.election.enable=false 的情況下,Kafka 會從 ISR 列表中選擇 第一個 follower 作為新的 Leader,因為這個分割槽擁有最新的已經 committed 的訊息。通過這個可以保證已經 committed 的訊息的資料可靠性。

20、如何進行 broker Leader 選舉?

(1) 在 kafka 叢集中,會有多個 broker 節點,叢集中第一個啟動的 broker 會通過在 zookeeper 中建立臨時節點 /controller 來讓自己成為控制器,其他 broker 啟動時也會在 zookeeper 中建立臨時節點,但是發現節點已經存在,所以它們會收到一個異常,意識到控制器已經存在,那麼就會在 zookeeper 中建立 watch 物件,便於它們收到控制器變更的通知。

(2) 如果叢集中有一個 broker 發生異常退出了,那麼控制器就會檢查這個 broker 是否有分割槽的副本 leader ,如果有那麼這個分割槽就需要一個新的 leader,此時控制器就會去遍歷其他副本,決定哪一個成為新的 leader,同時更新分割槽的 ISR 集合。

(3) 如果有一個 broker 加入叢集中,那麼控制器就會通過 Broker ID 去判斷新加入的 broker 中是否含有現有分割槽的副本,如果有,就會從分割槽副本中去同步資料。

(4) 叢集中每選舉一次控制器,就會通過 zookeeper 建立一個 controller epoch,每一個選舉都會建立一個更大,包含最新資訊的 epoch,如果有 broker 收到比這個 epoch 舊的資料,就會忽略它們,kafka 也通過這個 epoch 來防止叢集產生“腦裂”。

21、Kafka 事務

Kafka 在 0.11版本引入事務支援,事務可以保證 Kafka 在 Exactly Once 語義的基礎上,生產和消費可以跨分割槽和會話,要麼全部成功,要麼全部失敗。

Producer 事務

為了實現跨分割槽跨會話事務,需要引入一個全域性唯一的 Transaction ID,並將 Producer 獲取的 PID 和 Transaction ID 繫結。這樣當 Producer 重啟後就可以通過正在進行的 Transaction ID 獲取原來的 PID。

為了管理 Transaction,Kafka 引入了一個新的元件 Transaction Coordinator。Producer 就是通過和 Transaction Coordinator 互動獲得 Transaction ID 對應的任務狀態。Transaction Coordinator 還負責將事務所有寫入 Kafka 的一個內部 Topic,這樣即使整個服務重啟,由於事務狀態得到儲存,進行中的事務狀態可以得到恢復,從而繼續進行。

Consumer 事務

上述事務機制主要是從Producer 方面考慮,對於 Consumer 而言,事務的保證就會相對較弱,尤其是無法保證 Commit 的資訊被精確消費。這是由於 Consumer 可以通過 offset 訪問任意資訊,而且不同的Segment File 生命週期不同,同一事務的訊息可能會出現重啟後被刪除的情況。

22、Kafka的消費者組跟分割槽之間有什麼關係?

(1)在 Kafka 中,通過消費者組管理消費者,假設一個主題中包含 4 個分割槽,在一個消費者組中只要一個消費者。那消費者將收到全部 4 個分割槽的訊息。

(2)如果存在兩個消費者,那麼四個分割槽將根據分割槽分配策略分配個兩個消費者。

(3)如果存在四個消費者,將平均分配,每個消費者消費一個分割槽。

(4)如果存在5個消費者,就會出現消費者數量多於分割槽數量,那麼多餘的消費者將會被閒置,不會接收到任何資訊。

23、如何保證每個應用程式都可以獲取到 Kafka 主題中的所有訊息,而不是部分訊息?

為每個應用程式建立一個消費者組,然後往組中新增消費者來伸縮讀取能力和處理能力,每個群組消費主題中的訊息時,互不干擾。

24、如何實現 kafka 消費者每次只消費指定數量的訊息?

寫一個佇列,把 consumer 作為佇列類的一個屬性,然後增加一個消費計數的計數器,當到達指定數量時,關閉consumer。

25、Kafka 如何實現多執行緒的消費?

kafka 允許同組的多個 partition 被一個 consumer 消費,但不允許一個 partition 被同組的多個 consumer 消費。

實現多執行緒步驟如下:

生產者隨機分割槽提交資料(自定義隨機分割槽)。
消費者修改單執行緒模式為多執行緒,在消費方面得注意,得遍歷所有分割槽,否則還是隻消費了一個區。

image.png

26、 Kafka 消費支援幾種消費模式?

kafka消費訊息時支援三種模式:

at most once 模式 最多一次。保證每一條訊息 commit 成功之後,再進行消費處理。訊息可能會丟失,但不會重複。

at least once 模式 至少一次。保證每一條訊息處理成功之後,再進行commit。訊息不會丟失,但可能會重複。

exactly once 模式 精確傳遞一次。將 offset 作為唯一 id 與訊息同時處理,並且保證處理的原子性。訊息只會處理一次,不丟失也不會重複。但這種方式很難做到。

Kafka 預設的模式是 at least once ,但這種模式可能會產生重複消費的問題,所以在業務邏輯必須做冪等設計。

在業務場景儲存資料時使用了 INSERT INTO ...ON DUPLICATE KEY UPDATE語法,不存在時插入,存在時更新,是天然支援冪等性的。

27、Kafka 如何保證資料的不重複和不丟失(Exactly Once語義)?

1、Exactly once 模式 精確傳遞一次。將 offset 作為唯一 id 與訊息同時處理,並且保證處理的原子性。訊息只會處理一次,不丟失也不會重複。但這種方式很難做到。
kafka 預設的模式是 at least once ,但這種模式可能會產生重複消費的問題,所以在業務邏輯必須做冪等設計。

2、冪等性:Producer在生產傳送訊息時,難免會重複傳送訊息。Producer進行retry時會產生重試機制,發生訊息重複傳送。而引入冪等性後,重複傳送只會生成一條有效的訊息。

具體實現:每個 Producer 在初始化時都會被分配一個唯一的 PID,這個 PID 對應用是透明的,完全沒有暴露給使用者。對於一個給定的 PID,sequence number 將會從0開始自增。Producer 在傳送資料時,將會給每條 msg 標識一個 sequence number,broker 也就是通過這個來驗證資料是否重複。這裡的 PID 是全域性唯一的,Producer 故障後重新啟動後會被分配一個新的 PID,這也是冪等性無法做到跨會話的一個原因。broker上每個Topic-Partition也會維護pid-seq的對映,並且每次 Commit 都會更新 lastSeq。這樣Record Batch 到來時,broker會先檢查 Record Batch 再儲存資料。如果batch中 baseSeq(第一條訊息的seq)比Broker維護的序號(lastSeq)大1,則儲存資料,否則不儲存。

3、使用 Exactly Once + 冪等,可以保證資料不重複,不丟失。

28、Kafka 是如何清理過期資料的?

kafka 將資料持久化到了硬碟上,允許你配置一定的策略對資料清理,清理的策略有兩個,刪除和壓縮。

資料清理的方式

1、刪除

log.cleanup.policy=delete 啟用刪除策略

直接刪除,刪除後的訊息不可恢復。可配置以下兩個策略:

#清理超過指定時間清理:  
log.retention.hours=16
#超過指定大小後,刪除舊的訊息:
log.retention.bytes=1073741824

為了避免在刪除時阻塞讀操作,採用了 copy-on-write 形式的實現,刪除操作進行時,讀取操作的二分查詢功能實際是在一個靜態的快照副本上進行的,這類似於 Java 的 CopyOnWriteArrayList。

2、壓縮

將資料壓縮,只保留每個 key 最後一個版本的資料。

首先在 broker 的配置中設定 log.cleaner.enable=true 啟用 cleaner,這個預設是關閉的。

在 topic 的配置中設定 log.cleanup.policy=compact 啟用壓縮策略。

29、Kafka 與 CAP 理論

CAP理論作為分散式系統的基礎理論,它描述的是一個分散式系統最多隻能滿足一致性(Consistency)、可用性(Availability)和分割槽容錯性(Partition tolerance)這三者當中的兩個。

1、CAP 的含義

一致性(Consistency)
含義:所有節點訪問同一份最新的資料副本(在同一時刻相同)。

在寫操作完成後開始的任何讀操作都必須返回該值,或者後續寫操作的結果。也就是說,在一致性系統中,一旦客戶端將值寫入任何一臺伺服器並獲得響應,那麼之後client從其他任何伺服器讀取的都是剛寫入的資料。

可用性(Availability)
含義:系統中非故障節點收到的每個請求都必須有響應。

在可用系統中,如果我們的客戶端向伺服器傳送請求,並且伺服器未崩潰,則伺服器必須最終響應客戶端,不允許伺服器忽略客戶的請求。

分割槽容錯性(Partition tolerance)
含義:指的分散式系統中的某個節點或者網路分割槽出現了故障的時候,整個系統仍然能對外提供滿足一致性和可用性的服務,即部分故障不影響整體使用。

事實上我們在設計分散式系統是都會考慮到 bug、硬體、網路等各種原因造成的故障,所以即使部分節點或者網路出現故障,我們要求整個系統還是要繼續使用的 (不繼續使用,相當於只有一個分割槽,那麼也就沒有後續的一致性和可用性了)

2、注意:
(1)不是在任何時候,C 和 A 都要捨棄一個。在沒有出現分割槽問題時,分散式系統就應該有完美的資料一致性和可用性。

(2)C 和 A 的選擇不是一定針對整個系統的,可以分階段分時刻。如支付子系統中賬務流水相關的一定是選擇強一致性;像使用者名稱、使用者頭像、使用者等級等相關子系統可以選擇 A。

(3)CAP三種特性不是布林型別、二元對立、非黑即白的,三者都是範圍性的,例如當強調一致性時並不是完全摒棄可用性。

(4)CAP三者如何權衡

CA 系統:關注一致性和可用性,它需要非常嚴格的全體一致的協議,比如“兩階段提交協議”(2PC)。CA 系統不能容忍網路錯誤或節點錯誤,一旦出現這樣的問題,整個系統就會拒絕寫請求,因為它並不知道對面的那個結點是否掛掉了,還是隻是網路問題。唯一安全的做法就是把自己變成只讀的。

很遺憾,這種情況幾乎不存在。因為分散式系統,網路分割槽是必然的。如果要捨棄P,那麼就是要捨棄分散式系統,CAP也就無從談起了。可以說P是分散式系統的前提,所以這種情況是不存在的。

比如一般的關係型資料庫,像是MySQL或者是Oracle,它們都保證了一致性和可用性,但是並不是分散式系統。從這點上來說CAP並不是等價的,我們並不能通過犧牲CA來提升P。要想提升分割槽容錯性,只能通過提升基礎設施的穩定性來達到。也就是說這並不是一個軟體問題。

CP 系統:關注一致性和分割槽容忍性。它關注的是系統裡大多數人的一致性協議,比如:Paxos 演算法 (Quorum 類的演算法)。這樣的系統只需要保證大多數結點資料一致,而少數的結點會在沒有同步到最新版本的資料時變成不可用的狀態。這樣能夠提供一部分的可用性。

一個系統保證了一致性和分割槽容錯性,捨棄可用性。也就是說在極端情況下,允許出現系統無法訪問的情況出現,這個時候往往會犧牲使用者體驗,讓使用者保持等待,一直到系統資料一致了之後,再恢復服務。

對於有些系統而言,一致性是安身立命之本,比如 Hbase、Redis這種分散式儲存,資料一致性是最基本的要求。不滿足一致性的儲存顯然不會有使用者願意使用。

ZooKeeper也是一樣,任何時候訪問 ZK 都可以獲得一致性的結果。它的職責就是保證管轄下的服務保持同步和一致,顯然不可能放棄一致性。但是在極端情況下,ZK 可能會丟棄一些請求,消費者需要重新請求才能獲得結果。

AP 系統:這樣的系統關心可用性和分割槽容忍性。因此,這樣的系統不能達成一致性,需要給出資料衝突,給出資料衝突就需要維護資料版本。

這種是大部分的分散式系統的設計,保證高可用和分割槽容錯,但是會犧牲一致性。比如淘寶購物以及12306購票等等,前面說過淘寶可以做到全年可用性5個9的超高階別,但是此時就無法保證資料一致性了。

舉個例子,我們在12306買票的時候就經常會遇到。在我們點選購買的時候,系統並沒有提示沒票。等我們輸入了驗證碼,付款的時候才會告知,已經沒有票了。這就是因為我們在點選購買的時候,資料沒有達成一致性,在付款校驗的時候才檢驗出餘票不足。這種設計會犧牲一些使用者體驗,但是可以保證高可用,讓使用者不至於無法訪問或者是長時間等待,也算是一種取捨吧。

權衡三者的關鍵點取決於業務。

放棄了一致性,滿足分割槽容錯,那麼節點之間就有可能失去聯絡,為了高可用,每個節點只能用本地資料提供服務,而這樣會容易導致全域性資料不一致性。對於網際網路應用來說(如新浪,網易),機器數量龐大,節點分散,網路故障再正常不過了,那麼此時就是保障AP,放棄C的場景,而從實際中理解,像入口網站這種偶爾沒有一致性是能接受的,但不能訪問問題就非常大了。

對於銀行來說,就是必須保證強一致性,也就是說 C 必須存在,那麼就只用 CA 和 CP 兩種情況,當保障強一致性和可用性(CA),那麼一旦出現通訊故障,系統將完全不可用。另一方面,如果保障了強一致性和分割槽容錯(CP),那麼就具備了部分可用性。實際究竟應該選擇什麼,是需要通過業務場景進行權衡的(並不是所有情況都是 CP 好於 CA,只能檢視資訊但不能更新資訊有時候還不如直接拒絕服務)。

3、Kafka 中的 CAP 機制

Kafka 滿足的是 CAP 定律當中的 CA,其中 Partition tolerance 通過的是一定的機制儘量的保證分割槽容錯性。其中 C 表示的是資料一致性。A 表示資料可用性。

Kafka 首先將資料寫入到不同的分割槽裡面去,每個分割槽又可能有好多個副本,資料首先寫入到 leader 分割槽裡面去,讀寫的操作都是與 leader 分割槽進行通訊,保證了資料的一致性原則,也就是滿足了 Consistency 原則。然後 kafka 通過分割槽
副本機制,來保證了 kafka 當中資料的可用性。但是也存在另外一個問題,就是副本分割槽當中的資料與 leader 當中的資料存在差別的問題如何解決,這個就是Partition tolerance 的問題。

kafka 為了解決 Partition tolerance 的問題,使用了 ISR 的同步策略,來盡最大可能減少 Partition tolerance 的問題。

每個 leader 會維護一個 ISR(a set of in-sync replicas,基本同步)列表。ISR 列表主要的作用就是決定哪些副本分割槽是可用的,也就是說可以將 leader分割槽裡面的資料同步到副本分割槽裡面去,決定一個副本分割槽是否可用的條件有2個:

  • replica.lag.time.max.ms=10000 副本分割槽與主分割槽心跳時間延遲,超過這個時間就踢出 ISR
  • replica.lag.max.messages=4000 表示當前某個副本落後leader的訊息數量超過了這個引數的值,那麼leader就會把follower從ISR中刪除(0.10.0版本該引數被移除)

produce 請求被認為完成時的確認值:request.required.acks=0。

  • ack=0:producer 不等待 broker 同步完成的確認,繼續傳送下一條(批)資訊。
  • ack=1(預設):producer 要等待 leader 成功收到資料並得到確認,才傳送下一條 message。
  • ack=-1:leader 得到所有 ISR 中的 follwer 確認,向 producer 確認後,producer才傳送下一條資料。

30、為什麼 Kafka 不支援讀寫分離?

在 Kafka 中,生產者寫入訊息、消費者讀取訊息的操作都是與 leader 副本進行互動的,從 而實現的是一種主寫主讀的生產消費模型。 Kafka 並不支援主寫從讀,因為主寫從讀有 2 個很明顯的缺點:

(1)資料一致性問題:資料從主節點轉到從節點必然會有一個延時的時間視窗,這個時間 視窗會導致主從節點之間的資料不一致。某一時刻,在主節點和從節點中 A 資料的值都為 X, 之後將主節點中 A 的值修改為 Y,那麼在這個變更通知到從節點之前,應用讀取從節點中的 A 資料的值並不為最新的 Y,由此便產生了資料不一致的問題。

(2)延時問題:類似 Redis 這種元件,資料從寫入主節點到同步至從節點中的過程需要經歷:網路 → 主節點記憶體 → 網路 → 從節點記憶體 這幾個階段,整個過程會耗費一定的時間。而在 Kafka 中,主從同步會比 Redis 更加耗時,它需要經歷 網路 → 主節點記憶體 → 主節點磁碟 → 網路 → 從節點記憶體 → 從節點磁碟 這幾個階段。對延時敏感的應用而言,主寫從讀的功能並不太適用。

而 Kafka 的主寫主讀的優點就很多了:

(1)可以簡化程式碼的實現邏輯,減少出錯的可能;

(2)將負載粒度細化均攤,與主寫從讀相比,不僅負載效能更好,而且對使用者可控;

(3)沒有延時的影響;

(4)在副本穩定的情況下,不會出現資料不一致的情況。

31、Kafka 的資料 offset 讀取流程

(1)連線 ZK 叢集,從 ZK 中拿到對應 topic 的 partition 資訊和 partition的 Leader 的相關資訊

(2)連線到對應 Leader 對應的 broker

(3)consumer 將⾃自⼰己儲存的 offset 傳送給 Leader

(4)Leader 根據 offset 等資訊定位到 segment(索引⽂檔案和⽇日誌⽂檔案)

(5)根據索引⽂檔案中的內容,定位到⽇日誌⽂檔案中該偏移量量對應的開始位置讀取相應⻓長度的資料並返回給 consumer

32. Kafka 訊息資料積壓,Kafka 消費能力不足怎麼處理?

(1)如果是 Kafka 消費能力不足,則可以考慮增加 Topic 的分割槽數,並同時提升消費組的消費者數量,消費者數 = 分割槽數(兩者缺一不可)。

(2)如果是下游的資料處理不及時:提高每批次拉取的數量。
批次拉取資料過少,即:拉取資料/處理時間 < 生產速度,使處理的資料小於生產的資料,也會造成資料積壓。

相關文章