騰訊雲Elasticsearch集群規劃及性能優化實踐

  • 2020 年 9 月 30 日
  • 筆記

​一、引言

 

隨著騰訊雲 Elasticsearch 雲產品功能越來越豐富,ES 用戶越來越多,雲上的集群規模也越來越大。我們在日常運維工作中也經常會遇到一些由於前期集群規劃不到位,導致後期業務增長集群規模大了之後帶來的各種各樣的集群可用性及穩定性問題。

 

這裡列舉下其中比較典型的幾種集群規劃問題:

 

  • 節點規格規劃問題:集群數量很大,但是每個節點的配置很低;

  • 索引分片規劃問題:索引很小,但是設置了幾十個分片,或者索引很大,只設置了兩三個分片;

  • 分片數量規劃問題:集群中包含 10萬+ 的分片。

 

正所謂磨刀不誤砍柴工,只有前期做好充分的集群評估規劃工作,後期才能省去大量的運維工作。且能夠長期保證集群的高可用和高穩定性。

 

本文結合我們在給騰訊雲 ES 集群日常運維工作中遇到的各種集群問題及總結沉澱的一些運維經驗,來介紹下如何規劃好集群容量及索引配置,以及所遵循的一些原則和經驗。文章作者:吳容,騰訊雲Elasticsearch研發工程師。

 

二、集群規模及索引規劃

 

 

1. 集群規模評估

 

(1)評估什麼?

 

集群規模的評估主要評估以下三個方面:

 

第一,計算資源評估,計算資源的評估主要是評估單節點的CPU和記憶體。

 

ES的計算資源一般消耗在寫入和查詢過程,經過總結大量ES集群的運維經驗,2C8G 的配置大概能支援 5k doc/s 的寫入,32C64G 的配置大概能支撐 5w doc/s的寫入能力。

 

第二,存儲資源評估,存儲資源的評估主要是評估磁碟的類型及容量大小。

 

例如ES集群使用什麼類型的磁碟,SSD或者高性能雲盤。以及每塊盤的容量大小,是選擇單盤多容量,還是多盤少容量。而對於冷熱分離的集群,則默認使用SSD作為熱節點,高性能雲盤作為溫節點。

 

另外騰訊雲ES支援單節點掛載多塊雲硬碟,且經過性能壓測,3塊盤相比於1塊盤,吞吐量大約有2.8倍的提升。因此如果對寫入速度及IO性能要求較高,可選擇掛載多塊 SSD 磁碟。

 

ES冷熱分離多盤集群示意圖

 

第三,節點數量評估,節點數量的評估主要是評估集群數據節點的數量。

 

在同等集群性能的情況下,建議優先選擇高配置少節點的集群。例如 32C64G*3 節點的集群相比於 8C16G*12 節點的集群,在集群穩定性和擴容的便捷性上都有一定的優勢。

 

因為高配置的集群如果遇到性能瓶頸需要擴容,則只需要橫向擴容,即向集群中加入更多同等配置的節點即可;而低配置的集群在擴容節點配置時,則需要縱向擴容。

 

目前雲上的縱向擴容方式有兩種:

 

第一種是滾動重啟方式擴容,這對集群穩定性會有一定的影響。

 

第二種是數據遷移方式擴容,其原理是先向集群中加入同等數量的高配置節點,然後將低配置節點上的數據遷移到新節點上,最後再將低配置節點剔除集群,所以這種擴容流程時間會比較長,且成本較高。

 

數據遷移方式縱向擴容示意圖

 

(2)根據什麼評估?

 

集群規模評估主要根據以下三點來評估:

 

  • 具體的業務場景,如日誌分析、指標監控、搜索業務;

  • 業務預計的查詢及寫入QPS;

  • 索引的數據總量。

 

(3)集群規模評估準則

 

這裡結合我們的運維經驗,給出集群規模評估的幾點參考建議:

 

  • 32C64G單節點配置通常可承載5W次/s的寫入;

  • 寫入量和數據量較大時,優先選擇32C64G的節點配置;

  • 1T的數據量預計需消耗2-4GB的記憶體空間;

  • 搜索場景優先選擇大記憶體節點配置;

  • 存儲容量 = 源數據 * (1 + 副本數量) * 1.45 * (1 + 預留空間) ≈ 源數據 * (1 + 副本數量) * 2.2.

 

