Kafka爲何快到根本停不下來?


目前來講市面上能夠選擇的消息隊列很是多,像 ActiveMQ,RabbitMQ,ZeroMQ 已經被大多數人耳熟能詳。算法


image.png
圖片來自 Pexels


特別像 ActiveMQ 早期應用在企業中的總線通訊,基本做爲企業級 IT 設施解決方案中不可或缺的一部分。數據庫


目前 Kafka 已經很是穩定,而且逐步應用更加普遍,已經算不得新生事物,可是不能否認 Kafka 一枝獨秀如同雨後春筍,很是耀眼,今天咱們仔細分解一下 Kafka,瞭解一下它的內幕。


如下的內容版本基於當前最新的 Kafka 穩定版本 2.4.0。文章主要包含如下內容:
  • Kafka 爲何快緩存

  • Kafka 爲何穩安全

  • Kafka 該怎麼用服務器


該文章爲開篇引導之作,後續會有對應的 HBase,Spark,Kylin,Pulsar 等相關組件的剖析。

Kafka 爲何快網絡


快是一個相對概念,沒有對比就沒有傷害,所以一般咱們說 Kafka 是相對於咱們常見的 ActiveMQ,RabbitMQ 這類會發生 IO,而且主要依託於 IO 來作信息傳遞的消息隊列。


像 ZeroMQ 這種基本純粹依靠內存作信息流傳遞的消息隊列,固然會更快,可是此類消息隊列只有特殊場景下會使用,不在對比之列。


所以當咱們說 Kakfa 快的時候,一般是基於如下場景:
  • 吞吐量:當咱們須要每秒處理幾十萬上百萬 Message 的時候,相對其餘 MQ,Kafka 處理的更快。架構

  • 高併發:當具備百萬以及千萬的 Consumer 的時候,同等配置的機器下,Kafka 所擁有的 Producer 和 Consumer 會更多。併發

  • 磁盤鎖:相對其餘 MQ,Kafka 在進行 IO 操做的時候,其同步鎖住 IO 的場景更少,發生等待的時間更短。app

那麼基於以上幾點,咱們來仔細探討一下,爲何 Kafka 就快了。框架

消息隊列的推拉模型

首先,若是咱們單純站在 Consumer 的角度來看「Kafka 快」,是一個僞命題,由於相比其餘 MQ,Kafka 從 Producer 產生一條 Message 到 Consumer 消費這條 Message 來看,它的時間必定是大於等於其餘 MQ 的。


背後的緣由涉及到消息隊列設計的兩種模型:
  • 推模型

  • 拉模型


以下圖所示:

image.png


對於拉模型來講,Producer 產生 Message 後,會主動發送給 MQ Server,爲了提高性能和減小開支,部分 Client 還會設計成批量發送。


可是不管是單條仍是批量,Producer 都會主動推送消息到 MQ Server。

當 MQ Server 接收到消息後,對於拉模型,MQ Server 不會主動發送消息到 Consumer,同時也不會維持和記錄消息的 Offset,Consumer 會自動設置定時器到服務端去詢問是否有新的消息產生。

一般時間是不超過 100ms 詢問一次,一旦產生新的消息則會同步到本地,而且修改和記錄 Offset,服務端能夠輔助存儲 Offset,可是不會主動記錄和校驗 Offset 的合理性。

同時 Consumer 能夠徹底自主的維護 offset 以便實現自定義的信息讀取。

對於推模型來講,服務端收到 Message 後,首先會記錄消息的信息,而且從本身的元信息數據庫中查詢對應的消息的 Consumer 有誰。
因爲服務器和 Consumer 在連接的時候創建了長連接,所以能夠直接發送消息到 Consumer。
Kafka 是基於拉模型的消息隊列,所以從 Consumer 獲取消息的角度來講,延遲會小於等於輪詢的週期,因此會比推模型的消息隊列具備更高的消息獲取延遲,可是推模型一樣又其問題。




首先,因爲服務器須要記錄對應的 Consumer 的元信息,包括消息該發給誰,Offset 是多少,同時須要向 Consumer 推送消息,必然會帶來系列的問題。




假如這一刻網絡很差,Consumer 沒有收到,消息沒有發成功怎麼辦?假設消息發出去了,我怎麼知道它有沒有收到?




