避坑指南:Kafka集羣快速擴容的方案總結

熟悉Apache Kafka的同學都知道,當Kafka集羣負載到達瓶頸或者出現突發流量需要緊急擴容時,新加入集羣的節點需要經過數據遷移才能均分集羣壓力。而數據遷移會因爲數據堆積量,節點負載等因素的影響,導致遷移時間較長,甚至出現遷移不動的情況。同時數據遷移也會增大當前節點的壓力,可能導致集羣進一步崩潰。本文將探討應對需要緊急擴容的技術方案。
什麼是數據遷移

Apache Kafka對於數據遷移的官方說法是分區重分配。即重新分配分區在集羣的分佈情況。官方提供了kafka-reassign-partitions.sh腳本來執行分區重分配操作。其底層實現主要有如下三步:

通過副本複製的機制將老節點上的分區搬遷到新的節點上。

然後再將Leader切換到新的節點。

最後刪除老節點上的分區。

重分配過程中最重要的一步是數據複製。故本文用數據遷移來形容這一行爲,下面來看一下數據遷移的過程。

假設topicA有3個分區,2個副本,分區和副本分佈在節點1和節點2。此時加了一個節點3,如果要讓3個節點均分壓力,就需要從節點1,2中遷移兩個分區到節點3,如下所示:

擴容場景

瞭解了數據遷移,我們來看下哪些場景需要進行擴容,然後有哪些方法可以實現快速擴容的效果。通常有如下兩種需要緊急擴容的場景:

集羣所有節點負載都高,需要快速擴容。

集羣內某幾臺節點負載很高,需要降低這些節點的壓力。

首先,來談談什麼是節點壓力。從我們的運營經驗來說,Kafka集羣的壓力通常體現在磁盤util、CPU、網卡三個指標上。正常來說,通過加節點都可以解決這三個指標帶來的問題。但是,從精細化運維的角度來說,可以有針對性地解決負載問題,達到不擴容就可以快速降低集羣壓力的目的。比如通過參數調優,踢掉某臺故障節點/某塊壞盤等等。

關於精細化運維我們在後續的文章再展開,本文主要是討論如何能通過加節點實現快速實現集羣擴容。

找到Top主題

根據二八法則和現網運營來看,在大多數集羣中,頭部效應一般都比較明顯,即大部分壓力都是由少量Topic帶來的。所以一般只要解決導致問題的頭部主題,就會事半功倍的解決問題。給大家看一下典型的現網集羣的Topic流量排行示意圖,集羣的流量集中在下面的Top主題中:

另外,kafka-reassign-partitions.sh分區遷移工具支持分區粒度的遷移,也可以支持整個Topic的遷移。所以在進行集羣擴容的時候,不需要遷移所有的Topic。可以遷移某幾個Topic或者某幾個Topic中的某些分區。這樣儘量減少需要搬遷的數據量。

那怎麼樣找到Top主題呢?

如果系統內部有通過Broker暴露的的Jmx接口採集Topic入流量指標,那麼對這些流量做一個排序,可以快速的找到目標主題。

也可以寫一個shell腳本,使用cmdline-jmxclient.jar工具實時的取到所有topic的流量,然後再做排序。這個方法比較繁瑣,仍需考慮Topic的分區是否分佈在不同的Broker上,是否需要做彙總等。

如果不想用2的辦法,有一個簡單的辦法可以大概看出流量的分佈。先進入broker上的數據目錄,然後查看每個分區的堆積的數據量大小。比如執行如下命令:ll -h /data/kafka_data/。根據堆積的情況來判斷哪些Topic的流量相對較大。

當然,如果集羣中所有主題的流量都非常平均,那就對所有的Topic一起處理。接下來我們來討論下當遇到緊急擴容的需求時,有哪些方案可以選擇。

以下方案的核心思想:遷移儘量少的數據,或者不遷移實現壓力的轉移。

方案一:降低數據保留時間,精準遷移

這個方案是大家最常用的方法,也是操作起來最簡單的。無論集羣整體壓力都高還是某些Broker的壓力大,都可以通過這個方案來執行擴容。一般執行如下四步:

