Elasticsearch 集群 200 TB+

Elasticsearch 集群 200 TB+

許多人都在使用 Elasticsearch。但是,當您想使用它“以特別大的容量”儲存日誌時會發生什麼?經歷多個資料中心中任何一個的故障是否也不會造成痛苦?您應該建立什麼樣的架構,您會遇到哪些陷阱?

我們 Odnoklassniki 決定使用 Elasticsearch 來解決日誌管理問題,現在我們與 Habr 分享我們的經驗:既有關於架構的,也有關於陷阱的。

我是 Pyotr Zaitsev,在 Odnoklassniki 擔任系統管理員。在此之前,我也是一名管理員,曾使用 Manticore Search、Sphinx search、Elasticsearch。也許,如果出現另一個...搜索,我也可能會使用它。我還自願參與了一些開源專案。

當我來到 Odnoklassniki 時,我在面試時魯莽地說我可以使用 Elasticsearch。在我掌握了它的竅​​門並完成了一些簡單的任務後,我被賦予了改革當時存在的日誌管理系統的重大任務。

需求

系統要求制定如下:

  • Graylog 將被用作前端。因為公司已經有使用該產品的經驗,程式設計師和測試人員都知道它,對他們來說熟悉且方便。
  • 資料量:平均每秒50-80萬個訊息,但如果發生故障,那麼流量不受任何限制,可以是每秒2-3百萬行
  • 在與客戶討論了處理搜尋查詢的速度要求後,我們意識到使用此類系統的典型模式是這樣的:人們正在尋找最近兩天的應用程式日誌,並且不想等待超過第二個用於公式化查詢的結果。
  • 管理員堅持認為,如果需要,系統可以輕鬆擴展,而不需要他們深入研究其工作原理。
  • 因此,這些系統需要定期進行的唯一維護任務就是更換一些硬體。
  • 此外,Odnoklassniki 擁有優秀的技術傳統:我們推出的任何服務都必須在資料中心故障(突然的、無計劃的、絕對隨時發生的)中倖存下來。

實施這個專案的最後一個要求是我們花費最多的,我將更詳細地討論這一點。

星期三

我們在四個資料中心工作,而 Elasticsearch 資料節點只能位於三個(出於許多非技術原因)。

這四個資料中心包含約 18 個不同的日誌來源—硬體、容器、虛擬機器。

重要特點:叢集在容器中啟動 波德曼 不是在實體機器上,而是在 自有雲產品一雲。容器保證有 2 個核心,類似於 2.0Ghz v4,如果剩餘核心閒置,則可以回收它們。

換句話說:

Elasticsearch 集群 200 TB+

拓樸結構

我最初看到解決方案的一般形式如下:

  • Graylog域的A記錄後面有3-4個VIP,這是日誌傳送到的位址。
  • 每個 VIP 都是一個 LVS 平衡器。
  • 之後,日誌進入 Graylog 電池,有些資料是 GELF 格式,有些是 syslog 格式。
  • 然後,所有這些都會大批量寫入一組 Elasticsearch 協調器中。
  • 然後它們依次向相關資料節點發送寫入和讀取請求。

Elasticsearch 集群 200 TB+

術語

也許不是每個人都詳細了解這些術語,所以我想詳細說明一下。

Elasticsearch 有多種類型的節點 - 主節點、協調節點、資料節點。還有另外兩種類型用於不同的日誌轉換和不同集群之間的通信,但我們僅使用列出的那些。

碩士
它對叢集中存在的所有節點執行 ping 操作,維護最新的叢集映射並將其分發到節點之間,處理事件邏輯,並執行各種叢集範圍的內務處理。

協調員
執行一項任務:接受來自客戶端的讀取或寫入請求並路由此流量。如果有寫入請求,它很可能會詢問 master 應該將其放入相關索引的哪個分片中,並進一步重定向請求。

資料節點
儲存數據,執行來自外部的搜尋查詢並對位於其上的分片執行操作。

灰色日誌
這有點像是 ELK 堆疊中 Kibana 與 Logstash 的融合。 Graylog 結合了 UI 和日誌處理管道。在底層,Graylog 運行 Kafka 和 Zookeeper,它們作為叢集提供與 Graylog 的連接。 Graylog可以在Elasticsearch不可用的情況下快取日誌(Kafka),並重複不成功的讀寫請求,按照指定的規則對日誌進行分組和標記。與 Logstash 一樣,Graylog 具有在將行寫入 Elasticsearch 之前對其進行修改的功能。