所以服務器和 Consumer 之間須要首先多層確認口令,以達到至少消費一次,僅且消費一次等特性。




Kafka 此類的拉模型將這一塊功能都交由 Consumer 自動維護,所以服務器減小了更多的沒必要要的開支。




所以從同等資源的角度來說,Kafka 具有連接的 Producer 和 Consumer 將會更多,極大的下降了消息堵塞的狀況,所以看起來更快了。




OS Page Cache 和 Buffer Cache




太陽底下無新鮮事,對於一個框架來講,要想運行的更快,一般能用的手段也就那麼幾招,Kafka 在將這一招用到了極致。




其中之一就是極大化的使用了 OS 的 Cache,主要是 Page Cache 和 Buffer Cache。


對於這兩個 Cache,使用 Linux 的同窗一般不會陌生,例如咱們在 Linux 下執行 free 命令的時候會看到以下的輸出:

image.png


圖片來自網絡




會有兩列名爲 buffers 和 cached,也有一行名爲「-/+ buffers/cache」, 這兩個信息的具體解釋以下:
pagecache: 文件系統層級的緩存,從磁盤裏讀取的內容是存儲到這裏,這樣程序讀取磁盤內容就會很是快,好比使用 Linux 的 grep 和 find 等命令查找內容和文件時,第一次會慢不少,再次執行就快好多倍,幾乎是瞬間。
另外 page cache 的數據被修改事後,也即髒數據,等到寫入磁盤時機到來時,會轉移到 buffer cache 而不是直接寫入到磁盤。




咱們看到的 cached 這列的數值表示的是當前的頁緩存(page cache)的佔用量,page cache 文件的頁數據,頁是邏輯上的概念,所以 page cache 是與文件系統同級的。




buffer cache: 磁盤等塊設備的緩衝,內存的這一部分是要寫入到磁盤裏的 。



buffers 列表示當前的塊緩存(buffer cache)佔用量,buffer cache 用於緩存塊設備(如磁盤)的塊數據。塊是物理上的概念,所以 buffer cache 是與塊設備驅動程序同級的。

image.png


二者都是用來加速數據 IO,將寫入的頁標記爲 dirty,而後向外部存儲 flush,讀數據時首先讀取緩存,若是未命中,再去外部存儲讀取,而且將讀取來的數據也加入緩存。




操做系統老是積極地將全部空閒內存都用做 Page Cache 和 Buffer Cache,當 OS 的內存不夠用時也會用 LRU 等算法淘汰緩存頁。

image.png



有了以上概念後,咱們再看來 Kafka 是怎麼利用這個特性的。
首先,對於一次數據 IO 來講,一般會發生如下的流程:


  • 操做系統將數據從磁盤拷貝到內核區的 Page Cache。

  • 用戶程序將內核區的 Page Cache 拷貝到用戶區緩存。

  • 用戶程序將用戶區的緩存拷貝到 Socket 緩存中。

  • 操做系統將 Socket 緩存中的數據拷貝到網卡的 Buffer 上,發送數據。



能夠發現一次 IO 請求操做進行了 2 次上下文切換和 4 次系統調用,而同一份數據在緩存中屢次拷貝,實際上對於拷貝來講徹底能夠直接在內核態中進行。



也就是省去第二和第三步驟,變成這樣:

image.png


正由於能夠如此的修改數據的流程,因而 Kafka 在設計之初就參考此流程,儘量大的利用 OS 的 Page Cache 來對數據進行拷貝,儘可能減小對磁盤的操做。
若是 Kafka 生產消費配合的好,那麼數據徹底走內存,這對集羣的吞吐量提高是很大的。




早期的操做系統中的 Page Cache 和 Buffer Cache 是分開的兩塊 Cache,後來發現一樣的數據可能會被 Cache 兩次,因而大部分狀況下二者都是合二爲一的。




Kafka 雖然使用 JVM 語言編寫,在運行的時候脫離不了 JVM 和 JVM 的 GC, 可是 Kafka 並未本身去管理緩存,而是直接使用了 OS 的 Page Cache 做爲緩存。




