作爲一個爬蟲工程師,Kafka 對你而言就是一個消息隊列,你只需要掌握如何向裏面寫入數據,以及如何讀取數據就可以了。
請謹記:使用 Kafka 很容易,但對 Kafka 集羣進行搭建、維護與調優很麻煩。Kafka 集羣需要有專人來維護,不要以爲你能輕易勝任這個工作。
”
本文,以及接下來的幾篇針對 Kafka 的文章,我們面向的對象都是爬蟲工程師或者僅僅需要使用 Kafka 的讀者。關於 Kafka 更深入的底層細節與核心原理,不在我們的討論範圍中。爲了解釋方便,文章中對 Kafka 的一些術語會使用一些不太準確但能表明意思的類比。如果你需要在面試中解釋這些術語,還請閱讀Kafka 的官方文檔。
今天我們要討論的一個話題是,Kafka 是如何做到,對單個程序的多個進程而言,能持續消費,斷點續傳和並行消費;對多個程序而言又互不影響,各自獨立。
一個 Kafka 可以有多個不同的隊列,我們把這個隊列叫做Topic
,假設其中一個隊列如下圖所示:
信息從右邊進去,從左邊出來。如果這是Redis 的列表,那麼它彈出一條信息以後,隊列會變成下面這樣:
最左邊的信息1
不見了。所以即使程序在消費了信息1
後立刻關閉,再重新打開,程序也會接着從信息2
開始消費,不會把信息1
重複消費兩次。
但我如果有兩個程序呢?程序1讀取每一條數據,再轉存到數據庫。程序2讀取每一條數據,再檢查是否有關鍵詞。這種情況下,信息1
應該能被程序1消費,也能被程序2消費。但上面這種方案顯然是不行的。當程序1消費了信息1
,程序2就再也拿不到它了。
所以,在 Kafka 裏面,信息會停留在隊列裏面,但對每一個程序來說,有一個單獨的記號,來記錄當前消費到了哪一條數據,如下圖所示。
當程序1要讀取 Kafka 裏面下一條數據時,Kafka 先把當前位置的標記向右移動一位,把新的這個值返回出來。標記移動與返回這兩個操作合在一起算是一個原子操作,不會出現重複讀取的問題。
程序1與程序2使用的是不同的標記,所以各自的標記指向哪個值,是互不影響的。
當增加一個程序3的時候,只需要再加一個標記即可。新的這個標記也不受前兩個標記的影響。
這就實現了在多個不同的程序讀取 Kafka 時,各自互不影響。
現在如果你覺得程序1消費太慢了,把程序1同時運行了3次,那麼由於標記和移位是原子操作,即使你看起來程序是同時去讀取 Kafka,但在內部 Kafka 也會對他們進行“排隊”,從而使得他們返回的結果不重複,不遺漏。
如果你在網上看 Kafka 的教程,你會發現他們提到了一個叫做 Offset
的東西,實際上就是本文所說的各個程序裏面指向當前數據的標記
。
你還會看到一個關鍵詞叫做Group
,實際上對應到本文的程序1
,程序2
和程序3
。
對同一個隊列,如果多個程序使用不同的Group
消費,那麼他們讀取的數據就互不干擾。
對同一個隊列,相同 Group 的多個進程在消費數據時,看起來就像是在對 Redis 進行 lpop 操作一樣。
最後,你在網上關於 Kafka 的文章裏面,一定會看到一個詞叫做Paritition
或者中文分片
。而且你會發現你無法理解這個東西。
沒關係,忘記它吧。你只需要知道,一個 Topic 有多少個 Partition,那麼你最多能啓動多少個進程讀取同一個 Group。——如果一個Topic有3個Partition,那麼你只能最多開3個進程同時讀相同的 Group。Topic如果有5個Partition,那麼你只能最多開5個進程讀同一個 Group。