原文作者:Andree Newman 原文來源:Gremlin.com原文標題:The first 4 chaos experiments to run on Apache Kafka
Apache Kafka是一個開放原始碼的分散式訊息傳遞平臺,高吞吐量和低延遲交付資料。在規模上,它每天可以處理數萬億條記錄,同時還提供容錯、複製和自動災難恢復功能。
儘管Kafka有許多不同的使用場景,但最常見的是作為應用程式之間的訊息broker。Kafka可以從多個上游源頭接收、處理和重新分配訊息到多個下游使用者,而無需重新配置應用程式。這就可以流式傳輸大量資料,同時保持應用程式的鬆散耦合,支援諸如分散式計算、日誌記錄和監控、網站活動跟蹤以及物聯網(IoT)裝置通訊之類的場景。
由於Kafka提供了應用程式之間的關鍵管道,因此可靠性至關重要。我們需要計劃來減輕幾種可能的故障模式,包括:
- Apache ZooKeeper失效,這是Kafka的關鍵依賴項;
不必等待這些故障在預生產或生產中發生,我們就能透過混沌工程主動對其進行測試,以便制定適當的策略減輕影響。本文,我們將演示混沌工程如何幫助提高Kafka部署的可靠性。為此,我們將使用企業SaaS混沌工程平臺Gremlin建立並執行四個混沌實驗。透過閱讀本文,瞭解Kafka群集可能發生故障的不同方式,如何設計混沌實驗來測試這些故障模式,以及如何使用觀測結果提高其可靠性。本文中,我們將在Confluent平臺上演示混亂的實驗,該平臺是Kafka最初的建立者提供的企業事件流平臺。Confluent平臺基於Kafka構建並新增企業功能(例如基於Web的GUI,全面的安全控制以及輕鬆部署多區域叢集的功能)。但是,本文中的實驗將適用於任何Kafka叢集。為了瞭解Kafka如何從混沌工程中受益,我們應該首先研究一下Kafka的體系架構設計。
Kafka使用釋出者/訂閱者(或pub/sub)訊息傳遞模型傳輸資料。上游應用程式(在Kafka中稱為釋出者或生產者)會生成傳送到Kafka伺服器(稱為broker)的訊息。然後,下游應用程式(在Kafka中稱為訂閱者或使用者)可以從broker獲取這些訊息。訊息被組織在主題的類別中,消費者可以訂閱一個或多個主題以使用其訊息。透過充當生產者和消費者之間的中間人,Kafka使我們能夠彼此獨立地管理上游和下游應用程式。Kafka將每個主題細分為多個分割槽。分割槽可以跨多個broker映象以提供複製。還允許多個使用者(更具體地說,是使用者組)同時處理一個主題。為了防止多個生產者寫入單個分割槽,每個分割槽都有一個充當領導者的broker,以及沒有或多個充當追隨者的broker。新訊息將被寫入領導者,而追隨者則將其複製。追隨者被完全複製後,稱為同步副本(ISR)。該過程由Apache ZooKeeper協調,ZooKeeper管理有關Kafka叢集的後設資料,例如哪些分割槽分配給了哪些broker。ZooKeeper是Kafka的必要依賴項(編者注:2.8版本就可以不需要ZooKeeper了),但在其自己的叢集上作為完全獨立的服務執行。改善Kafka叢集的可靠性必然涉及提高其關聯的ZooKeeper叢集的可靠性。Kafka和Confluent平臺還有其他元件,但是在提高可靠性時,這些是最重要的考慮因素。當本文介紹其他元件時,我們會對其進行更詳細的說明。混沌工程是一種主動測試系統故障的方法,目的是使它們更具韌性。我們透過向系統中注入少量受控故障,觀察影響並解決觀察到的問題。這使我們能夠在系統出現問題之前,為使用者找到解決問題的方法,同時還教會了我們更多關於系統在各種條件下的行為資訊。
由於有數不清的配置選項,靈活的生產者和消費者的部署方式以及許多其他因素,使得像Kafka這樣的分散式系統難以高效管理和運維。僅僅使我們的broker和ZooKeeper節點不失效是不夠的,我們需要考慮在應用程式、副本和其他基礎架構元件中可能發生的更加細微和難以預測的問題。這些可能會以意想不到的方式影響整個部署,如果在生產中發生,則可能需要進行大量的排障開銷。使用混沌工程,我們可以主動測試這些型別的故障,並在部署到生產之前解決這些故障,從而降低停機和緊急事件的風險。在本節中,我們將逐步介紹在Confluent平臺上部署執行四個不同的混沌實驗。混沌實驗是一個有計劃的過程,將故障注入系統以瞭解其響應方式。在系統上執行任何實驗之前,應充分考慮並開發要執行的實驗。
- 第一步:設定假說要回答的問題以及預期結果是什麼。例如,如果實驗是測試承受broker中斷的能力,則假說可能會指出:“如果broker節點發生故障,則訊息會自動路由到其他broker,而不會丟失資料。”
- 第二步:定義爆炸半徑,受實驗影響的基礎架構元件。減小爆炸半徑限制了實驗可能對基礎架構造成的潛在危害,同時還可以將精力集中在特定系統上。我們強烈建議從儘可能小的爆炸半徑開始,然後隨著對進行混沌實驗的適應性提高,將其增大。另外,還應該定義幅度,即注入攻擊的規模或影響力。例如,低幅度的實驗可能是在生產者和broker之間的網路流量中增加20毫秒的延遲。大幅度的實驗可能是增加500毫秒的延遲,因為這將對效能和吞吐量產生顯著的影響。與爆炸半徑一樣,從低幅度開始,然後逐漸增大。
- 第三步:監控基礎架構。確定哪些指標將助力得出有關假說的結論,在測試之前進行觀測以建立基線,並在整個測試過程中記錄這些指標,以便可以監測預期和意外的變化。藉助Confluent平臺,我們可以使用Control Center從Web瀏覽器實時直觀地觀察群集的效能。
- 第四步:執行實驗。Gremlin允許以簡單、安全和可靠的方式在應用程式和基礎架構上執行實驗。我們透過執行注入攻擊來做到這一點,它提供了各種向系統中注入故障的方法。我們還要定義中止條件,這是我們應該停止測試以避免意外損壞的條件。使用Gremlin,我們可以將狀態檢查定義為場景的一部分。透過狀態檢查,我們可以在進行注入攻擊時驗證服務狀態。如果基礎架構執行不正常,並且狀態檢查失敗,將自動停止實驗。另外,我們可以使用內建的暫停按鈕立即停止實驗。
- 第五步:從觀察結果得出結論。它是否證實或反駁了原先的假設?使用收集的結果來修改基礎架構,然後圍繞這些改進設計新的實驗。隨著時間的推移,重複此過程將有助於使Kafka部署更具韌性。本文中介紹的實驗絕不是詳盡無遺的,而應將其作為在系統上進行實驗的起點。請記住,儘管我們正在Confluent平臺上執行這些實驗,但它們可以在任何Kafka叢集上執行。
請注意,我們使用的是基於Kafka 2.5.0構建的Confluent 平臺 5.5.0。螢幕截圖和配置詳細資訊可能會因版本而異。資源利用率可能會對訊息吞吐量產生顯著影響。如果broker正在經歷較高的CPU、記憶體或磁碟I/O利用率,則處理訊息的能力將受到限制。由於Kafka的效率依賴於最慢的元件,因此延遲可能會在整個管道中產生級聯效應,並導致故障條件,例如生產者備份和複製延遲。高負載還會影響叢集操作,例如broker執行狀況檢查,分割槽重新分配和領導者選舉,從而使整個叢集處於不正常狀態。最佳化Kafka時要考慮的兩個最重要的指標是網路延遲和磁碟I/O。Broker不斷在本地儲存中讀寫資料,隨著訊息速率和群集大小的增加,頻寬使用可能成為限制因素。在確定叢集大小時,我們應該確定資源利用率在哪一點上會對效能和穩定性產生不利影響。為了確定這一點,我們將進行一個混沌實驗,逐步提高broker之間的磁碟I/O利用率,並觀察其對吞吐量的影響。在執行此實驗時,我們將使用Kafka Music演示應用程式傳送連續的資料流。該應用程式將訊息傳送到分佈在所有三個broker中的多個主題,並使用Kafka Streams聚合和處理訊息。在本實驗中,我們將使用IO Gremlin在broker節點上生成大量磁碟I/O請求。我們將建立一個方案,並在四個階段中逐步增加註入攻擊的強度。每次注入攻擊將執行三分鐘,中間會間隔一分鐘,因此我們可以輕鬆地將I/O利用率的變化與吞吐量的變化聯絡起來。此外,我們將建立一個狀態檢查,該狀態檢查使用Kafka Monitoring API在每個階段之間檢查broker的執行狀況。狀態檢查是透過Gremlin傳送自動HTTP請求到我們選定的端點,在這種情況下,該端點是我們叢集的REST API伺服器。我們將使用主題的端點來檢索broker的狀態,並解析JSON響應以確定它們當前是否處於同步狀態。如果任何broker不同步,我們將立即停止實驗並將其標記為失敗。在場景執行期間,我們還將使用Confluent Control Center監視吞吐量和延遲。- 結論:即使將磁碟I/O增加到150 MB/s以上,技術攻擊也不會對吞吐量或延遲產生明顯影響。兩項指標均保持穩定,我們的broker均未失去同步或複製不足,也沒有訊息丟失或損壞。
目前,這給我們留下了很多開銷,但是隨著應用範圍的擴大,對吞吐量的要求可能會增加。我們應該密切關注磁碟I/O利用率,以確保有足夠的擴充套件空間。如果開始注意到磁碟I/O增加和吞吐量減少,則應考慮:- 使用更快的儲存裝置,例如更高的RPM磁碟或固態儲存;
- 使用更有效的壓縮演算法,例如Snappy或LZ4。
為了確保成功傳遞訊息,生產者和broker使用確認機制。當broker將訊息提交到其本地日誌時,它會與響應生產者進行確認,表明已成功接收到該訊息,並且生產者可以傳送下一條訊息。這樣可以減少生產者和broker之間丟失訊息的風險,但是不能防止broker之間丟失訊息。例如,假設我們有一個broker領導,剛從生產者那裡收到訊息併傳送了確認。Broker的每個訂閱者現在都應該獲取訊息並將其提交到他們自己的本地日誌中。但是,broker在其任何訂閱者獲取最新訊息之前意外失敗。跟隨者沒有一個知道生產者傳送了一條訊息,但是生產者已經收到了確認,因此它已移至下一條訊息。除非我們可以恢復發生故障的broker,或者找到其他重新傳送訊息的方法,否則訊息實際上已經丟失了。我們如何確定在叢集上發生這種情況的風險?藉助混沌工程,我們可以模擬broker領導的故障並監控訊息流,以確定潛在的資料丟失。在此實驗中,我們將使用黑洞Gremlin刪除往返於broker領導的所有網路流量。此實驗很大程度上取決於時間安排,因為我們希望在broker收到訊息之後,但在其訂閱者可以複製訊息之前,引起broker的失敗。這可透過兩種方式執行此操作:- 比追隨者低的時間間隔,傳送連續的訊息流,啟動實驗,並尋找使用者輸出中的空白(replica.fetch.wait.max.ms)。
- 傳送訊息後,立即使用Gremlin API從生產者應用程式觸發混沌實驗。
在本實驗中,我們將使用第一種方式。應用程式每100毫秒產生一條新訊息。訊息流的輸出記錄為JSON列表,並對其分析以查詢任何差距或時序上的不一致。我們將對其注入攻擊30秒,這將生成300條訊息(每100毫秒一條訊息)。- 假設:由於領導者失敗,我們將丟失一些訊息,但是Kafka將迅速選出新的領導者,並再次成功複製訊息。
- 結果:儘管領導者突然失敗,訊息列表仍顯示所有成功透過的訊息。由於實驗前後記錄了額外的訊息,因此我們的管道總共產生了336個事件,每個訊息在上一個事件之後大約有100毫秒的時間戳。訊息沒有按時間順序顯示,但這很好,因為Kafka不保證分割槽之間訊息的順序。這是輸出示例:
如果想保證所有訊息都已儲存,則可以在生產者配置中設定acks = all。這告訴生產者在將訊息複製到broker領導及其所有訂閱者之前,不要傳送新訊息。這是最安全的選擇,但是它將吞吐量限制為最慢broker的速度,因此可能會對效能和延遲產生重大影響。Kafka、ZooKeeper和類似的分散式系統容易受到稱為“腦裂”問題的影響。在腦裂中,同一群集內的兩個節點失去同步並分割槽,從而產生群集中兩個單獨且可能不相容的檢視。這會導致資料不一致,資料損壞,甚至形成第二個群集。這是怎麼發生的?在Kafka中,為單個broker節點分配了控制器角色。控制器負責檢測群集狀態的更改,例如失敗的broker、領導者選舉和分割槽分配。每個叢集只有一個且只有一個控制器,以維護叢集的單個一致性檢視。儘管這使控制器成為單點故障,但Kafka擁有處理此類故障的過程。所有broker都會定期向ZooKeeper登記,以證明自己的健康。如果broker的響應時間超過zookeeper.session.timeout.ms設定(預設為18,000毫秒),則ZooKeeper會將broker標記為不正常。如果該broker是控制者,則觸發控制者選舉,副本ISR成為新的控制者。這個新的控制器被分配了一個稱為控制器紀元的編號,該編號跟蹤最新的控制器選舉。如果發生故障的控制器重新聯機,它將比較自己的控制器紀元與ZooKeeper中儲存的紀元,識別出新選的控制器,然後退回為普通broker。這個過程可以防止少數broker失敗,但如果大部分broker都發生了重大故障,那該怎麼辦呢?我們可以在不產生腦裂的情況下重新啟動它們嗎?我們可以使用混沌工程對此進行驗證。此實驗中,我們將使用關機Gremlin重新啟動叢集中的三個broker節點中的兩個。由於此實驗可能會對叢集穩定性造成潛在風險(例如,我們不想意外關閉所有ZooKeeper節點),因此想確保在執行該broker之前,所有三個broker都應處於健康狀態。我們將建立一個狀態檢查,從Kafka Monitoring API中獲取健康broker的列表,以驗證所有三個broker均已啟動並正在執行。這是我們完全配置的場景,顯示狀態檢查和關機Gremlin:- 假設:Kafka的吞吐量將暫時停止,但是兩個broker節點都將重新加入群集,而不會出現問題。
- 結果:控制中心仍列出了三個broker,但顯示其中兩個不同步且分割槽複製不足。這是預料之中的,因為節點已經失去了與其餘broker和ZooKeeper的聯絡。
當先前的控制器(broker1)離線時,ZooKeeper立即選舉剩餘的broker(broker3)為新的控制器。由於這兩個broker在不超過ZooKeeper會話超時的情況下重新啟動,因此,從broker正常執行時間的圖表,可以看出它們一直處於聯機狀態。但是,當我們的訊息管道轉移到broker3時,檢視吞吐量和副本的圖表,就會發現這明顯地影響了吞吐量和分割槽執行狀況。儘管如此,broker毫無意外地重新加入了叢集。由此可以得出結論,我們的叢集可以承受暫時的多數失敗。效能將顯著下降,叢集將需要選舉新的領導者,重新分配分割槽,並在其餘broker之間複製資料,但是不會陷入腦裂的局面。如果花更長的時間來恢復broker,則此結果可能會有所不同,因此,我們希望確定在重大生產中斷的情況下,已制定了事件響應計劃。ZooKeeper是Kafka的基本依賴項。它負責諸如識別broker、選舉領導者以及跟蹤broker之間的分割槽分佈等活動。ZooKeeper中斷不一定會導致Kafka發生故障,但是如果解決時間越長,它可能會導致意外問題。在一個示例中,HubSpot遇到了ZooKeeper故障,原因是短時產生大量備份的請求。ZooKeeper在幾分鐘內無法恢復,這又導致Kafka節點開始崩潰。最終導致資料損壞,團隊不得不手動將備份資料還原到伺服器。雖然這是HubSpot解決的一個不尋常的情況,但它強調了將ZooKeeper和Kafka作為單獨服務和整體服務進行測試的重要性。https://blog.hubspot.com/customers/last-weeks-product-outage-what-happened-how-were-changing-as-a-result此實驗中,我們要驗證Kafka叢集可以在ZooKeeper意外中斷時能倖免於難。我們將使用黑洞Gremlin丟棄往返ZooKeeper節點的所有流量。我們將在控制中心中監視群集狀態的同時執行注入攻擊五分鐘。- 假設:Kafka可以忍受短期的ZooKeeper中斷,而不會崩潰,丟失資料或損壞資料。但是,直到ZooKeeper重新聯機之前,對群集狀態的任何更改都將無法解決。
- 結果:執行實驗對訊息吞吐量或broker可用性沒有任何結果。正如我們所假設的,可以繼續產生和使用訊息而不會出現意外問題。
如果其中一個broker失敗,則在ZooKeeper重新聯機之前,broker無法重新加入群集。僅此一項並不太可能導致失敗,但是可能導致另一個問題:級聯失敗。例如,broker失敗將導致生產者將負擔轉移到其餘broker上。如果這些broker接近生產極限,就會依次崩潰。即使我們使失敗的broker重新聯機,也將無法加入叢集,直到ZooKeeper再次可用。該實驗表明,我們可以容忍ZooKeeper暫時失敗,但是仍然應該迅速工作以使其恢復線上狀態。還應該尋找減輕完全中斷風險的方法,例如在多個區域之間分佈ZooKeeper以實現冗餘。儘管這將增加運營成本並增加延遲,但與使整個叢集出現生產故障相比,這是一個很小的成本。Kafka是一個複雜的平臺,具有大量相互依賴的元件和流程。要使Kafka可靠地執行,就需要進行計劃、持續的監控和主動的故障測試。這不僅適用於我們的Kafka和ZooKeeper叢集,還適用於使用Kafka的應用程式。混沌工程使我們能夠安全有效地發現Kafka部署中的可靠性問題。為今天的故障做準備可以防止或減少將來發生故障的風險和影響,從而節省組織的時間、工作量以及挽救客戶的信任。
現在,我們已經展示了Kafka的四個不同的混沌實驗,請嘗試註冊Gremlin Free帳戶來執行這些實驗。建立實驗時,請考慮Kafka部署中的潛在故障點(例如,broker與使用者之間的連線),並觀察它們如何應對不同的注入攻擊。如果發現問題,請實施修復程式並重復實驗以驗證它可以解決問題。隨著系統變得更加可靠,逐漸增加幅度(實驗的強度)和爆炸半徑(受實驗影響的系統數量),以便全面地測試整個部署。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31558019/viewspace-2780765/,如需轉載,請註明出處,否則將追究法律責任。