Pulsar 架構(gòu)的的疑問(wèn)和改進(jìn)
本文會(huì)討論 Pulsar 整體架構(gòu)的一些疑問(wèn)和可能的改進(jìn),一家之言。當(dāng)前的 Pulsar 版本是2.9.1,使用的 BookKeeper 和 Zookeeper 版本分別是 4.14.2和3.6.3。
實(shí)際上,BookKeeper 和 Zookeeper 也是單獨(dú)的 Apache 項(xiàng)目,Pulsar 項(xiàng)目其實(shí)主要就是 Pulsar Broker。為了便于理解,本文用 Broker 代指 Pulsar Broker,用 Pulsar 代指包括 Broker、BookKeeper 和 Zookeeper 三個(gè)模塊在內(nèi)的整個(gè)系統(tǒng)。
關(guān)于無(wú)狀態(tài)(Stateless)的疑問(wèn)
其實(shí),Pulsar 的每一個(gè)模塊都不是無(wú)狀態(tài)的。
Zookeeper 和 BookKeeper 是無(wú)狀態(tài)的,很好理解。因?yàn)?,這兩個(gè)模塊都存有數(shù)據(jù),Zookeeper 的各個(gè)節(jié)點(diǎn)雖然是有最終一致性保證,但是 Leader、Follower 等節(jié)點(diǎn)角色的存在,導(dǎo)致了節(jié)點(diǎn)處理流程的不同,是有狀態(tài)的;BookKeeper 就更好解釋了,因?yàn)?BookKeeper 節(jié)點(diǎn)本地存儲(chǔ)不同 Ledger 的數(shù)據(jù),訪問(wèn)不同 Ledger 要路由到不同的節(jié)點(diǎn),因此也是有狀態(tài)的。
Broker 本身并不持久化任何數(shù)據(jù),其運(yùn)行所需要的數(shù)據(jù)都存儲(chǔ)在 BookKeeper 和 Zookeeper 上。那么我們可以說(shuō) Broker 是無(wú)狀態(tài)的嗎?
仍然不可以,無(wú)狀態(tài)的定義最早是來(lái)自微服務(wù)領(lǐng)域,參考 Microsoft Azure 的定義,
A stateless service is one where there is no state maintained within the service across calls. Any state that is present is entirely disposable and doesn't require synchronization, replication, persistence, or high availability.
指的是,除了服務(wù)調(diào)用帶來(lái)的狀態(tài),即參數(shù),本身沒(méi)有任何狀態(tài)。這個(gè)定義之后,Azure 還舉例 Calculator 服務(wù)是無(wú)狀態(tài)的,還做了一些補(bǔ)充說(shuō)明,
Not storing any internal state makes this example calculator simple. But most services aren't truly stateless. Instead, they externalize their state to some other store. (For example, any web app that relies on keeping session state in a backing store or cache is not stateless.)
進(jìn)一步說(shuō)明,不存儲(chǔ)“內(nèi)部狀態(tài)”的服務(wù)才可以算是無(wú)狀態(tài)服務(wù),對(duì)于一些存儲(chǔ)了“內(nèi)部狀態(tài)”的服務(wù),例如,Session 信息,內(nèi)存緩存,這些都不能算是無(wú)狀態(tài)服務(wù)。除非這些內(nèi)部狀態(tài)不存儲(chǔ)在服務(wù)內(nèi),而是由其他的“有狀態(tài)”服務(wù)來(lái)完成。
那么很顯然,按上面的定義,Broker 肯定不是無(wú)狀態(tài)的。
首先,Broker 是 Topic 的所有者,所有某個(gè)特定 Topic 的訪問(wèn),都需要經(jīng)過(guò)特定的 Broker 來(lái)完成。這就是有狀態(tài)的,內(nèi)部存儲(chǔ)了 Topic Owner 信息。
還有,Broker 是有緩存的,內(nèi)存緩存了 Entry 信息,按上面的定義,這也是有狀態(tài)的。
再有,當(dāng) Broker 故障的時(shí)候,需要有特別的 Fencing 機(jī)制來(lái)啟動(dòng)新的 Broker 以提供服務(wù)。而無(wú)狀態(tài)服務(wù)是不需要特別的高可用策略的,只需要提供服務(wù)節(jié)點(diǎn)即可。
綜上,Broker 并不是“無(wú)狀態(tài)”的。
當(dāng)然,中間件的優(yōu)秀與否,并不是由是否“無(wú)狀態(tài)”來(lái)判定的,厘清這個(gè)概念會(huì)有助于更好的理解 Pulsar。
關(guān)于多 Ledger 存儲(chǔ)
Pulsar 中有 Topic、Ledger 的概念,BookKeeper 中有 Ledger、Fragment、Entry 的概念。
對(duì)比 Kafka 的設(shè)計(jì),Kafka 中只有 Topic、Ledger 的概念,而且 Topic 和 Ledger 是一對(duì)一的,而且 Ledger 和物理存儲(chǔ)也是一對(duì)一的。也就是說(shuō),Kafka 中,同一個(gè) Topic 的存儲(chǔ)是存儲(chǔ)在一起的,放在一個(gè) Ledger 中。這帶來(lái)的問(wèn)題就是,隨著topic中的數(shù)據(jù)越來(lái)越多,Kafka 的存儲(chǔ)會(huì)變的很大。如果單個(gè)節(jié)點(diǎn)發(fā)生異常,恢復(fù)節(jié)點(diǎn)的時(shí)候,復(fù)制的數(shù)據(jù)也會(huì)非常多,而且必須要等待數(shù)據(jù)都恢復(fù)完了才可以提供服務(wù),影響恢復(fù)時(shí)間。如果需要增加 Topic 的副本數(shù),也需要復(fù)制整個(gè) Topic 的數(shù)據(jù),也非常耗時(shí)。通常情況下,歷史的消息往往是不重要的,我們更關(guān)注的是未來(lái)的消息,但在 Kafka 的機(jī)制下,我們別無(wú)選擇,只能復(fù)制全部數(shù)據(jù)。
Pulsar 的多 Ledger 存儲(chǔ)機(jī)制解決了這個(gè)問(wèn)題,是消息中間件的一次成功嘗試。
首先,Pulsar 中的 Topic 和 Ledger 是一對(duì)多的,一個(gè)Topic有多個(gè) Ledger。不同的 Ledger 可以存儲(chǔ)在不同的節(jié)點(diǎn)上,每個(gè)節(jié)點(diǎn)上也可以存儲(chǔ)多個(gè) Ledger 的數(shù)據(jù)。不同的 Ledger 也可以有不同的副本數(shù)配置。當(dāng)發(fā)生節(jié)點(diǎn)故障,需要數(shù)據(jù)恢復(fù)的時(shí)候,恢復(fù)的流程會(huì)相對(duì)復(fù)雜一點(diǎn),需要復(fù)制多個(gè) Ledger 的數(shù)據(jù)。雖然需要復(fù)制的數(shù)據(jù)量仍然很大,但是一個(gè)一個(gè) Ledger 的復(fù)制,先復(fù)制完的 Ledger 就可以正常提供服務(wù)了。當(dāng)然,多個(gè) Ledger 的設(shè)計(jì)也會(huì)有副作用,當(dāng)一個(gè) Bookie 節(jié)點(diǎn)故障的時(shí)候,可能會(huì)影響多個(gè) Topic 提供服務(wù),這需要我們?cè)谠O(shè)計(jì) Ledger 副本數(shù)的時(shí)候,對(duì)每個(gè) Ledger 都有災(zāi)備的考慮。
還有,對(duì)于增加副本的場(chǎng)景,如果不溯及過(guò)往的話,在 Pulsar 中是非常簡(jiǎn)單的,只要將當(dāng)前 Topic 新增一個(gè) Ledger,并在新增的 Ledger 中設(shè)置期望的副本數(shù)即可。這個(gè)應(yīng)該是 Pulsar 的殺手級(jí)應(yīng)用場(chǎng)景。
再有,Bookie 的 Ledger 是混雜存儲(chǔ)的,同一個(gè)存儲(chǔ)內(nèi)的數(shù)據(jù)可能來(lái)自多個(gè) Ledger,這個(gè)存儲(chǔ)的設(shè)計(jì)有點(diǎn)類似于 RocketMQ。Bookie 為了利用磁盤順序讀緩存 Entry,在每次將 Write Cache 寫入磁盤的時(shí)候,將 Entry 按 Ledger id 和 Entry id 進(jìn)行排序,結(jié)果是同一個(gè) Ledger 的數(shù)據(jù)基本上得到了聚合。這個(gè)機(jī)制非常依賴于 Write Cache 的大小和當(dāng)前 Bookie 打開(kāi)的 Ledger 數(shù)量。如果 Write Cache 太小了,或者打開(kāi)的 Ledger 數(shù)量太多了,起到的聚合作用也很弱。推薦 Write Cache 盡量大一些。可以做一個(gè)估算,假設(shè)預(yù)讀取(readahead)1000個(gè) Entry,平均每個(gè) Entry 大小5KB,一次預(yù)讀取,大約需要讀5MB。假設(shè) Bookie 打開(kāi)了5個(gè) Ledger,同時(shí),消息的大小,產(chǎn)生消息的速度,和預(yù)讀取的參數(shù)也都基本一致,那么,理想情況下,Write Cache 需要至少有5MB*5=25MB,預(yù)讀取才會(huì)比較有效,假設(shè)預(yù)讀取的起始 Cursor 位置是平均分布的,那么每次預(yù)讀的數(shù)量大概在500條。如果增大 Write Cache 的大小到250MB(10倍),同樣進(jìn)行上面的計(jì)算,得到每次預(yù)錄取的消息數(shù)量是950條。這就很接近于我們預(yù)設(shè)的預(yù)讀取參數(shù)了,而且預(yù)讀取消息數(shù)量的方差也比原先要小很多,會(huì)降低很多系統(tǒng)性能的抖動(dòng)。實(shí)際上,可以對(duì) Entry 進(jìn)行重新整理,使 Entry 進(jìn)一步聚合(但不一定真的需要)。這個(gè)可以在 Pulsar 做GC,或者分層存儲(chǔ),把數(shù)據(jù)刷到長(zhǎng)期存儲(chǔ)的時(shí)候?qū)崿F(xiàn)。
此外,BookKeeper 的存儲(chǔ)把 Journal 和 Entry Log 分開(kāi)了,可以存儲(chǔ)在不同的磁盤上,這個(gè)相對(duì)于 Kafka 的確是一個(gè)進(jìn)步。因?yàn)檫@兩者的存儲(chǔ)特性是不一樣的。Journal 要求的是事務(wù)性,速度快,不要求大容量;Entry Log 要求的是讀取高效,寫入可以慢,容量要大。分開(kāi)存儲(chǔ)可以利用好不同硬件設(shè)備的硬件特性。
關(guān)于共識(shí)機(jī)制和存儲(chǔ)的分離
Pulsar 的另一個(gè)顯著的特性是共識(shí)機(jī)制(Consensus)和存儲(chǔ)的分離。例如,BookKeeper 在整個(gè)系統(tǒng)中只是一個(gè)單純的存儲(chǔ)功能,每個(gè) Bookie 節(jié)點(diǎn)的功能是相同的,不會(huì)有角色的差異,雖然存儲(chǔ)的數(shù)據(jù)有不同。Leader 的身份由 Broker 來(lái)?yè)?dān)當(dāng),而 Broker 又不存儲(chǔ)數(shù)據(jù),于是就達(dá)到了共識(shí)機(jī)制和存儲(chǔ)相分離的架構(gòu)。這種架構(gòu)有個(gè)好處,可以比較容易的變更集群的節(jié)點(diǎn)成員,例如增加存儲(chǔ)節(jié)點(diǎn),也就是動(dòng)態(tài)成員管理功能。當(dāng)然,由于分離會(huì)帶來(lái)一些代碼實(shí)現(xiàn)上的復(fù)雜性,這個(gè)Pulsar 和 BookKeeper 已經(jīng)克服。
那么,共識(shí)機(jī)制和存儲(chǔ)的分離,是動(dòng)態(tài)成員管理的先決條件嗎?動(dòng)態(tài)成員管理是一個(gè)很好的特性,但我們可以不要共識(shí)機(jī)制和存儲(chǔ)分離,只要?jiǎng)討B(tài)成員管理嗎?
類比 Kafka 的設(shè)計(jì),Kafka Broker 節(jié)點(diǎn)有 Leader 和 Follower 的區(qū)別,數(shù)據(jù)同步的時(shí)候,由 Leader 流向 Follower。但 Kafka 是靜態(tài)成員管理,Kafka Leader 故障的時(shí)候,需要選舉出新的 Leader;如果有新的節(jié)點(diǎn)加入,也需要復(fù)制全部的數(shù)據(jù)。如此這般的特性和 Raft 非常類似,Raft 的成員管理就非常麻煩,設(shè)計(jì)了一整套機(jī)制(joint consensus)來(lái)應(yīng)對(duì)成員變更的場(chǎng)景。
誠(chéng)然,每一個(gè)分布式算法中,成員變更的場(chǎng)景都是非常復(fù)雜的,甚至有些分布式算法還不能很好支持成員變更的場(chǎng)景,Raft 的 Joint Consensus 方法來(lái)處理成員變更,已經(jīng)是相對(duì)簡(jiǎn)單,但其復(fù)雜程度也是無(wú)法一眼就看明白的。Pulsar 的動(dòng)態(tài)成員管理非常的簡(jiǎn)易,是如何跳出這么復(fù)雜的算法陷阱的?
實(shí)際上,Pulsar 并沒(méi)有什么獨(dú)到的辦法,只是將增加數(shù)據(jù)節(jié)點(diǎn)(Bookie),解釋成了成員管理而已。Pulsar 的動(dòng)態(tài)成員管理,并不是分布式算法中的分布式集群成員管理。Pulsar 的共識(shí)機(jī)制的來(lái)源是 Zookeeper(使用 ZAB 分布式協(xié)議,與 Raft 相似),其增加的 Bookie 節(jié)點(diǎn),并不是 Zookeeper 的成員,而只是數(shù)據(jù)存儲(chǔ)的一個(gè)地址而已。如果增加了一個(gè) Bookie 節(jié)點(diǎn),只需要在 Zookeeper 中增加該節(jié)點(diǎn)的地址,于是,復(fù)雜的分布式集群成員管理問(wèn)題,被取代為分布式集群中增加一條數(shù)據(jù)的問(wèn)題,問(wèn)題的復(fù)雜度被大大降低了。
同樣的道理,Pulsar 的 Broker 節(jié)點(diǎn)也可以增加,利用 Zookeeper 增加多個(gè)備選的 Broker。當(dāng) Leader Broker 故障的時(shí)候,選取新的 Leader 也不是分布式算法中選舉 Leader 的過(guò)程,而是多個(gè) Broker 去“搶占”某個(gè) Topic 的所有權(quán)。整個(gè)過(guò)程類似于競(jìng)爭(zhēng)分布式鎖,哪個(gè) Broker 獲得了這個(gè)鎖,就相當(dāng)于持有了 Topic 的所有權(quán),成為了 Leader。選舉問(wèn)題也被大大簡(jiǎn)化了。
想明白了 Pulsar 動(dòng)態(tài)成員管理功能的實(shí)現(xiàn),就可以類比 Raft 和 Kafka 了。Raft 是一個(gè)分布式算法,Raft 集群如果需要成員變更就需要按算法的機(jī)制進(jìn)行,即使復(fù)雜也沒(méi)辦法。Kafka 不是一個(gè)分布式算法,其共識(shí)機(jī)制的來(lái)源是 Zookeeper,Kafka Broker 集群如果要變更 Broker 節(jié)點(diǎn),也是向 Zookeeper 寫入數(shù)據(jù);Kafka Broker 如果要重新選取 Leader,也是要去 Zookeeper 中搶占一個(gè)鎖。所以 Kafka 共識(shí)機(jī)制的實(shí)現(xiàn)原理和 Pulsar 是一致的,那么,為什么 Kafka 沒(méi)有動(dòng)態(tài)成員管理呢?
如果按照 Pulsar 對(duì)成員管理的解釋,Kafka 也是有動(dòng)態(tài)成員管理功能的,但是這個(gè)功能實(shí)在有些糟糕。比如,加入一個(gè)節(jié)點(diǎn),需要復(fù)制 Topic 所有已知?dú)v史的數(shù)據(jù),性能上并不可觀,這樣的功能無(wú)法作為一個(gè)功能點(diǎn)來(lái)宣傳,
反觀 Pulsar,加入節(jié)點(diǎn)不需要溯及歷史,也沒(méi)有數(shù)據(jù)復(fù)制,加入之后只需要等待 Topic 下一次創(chuàng)建 Ledger 的時(shí)候,選到新的 Bookie 即可;即使成員的數(shù)量有變化,例如從原先 3 個(gè) Bookie 節(jié)點(diǎn),變?yōu)?5 個(gè) Bookie 節(jié)點(diǎn),也就是在創(chuàng)建 Ledger 的時(shí)候多選幾個(gè)節(jié)點(diǎn),代碼的參數(shù)發(fā)生了變化而已。
因此,Pulsar 動(dòng)態(tài)成員管理的功能,并不是來(lái)自于共識(shí)機(jī)制與存儲(chǔ)的分離,在 Pulsar 的不溯及歷史、單 Topic 多 Ledger 等特性的前提下,動(dòng)態(tài)成員管理是一個(gè)非?!白匀弧蹦芟氲降墓δ堋afka 是吃了需要復(fù)制歷史數(shù)據(jù)的虧。
Pulsar 的這種分離架構(gòu)也不是盡善盡美,這個(gè)設(shè)計(jì)導(dǎo)致了 Pulsar 過(guò)度依賴 Zookeeper,在 Zookeeper 中存儲(chǔ)了太多元數(shù)據(jù)信息,如果 Zookeeper 故障不可用,Pulsar 集群將幾乎完全宕機(jī)。而 Kafka 在 Zookeeper 中的元數(shù)據(jù)較少,在 Zookeeper 宕機(jī)的情況下,依然可以保持基本的服務(wù)。當(dāng)然,綜合來(lái)看,Pulsar 的這個(gè)設(shè)計(jì)還是利大于弊的。
關(guān)于腦裂問(wèn)題
Pulsar 為了解決 Leader Broker 節(jié)點(diǎn)故障切換的問(wèn)題,使用了一個(gè)叫 Fencing 的機(jī)制,解決了 Leader 節(jié)點(diǎn)故障時(shí)候的腦裂問(wèn)題。
方案本身不詳細(xì)描述了,F(xiàn)encing 方案和 Raft Leader 的故障恢復(fù)機(jī)制實(shí)際上是沒(méi)有什么差別的,應(yīng)該是有所借鑒。至于解決了腦裂,這個(gè)也不是真正解決,也是由于消息系統(tǒng)的特性導(dǎo)致的直接結(jié)果。為什么這么說(shuō)呢?
Raft 中也有類似的概念,叫 committed index(Pulsar與之對(duì)應(yīng)的是LAC),只有在收到多數(shù)節(jié)點(diǎn)寫入 Entry 返回成功之后,才可以更新 committed index,再更新 Entry 到狀態(tài)機(jī)中,并返回給客戶端。對(duì)尚未更新 committed index 的 Entry,Raft 也是不可讀的。可以發(fā)現(xiàn),Pulsar 的 Fencing 和 Raft 的機(jī)制幾乎一致,但是 Raft 有腦裂問(wèn)題。
先回顧一下 Raft 的腦裂問(wèn)題。當(dāng) Raft Leader 節(jié)點(diǎn)故障發(fā)生時(shí),例如 Raft Leader 網(wǎng)絡(luò)斷開(kāi),其他節(jié)點(diǎn)已經(jīng)發(fā)現(xiàn)當(dāng)前 Leader 超時(shí),并發(fā)起下一輪選舉投票,快速選舉出新的 Leader,但是老 Raft Leader 的 Follower 無(wú)響應(yīng)超時(shí)時(shí)間尚未到達(dá),導(dǎo)致老 Leader 仍然認(rèn)為自己是真正的 Leader,并響應(yīng)客戶端的請(qǐng)求,因此導(dǎo)致客戶端讀取到了舊的數(shù)據(jù)。而與此同時(shí),部分客戶端連接到了新 Raft Leader,寫入并讀取到新的數(shù)據(jù),造成不一致,這是 Raft 發(fā)生腦裂的原因。Raft 發(fā)生腦裂不會(huì)持續(xù)很長(zhǎng)時(shí)間,當(dāng)老 Leader 發(fā)現(xiàn)長(zhǎng)時(shí)間沒(méi)有收到 Follower 響應(yīng)而超時(shí)(主要取決于超時(shí)參數(shù)的配置),或者發(fā)現(xiàn)有新 Leader 產(chǎn)生時(shí),老 Leader 就會(huì)將自己重置為 Follower。
那使用了同樣機(jī)制的 Pulsar 為什么就沒(méi)有腦裂問(wèn)題呢?那是因?yàn)?,Pulsar 是個(gè)消息系統(tǒng),寫入的消息類似 WAL,是不可變的(immutable),追加的。當(dāng)發(fā)生和 Raft 一樣的故障的時(shí)候,老的 Pulsar Broker 也會(huì)讀到老的數(shù)據(jù),但老的數(shù)據(jù)仍然合法,因?yàn)閷?duì)同樣的 Cursor,在新的 Broker 上也是讀到相同的數(shù)據(jù),只要讀取 Entry 不超過(guò) LAC 就沒(méi)問(wèn)題,最多只是無(wú)法獲取到最新的消息而已,獲取的消息并不會(huì)錯(cuò)。而 Raft 的存儲(chǔ)是偏向于通用存儲(chǔ)場(chǎng)景,因此就會(huì)有新舊數(shù)據(jù)版本不一致的問(wèn)題。
腦裂一般都是指讀取數(shù)據(jù)發(fā)生的不一致,如果是寫入數(shù)據(jù)的腦裂,那可能是分布式算法有問(wèn)題,成熟的算法一般不會(huì)有這個(gè)問(wèn)題。
關(guān)于和Kafka的性能對(duì)比
老實(shí)說(shuō),目前的性能測(cè)試報(bào)告都顯示,Pulsar 的性能高于 Kafka 很多,其實(shí)有些費(fèi)解。尤其是簡(jiǎn)化的測(cè)試場(chǎng)景,比如單個(gè) Topic,副本3個(gè),寫入集2個(gè),這樣的場(chǎng)景說(shuō)實(shí)話,Kafka 的實(shí)現(xiàn)和 Pulsar 幾乎就是一樣的,為什么會(huì)有如此大的性能差異呢?
我們假定性能差異確實(shí)是存在的(很遺憾我手邊沒(méi)有環(huán)境做這樣的性能測(cè)試),Pulsar 的性能高出 Kafka 一截,這樣的性能差異是如何造成的呢?
我們先排除代碼實(shí)現(xiàn)層面的差異,兩個(gè)中間件的開(kāi)發(fā)者應(yīng)該都是相當(dāng)資深的工程師,本身代碼寫法引起的性能差異應(yīng)該并不顯著,也太過(guò)于細(xì)節(jié)。
首先,比較一下簡(jiǎn)單場(chǎng)景的測(cè)試場(chǎng)景是否公平。單個(gè) Topic,副本3個(gè),寫入集2個(gè),一邊寫入,另一邊消費(fèi),這個(gè)場(chǎng)景應(yīng)該沒(méi)有任何問(wèn)題,也是我們絕大多數(shù)情況下遇到的場(chǎng)景。由于幾乎在寫入完成后的同時(shí),就會(huì)有消息消費(fèi),因此,這些消息都會(huì)有內(nèi)存緩存。簡(jiǎn)單來(lái)說(shuō),這個(gè)場(chǎng)景就是一邊磁盤寫,一邊內(nèi)存讀的場(chǎng)景。
測(cè)試場(chǎng)景沒(méi)有問(wèn)題,就需要細(xì)節(jié)分析兩者處理流程的差異了。先分析一下寫入的性能,Pulsar 的寫入是先到 Broker,再到 Bookie,然后返回;Kafka 的寫入是先到 Leader Broker,再同步到 insync 的 Broker,然后返回。從網(wǎng)絡(luò)調(diào)用的路徑上來(lái)說(shuō),兩者都有一層網(wǎng)絡(luò)調(diào)用。在寫入磁盤的時(shí)候,兩者都是以追加寫的方式把消息寫入文件,如果寫入的存儲(chǔ)沒(méi)有硬件上的差異,那么這一塊應(yīng)該也沒(méi)有什么差異。總之,并不覺(jué)得寫入的部分造成了性能的差異。
再看一下寫入的性能差異。其實(shí),兩者都是內(nèi)存讀,性能差異應(yīng)該很小,實(shí)現(xiàn)層面上來(lái)看,Pulsar 的內(nèi)存緩存是自己實(shí)現(xiàn)的,Broker 和 BookKeeper 有2層緩存,這個(gè)場(chǎng)景下,基本都會(huì)命中 Broker 緩存,也就是第一層緩存;Kafka 的內(nèi)存緩存是借用的 Linux Page Cache 機(jī)制,相當(dāng)于是操作系統(tǒng)提供的,只有1層緩存,在這個(gè)場(chǎng)景下也基本都能命中。如果說(shuō),讀取的性能有差異,那么就是內(nèi)存緩存和 Page Cache 的差異,畢竟 Page Cache 是內(nèi)核態(tài)的東西,系統(tǒng)調(diào)用的確是有損耗的。但 Kafka 用了零拷貝的 sendfile() 系統(tǒng)調(diào)用,應(yīng)該也賺回來(lái)了一次系統(tǒng)調(diào)用的消耗,Pulsar 在發(fā)送的時(shí)候,需要調(diào)用 Socket 的 send() 將緩存的數(shù)據(jù)發(fā)送除去,也有一次內(nèi)存從用戶態(tài)到內(nèi)核態(tài)的消耗。如果說(shuō)這里真的有差異的話,也就是說(shuō),“內(nèi)存緩存+send()”勝過(guò)了“Page Cache+sendfile()”,硬要找個(gè)理由的話,Pulsar 的實(shí)現(xiàn)只需要1次系統(tǒng)調(diào)用,而 Kafka 需要2次(雖然有一次是零拷貝)。
以上差異,不足以構(gòu)成顯著的性能差異,最多5%以內(nèi)的性能差別。
但有一個(gè)場(chǎng)景,也是比較常見(jiàn)的場(chǎng)景,Pulsar 是遠(yuǎn)勝于 Kafka 的,就是追趕讀(catch-up read)場(chǎng)景。Pulsar 的 BookKeeper 緩存管理是分為 Write Cache 和 Read Cache 的,寫入數(shù)據(jù)與讀取數(shù)據(jù)的緩存分開(kāi)管理,Write Cache 用于追尾讀(tail read)場(chǎng)景,Read Cache 用于追趕讀場(chǎng)景,互不干擾。而 Kafka 的緩存是操作系統(tǒng) Page Cache 提供的,在追趕讀場(chǎng)景,會(huì)擦除掉寫入數(shù)據(jù)的緩存,兩者在競(jìng)爭(zhēng) Page Cache 的使用,導(dǎo)致了很多加載到 Cache 的數(shù)據(jù)被擦除后,反復(fù)加載,增大 Cache Mis。尤其是追尾讀和追趕讀同時(shí)存在的時(shí)候,例如比例是一半一半的時(shí)候,Kafka 的緩存競(jìng)爭(zhēng)會(huì)更加激烈,性能會(huì)下降比較厲害。
關(guān)于緩存的機(jī)制
BookKeeper 的緩存分為 Write Cache 和 Read Cache,但 Broker 的緩存設(shè)計(jì)卻不一樣,沒(méi)有做這樣的區(qū)分。這樣的設(shè)計(jì)是非常難以理解的,同樣的技術(shù)場(chǎng)景為什么要采用兩種不同的緩存設(shè)計(jì)策略?
BookKeeper 的讀寫緩存分離,是為了優(yōu)化追趕讀和追尾讀共存的場(chǎng)景,減少對(duì)緩存的競(jìng)爭(zhēng),以達(dá)到更高的 Cache Hit。對(duì) Broker 來(lái)說(shuō),這個(gè)場(chǎng)景依然存在,是完全一樣的,如果 Broker 沒(méi)有采用讀寫緩存分離,也會(huì)有緩存競(jìng)爭(zhēng),會(huì)有很多的請(qǐng)求落到 BookKeeper 上,白白多了一次網(wǎng)絡(luò)IO的消耗。
另外,Bookie 上有多個(gè) Ledger 的數(shù)據(jù),緩存要按 Ledger 分多份,Broker 是單個(gè) Topic 的 Owner,如果按 Topic 維度來(lái)進(jìn)行緩存,應(yīng)該也不會(huì)涉及到內(nèi)存的過(guò)多消耗。
因此,我覺(jué)得 Pulsar Broker 也應(yīng)該采用 Write Cache 和 Read Cache 分離的機(jī)制,以進(jìn)一步提升性能。
改進(jìn)點(diǎn)1:集成 Broker 和 BookKeeper
因?yàn)楣沧R(shí)機(jī)制和存儲(chǔ)的分離并不會(huì)帶來(lái)明顯的收益,Pulsar 的動(dòng)態(tài)成員管理是來(lái)自于單 Topic 多 Ledger 特性的自然推論,所以,把 Broker 和 BookKeeper 分離成兩個(gè)服務(wù),恐怕不是一個(gè)好的選擇。如果將共識(shí)機(jī)制和存儲(chǔ)重新合并在同一個(gè)節(jié)點(diǎn),稱為新 Bookie 節(jié)點(diǎn),性能將勢(shì)必提升。
首先,之前 Pulsar 中需要直接訪問(wèn) BookKeeper 的場(chǎng)景將少去一次網(wǎng)絡(luò)調(diào)用,也就是 Broker 在 Cache Mis 的時(shí)候,也不會(huì)有網(wǎng)絡(luò)調(diào)用的消耗,這樣明顯會(huì)提升性能。這樣的設(shè)計(jì)帶來(lái)了另一個(gè)直覺(jué)的結(jié)果,Leader 節(jié)點(diǎn)上有當(dāng)前 Topic 的最新 Entry,因此,Leader 的 Write Cache 數(shù)據(jù)就是最新的,可以很快響應(yīng)追尾讀。
其次,功能方面,并沒(méi)有減少原先的功能,尤其在動(dòng)態(tài)成員管理這里。如果是非 Leader 節(jié)點(diǎn)故障,那么恢復(fù)方案和原先一樣,新增 Bookie 加入到成員中,進(jìn)行成員更新,并數(shù)據(jù)復(fù)制;如果是 Leader 節(jié)點(diǎn)發(fā)生故障,那么恢復(fù)方案會(huì)相對(duì)復(fù)雜一點(diǎn),可以分幾步來(lái)描述。如下,
- 在剩下的
E-1(E 表示集群數(shù)量)個(gè) Bookie 節(jié)點(diǎn)發(fā)起選舉,產(chǎn)生新的 Leader; - 新的 Leader 先同步存活的各個(gè)節(jié)點(diǎn)的數(shù)據(jù)到 Commit Index,此時(shí)的場(chǎng)景和非 Leader 節(jié)點(diǎn)故障的情況就是一樣的了;
- 新增 Bookie 加入到成員中,進(jìn)行成員更新,并數(shù)據(jù)復(fù)制
在選舉 Leader 和同步數(shù)據(jù)到 Commit Index 的時(shí)候,細(xì)節(jié)上仍然可以采用類似 Fencing 的機(jī)制。這樣也不會(huì)減少原有的 Pulsar 靈活的特性。
還有,Pulsar 原先的設(shè)計(jì)造成了,對(duì)于單個(gè) Topic 來(lái)說(shuō) Broker 就是實(shí)際上的單點(diǎn),擴(kuò)展性受限。所有的寫入讀取,都需要通過(guò)單點(diǎn)的 Broker 來(lái)完成,造成系統(tǒng)整體壓力的不平均。當(dāng)新 Bookie 同時(shí)有 Broker 的功能的時(shí)候,還可以分散單點(diǎn) Leader 的壓力,進(jìn)行另一項(xiàng)優(yōu)化,“追趕讀優(yōu)化”。
改進(jìn)點(diǎn)2:追趕讀優(yōu)化
當(dāng)共識(shí)機(jī)制和存儲(chǔ)重新結(jié)合,成為新 Bookie 節(jié)點(diǎn)的時(shí)候,可以引入新的追趕讀優(yōu)化,設(shè)計(jì)原則如下,
- 所有的追趕讀由非 Leader 節(jié)點(diǎn)完成,Leader 節(jié)點(diǎn)要提示客戶端可供訪問(wèn)的非 Leader 節(jié)點(diǎn)列表;
- 所有節(jié)點(diǎn)增加限流機(jī)制,Leader 節(jié)點(diǎn)達(dá)到限流的時(shí)候(指追尾讀達(dá)到上限),提示客戶端訪問(wèn)非 Leader 節(jié)點(diǎn);
- 非 Leader 節(jié)點(diǎn)也達(dá)到限流的時(shí)候,拒絕訪問(wèn);
在這樣的設(shè)計(jì)下,再加上 Write Cache 和 Read Cache 分離的機(jī)制,Leader 節(jié)點(diǎn)的壓力被平分到了非 Leader 節(jié)點(diǎn)上,從根本上解決原有的 Broker 單點(diǎn)的問(wèn)題,充分發(fā)揮了集群中每個(gè)節(jié)點(diǎn)的處理能力。