此外,Graylog具有內建的服務發現功能,可以基於一個可用的Elasticsearch節點獲取整個集群圖並透過特定標籤進行過濾,從而可以將請求定向到特定容器。

從視覺上看,它看起來像這樣:

Elasticsearch 集群 200 TB+

這是特定實例的螢幕截圖。在這裡,我們根據搜尋查詢建立直方圖並顯示相關行。

指數

回到系統架構,我想更詳細地討論我們如何建立索引模型以使其一切正常運作。

在上圖中,這是最低層級:Elasticsearch 資料節點。

索引是由 Elasticsearch 分片組成的大型虛擬實體。就其本身而言,每個分片只不過是一個 Lucene 索引。而每個 Lucene 索引又由一個或多個段落組成。

Elasticsearch 集群 200 TB+

在設計時,我們認為為了滿足大量資料讀取速度的要求,需要將這些資料均勻地「分佈」在資料節點上。

這導致每個索引的分片數量(帶有副本)應該嚴格等於資料節點的數量。首先,為了確保複製因子等於XNUMX(也就是說,我們可以失去一半的群集)。其次,為了在至少一半的叢集上處理讀寫請求。

我們首先確定儲存時間為30天。

分片的分佈可以用圖形表示如下:

Elasticsearch 集群 200 TB+

整個深灰色矩形是索引。其中左邊的紅色方塊是主分片,即索引中的第一個分片。藍色方塊是複製品碎片。它們位於不同的資料中心。

當我們新增另一個分片時,它會轉到第三個資料中心。最後,我們得到了這樣的結構,它可以在不丟失資料一致性的情況下丟失 DC:

Elasticsearch 集群 200 TB+

索引的旋轉,即建立一個新索引並刪除最舊的索引,我們將其設定為 48 小時(根據索引使用模式:最近 48 小時搜尋最頻繁)。

這種索引輪轉間隔是由於以下原因:

當搜尋請求到達特定資料節點時,從效能角度來看,如果查詢一個分片的大小與節點的臀部大小相當,則查詢一個分片會更有利可圖。這允許您將索引的“熱”部分保留在堆中並快速存取它。當「熱點部分」較多時,索引搜尋的速度就會降低。

當節點開始在一個分片上執行搜尋查詢時,它會分配與實體機的超線程核心數量相等的執行緒數量。如果搜尋查詢影響大量分片,則執行緒數量會成比例增長。這會對搜尋速度產生負面影響,並對新資料的索引產生負面影響。

為了提供必要的搜尋延遲,我們決定使用 SSD。為了快速處理請求,託管這些容器的機器必須至少有 56 個核心。選擇數字 56 作為有條件的足夠值,它確定 Elasticsearch 在操作期間將產生的線程數。在Elasitcsearch中,許多線程池參數直接取決於可用核心的數量,而根據“更少的核心-更多的節點”的原則,這反過來又直接影響集群中所需的節點數量。

結果,我們發現平均一個分片的大小約為 20 GB,每個索引有 1 個分片。因此,如果我們每 360 小時輪換一次,那麼我們就有 48 個。每個索引包含 15 天的資料。

資料寫入和讀取電路

讓我們弄清楚數據是如何記錄在這個系統中的。

假設某個請求從 Graylog 到達協調器。例如,我們想要索引 2-3 千行。

協調員收到 Graylog 的請求後,向 master 詢問:“在索引請求中,我們專門指定了一個索引,但沒有指定將其寫入哪個分片。”

主節點回應:“將此資訊寫入71號分片”,然後直接傳送到71號主分片所在的相關資料節點。

之後,交易日誌被複製到位於另一個資料中心的副本分片。

Elasticsearch 集群 200 TB+

搜尋請求從 Graylog 到達協調器。協調器根據索引進行重定向,而 Elasticsearch 使用循環原理在主分片和副本分片之間分配請求。

Elasticsearch 集群 200 TB+

180 個節點的響應不均勻,在它們響應的同時,協調器正在累積已經被更快的資料節點「吐出」的資訊。此後,當所有資訊都已到達或要求逾時時,它會將所有資訊直接提供給客戶端。

整個系統平均在 48-300 毫秒內處理過去 400 小時的搜尋查詢,不包括那些帶有前導通配符的查詢。