2. 索引配置評估

 

(1)評估什麼?

 

索引配置的評估主要評估兩點:

 

第一,如何劃分索引?

 

在使用 index 時,建議做好定期切換索引的計劃。對於日誌場景來說,寫入不大的情況下建議按天創建索引,而寫入較大的情況下,則建議按小時創建索引。

 

定期滾動索引的好處主要包括:能夠控制單個索引的大小,提升讀寫性能;同時能夠方式單個索引太大,影響故障恢復的時間;另外也能避免熱索引過大,從而影響快照備份恢復的時間。

 

第二,如何設置索引主分片數?

 

雲上的索引主分片數默認是5個,具體的大小則需要業務根據具體的場景及數據量來優化。下面會給出具體的一些準則和經驗。

 

(2)根據什麼評估?

 

索引配置的評估同樣也要結合具體的業務場景及索引的數據量來評估,尤其是單日新增的數據量。

 

(3)索引配置評估準則

 

索引配置的評估可根據下面幾點準則進行評估:

 

  • 單個分片大小控制在 30-50GB;

  • 集群總分片數量控制在 3w 以內;

  • 1GB 的記憶體空間支援 20-30 個分片為佳;

  • 一個節點建議不超過 1000 個分片;

  • 索引分片數量建議和節點數量保持一致;

  • 集群規模較大時建議設置專用主節點;

  • 專用主節點配置建議在 8C16G 以上;

  • 如果是時序數據,建議結合冷熱分離+ILM 索引生命周期管理。

 

特別需要說明的是集群分片總數的大小控制上,我們經過一些性能測試發現:當集群的總分片數超過 10w 個以後,創建索引時間會增長到分鐘級。

 

尤其是對於寫入量在百萬 qps 以上的集群,如果總分片數在 10W+,且索引是自動創建的,那麼就經常會在每次切換新索引時候,出現寫入陡降、集群不可用的情況。

 

下面這張圖是雲上一個 100個節點,總分片數在 11W+ 的集群。每天 8點切換新索引時,寫入直接掉0,集群不可用時間在數小時不等。

 

集群每天8點寫入性能受到影響

 

對於這種問題,我們騰訊雲ES團隊也有一些非常成熟的優化方案。

 

其中對於每天八點切換新索引時寫入陡降的問題,可通過提前創建索引來解決,且建議使用固定的 index mapping,避免大量的 put-mapping 元數據更新操作。因為對於這種節點數量和總分片數量都很大的集群來說,更新元數據是一個非常消耗性能的操作。

 

對於總分片數超過 10W 的問題,這種一般在日誌分析場景中較為常見,如果歷史數據不是很重要,則可定期刪除歷史索引即可。

 

而對於歷史數據較為重要,任何數據都不能刪除的場景,則可通過冷熱分離架構+索引生命周期管理功能,將7天之前的數據存儲到溫節點,且在索引數據從熱節點遷移到溫節點時,通過 Shrink 來將主分片個數降低到一個較小的值,並且可將溫節點數據通過快照方式備份到騰訊雲COS中,然後將溫節點上索引的副本設置為0,這樣便可進一步降低集群中的總分片數量。

 

冷熱分離+ILM+COS備份集群架構

 

二、ES寫入性能優化

 

ES集群的寫入性能受到很多因素的影響,下面是一些寫入性能方面的優化建議:

 

1. 寫入數據不指定doc_id,讓 ES 自動生成

 

索引中每一個 doc 都有一個全局唯一的 doc_id,這個 doc_id 可自定義,也可以讓ES自動生成。

 

如果自定義的話,則ES在寫入過程中會多一步判斷的過程,即先Get下該 doc_id 是否已經存在。如果存在的話則執行 Update 操作,不存在則創建新的 doc。

 