這樣作帶來了如下好處:


  • JVM 中的一切皆對象,因此不管對象的大小,總會有些額外的 JVM 的對象元數據浪費空間。

  • JVM 本身的 GC 不受程序手動控制,因此若是使用 JVM 做爲緩存,在遇到大對象或者頻繁 GC 的時候會下降整個系統的吞吐量。

  • 程序異常退出或者重啓,全部的緩存都將失效,在容災架構下會影響快速恢復。而 Page Cache 由於是 OS 的 Cache,即使程序退出,緩存依舊存在。



因此 Kafka 優化 IO 流程,充分利用 Page Cache,其消耗的時間更短,吞吐量更高,相比其餘 MQ 就更快了。



用一張圖來簡述三者之間的關係以下:

image.png


當 Producer 和 Consumer 速率相差不大的狀況下,Kafka 幾乎能夠徹底實現不落盤就完成信息的傳輸。




追加順序寫入



除了前面的重要特性以外,Kafka 還有一個設計,就是對數據的持久化存儲採用的順序的追加寫入,Kafka 在將消息落到各個 Topic 的 Partition 文件時,只是順序追加,充分的利用了磁盤順序訪問快的特性。

image.png


圖片來自網絡




Kafka 的文件存儲按照 Topic 下的 Partition 來進行存儲,每個 Partition 有各自的序列文件,各個 Partition 的序列不共享, 主要的劃分按照消息的 Key 進行 Hash 決定落在哪一個分區之上。




咱們先來詳細解釋一下 Kafka 的各個名詞,以便充分理解其特色:


  • Broker:Kafka 中用來處理消息的服務器,也是 Kafka 集羣的一個節點,多個節點造成一個 Kafka 集羣。

  • Topic:一個消息主題,每個業務系統或者 Consumer 須要訂閱一個或者多個主題來獲取消息,Producer 須要明確發生消息對於的 Topic,等於信息傳遞的口令名稱。

  • Partition:一個 Topic 會拆分紅多個 Partition 落地到磁盤,在 Kafka 配置的存儲目錄下按照對應的分區 ID 建立的文件夾進行文件的存儲,磁盤能夠見的最大的存儲單元。

  • Segment:一個 Partition 會有多個 Segment 文件來實際存儲內容。

  • Offset:每個 Partition 有本身的獨立的序列編號,做用域僅在當前的 Partition 之下,用來對對應的文件內容進行讀取操做。

  • Leader:每個 Topic 須要有一個 Leader 來負責該 Topic 的信息的寫入,數據一致性的維護。

  • Controller:每個 Kafka 集羣會選擇出一個 Broker 來充當 Controller,負責決策每個 Topic 的 Leader 是誰,監聽集羣 Broker 信息的變化,維持集羣狀態的健康。

image.png

image.png


能夠看到最終落地到磁盤都是 Segment 文件,每個 Partion(目錄)至關於一個巨型文件被平均分配到多個大小相等 Segment(段)數據文件中。
但每一個段 segment file 消息數量不必定相等,這種特性方便老的 segment file 快速被刪除。




由於 Kafka 處理消息的力度是到 Partition,所以只須要保持好 Partition 對應的順序處理,Segment 能夠單獨維護其狀態。 



Segment 的文件由 index file 和 data file 組成,落地在磁盤的後綴爲 .index 和 .log,文件按照序列編號生成,以下所示:

image.png


圖片來自網絡




其中 index 維持着數據的物理地址,而 data 存儲着數據的偏移地址,相互關聯,這裏看起來彷佛和磁盤的順序寫入關係不大,想一想 HDFS 的塊存儲,每次申請固定大小的塊和這裏的 Segment?是否是挺類似的?




另外由於有 index 文的自己命名是以 Offset 做爲文件名的,在進行查找的時候能夠快速根據須要查找的 Offset 定位到對應的文件,再根據文件進行內容的檢索。




所以 Kafka 的查找流程爲先根據要查找的 Offset 對文件名稱進行二分查找,找到對應的文件,再根據 index 的元數據的物理地址和 log 文件的偏移位置結合順序讀區到對應 Offset 的位置的內容便可。
segment index file 採起稀疏索引存儲方式,它減小索引文件大小,經過 mmap 能夠直接內存操做,稀疏索引爲數據文件的每一個對應 Message 設置一個元數據指針。




