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