因此如果我們對索引 doc_id 沒有特別要求,則建議讓ES自動生成 doc_id,這樣可提升一定的寫入性能。

 

2. 對於規模較大的集群,建議提前創建好索引,且使用固定的 Index mapping

 

這一條優化建議在上面也提到了,因為創建索引及新加欄位都是更新元數據操作,需要 master 節點將新版本的元數據同步到所有節點。

 

因此在集群規模比較大,寫入qps較高的場景下,特別容易出現master更新元數據超時的問題,這可導致 master 節點中有大量的 pending_tasks 任務堆積,從而造成集群不可用,甚至出現集群無主的情況。

 

更新集群元數據超時

 

集群大量pending_tasks任務堆積

 

3. 對於數據實時性要求不高的場景,適當增加 refresh_interval 時間

 

ES默認的 refresh_interval 是1s,即 doc 寫入1s後即可被搜索到。

 

如果業務對數據實時性要求不高的話,如日誌場景,可將索引模版的 refresh_interval 設置成30s,這能夠避免過多的小 segment 文件的生成及段合併的操作。

 

4. 對於追求寫入效率的場景,可以將正在寫入的索引設置為單副本,寫入完成後打開副本

 

越來越多的外部客戶正選擇將自建的ES集群遷移到騰訊雲上來,客戶通常是使用 logstash 來遷移數據,由於自建集群中完整保留了數據,因此這時候可以將雲上的正在寫入的索引副本設置為0, 這樣可最快完成集群遷移工作。數據遷移完成後再將副本打開即可。

 

5. 使用 Bulk 介面批量寫入數據,每次 bulk 數據量大小控制在 10M 左右

 

ES為了提升寫入性能,提供了 Bulk 批量寫入的API,通常客戶端會準備好一批數據往ES中寫入,ES收到 Bulk 請求後則根據routing 值進行分發,將該批數據組裝成若干分子集,然後非同步得發送給各分片所在的節點。

 

這樣能夠大大降低寫入請求時的網路交互和延遲。通常我們建議一次Bulk的數據量控制在10M以下,一次Bulk的doc數在 10000 上下浮動。

 

ES Bulk請求示意圖

 

6. 使用自定義 routing 功能,盡量將請求轉發到較少的分片

 

上面我們提到ES提供了Bulk介面支援將數據批量寫入到索引,雖然協調節點是非同步得將數據發送給所有的分片,但是卻需要等待所有的分片響應後才能返回給客戶端,因此一次Bulk的延遲則取決於響應最慢的那個分片所在的節點。這就是分散式系統的長尾效應。

 

因此,我們可以自定義 routing 值,將一次Bulk盡量轉發到較少的分片上。  

POST _bulk?routing=user_id

 

自定義routing

 

7. 盡量選擇 SSD 磁碟類型,並且可選擇掛載多塊雲硬碟

 

雲上目前提供多種類型的磁碟可用選擇,其中1T的 SSD 雲盤吞吐量為 260M/s,高性能雲盤為 150M/s。因此使用SSD磁碟對於寫入性能和IO性能都會有一定的提升。

 

另外騰訊雲現在也提供了多盤的能力,相對於單盤節點來說,3塊盤的吞吐量大約有2.8倍的提升。

 

8. 凍結歷史索引,釋放更多的記憶體空間

 

我們知道ES的索引有三種狀態,分別是 Open狀態、Frozen狀態和 Close狀態。如下圖所示:

 

ES索引的三種狀態

 

Open狀態的索引由於是通過將倒排索引以FST數據結構的方式載入進記憶體中,因此索引是能夠被快速搜索的,且搜索速度也是最快的。

 

但是需要消耗大量的記憶體空間,且這部分記憶體為常駐記憶體,不會被GC的。1T的索引預計需要消耗2-4GB的JVM堆記憶體空間。

 

Frozen狀態的索引特點是可被搜索,但是由於它不佔用記憶體,只是存儲在磁碟上,因此凍結索引的搜索速度是相對比較慢的。如果我們集群中的數據量比較大,歷史數據也不能被刪除,則可以考慮使用下面的API將歷史索引凍結起來,這樣便可釋放出較多的記憶體空間。