找出需要操作的Topic

調整這些Topic的數據保留時間

對這些Topic進行遷移

等待遷移完成,完成Leader切換,均分集羣壓力。

此時當整個集羣壓力都很大的時候,這個方案下最好的處理方式是:調整流量最大的分區的保留時間,然後針對每條broker遷移幾個分區到新的節點上。比如,從每臺Broker挑出3個分區,然後遷移到目標節點上。如果只有幾臺Broker壓力大,也是一樣的處理方式。

這種方式有兩個缺點:

業務數據的保留時間不一定能調短,因爲調短了數據保留時間,這些數據就會被刪除,無法恢復。所以,當業務不允許刪除數據的時候,這個方案就不允許使用。

當業務允許調整數據保留時間,比如調整到1個小時。此時還有一個小時的數據需要遷移,如果此時當前節點的負載已經很高了。此時副本拉取數據即會增加當前節點的負載,導致集羣更加無法提供正常服務。當前節點壓力大的話,可能導致新副本同步數據比較慢,會導致集羣的壓力沒法快速降下來。

那有沒有方案可以解決這個問題呢?我們再來看下一個方案。

方案二:往指定節點上添加分區,均分壓力

如方案一所示,當整個集羣壓力都很大時,擴容節點後,因爲數據遷移的方案無法使用,新節點無法承擔壓力,集羣負載也降不下來。

此時可以通過擴容分區到新節點,將流量導到新的節點,讓新的節點也可以承擔流量。一般執行如下三步:

找出需要操作的Topic

評估這些Topic需要導多少流量到新節點。這一步比較好做,一般比如要把30%,50%的流量導入到新節點,大概評估一下即可。

擴容目標Topic的分區數,將這些新增的分區指定擴容到新的節點上。

因爲擴容Topic的目標分區是秒級完成的,所以不需要等待。在沒有分區變更的情況下客戶端的metadata機制默認是每隔30s自動更新一次,所以客戶端會很快感知到分區變化,默認的生產者寫入策略是輪詢的,此時新增的流量會自動流入到新節點,原先的節點的負載很快就降下來了。

方案二的核心點:新增擴容分區,比如指定添加到目標節點。如果只是擴容分區,而這些分區還是落到老的節點上,是解決不了問題的。因爲一般情況下Topic的分區的流量都是均勻的,假設Topic當前分區是100,想讓新的節點承擔該Topic 50%的壓力,可以將該Topic的分區數擴容到200。如下圖所示:

這種方式有如下幾個缺點:

如果業務邏輯限制了分區數不可變更,比如業務根據分區數做了哈希,根據哈希值往分區寫數據,又需要要求數據保持有序。那麼該方案就行不通了。但是一般情況下,有如上業務場景的Topic數據量都比較小,不會成爲瓶頸Topic。反之,成爲瓶頸的Topic,數據量都很大,一般都允許擴容分區。所以,這點通常情況下不會成爲限制,但是在操作前最好和業務確認下。

這種行爲針對單個Topic不能多次使用。因爲主題的分區一般不能刪除,因爲刪除分區後,分區中的數據也會丟失。如果在單個Topic多次使用的策略下,該Topic的分區數就會膨脹到很大。比如每次都需要導出50%的流量,則需要再添加100個分區,此時總分區數量就需要爲200。以此類推,則總分區數的變化趨勢爲:100,200,400,800,1600......。

如果客戶端寫入有傾斜,即客戶端指定了寫入策略,只針對某些分區寫入,現在的方案就會失效。遇到這種情況,只能協調客戶端處理。

雖然這個方案有以上幾個缺點,但是整體方案可以應對90%以上的緊急情況。所以,在不用遷移就降低集羣負載的情況下,這個方案是很好用的。

方案三:切換Leader,降低單機負載

方案二比較適合整個集羣負載較高的場景,或者因爲某些頭部Topic的流量均勻的集中在部分節點的情況下。現在我們來看一下如下場景:

假設集羣有20臺節點,節點的機型和規格是一樣的,從網卡進出流量上來看,流量是均衡的。但是因爲節點間性能的差異(原因可能是機型年限不一樣,不同節點上Topic的業務形態不一樣等等),導致某幾臺節點的負載很高。