它比稠密索引節省了更多的存儲空間,但查找起來須要消耗更多的時間,特別是在隨機讀取的場景下,Kafka 很是不合適。因此由於 Kafka 特殊的存儲設計,也讓 Kafka 感受起來,更快。




Kafka 爲何穩




前面提到 Kafka 爲何快,除了快的特性以外,Kafka 還有其餘特色,那就是:穩。




Kafka 的穩體如今幾個維度:


  • 數據安全,幾乎不會丟數據。

  • 集羣安全,發生故障幾乎能夠 Consumer 無感知切換。

  • 可用性強,即使部分 Partition 不可用,剩餘的 Partition 的數據依舊不影響讀取。

  • 流控限制,避免大量 Consumer 拖垮服務器的帶寬。



限流機制


對於 Kafka 的穩,一般是由其總體架構設計決定,不少優秀的特性結合在一塊兒,就更加的優秀,像 Kafka 的 Qutota 就是其中一個。




既然是限流,那就意味着須要控制 Consumer 或者 Producer 的流量帶寬,一般限制流量這件事須要在網卡上做處理,像常見的 N 路交換機或者高端路由器。




因此對於 Kafka 來講,想要操控 OS 的網卡去控制流量顯然具備很是高的難度,所以 Kafka 採用了另一個特別的思路。




即:沒有辦法控制網卡經過的流量大小,就控制返回數據的時間。對於 JVM 程序來講,就是一個 Wait 或者 Seelp 的事情。




因此對於 Kafka 來講,有一套特殊的時延計算規則,Kafka 按照一個窗口來統計單位時間傳輸的流量。




當流量大小超過設置的閾值的時候,觸發流量控制,將當前請求丟入 Kafka 的 Qutota Manager,等到延遲時間到達後,再次返回數據。



咱們經過 Kafka 的 ClientQutotaManager 類中的方法來看:

image.png


這幾行代碼表明瞭 Kafka 的限流計算邏輯,大概的思路爲:假設咱們設定當前流量上限不超過 T,根據窗口計算出當前的速率爲 O。



若是 O 超過了 T,那麼會進行限速,限速的公示爲:


X = (O - T)/ T * W




X 爲須要延遲的時間,讓我舉一個形象的例子,假設咱們限定流量不超過 10MB/s,過去 5 秒(公示中的 W,窗口區間)內經過的流量爲 100MB,則延遲的時間爲:(100-5*10)/10=5 秒。




這樣就可以保障在下一個窗口運行完成後,整個流量的大小是不會超過限制的。



經過 KafkaApis 裏面對 Producer 和 Consumer 的 call back 代碼能夠看到對限流的延遲返回:

image.png


對於 Kafka 的限流來說,默認是按照 client id 或者 user 來進行限流的,從實際使用的角度來講,意義不是很大,基於 Topic 或者 Partition 分區級別的限流,相對使用場景更大。

競選機制




Kafka 背後的元信息重度依賴 Zookeeper,再次咱們不解釋 Zookeeper 自己,而是關注 Kafka 究竟是如何使用 ZK 的。



首先一張圖解釋 Kafka 對 ZK 的重度依賴:

image.png


圖片來源於網絡




利用 ZK 除了自己信息的存儲以外,最重要的就是 Kafka 利用 ZK 實現選舉機制,其中以 Controller 爲主要的介紹。



首先 Controller 做爲 Kafka 的心臟,主要負責着包括不限於如下重要事項:

image.png


也就是說 Controller 是 Kafka 的核心角色,對於 Controller 來講,採用公平競爭,任何一個 Broker 都有可能成爲 Controller,保障了集羣的健壯性。




對於 Controller 來講,其選舉流程以下:




①先獲取 ZK 的 /Cotroller 節點的信息,獲取 Controller 的 broker id,若是該節點不存在(好比集羣剛建立時),* 那麼獲取的 controller id 爲 -1。




②若是 controller id 不爲 -1,即 Controller 已經存在,直接結束流程。




③若是 controller id 爲 -1,證實 Controller 還不存在,這時候當前 Broker 開始在 ZK 註冊 Controller。




④若是註冊成功,那麼當前 Broker 就成爲了 Controller,這時候開始調用 onBecomingLeader() 方法,正式初始化 Controller。




注意:Controller 節點是臨時節點,若是當前 Controller 與 ZK 的 Session 斷開,那麼 Controller 的臨時節點會消失,會觸發 Controller 的從新選舉。