POST /index_name/_freeze

 

對於凍結索引的搜索,可以在API中指定 ignore_throttled=false 參數:

 

GET /index_name/_search?ignore_throttled=false
{
 "query": {
   "match": {
     "name": "wurong"
   }
 }
}

 

上面介紹了一些較為常見的寫入性能優化的建議和經驗,但是更為高效的優化還需要結合具體的業務場景和集群規模。

 

三、ES集群常規運維經驗總結

 

 

1. 查看集群健康狀態

 

ES集群的健康狀態分為三種,分別是Green、Yellow和Red。

 

  • Green(綠色):全部主&副本分片分配成功;

  • Yellow(黃色):至少有一個副本分片未分配成功;

  • Red(紅色):至少有一個主分片未分配成功。

 

我們可以通過下面的API來查詢集群的健康狀態及未分配的分片個數:

 

GET _cluster/health
{
  "cluster_name": "es-xxxxxxx",
  "status": "yellow",
  "timed_out": false,
  "number_of_nodes": 103,
  "number_of_data_nodes": 100,
  "active_primary_shards": 4610,
  "active_shards": 9212,
  "relocating_shards": 0,
  "initializing_shards": 0,
  "unassigned_shards": 8,
  "delayed_unassigned_shards": 0,
  "number_of_pending_tasks": 0,
  "number_of_in_flight_fetch": 0,
  "task_max_waiting_in_queue_millis": 0,
  "active_shards_percent_as_number": 99.91323210412148
}

 

其中需要重點關注的幾個欄位有 status、number_of_nodes、unassigned_shards 和 number_of_pending_tasks。

 

number_of_pending_tasks 這個欄位如果很高的話,通常是由於 master 節點觸發的元數據更新操作,部分節點響應超時導致的大量的任務堆積。

 

我們可以通過下面的API來查看具體有那些 task 需要執行:

 

GET /_cat/pending_tasks
insertOrder timeInQueue priority source
       1685       855ms HIGH     update-mapping [foo][t]
       1686       843ms HIGH     update-mapping [foo][t]
       1693       753ms HIGH     refresh-mapping [foo][[t]]
       1688       816ms HIGH     update-mapping [foo][t]

 

其中 priority 欄位則表示該 task 的優先順序,翻看 ES 的源碼可以看到一共有六種優先順序:

 

IMMEDIATE((byte) 0),
URGENT((byte) 1),
HIGH((byte) 2),
NORMAL((byte) 3),
LOW((byte) 4),
LANGUID((byte) 5);

 

2. 查看分片未分配原因

 

當集群Red時候,我們可以通過下面的API來查看分片未分配的原因:

 

GET _cluster/allocation/explain

 

查看分片未分配的原因

 

其中 index和shard 列出了具體哪個索引的哪個分片未分配成功。reason 欄位則列出了哪種原因導致的分片未分配。這裡也將所有可能的原因列出來:

 

INDEX_CREATED:由於創建索引的API導致未分配。

CLUSTER_RECOVERED :由於完全集群恢復導致未分配。

INDEX_REOPENED :由於打開open或關閉close一個索引導致未分配。

DANGLING_INDEX_IMPORTED :由於導入dangling索引的結果導致未分配。

NEW_INDEX_RESTORED :由於恢復到新索引導致未分配。

EXISTING_INDEX_RESTORED :由於恢復到已關閉的索引導致未分配。

REPLICA_ADDED:由於顯式添加副本分片導致未分配。

ALLOCATION_FAILED :由於分片分配失敗導致未分配。

NODE_LEFT :由於承載該分片的節點離開集群導致未分配。

REINITIALIZED :由於當分片從開始移動到初始化時導致未分配(例如,使用影子shadow副本分片)。

REROUTE_CANCELLED :作為顯式取消重新路由命令的結果取消分配。

REALLOCATED_REPLICA :確定更好的副本位置被標定使用,導致現有的副本分配被取消,出現未分配。

 

detail 欄位則列出了更為詳細的未分配的原因。下面我會總結下在日常運維工作中常見的幾種原因。

 