Elasticsearch 之花:Java 設定

Elasticsearch 集群 200 TB+

為了讓一切按照我們最初想要的方式運作,我們花了很長時間來調試叢集中的各種東西。

發現的第一部分問題與 Elasticsearch 中預設預先配置 Java 的方式有關。

問題一
我們觀察到大量報告表明,在 Lucene 級別,當後台作業運行時,Lucene 段合併會失敗並出現錯誤。同時,日誌中清楚地表明這是一個 OutOfMemoryError 錯誤。我們從遙測中看到髖部是自由的,但尚不清楚手術失敗的原因。

事實證明,Lucene 索引合併發生在臀部之外。而且容器在消耗的資源方面受到相當嚴格的限制。只有堆可以容納這些資源(heap.size 值大約等於 RAM),並且如果由於某種原因它們無法容納在限制之前剩餘的約 500MB 中,則某些堆外操作會因內存分配錯誤而崩潰。

修復非常簡單:增加了容器可用的 RAM 量,之後我們忘記了我們甚至遇到這樣的問題。

問題二
叢集啟動後4-5天,我們注意到資料節點開始週期性地脫離叢集並在10-20秒後進入叢集。

當我們開始弄清楚這一點時,發現 Elasticsearch 中的堆外記憶體不受任何方式控制。當我們為容器提供更多記憶體時,我們能夠用各種資訊填充直接緩衝池,並且只有在從 Elasticsearch 啟動顯式 GC 後才會清除它。

在某些情況下,此操作需要相當長的時間,在此期間叢集設法將該節點標記為已退出。這個問題描述得很好 就在這裡.

解決方案如下:我們限制了 Java 使用堆外的大量記憶體來執行這些操作的能力。我們將其限制為 16 GB (-XX:MaxDirectMemorySize=16g),確保更頻繁地呼叫顯式 GC 並更快地處理,從而不再破壞叢集的穩定性。

問題三
如果您認為「節點在最意想不到的時刻離開叢集」的問題已經結束,那您就錯了。

當我們配置索引工作時,我們選擇了 mmapfs 來 減少搜尋時間 在具有良好分割的新鮮碎片上。這是一個很大的錯誤,因為當使用 mmapfs 時,檔案被映射到 RAM,然後我們使用映射的檔案。正因為如此,事實證明,當 GC 嘗試停止應用程式中的執行緒時,我們會在很長一段時間內到達安全點,並且在到達安全點的途中,應用程式停止回應主伺服器關於其是否存活的請求。因此,master 認為該節點不再存在於叢集中。此後,5-10秒後,垃圾收集器開始工作,節點恢復活力,再次進入叢集並開始初始化分片。這一切感覺很像“我們應得的作品”,不適合任何嚴肅的事情。

為了擺脫這種行為,我們首先切換到標準 niofs,然後,當我們從 Elastic 的第五個版本遷移到第六個版本時,我們嘗試了 Hybridfs,這個問題沒有重現。您可以閱讀有關存儲類型的更多信息 這裡.

問題四
然後還有另一個非常有趣的問題,我們處理了創紀錄的時間。我們抓了它2-3個月,因為它的模式完全無法理解。

有時,我們的協調員通常會在午餐後的某個時間進行 Full GC,然後就再也沒有從那裡返回。同時,在記錄 GC 延遲時,它看起來像這樣:一切都很順利,很好,很好,然後突然一切都變得非常糟糕。

起初,我們認為有一個邪惡的使用者正在發起某種請求,使協調器脫離工作模式。我們記錄了很長時間的請求,試圖弄清楚發生了什麼事。

結果是,當使用者發起巨大請求並到達特定 Elasticsearch 協調器時,某些節點的回應時間會比其他節點長。

當協調器等待所有節點的回應時,他會累積從已回應的節點發送的結果。對於 GC,這意味著我們的堆使用模式變化得非常快。而我們所使用的GC無法應付這個任務。

在這種情況下,我們發現改變叢集行為的唯一解決方法是遷移到 JDK13 並使用 Shenandoah 垃圾收集器。這解決了問題,我們的協調員不再跌倒了。

這就是 Java 問題結束和頻寬問題開始的地方。

Elasticsearch 的「漿果」:吞吐量

Elasticsearch 集群 200 TB+

吞吐量問題意味著我們的叢集工作穩定,但在索引文件數量達到峰值時以及操作過程中,效能不足。