此時,如果通過方案二處理,就會有種殺雞用牛刀的感覺,而且效果並不好。爲什麼呢?

假設頭部流量的Topic分區均勻分配在了20臺節點上。如果要通過擴容分區降低其中幾臺的負載,因爲生產端是均勻寫入的,則需要擴容很多倍的分區到新的節點上,才能把這幾臺的流量降下來。這會導致該Topic的分區數急速膨脹。

在這種情況下,我們可以嘗試切換分區Leader的方案來降低單機的壓力。這個操作比較簡單,因爲Leader切換的速度是秒級的,所以見效也很快。思路如下:

找出負載高的節點上的所有Leader

將節點上分區的Leader切換到負載較低的Follower節點上

這個操作的原理是:Kafka分區都在Leader進行讀寫,從單個分區看,即分區Leader所在節點的負載就會比Follower的節點負載高。所以,思路就是將負載高的節點上的Leader變爲Fllower,降低單機壓力,來看下圖:

這種方式的缺點如下:

這種方式適用於部分節點負載較高的情況,因爲負載會轉移到Follower上,如果Follower的負載本身就很高,則這種手段會加大Follower的壓力。

當Leader出現負載較高的時候,副本可能會掉出ISR。因爲Leader負載高,Follower拉取數據慢,導致副本跟不上Leader,掉出ISR。當出現這種情況,就不能切換Leader,強行切換的話,會出現數據截斷,導致數據丟失。

我們繼續來看一下,有沒有其他方法可以解決這種問題。

方案四:單副本運行,降低單機負載

當出現方案三中的無法處理的情況時,即無法使用切換Leader的手段降低壓力時。我們可以通過將高負載節點上的分區Follower剔除,將分區切換爲單副本運行,來臨時降低節點的壓力,讓集羣暫時的快速回歸正常。

思路如下:

找出負載高節點上的所有Follower

將節點上的Follower暫時移除

這個操作的原理:分區的Follower會不斷的從Leader同步數據,即從Leader拉取數據到本地進行報錯。這個行爲會佔用CPU,網卡,磁盤等資源,如果把這一部分流量去掉,則會把節點上這部分負載空出來。能快速地降低節點壓力。

方案五:其他思路

除了上面提到的擴容方案。還有其他一些可選的解決方法。這些方案的缺點都比較明顯,比如可能出現數據丟失,業務中斷,或者需要客戶端配合,但勝在見效快。

選擇這種方案需要從業務來考慮:爲了快速恢復業務,可以允許一定程度的數據丟失和服務中斷。所以方案還是存在一定的風險,需要和業務側討論後,再決定可否執行。一起簡單的來看下方案思路及其缺點:

刪除Topic重建

因爲創建Topic的時候,Kafka默認的算法會將分區均勻的放到所有節點上。所以可以通過刪除,重建Topic的形式快速擴容,均分壓力。操作步驟如下:

往集羣添加新節點

刪除流量大的Topic

重建Topic

當Topic創建完成後,流量就會均勻寫入到所有節點。整個過程最大的優點就是恢復快,幾分鐘就完成了。缺點就是:

如果刪除Topic前,消費還有堆積,則這些數據就不會被消費到,會丟失。

在重建Topic的過程,客戶端會報UnknownTopicOrPartitionException錯誤。

垂直升配,替換爲更高規格的節點

如果集羣負載的壓力是在CPU或者網卡,並且是使用雲上的虛擬機搭建的Kafka集羣,可以利用CVM的熱遷移能力,垂直升配虛擬機的規格。

這個方法也是大家通常的做法。但是在做這個操作的時候,有一個注意點,需要關注這個節點上是否有未同步副本。也需要關注一下集羣參數unclean.leader.election.enable參數的值。

如果存在未同步副本,當unclean.leader.election.enable=true時,則表示允許選擇不再ISR中的副本爲Leader。此時如果垂直升配,則會出現未同步副本當選爲Leader,出現數據階段,出現數據丟失。