如果未分配的分片比較多的話,我們也可以通過下面的API來列出所有未分配的索引和主分片:

GET /_cat/indices?v&health=red

 

3. 常見分片未分配原因總結

 

(1)磁碟滿了

the node is above the high watermark cluster setting [cluster.routing.allocation.disk.watermark.high=95%], using more disk space than the maximum allowed [95.0%], actual free: [4.055101177689788%]

 

當我們執行 _cluster/allocation/explain 命令後看到上面的一行語句的話,則可以判斷是該索引主分片所在的節點磁碟滿了。

 

解決方法:擴容磁碟提升磁碟容量或者刪除歷史數據釋放磁碟空間。

 

通常如果磁碟滿了,ES為了保證集群的穩定性,會將該節點上所有的索引設置為只讀。ES 7.x版本之後當磁碟空間提升後可自動解除,但是7.x版本之前則需要手動執行下面的API來解除只讀模式:

 

PUT index_name/_settings
{
 "index": {
   "blocks": {
     "read_only_allow_delete": null
    }
  }
}

(2)分片的文檔數超過了21億條限制

 

failure IllegalArgumentException[number of documents in the index cannot exceed 2147483519

 

該限制是分片維度而不是索引維度的。因此出現這種異常,通常是由於我們的索引分片設置的不是很合理。

 

解決方法:切換寫入到新索引,並修改索引模版,合理設置主分片數。

 

(3)主分片所在節點掉線

 

cannot allocate because a previous copy of the primary shard existed but can no longer be found on the nodes in the cluster

 

這種情況通常是由於某個節點故障或者由於負載較高導致的掉線。

 

解決方法:找到節點掉線原因並重新啟動節點加入集群,等待分片恢復。

 

(4)索引所需屬性和節點屬性不匹配

 

node does not match index setting [index.routing.allocation.require] filters [temperature:\"warm\",_id:\"comdNq4ZSd2Y6ycB9Oubsg\"]

 

解決方法:重新設置索引所需的屬性,和節點保持一致。因為如果重新設置節點屬性,則需要重啟節點,代價較高。

 

例如通過下面的API來修改索引所需要分配節點的溫度屬性:

 

PUT /index_name/_settings
{
  "index": {
    "routing": {
      "allocation": {
        "require": {
          "temperature": "warm"
        }
      }
    }
  }
}

 

(5)節點長時間掉線後重新加入集群,引入了臟數據

 

cannot allocate because all found copies of the shard are either stale or corrupt

 

解決方法:通過reroute API來重新分配一個主分片:

 

POST _cluster/reroute?pretty" -d '{
    "commands" : [
        {
          "allocate_stale_primary" : {
              "index" : "article", 
              "shard" : 1,
              "node" : "98365000222032",
              "accept_data_loss": true
          }
        }
    ]
}

 

(6)未分配分片太多,達到了分片恢復的閾值,其他分片排隊等待

 

reached the limit of incoming shard recoveries [2], cluster setting [cluster.routing.allocation.node_concurrent_incoming_recoveries=2] (can also be set via [cluster.routing.allocation.node_concurrent_recoveries])

 

這種情況通常出現在集群重啟,或者某一個節點重啟後。且由於設置的分片並發恢復的值較低導致。為了儘快恢復集群健康狀態。

 

解決方法:可以通過調用下面的API來提升分片恢復的速度和並發度:

 

PUT /_cluster/settings
{
    "transient" : {
        "cluster.routing.allocation.node_concurrent_recoveries": "20",
        "indices.recovery.max_bytes_per_sec": "100mb"
    }
}

 

結語

 

本文介紹了集群規模和索引配置規劃的評估準則,依據這些準則提前規劃集群,可以保證集群的穩定性和可用性,簡化複雜的運維工作。

 

另外介紹了一些常見的寫入性能優化的建議和方法。能夠進一步提升集群的寫入性能和穩定性。最後介紹了日常運維工作中常見的排查集群問題的方法和思路。希望本文能夠幫助到騰訊雲的每一個ES客戶。