第一個症狀是:在生產中的一些「爆發」過程中,突然產生大量日誌時,Graylog 中開始頻繁閃爍索引錯誤 es_rejected_execution。

這是因為,在 Elasticsearch 能夠處理索引請求並將資訊上傳到磁碟上的分片之前,一個資料節點上的 thread_pool.write.queue 預設只能快取 200 個請求。並且在 Elasticsearch 文件 關於這個參數的說法很少。僅指示最大線程數和預設大小。

當然,我們去扭曲這個值並發現了以下內容:具體來說,在我們的設定中,最多 300 個請求被很好地緩存,並且更高的值會導致我們再次飛入 Full GC。

此外,由於這些訊息是在一個請求內到達的一批訊息,因此有必要調整Graylog,使其不經常小批量寫入,而是大批量寫入,或者如果批量尚未完成,則每3 秒寫入一次。在這種情況下,事實證明,我們在 Elasticsearch 中寫入的資訊不是在兩秒內可用,而是在五秒內可用(這非常適合我們),但是為了推動大量資料而必須進行的重發次數資訊堆疊減少。

當某些東西在某個地方崩潰並瘋狂地報告它時,這一點尤其重要,以免得到完全垃圾郵件的 Elastic,並且在一段時間後 - Graylog 節點由於緩衝區堵塞而無法運行。

此外,當我們在生產中遇到相同的爆炸性增長時,我們收到了程式設計師和測試人員的抱怨:當他們真正需要這些日誌時,卻提供得非常緩慢。

他們開始想辦法。一方面,很明顯,搜尋查詢和索引查詢本質上都是在同一台實體機器上處理的,無論如何都會有一定的縮減。

但這可以部分規避,因為在 Elasticsearch 的第六個版本中出現了一種演算法,允許您在相關資料節點之間分配查詢,而不是根據隨機循環原理(執行索引並保存主資料的容器)分片可能會非常繁忙,將無法快速回應),但是將此請求轉發到具有副本分片的負載較少的容器,這會響應得更快。換句話說,我們得到了 use_adaptive_replica_selection: true。

閱讀圖片開始是這樣的:

Elasticsearch 集群 200 TB+

當我們要寫入大量日誌時,過渡到該演算法可以顯著縮短查詢時間。

最後,主要問題是輕鬆拆除資料中心。

在與一台 DC 失去連接後,我們立即希望從集群中得到什麼:

  • 如果我們在發生故障的資料中心中有一個當前的主節點,那麼它將被重新選擇並作為角色移動到另一個 DC 中的另一個節點。
  • master會快速從叢集中刪除所有無法存取的節點。
  • 根據剩下的,他會明白:在遺失的資料中心我們有這樣那樣的主分片,他會在剩下的資料中心快速提升互補的副本分片,我們會繼續對資料建立索引。
  • 因此,叢集的寫入和讀取吞吐量將逐漸下降,但總的來說一切都會正常進行,儘管緩慢但穩定。

事實證明,我們想要這樣的東西:

Elasticsearch 集群 200 TB+

我們得到以下結果:

Elasticsearch 集群 200 TB+

它怎麼發生的?

當資料中心垮掉的時候,我們的master就成了瓶頸。

為什麼呢?

事實上,master有一個TaskBatcher,它負責在叢集中分發某些任務和事件。任何節點退出、從副本到主分片的任何提升、在某處創建分片的任何任務 - 所有這些都首先進入 TaskBatcher,在那裡它在一個線程中按順序進行處理。

當一個資料中心撤出時,倖存資料中心的所有資料節點都認為有責任通知Master「我們遺失了某某分片、某某資料節點」。

同時,倖存的資料節點將所有這些資訊發送給當前的主節點,並嘗試等待他接受的確認。他們沒有等到這一刻,因為主人接到任務的速度比他回答的速度還要快。節點重複請求超時,此時的主節點甚至沒有嘗試回答它們,而是完全專注於按優先順序對請求進行排序的任務。

在終端形式中,事實證明,資料節點向主節點發送垃圾郵件,導致主節點進入完全垃圾收集。之後,我們的主角色轉移到下一個節點,完全相同的事情發生在它身上,結果叢集完全崩潰了。

我們進行了測量,在 6.4.0 版本之前(該問題已修復),我們只需同時輸出 10 個資料節點中的 360 個資料節點即可完全關閉叢集。