如果存在未同步副本,當unclean.leader.election.enable=false時,則表示不允許選擇不再ISR中的副本爲Leader。此時在CVM升配過程,Broker重啓的過程中,就會出現服務中斷。但是不會數據截斷導致的數據丟失。

客戶端控制寫入分區策略

這種方法嚴重依賴客戶端。需要客戶端有指定往哪些分區寫入數據的能力。因爲大部分業務的Producer客戶端,基本都是直接調用官方SDK的Produce方法進行數據發送,數據會均勻的寫入到所有分區。所以大部分客戶端沒有這個能力。而一旦業務的客戶端可以動態的指定分區寫入數據(官方SDK自帶指定分區寫入的功能)。降低負載就會變的很簡單,不需要進行數據遷移。

即讓客戶端指定數據寫入到負載較低的分區,就可以降低高負載節點的壓力。

從最新的數據遷移,丟棄老數據

這個思路是之前網上看到的一個方案,適用於某些允許數據丟失的場景。因爲Kakfa默認的遷移機制,新副本都是從分區最早的可用的位置拉取數據,然後進行同步。

在某些場景,如數據量很大,數據保留時間卻很短的場景中,可能出現當副本同步完數據後,這些數據其實已經過期了,同步完數據就需要立即刪除。則這個遷移的行爲即是多餘的。所以還不如直接從最新的數據進行同步,這樣在新添加的節點上的新副本立即就可以加入ISR。則立即可以Leader提供服務。

這個方案的缺點是:

如果新加入的副本立即進入ISR,同時又成爲Leader,則會出現數據丟失,即從加入ISR那一刻起,往前推這個Topic的數據保留時間這段時間的數據都會丟失。

需要修改Broker默認的數據複製機制,對研發能力要求較高。

關於架構的一些想法

看到這裏,會發現整體下來,Kafka的擴容還是不夠靈活,快捷和方便。操作起來比較麻煩。有沒有更好的方案呢?

首先來看下造成擴容問題的原因,是受Kafka本身架構的限制。Kafka是以分區爲讀寫單位,分區是和節點綁定的,這些數據會寫入到元數據存儲中。此時一旦計算層(CPU/網卡)或存儲層(util)出現瓶頸,是沒辦法讓其他節點承載壓力的。如果要解決這個問題,Kafka在架構上要做很大的改動。

從架構的角度出發,我個人理解,解決的思路就是:計算存儲分離 + 存儲分段。這一點Apache Pulsar就做的很好。我們來簡單看一下Pulsar的做法。來看下圖:

計算存儲分離:解決的是計算壓力的快速轉移。計算節點和存儲節點是分開的。計算節點只負責計算邏輯的處理,是無狀態的節點。當節點出現瓶頸,可以快速橫向擴容。

存儲分段:解決的主要是存儲層IO壓力的快速轉移。Pulsar使用Bookeeper作爲存儲層,Pulsar將邏輯上的分區,在實際存儲層面,分爲多個段(segment)進行管理和存儲。如果出現某個存儲的機器有瓶頸,直接禁用該機器上segment,在新的機器上拉起新的Segment即可。

總結一下,一旦Pulsar集羣遇到上面說的Kafka集羣類似的瓶頸,從擴容的角度來說,會更優雅和便捷。這是架構自身帶來的優勢。

總結

本文列舉了一些快速擴容的手段和方案,幫助大家儘量避免遷移、儘量降低需要遷移的數據量。大家遇到問題的時候,可以根據實際的業務場景選擇適合的方案進行處理。

Apache Kafka的擴容複雜度源於Kakfa存算一體的架構。即數據生產和消費都是以分區爲單位的,而分區從創建開始就會和某一個節點進行綁定。如果沒有進行分區遷移,則分區和節點的綁定關係不會發生改變。當遇到節點出現性能問題時,這個分區也會受到影響,從而產生本文討論的問題。

在雲原生架構,存算分離成爲了一種趨勢。在消息隊列領域,最近新興的開源消息隊列Apache Pulsar在架構上實現了存儲計算分離,通過Apache Bookeeper實現對分區數據的分段分節點存儲,從而避免了Kakfa遇到的擴容遷移問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章