Kafka如何實現(xiàn)每秒上百萬的超高并發(fā)寫入?
這篇文章來聊一下 Kafka 的一些架構設計原理,這也是互聯(lián)網公司面試時非常高頻的技術考點。
Kafka 是高吞吐低延遲的高并發(fā)、高性能的消息中間件,在大數(shù)據(jù)領域有極為廣泛的運用。配置良好的 Kafka 集群甚至可以做到每秒幾十萬、上百萬的超高并發(fā)寫入。
那么 Kafka 到底是如何做到這么高的吞吐量和性能的呢?這篇文章我們來詳細說一下。
頁緩存技術 + 磁盤順序寫
首先 Kafka 每次接收到數(shù)據(jù)都會往磁盤上去寫,如下圖所示:
那么在這里我們不禁有一個疑問了,如果把數(shù)據(jù)基于磁盤來存儲,頻繁的往磁盤文件里寫數(shù)據(jù),這個性能會不會很差?大家肯定都覺得磁盤寫性能是極差的。
沒錯,要是真的跟上面那個圖那么簡單的話,那確實這個性能是比較差的。
但是實際上 Kafka 在這里有極為優(yōu)秀和出色的設計,就是為了保證數(shù)據(jù)寫入性能,首先 Kafka 是基于操作系統(tǒng)的頁緩存來實現(xiàn)文件寫入的。
操作系統(tǒng)本身有一層緩存,叫做 Page Cache,是在內存里的緩存,我們也可以稱之為 OS Cache,意思就是操作系統(tǒng)自己管理的緩存。
你在寫入磁盤文件的時候,可以直接寫入這個 OS Cache 里,也就是僅僅寫入內存中,接下來由操作系統(tǒng)自己決定什么時候把 OS Cache 里的數(shù)據(jù)真的刷入磁盤文件中。
僅僅這一個步驟,就可以將磁盤文件寫性能提升很多了,因為其實這里相當于是在寫內存,不是在寫磁盤,大家看下圖:
接著另外一個就是 kafka 寫數(shù)據(jù)的時候,非常關鍵的一點,它是以磁盤順序寫的方式來寫的。
也就是說,僅僅將數(shù)據(jù)追加到文件的末尾,不是在文件的隨機位置來修改數(shù)據(jù)。
普通的機械磁盤如果你要是隨機寫的話,確實性能極差,也就是隨便找到文件的某個位置來寫數(shù)據(jù)。
但是如果你是追加文件末尾按照順序的方式來寫數(shù)據(jù)的話,那么這種磁盤順序寫的性能基本上可以跟寫內存的性能本身也是差不多的。
所以大家就知道了,上面那個圖里,Kafka 在寫數(shù)據(jù)的時候,一方面基于 OS 層面的 Page Cache 來寫數(shù)據(jù),所以性能很高,本質就是在寫內存罷了。
另外一個,它是采用磁盤順序寫的方式,所以即使數(shù)據(jù)刷入磁盤的時候,性能也是極高的,也跟寫內存是差不多的。
基于上面兩點,Kafka 就實現(xiàn)了寫入數(shù)據(jù)的超高性能。那么大家想想,假如說 Kafka 寫入一條數(shù)據(jù)要耗費 1 毫秒的時間,那么是不是每秒就是可以寫入 1000 條數(shù)據(jù)?
但是假如 Kafka 的性能極高,寫入一條數(shù)據(jù)僅僅耗費 0.01 毫秒呢?那么每秒是不是就可以寫入 10 萬條數(shù)據(jù)?
所以要保證每秒寫入幾萬甚至幾十萬條數(shù)據(jù)的核心點,就是盡***可能提升每條數(shù)據(jù)寫入的性能,這樣就可以在單位時間內寫入更多的數(shù)據(jù)量,提升吞吐量。
零拷貝技術
說完了寫入這塊,再來談談消費這塊。
大家應該都知道,從 Kafka 里我們經常要消費數(shù)據(jù),那么消費的時候實際上就是要從 Kafka 的磁盤文件里讀取某條數(shù)據(jù)然后發(fā)送給下游的消費者,如下圖所示:
那么這里如果頻繁的從磁盤讀數(shù)據(jù)然后發(fā)給消費者,性能瓶頸在哪里呢?
假設要是 Kafka 什么優(yōu)化都不做,就是很簡單的從磁盤讀數(shù)據(jù)發(fā)送給下游的消費者,那么大概過程如下所示:
- 先看看要讀的數(shù)據(jù)在不在 OS Cache 里,如果不在的話就從磁盤文件里讀取數(shù)據(jù)后放入 OS Cache。
- 接著從操作系統(tǒng)的 OS Cache 里拷貝數(shù)據(jù)到應用程序進程的緩存里,再從應用程序進程的緩存里拷貝數(shù)據(jù)到操作系統(tǒng)層面的 Socket 緩存里。
- ***從 Socket 緩存里提取數(shù)據(jù)后發(fā)送到網卡,***發(fā)送出去給下游消費。
整個過程,如下圖所示:
大家看上圖,很明顯可以看到有兩次沒必要的拷貝吧!一次是從操作系統(tǒng)的 Cache 里拷貝到應用進程的緩存里,接著又從應用程序緩存里拷貝回操作系統(tǒng)的 Socket 緩存里。
而且為了進行這兩次拷貝,中間還發(fā)生了好幾次上下文切換,一會兒是應用程序在執(zhí)行,一會兒上下文切換到操作系統(tǒng)來執(zhí)行。
所以這種方式來讀取數(shù)據(jù)是比較消耗性能的。Kafka 為了解決這個問題,在讀數(shù)據(jù)的時候是引入零拷貝技術。
也就是說,直接讓操作系統(tǒng)的 Cache 中的數(shù)據(jù)發(fā)送到網卡后傳輸給下游的消費者,中間跳過了兩次拷貝數(shù)據(jù)的步驟,Socket 緩存中僅僅會拷貝一個描述符過去,不會拷貝數(shù)據(jù)到 Socket 緩存。
大家看下圖,體會一下這個精妙的過程:
通過零拷貝技術,就不需要把 OS Cache 里的數(shù)據(jù)拷貝到應用緩存,再從應用緩存拷貝到 Socket 緩存了,兩次拷貝都省略了,所以叫做零拷貝。
對 Socket 緩存僅僅就是拷貝數(shù)據(jù)的描述符過去,然后數(shù)據(jù)就直接從 OS Cache 中發(fā)送到網卡上去了,這個過程大大的提升了數(shù)據(jù)消費時讀取文件數(shù)據(jù)的性能。
而且大家會注意到,在從磁盤讀數(shù)據(jù)的時候,會先看看 OS Cache 內存中是否有,如果有的話,其實讀數(shù)據(jù)都是直接讀內存的。
如果 Kafka 集群經過良好的調優(yōu),大家會發(fā)現(xiàn)大量的數(shù)據(jù)都是直接寫入 OS Cache 中,然后讀數(shù)據(jù)的時候也是從 OS Cache 中讀。
相當于是 Kafka 完全基于內存提供數(shù)據(jù)的寫和讀了,所以這個整體性能會極其的高。
說個題外話,下回有機會給大家說一下 Elasticsearch 的架構原理,其實 ES 底層也是大量基于 OS Cache 實現(xiàn)了海量數(shù)據(jù)的高性能檢索的,跟 Kafka 原理類似。
總結
通過這篇文章對 Kafka 底層的頁緩存技術的使用,磁盤順序寫的思路,以及零拷貝技術的運用,大家應該就明白 Kafka 每臺機器在底層對數(shù)據(jù)進行寫和讀的時候采取的是什么樣的思路,為什么它的性能可以那么高,做到每秒幾十萬的吞吐量。
這種設計思想對我們平時自己設計中間件的架構,或者是出去面試的時候,都有很大的幫助。
中華石杉:十余年 BAT 架構經驗,一線互聯(lián)網公司技術總監(jiān)。帶領上百人團隊開發(fā)過多個億級流量高并發(fā)系統(tǒng)。現(xiàn)將多年工作中積累下的研究手稿、經驗總結整理成文,傾囊相授。微信公眾號:石杉的架構筆記(ID:shishan100)。




