它看起來像這樣:

Elasticsearch 集群 200 TB+

在修復了這個可怕的錯誤的 6.4.0 版本之後,資料節點不再殺死主節點。但這並沒有讓他變得「更聰明」。即:當我們輸出2、3 或10 個(除XNUMX 之外的任何數量)資料節點時,主節點會收到一些第一個訊息,表示節點A 已離開,並嘗試將此資訊告訴節點B、節點C、節點D。

目前,只能透過設定嘗試告訴某人某件事的超時時間(大約為 20-30 秒)來解決此問題,從而控制資料中心移出叢集的速度。

原則上,這符合最初作為專案一部分向最終產品提出的要求,但從「純科學」的角度來看,這是一個錯誤。順便說一句,開發人員在 7.2 版本中成功修復了這個問題。

而且,當某個資料節點發生故障時,事實證明,傳播有關其退出的信息比告訴整個集群上存在這樣那樣的主分片(以便在另一個數據中提升副本分片)更重要。中心在初級,並且訊息可以寫在它們上)。

因此,當一切都已經平息下來時,釋放的資料節點不會立即被標記為過時。因此,我們被迫等待,直到所有對已釋放資料節點的 ping 逾時,只有在那之後,我們的叢集才開始告訴我們,我們需要繼續記錄資訊。您可以閱讀更多相關內容 這裡.

所以今天尖峰時段撤出資料中心的操作大約要5分鐘。對於這樣一個龐大而笨拙的龐然大物來說,這已經是一個相當不錯的結果了。

因此,我們做出以下決定:

  • 我們有 360 個資料節點和 700 GB 磁碟。
  • 60 個協調器,用於透過這些相同的資料節點路由流量。
  • 從 40 之前的版本開始,我們留下了 6.4.0 個 master 作為遺產 - 為了在數據中心撤出後生存下來,我們做好了失去幾台機器的心理準備,以保證即使在最壞的情況
  • 任何在一個容器上組合角色的嘗試都會遇到這樣的事實:節點遲早會在負載下崩潰。
  • 整個叢集使用 31 GB 的 heap.size:所有減小大小的嘗試都導致要么在使用前導通配符的繁重搜尋查詢中殺死一些節點,要么在 Elasticsearch 本身中得到斷路器。
  • 另外,為了確保搜尋效能,我們盡量保持叢集中的物件數量盡可能少,以便在master遇到的瓶頸中處理盡可能少的事件。

最後關於監控

為了確保這一切按預期進行,我們監控以下內容:

  • 每個資料節點都會向我們的雲端報告它的存在,而且上面有這樣那樣的分片。當我們在某處消滅某些東西時,集群會在2-3 秒後報告說,在中心A 我們消滅了節點2、3 和4 - 這意味著在其他資料中心,我們在任何情況下都無法消滅那些只有一個分片的節點左邊。
  • 了解了 master 行為的本質後,我們非常仔細地查看待處理任務的數量。因為即使是一個卡住的任務,如果沒有及時超時,理論上在某些緊急情況下也可能成為主節點中副本分片升級不起作用的原因,這就是索引將停止工作的原因。
  • 我們也非常仔細地關注垃圾收集器延遲,因為我們在優化過程中已經遇到了很大的困難。
  • 透過線程拒絕,提前了解瓶頸在哪裡。
  • 嗯,標準指標,如堆、RAM 和 I/O。

在建置監控時,必須考慮到Elasticsearch中Thread Pool的特性。 Elasticsearch 文件 描述了搜尋和索引的配置選項和預設值,但是完全沒有提到thread_pool.management,這些線程處理,特別是像_cat/shards和其他類似的查詢,在編寫監控時使用起來很方便。叢集越大,單位時間內執行的此類請求就越多,而前面提到的thread_pool.management不僅官方文件中沒有介紹,而且預設限制為5個線程,很快就被處理掉了哪個監控停止正常工作。

最後我想說的是:我們做到了!我們能夠為程式設計師和開發人員提供一種工具,幾乎在任何情況下都可以快速可靠地提供有關生產中發生的情況的資訊。

是的,事實證明它相當複雜,但是,儘管如此,我們還是設法將我們的願望融入到現有產品中,而我們不必自己修補和重寫。

Elasticsearch 集群 200 TB+

來源: www.habr.com

添加評論