⑤若是註冊失敗(恰好 Controller 被其餘 Broker 建立了、拋出異常等),那麼直接返回。



其代碼直接經過 KafkaController 能夠看到:

image.png

一旦 Controller 選舉出來以後,則其餘 Broker 會監聽 ZK 的變化,來響應集羣中 Controller 掛掉的狀況:

image.png


從而觸發新的 Controller 選舉動做。對於 Kafka 來講,整個設計很是緊湊,代碼質量至關高,不少設計也很是具備借鑑意義,相似的功能在 Kafka 中有很是多的特性體現,這些特性結合一塊兒,造成了 Kafka 整個穩定的局面。




Kafka 該怎麼用




雖然 Kafka 總體看起來很是優秀,可是 Kafka 也不是全能的銀彈,必然有其對應的短板,那麼對於 Kafka 如何,或者如何能用的更好,則須要通過實際的實踐才能得感悟的出。
通過概括和總結,可以發現如下不一樣的使用場景和特色:



①Kafka 並不合適高頻交易系統




Kafka 雖然具備很是高的吞吐量和性能,可是不能否認,Kafka 在單條消息的低延遲方面依舊不如傳統 MQ,畢竟依託推模型的 MQ 可以在實時消息發送的場景下取得先天的優點。


②Kafka 並不具有完善的事務機制




0.11 以後 Kafka 新增了事務機制,能夠保障 Producer 的批量提交,爲了保障不會讀取到髒數據,Consumer 能夠經過對消息狀態的過濾過濾掉不合適的數據,可是依舊保留了讀取全部數據的操做。




即使如此,Kafka 的事務機制依舊不完備,背後主要的緣由是 Kafka 對 Client 並不感冒,因此不會統一全部的通用協議,所以在相似僅且被消費一次等場景下,效果很是依賴於客戶端的實現。


③Kafka 的異地容災方案很是複雜




對於 Kafka 來講,若是要實現跨機房的無感知切換,就須要支持跨集羣的代理。




由於 Kafka 特殊的 append log 的設計機制,致使一樣的 Offset 在不一樣的 Broker 和不一樣的內容上沒法複用。




也就是文件一旦被拷貝到另一臺服務器上,將不可讀取,相比相似基於數據庫的 MQ,很難實現數據的跨集羣同步。




同時對於 Offset 的復現也很是難,曾經幫助客戶實現了一套跨機房的 Kafka 集羣 Proxy,投入了很是大的成本。


④Kafka Controller 架構沒法充分利用集羣資源




Kafka Controller 相似於 ES 的去中心化思想,按照競選規則從集羣中選擇一臺服務器做爲 Controller。


意味着改服務器即承擔着 Controller 的職責,同時又承擔着 Broker 的職責,致使在海量消息的壓迫下,該服務器的資源很容易成爲集羣的瓶頸,致使集羣資源沒法最大化。




Controller 雖然支持 HA 可是並不支持分佈式,也就意味着若是要想 Kafka 的性能最優,每一臺服務器至少都須要達到最高配置。


⑤Kafka 不具有很是智能的分區均衡能力




一般在設計落地存儲的時候,對於熱點或者要求性能足夠高的場景下,會是 SSD 和 HD 的結合。



同時若是集羣存在磁盤容量大小不均等的狀況,對於 Kafka 來講會有很是嚴重的問題,Kafka 的分區產生是按照 Paratition 的個數進行統計,將新的分區建立在個數最少的磁盤上,見下圖: image.png
曾經我幫助某企業修改了分區建立規則,考慮了容量的狀況,也就是按照磁盤容量進行分區的選擇。




緊接着帶來第二個問題:容量大的磁盤具有更多的分區,則會致使大量的 IO 都壓向該盤,最後問題又落回 IO,會影響該磁盤的其餘 Topic 的性能。




因此在考慮 MQ 系統的時候,須要合理的手動設置 Kafka 的分區規則。

結尾




Kafka 並非惟一的解決方案,像幾年前新生勢頭挺厲害的 Pulsar,以取代 Kafka 的口號衝入市場,也許會成爲下一個解決 Kafka 部分痛點的框架,下文再講述 Pulsar。
相關文章
相關標籤/搜索