您好,登錄后才能下訂單哦!
這篇文章主要介紹“Kafka為什么那么快”,在日常操作中,相信很多人在Kafka為什么那么快問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Kafka為什么那么快”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
Kafka 為什么快
快是一個相對概念,沒有對比就沒有傷害,因此通常我們說 Kafka 是相對于我們常見的 ActiveMQ,RabbitMQ 這類會發生 IO,并且主要依托于 IO 來做信息傳遞的消息隊列。
像 ZeroMQ 這種基本純粹依靠內存做信息流傳遞的消息隊列,當然會更快,但是此類消息隊列只有特殊場景下會使用,不在對比之列。
因此當我們說 Kakfa 快的時候,通常是基于以下場景:
吞吐量:當我們需要每秒處理幾十萬上百萬 Message 的時候,相對其他 MQ,Kafka 處理的更快。
高并發:當具有百萬以及千萬的 Consumer 的時候,同等配置的機器下,Kafka 所擁有的 Producer 和 Consumer 會更多。
磁盤鎖:相對其他 MQ,Kafka 在進行 IO 操作的時候,其同步鎖住 IO 的場景更少,發生等待的時間更短。
那么基于以上幾點,我們來仔細探討一下,為什么 Kafka 就快了。
消息隊列的推拉模型
首先,如果我們單純站在 Consumer 的角度來看“Kafka 快”,是一個偽命題,因為相比其他 MQ,Kafka 從 Producer 產生一條 Message 到 Consumer 消費這條 Message 來看,它的時間一定是大于等于其他 MQ 的。
背后的原因涉及到消息隊列設計的兩種模型:
推模型
拉模型
如下圖所示:
對于拉模型來說,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 命令的時候會看到如下的輸出:
圖片來自網絡
會有兩列名為 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 是與塊設備驅動程序同級的。
兩者都是用來加速數據 IO,將寫入的頁標記為 dirty,然后向外部存儲 flush,讀數據時首先讀取緩存,如果未命中,再去外部存儲讀取,并且將讀取來的數據也加入緩存。
操作系統總是積極地將所有空閑內存都用作 Page Cache 和 Buffer Cache,當 OS 的內存不夠用時也會用 LRU 等算法淘汰緩存頁。
有了以上概念后,我們再看來 Kafka 是怎么利用這個特性的。
首先,對于一次數據 IO 來說,通常會發生以下的流程:
操作系統將數據從磁盤拷貝到內核區的 Page Cache。
用戶程序將內核區的 Page Cache 拷貝到用戶區緩存。
用戶程序將用戶區的緩存拷貝到 Socket 緩存中。
操作系統將 Socket 緩存中的數據拷貝到網卡的 Buffer 上,發送數據。
可以發現一次 IO 請求操作進行了 2 次上下文切換和 4 次系統調用,而同一份數據在緩存中多次拷貝,實際上對于拷貝來說完全可以直接在內核態中進行。
也就是省去第二和第三步驟,變成這樣:
正因為可以如此的修改數據的流程,于是 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 就更快了。
用一張圖來簡述三者之間的關系如下:
當 Producer 和 Consumer 速率相差不大的情況下,Kafka 幾乎可以完全實現不落盤就完成信息的傳輸。
追加順序寫入
除了前面的重要特性之外,Kafka 還有一個設計,就是對數據的持久化存儲采用的順序的追加寫入,Kafka 在將消息落到各個 Topic 的 Partition 文件時,只是順序追加,充分的利用了磁盤順序訪問快的特性。
圖片來自網絡
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 信息的變化,維持集群狀態的健康。
可以看到最終落地到磁盤都是 Segment 文件,每一個 Partion(目錄)相當于一個巨型文件被平均分配到多個大小相等 Segment(段)數據文件中。
但每個段 segment file 消息數量不一定相等,這種特性方便老的 segment file 快速被刪除。
因為 Kafka 處理消息的力度是到 Partition,因此只需要保持好 Partition 對應的順序處理,Segment 可以單獨維護其狀態。
Segment 的文件由 index file 和 data file 組成,落地在磁盤的后綴為 .index 和 .log,文件按照序列編號生成,如下所示:
圖片來自網絡
其中 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 類中的方法來看:
這幾行代碼代表了 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 代碼可以看到對限流的延遲返回:
對于 Kafka 的限流來講,默認是按照 client id 或者 user 來進行限流的,從實際使用的角度來說,意義不是很大,基于 Topic 或者 Partition 分區級別的限流,相對使用場景更大。
競選機制
Kafka 背后的元信息重度依賴 Zookeeper,再次我們不解釋 Zookeeper 本身,而是關注 Kafka 到底是如何使用 ZK 的。
首先一張圖解釋 Kafka 對 ZK 的重度依賴:
圖片來源于網絡
利用 ZK 除了本身信息的存儲之外,最重要的就是 Kafka 利用 ZK 實現選舉機制,其中以 Controller 為主要的介紹。
首先 Controller 作為 Kafka 的心臟,主要負責著包括不限于以下重要事項:
也就是說 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 可以看到:
一旦 Controller 選舉出來之后,則其他 Broker 會監聽 ZK 的變化,來響應集群中 Controller 掛掉的情況:
從而觸發新的 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 的個數進行統計,將新的分區創建在個數最少的磁盤上,見下圖:
曾經我幫助某企業修改了分區創建規則,考慮了容量的情況,也就是按照磁盤容量進行分區的選擇。
緊接著帶來第二個問題:容量大的磁盤具備更多的分區,則會導致大量的 IO 都壓向該盤,最后問題又落回 IO,會影響該磁盤的其他 Topic 的性能。
所以在考慮 MQ 系統的時候,需要合理的手動設置 Kafka 的分區規則。
到此,關于“Kafka為什么那么快”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。