elasticsearch高可用集羣二

 第 2-1 課:分片管理

一個 shard 本質上就是一個 Lucene 索引,也是 Elasticsearch 分佈式化 Lucene 的關鍵抽象,是 Elasticsearch 管理 Lucene 文件的最小單位。

所以,Elasticsearch 提供了大量的接口,可以對集羣內的 shard 進行管理。

1 常用 shard REST API 操作

1.1 shard 移動

將分片從一個節點移動到另一個節點,在使用 Elasticsearch 中,鮮有需要使用該接口去移動分片,更多的是使用 AllocationDecider 參數以及平衡參數去自動調整 shard 的位置。

在一些特別的情況下,例如發現大部分熱點數據集中在幾個節點,可以考慮手工 move 一下。

curl -XPOST 'localhost:9200/_cluster/reroute' -d '
{
    "commands" : [ {
        "move" :
            {
              "index" : "test", "shard" : 0,
              "from_node" : "node1", "to_node" : "node2"
            }
        }
    ]
}'

1.2 explain api

explain api 是 Elasticsearch 5.x 以後加入的非常實用的運維接口,可以用來診斷 shard 爲什麼沒有分配,以及 shard 爲什麼分配在某個節點。

    curl -XGET "http://localhost:9200/_cluster/allocation/explain
      {
          "index": "myindex",
          "shard": 0,
          "primary": true
      }

如果不提供參數調用該 api,Elasticsearch 返回第一個 unassigned shard 未分配的原因。

    GET /_cluster/allocation/explain

1.3 分配 stale 分片

在索引過程中,Elasticsearch 首先在 primary shard 上執行索引操作,之後將操作發送到 replica shards 執行,通過這種方式使 primary 和 replica 數據同步。

對於同一個分片的所有 replicas,Elasticsearch 在集羣的全局狀態裏保存所有處於同步狀態的分片,稱爲 in-sync copies。

如果修改操作在 primary shard 執行成功,在 replica 上執行失敗,則 primary 和 replica 數據就不在同步,這時 Elasticsearch 會將修改操作失敗的 replica 標記爲 stale,並更新到集羣狀態裏。

當由於某種原因,對於某個 shard 集羣中可用數據只剩 stale 分片時,集羣會處於 red 狀態,並不會主動將 stale shard 提升爲 primary shard,因爲該 shard 的數據不是最新的。這時如果不得不將 stale shard 提升爲主分片,需要人工介入:

curl -XPOST "http://localhost:9200/_cluster/reroute" -d '
{
        "commands":[{
            "allocate_stale_primary":{
                "index":"my_index",
                "shard":"10",
                "node":"node_id",
                "accept_data_loss":true
            }
        }]
    }'

1.4 分配 empty 分片

當由於 lucene index 損壞或者磁盤故障導致某個分片的主副本都丟失時,爲了能使集羣恢復 green 狀態,最後的唯一方法是劃分一個空 shard。

curl -XPOST "http://localhost:9200/_cluster/reroute" -d '
{
        "commands":[{
            "allocate_empty_primary":{
                "index":"my_index",
                "shard":"10",
                "node":"node_id",
                "accept_data_loss":true
            }
        }]
    }'

一定要慎用該操作,會導致對應分片的數據完全清空。

2 控制 shard 數量

一般來說,增加主分片數量可以增加寫入速度和查詢速度,因爲數據分佈到了更多的節點,可以利用更多的計算和 IO 資源。增加副分片數量可以提升查詢速度,併發的查詢可以在多個分片之間輪詢。

但是 shard 管理並不是 “免費” 的,shard 數量過多會消耗更多的 cpu、內存資源,引發一系列問題,主要包括如下幾個方面。

2.1 shard 過多問題

  • 引起 master 節點慢

任一時刻,一個集羣中只有一個節點是 master 節點,master 節點負責維護集羣的狀態信息,而且狀態的更新是在單線程中運行的,大量的 shard 會導致集羣狀態相關的修改操作緩慢,比如創建索引、刪除索引,更新 setting 等。

單個集羣 shard 超過 10 萬,這些操作會明顯變慢。集羣在恢復過程中,會頻繁更顯狀態,引起恢復過程漫長。

我們曾經在單個集羣維護 30 多萬分片,集羣做一次完全重啓有時候需要2-4個小時的時間,對於業務來說是難以忍受的。

查詢慢

查詢很多小分片會降低單個 shard 的查詢時間,但是如果分片過多,會導致查詢任務在隊列中排隊,最終可能會增加查詢的整體時間消耗。

引起資源佔用高

Elasticsearch 協調節點接收到查詢後,會將查詢分發到查詢涉及的所有 shard 並行執行,之後協調節點對各個 shard 的查詢結果進行歸併。

如果有很多小分片,增加協調節點的內存壓力,同時會增加整個集羣的 cpu 壓力,甚至發生拒絕查詢的問題。因爲我們經常會設置參與搜索操作的分片數上限,以保護集羣資源和穩定性,分片數設置過大會更容易觸發這個上限。

2.2 如何減少 shard

  • 設置合理的分片數

創建索引時,可以指定 number_of_shards,默認值是 5,對於物理大小隻有幾個 GB 的索引,完全可以設置成更小的值。

  • shard 合併

如果集羣中有大量的 MB、KB 級分片,可以通過 Elasticsearch 的 shard 合併功能,將索引的多個分片合併成 1 個分片。

  • 刪除無用索引 根據業務場景,每個索引都有自己的生命週期。尤其對於日誌型索引,超過一定時間週期後,業務就不再訪問,應該及時從集羣中刪除。
  • 控制 replica 數量

replica 可以提高數據安全性,並可以負載讀請求,但是會增加寫入時的資源消耗,同時使集羣維護的分片數成倍的增長,引起上面提到的諸多問題。所以要儘量降低 replica 數量。

3 shard 分配

Elasticsearch 通過 AllocationDecider 策略來控制 shard 在集羣內節點上的分佈。

3.1 allocation deciders(分配決策者)

  • same shard allocation decider

控制一個 shard 的主副本不會分配到同一個節點,提高了數據的安全性。

  • MaxRetryAllocationDecider

該 Allocationdecider 防止 shard 分配失敗一定次數後仍然繼續嘗試分配。可以通過 index.allocation.max_retries 參數設置重試次數。當重試次數達到後,可以通過手動方式重新進行分配。

curl -XPOST "http://localhost:9200/_cluster/reroute?retry_failed"
  • awareness allocation decider

可以確保主分片及其副本分片分佈在不同的物理服務器,機架或區域之間,以儘可能減少丟失所有分片副本的風險。

  • filter allocation decider

該 decider 提供了動態參數,可以明確指定分片可以分配到指定節點上。

index.routing.allocation.include.{attribute}
index.routing.allocation.require.{attribute}
index.routing.allocation.exclude.{attribute}

require 表示必須分配到具有指定 attribute 的節點,include 表示可以分配到具有指定 attribute 的節點,exclude 表示不允許分配到具有指定 attribute 的節點。Elasticsearch 內置了多個 attribute,無需自己定義,包括 _name_host_ip_publish_ip_ip_host。attribute 可以自己定義到 Elasticsearch 的配置文件。

  • disk threshold allocation decider

根據磁盤空間來控制 shard 的分配,防止節點磁盤寫滿後,新分片還繼續分配到該節點。啓用該策略後,它有兩個動態參數。

cluster.routing.allocation.disk.watermark.low參數表示當磁盤空間達到該值後,新的分片不會繼續分配到該節點,默認值是磁盤容量的 85%。

cluster.routing.allocation.disk.watermark.high參數表示當磁盤使用空間達到該值後,集羣會嘗試將該節點上的分片移動到其他節點,默認值是磁盤容量的 90%。

  • shards limit allocation decider

通過兩個動態參數,控制索引在節點上的分片數量。其中 index.routing.allocation.total _ shards_per_node 控制單個索引在一個節點上的最大分片數;

cluster.routing.allocation.total_shards_per_node 控制一個節點上最多可以分配多少個分片。

應用中爲了使索引的分片相對均衡的負載到集羣內的節點,index.routing.allocation.total_shards_per_node 參數使用較多。

4 shard 平衡

分片平衡對 Elasticsearch 穩定高效運行至關重要。下面介紹 Elasticsearch 提供的分片平衡參數。

4.1 Elasticsearch 分片平衡參數

  • cluster.routing.rebalance.enable

控制是否可以對分片進行平衡,以及對何種類型的分片進行平衡。可取的值包括:allprimariesreplicasnone,默認值是all

all 是可以對所有的分片進行平衡;primaries 表示只能對主分片進行平衡;replicas 表示只能對副本進行平衡;none表示對任何分片都不能平衡,也就是禁用了平衡功能。該值一般不需要修改。

  • cluster.routing.allocation.balance.shard

控制各個節點分片數一致的權重,默認值是 0.45f。增大該值,分配 shard 時,Elasticsearch 在不違反 Allocation Decider 的情況下,儘量保證集羣各個節點上的分片數是相近的。

  • cluster.routing.allocation.balance.index

控制單個索引在集羣內的平衡權重,默認值是 0.55f。增大該值,分配 shard 時,Elasticsearch 在不違反 Allocation Decider 的情況下,儘量將該索引的分片平均的分佈到集羣內的節點。

  • index.routing.allocation.total_shards_per_node

控制單個索引在一個節點上的最大分片數,默認值是不限制。

當使用cluster.routing.allocation.balance.shardindex.routing.allocation.total_shards_per_node不能使分片平衡時,就需要通過該參數來控制分片的分佈。

所以,我們的經驗是:創建索引時,儘量將該值設置的小一些,以使索引的 shard 比較平均的分佈到集羣內的所有節點。

但是也要使個別節點離線時,分片能分配到在線節點,對於有 10 個節點的集羣,如果單個索引的主副本分片總數爲 10,如果將該參數設置成 1,當一個節點離線時,集羣就無法恢復成 Green 狀態了。

所以我們的建議一般是保證一個節點離線後,也可以使集羣恢復到 Green 狀態。

4.2 關於磁盤平衡

Elasticsearch 內部的平衡策略都是基於 shard 數量的,所以在運行一段時間後,如果不同索引的 shard 物理大小差距很大,最終會出現磁盤使用不平衡的情況。

所以,目前來說避免該問題的以辦法是讓集羣內的 shard 物理大小盡量保持相近

總結

主節點對 shard 的管理是一種代價相對比較昂貴的操作,因此在滿足需求的情況下建議儘量減少 shard 數量,將分片數量和分片大小控制在合理的範圍內,可以避免很多問題。

第 2-2 課:段合併優化及注意事項

當新文檔被索引到 Elasticsearch,他們被暫存到索引緩衝中。當索引緩衝達到 flush 條件時,緩衝中的數據被刷到磁盤,這在 Elasticsearch 稱爲 refreshrefresh 會產生一個新的 Lucene 分段,這個分段會包含一系列的記錄正排和倒排數據的文件。

當他們還在索引緩衝,沒有被 refresh 到磁盤的時候,是無法被搜索到的。因此爲了保證較高的搜索可見性,默認情況下,每1秒鐘會執行一次 refresh。

因此這會頻繁地產生 Lucene 段文件,爲了降低需要打開的 fd 的數量,優化查詢速度,需要將這些較小的 Lucene 分段合併成較大的段,引用一張官網的示意圖如下:

在段合併之前,有四個較小的分段對搜索可見,段合併過程選擇了其中三個分段進行合併,當合並完成之後,老的段被刪除:

在段合併(也可以稱爲 merge)的過程中,此前被標記爲刪除的文檔被徹底刪除。因此 merge 過程是必要的,但是進行段合併耗費的資源比較高,他不能僅僅進行 io 的讀寫操作就完成合並過程,而是需要大量的計算,因此數據入庫過程中有可能會因爲 merge 操作佔用了大量 CPU 資源。進而影響了入庫速度。

查看es中merge的api

我們可以通過 _nodes/hot_threads 接口查看節點有多少個線程在執行 merge。

hot_threads 接口返回每個節點,或者指定節點的熱點線程,對於 merge 來說,他的堆棧長成下面這個樣子:

可以通過紅色框中標記出來的文字來找到 merge 線程。

1 merge 優化

很多時候我們希望降低 merge 操作對系統的影響,通常從以下幾個方面入手:

  • 降低分段產生的數量和頻率,少生成一些分段,自然就可以少執行一些 merge 操作
  • 降低最大分段大小,達到我們指定的大小後,不再進行段合併操作。這可以讓較大的段不再參與 merge,節省大量資源,但最終分段數會更多一些

具體來說可以執行以下調整:

1. refresh

最簡單的是增大 refresh 間隔時間,可以動態的調整索引級別的 refresh_interval 參數,-1 代表關閉自動刷新。

具體取值應該參考業務對搜索可見性的要求。在搜索可見性要求不高的業務上,我們將此值設置爲分鐘級

2. indices.memory.index_buffer_size

索引緩衝用於存儲剛剛被索引的文檔,當緩衝滿的時候,這些數據被刷到磁盤產生新的分段。默認值爲整個堆內存的10%,可以適當提高此值,例如調整到30%。該參數不支持動態調整。

3. 避免更新操作

儘量避免更新文檔,也就是說,儘量避免使用同一個 docid 進行文檔更新。

對文檔的 update 需要先執行 Get 操作,再執行 Index 操作,執行 Get 操作時,realtime 參數被設置爲 true,在 Elasticsearch 5.x 及以後之後的版本中,這會導致一個對索引的 refresh 操作。

同理,Get 操作默認是實時的,應該儘量避免客戶端直接發起的 Get 操作,或者將 Get 操作的請求中將 realtime 參數設置爲 false。

4. 調整 merge 線程數

執行 merge 操作的線程池由 Lucene 創建,其最大線程池數由以下公式計算:

Math.max(1, Math.min(4, Runtime.getRuntime().availableProcessors() / 2))

你可以通過以下配置項來調整:

index.merge.scheduler.max_thread_count

5. 調整段合併策略

Lucene 內置的段合併策略有三種,默認爲分層的合併策略:tiered。對於這種策略,我們可以調整下面兩個值,來降低段合併次數。

index.merge.policy.segments_per_tier

該參數設置每層允許存在的分段數量,值越小,就需要更多的合併操作,但是最終分段數越少。默認爲10,可以適當增加此值,我們設置爲24。

注意該值必須大於等於 index.merge.policy.max_merge_at_once(默認爲10)。

index.merge.policy.max_merged_segment

當分段達到此參數配置的大小後,不再參與後續的段合併操作。默認爲 5Gb,可以適當降低此值,我們使用 2Gb,但是索引最終會產生相對更多一些的分段,對搜索速度有些影響。

2 force(強制) merge 成幾個?

在理想情況下,我們應該對不再會有新數據寫入的索引執行 force merge,force merge 最大的好處是可以提升查詢速度,並在一定情況下降低內存佔用。

未進行 force merge 的時候,對分片的查詢需要遍歷查詢所有的分段,很明顯,在一次查詢中會涉及到很多文件的隨機 io,force merge 降低分段數量大大降低了所需隨機 io 的數量,帶來查詢性能的提升。

但是對一個分片來說, force merge 成幾個分段比較合適?這沒有明確的建議值,我們的經驗是,維護分片下的分段數量越少越好,理想情況下,你可以 force merge 成一個,但是 merge 過程佔用大量的網絡、io、以及計算資源。

如果在業務底峯期開始執行的 force merge 到了業務高峯期還沒執行完,已經影響到集羣的性能,就應該增加 force merge 最終的分段數量。

目前我們讓分段合併到 2GB 就不再合併,因此 force merge 的數量爲:分片大小/2GB

3 flush merge 的其他問題

我們總結一下關於 flush 和 merge 的一些原理,這是一些新同學學習 Elasticsearch 過程中的常見問題。

  • 從索引緩衝刷到磁盤的 refresh 過程是同步執行的。

像 hbase 這種從緩衝刷到磁盤的時候是異步的,hbase 會開闢一個新的緩衝去寫新數據。但同步執行不意味着這是耗時很久的 io 操作,因爲數據會被先寫入到系統 cache,因此通常情況下這不會產生磁盤 io,很快就會執行完成。

但是這個過程中操作系統會判斷 page cache 的髒數據是否需要進行落盤,如果需要進行落盤,他先執行異步落盤,如果異步的落盤來不及,此時會阻塞寫入操作,執行同步落盤。

因此在 io 比較密集的系統上,refresh 有可能會產生阻塞時間較長的情況,這種情況下可以調節操作系統內核參數,讓髒數據儘早落盤,需要同時調整異步和同步落盤的閾值,具體可以參考《Elasticsearch 源碼解析與優化實戰》 21.3.3 章節。

  • merge 策略和具體的執行過程,以及merge 過程所用的線程池是 Lucene 維護的,而不是在 Elasticsearch 中。
  • merge 過程是異步執行的,也就是說,refresh 過程中判斷是否需要執行 merge,如果需要執行 merge,merge 不會阻塞 refresh 操作。
  • 很多同學對 Elasticsearch 的刷盤與 Lucene 的 commit 之間的關係容易搞混亂,我們在此用一句話總結兩者之間概念的關係: Elasticsearch 的 refresh 調用 Lucene 的 flush;Elasticsearch 的 flush 調用 Lucene 的 commit

總結

本章介紹了分段合併的原理及實際使用過程中常見問題,以及應對方法,段合併是必要的,但是堆棧中如果出現過多的 merge 線程,並且在長時間週期內佔據堆棧,則需要注意一下,可能需要一些調整,在調整之前,應該首先排查一下如此多的 merge 是什麼原因產生的。

第二部分的這兩節課程我們深入介紹了 Elasticsearch 分片及分段在實際應用時的原則和注意事項,下一節我們將介紹一些 Elasticsearch 的 Cache 機制和實際應用。

第 2-3 課:Elasticsearch Cache

1 Elasticsearch Cache 機制

1.1 Cache 類型

Elasticsearch 內部包含三個類型的讀緩衝,分別爲 Node Query Cache、Shard Request Cache 以及 Fielddata Cache。

1. Node Query Cache

Elasticsearch 集羣中的每個節點包含一個 Node Query Cache由該節點的所有 shard 共享。該 Cache 採用 LRU 算法,Node Query Cache 只緩存 filter 的查詢結果。

2. Shard Request Cache

Shard Request Cache 緩存每個分片上的查詢結果跟 Node Query Cache 一樣,同樣採用 LRU 算法。默認情況下,Shard Request Cache 只會緩存設置了 size=0 的查詢對應的結果,並不會緩存 hits,但是會緩存命中總數,aggregations,and suggestions。

有一點需要注意的是,Shard Request Cache 把整個查詢 JSON 串作爲緩存的 key,如果 JSON 對象的順序發生了變化,也不會在緩存中命中。所以在業務代碼中要保證生成的 JSON 是一致的,目前大部分 JSON 開發庫都支持 canonical 模式。

3. Fielddata Cache

Elasticsearch 從 2.0 開始,默認在非 text 字段開啓 doc_values,基於 doc_values 做排序和聚合,可以極大降低節點的內存消耗,減少節點 OOM 的概率,性能上損失卻不多。

5.0 開始,text 字段默認關閉了 Fielddata 功能,由於 text 字段是經過分詞的,在其上進行排序和聚合通常得不到預期的結果。所以我們建議 Fielddata Cache 應當只用於 global ordinals(預熱,只適用於keyword類型字段的term查詢)。

1.2 Cache 失效

不同的 Cache 失效策略不同,下面分別介紹:

1. Node Query Cache

Node Query Cache 在 segment 級緩存命中的結果,當 segment 被合併後,緩存會失效。

2. Shard Request Cache

每次 shard 數據發生變化後,在分片 refresh 時,Shard Request Cache 會失效,如果 shard 對應的數據頻繁發生變化,該緩存的效率會很差。

3. Fielddata Cache

Fielddata Cache 失效機制和 Node Query Cache 失效機制完全相同,當 segment 被合併後,纔會失效。

1.3 手動清除 Cache

Elasticsearch 提供手動清除 Cache 的接口:

POST /myindex/_cache/clear?query=true      
POST /myindex/_cache/clear?request=true    
POST /myindex/_cache/clear?fielddata=true   

Cache 對查詢性能很重要,不建議在生產環境中進行手動清除 Cache。這些接口一般在進行性能壓測時使用,在每輪測試開始前清除緩存,減少緩存對測試準確性的影響。

2 Cache 大小設置

2.1 關鍵參數

下面幾個參數可以控制各個類型的 Cache 佔用的內存大小。

  • indices.queries.cache.size:控制 Node Query Cache 佔用的內存大小,默認值爲堆內存的10%。
  • index.queries.cache.enabled:索引級別的設置,是否啓用 query cache,默認啓用。
  • indices.requests.cache.size:控制 Shard Request Cache 佔用的內存大小,默認爲堆內存的 1%。
  • indices.fielddata.cache.size:控制 Fielddata Cache 佔用的內存,默認值爲unbounded。

2.2 Cache 效率分析

要想合理調整上面提到的幾個參數,首先要了解當前集羣的 Cache 使用情況,Elasticsearch 提供了多個接口來獲取當前集羣中每個節點的 Cache 使用情況。

cat api

# curl -sXGET'http://localhost:9200/_cat/nodes?v
&h=name,queryCacheMemory,queryCacheEvictions,requestCacheMemory,requestCacheHitCount,
request_cache.miss_count'

得到如下結果,可以獲取每個節點的 Cache 使用情況:

name queryCacheMemory queryCacheEvictions requestCacheMemory requestCacheHitCount request_cache.miss_count
test01 1.6gb 52009098 15.9mb 1469672533 205589258
test02 1.6gb 52196513 12.2mb 2052084507 288623357

nodes_stats

curl -sXGET 'http://localhost:9200/_nodes/stats/indices?pretty'

從結果中可以分別找到 Query Cache、Request Cache、Fielddata 相關統計信息

...
"query_cache" : {
     "memory_size_in_bytes" : 1736567488,
     "total_count" : 14600775788,
     "hit_count" : 9429016073,
     "miss_count" : 5171759715,
     "cache_size" : 292327,
     "cache_count" : 52298914,
     "evictions" : 52006587
}
...
"fielddata" : {
     "memory_size_in_bytes" : 186953184,
     "evictions" : 0
}
...
"request_cache" : {
     "memory_size_in_bytes" : 16369709,
     "evictions" : 307303,
     "hit_count" : 1469518738,
     "miss_count" : 205558017
}
...

2.3 設置 Cache 大小

在收集了集羣中節點的 Cache 內存佔用大小、命中次數、驅逐次數後,就可以根據收集的數據計算出命中率和驅逐比例。

過低的命中率和過高的驅逐比例說明對應 Cache 設置的過小。合理的調整對應的參數,使命中率和驅逐比例處於期望的範圍。但是增大 Cache 要考慮到對 GC 的壓力。

總結

Elasticsearch 並不會緩存每一個查詢結果,他只緩存特定的查詢方式,如果你增大了 Cache 大小,一定要關注 JVM 的使用率是否在合理的範圍,我們建議保持在 60% 以下比較安全,同時關注 GC 指標是否存在異常。

下一節我們介紹一下如何使用熔斷器(breaker)來保護 Elasticsearch 節點的內存使用率

第 2-4 課:Breaker 限制內存使用量

內存問題 -- OutOfMemoryError 問題是我們在使用 Elasticsearch 過程中遇到的最大問題,Circuit breakers 是 Elasticsearch 用來防止集羣出現該問題的解決方案。

Elasticsearch 含多種斷路器用來避免因爲不合理的操作引起來的 OutOfMemoryError(內存溢出錯誤)。每個斷路器指定可以使用多少內存的限制。 另外,還有一個父級別的斷路器(indices.breaker.total.limit),指定可以在所有斷路器上使用的內存總量。

1 Circuit breadkers 分類

所有的 Circuit breaker 都支持動態配置,例如:

curl -XPUT localhost:9200/_cluster/settings -d '{"persistent" : {"indices.breaker.total.limit":"40%"}}'
curl -XPUT localhost:9200/_cluster/settings -d '{"persistent" : {"indices.breaker.fielddata.limit":"10%"}}'
curl -XPUT localhost:9200/_cluster/settings -d '{"persistent" : {"indices.breaker.request.limit":"20%"}}'
curl -XPUT localhost:9200/_cluster/settings -d '{"transient" : {"network.breaker.inflight_requests.limit":"20%"}}'
curl -XPUT localhost:9200/_cluster/settings -d '{"transient" : {"indices.breaker.accounting.limit":"20%"}}'
curl -XPUT localhost:9200/_cluster/settings -d '{"transient" : {"script.max_compilations_rate":"20%"}}'

1.1 Parent circuit breaker

1. 作用

設置所有 Circuit breakers 可以使用的內存的總量。

2. 配置項

  • indices.breaker.total.limit

默認值爲 70% JVM 堆大小。

1.2 Field data circuit breaker

1. 作用

估算加載 fielddata 需要的內存,如果超過配置的值就短路。

2. 配置項

  • indices.breaker.fielddata.limit

默認值 是 60% JVM 堆大小。

  • indices.breaker.fielddata.overhead

所有估算的列數據佔用內存大小乘以一個常量得到最終的值。默認值是1.03。

  • indices.fielddata.cache.size

該配置不屬於 Circuit breaker,但是都與 Fielddata 有關,所以有必要在這裏提一下。主要控制 Fielddata Cache 佔用的內存大小。

默認值是不限制,Elasticsearch 認爲加載 Fielddata 是很重的操作,頻繁的重複加載會嚴重影響性能,所以建議分配足夠的內存做 field data cache。

該配置和 Circuit breaker 配置還有一個不同點是這是一個靜態配置,如果修改需要修改集羣中每個節點的配置文件,並重啓節點。

可以通過 cat nodes api 監控 field data cache 佔用的內存空間:

curl -sXGET "http://localhost:9200/_cat/nodes?h=name,fielddata.memory_size&v"

輸出如下(注:存在 fielddata.memory_size 爲 0 是因爲本集羣部署了 5 個查詢節點,沒有存儲索引數據):

name    fielddata.memory_size
node1               224.2mb
node2               225.5mb
node3               168.7mb
node4                    0b
node5                    0b
node6               168.4mb
node7               223.8mb
node8               150.6mb
node9               169.5mb
node10                   0b
node11              224.7mb
node12                   0b
node13                   0b

indices.fielddata.cache.size 與 indices.breaker.fielddata.limit 區別前者是控制 fielddata 佔用內存的大小,後者是防止加載過多大的 fielddata 導致 OOM 異常。

1.3 Request circuit breaker

1. 作用

請求斷路器允許 Elasticsearch 防止每個請求數據結構(例如,用於在請求期間計算聚合的內存)超過一定量的內存。

2. 配置項

  • indices.breaker.request.limit

默認值是 60% JVM 堆大小。

  • indices.breaker.request.overhead

所有請求乘以一個常量得到最終的值。默認值是 1。

1.4 In flight circuit breaker

1. 作用

請求中的斷路器,允許 Elasticsearch 限制在傳輸或 HTTP 級別上的所有當前活動的傳入請求的內存使用超過節點上的一定量的內存。 內存使用是基於請求本身的內容長度

2. 配置項

  • network.breaker.inflight_requests.limit

默認值是 100% JVM 堆大小,也就是說該 breaker 最終受 indices.breaker.total.limit 配置限制。

  • network.breaker.inflight_requests.overhead

所有 (inflight_requests) 請求中估算的常數乘以確定最終估計,默認值是1。

1.5 Accounting requests circuit breaker

1. 作用

估算一個請求結束後不能釋放的對象佔用的內存。包括底層 Lucene 索引文件需要常駐內存的對象。

2. 配置項

  • indices.breaker.accounting.limit

默認值是 100% JVM 堆大小,也就是說該 breaker 最終受indices.breaker.total.limit配置限制。

  • indices.breaker.accounting.overhead

默認值是1。

1.6 Script compilation circuit breaker

1. 作用

與上面的基於內存的斷路器略有不同,腳本編譯斷路器在一段時間內限制腳本編譯的數量。

2. 配置項

  • script.max_compilations_rate

默認值是 75/5m。也就是每 5 分鐘可以進行 75 次腳本編譯。

2 Circuit breaker 狀態

合理配置 Circuit breaker 大小需要了解當前 breaker 的狀態,可以通過 Elasticsearch 的 stats api 獲取當前 breaker 的狀態,包括配置的大小、當前佔用大小、overhead 配置以及觸發的次數。

curl -sXGET     "http://localhost:9200/_nodes/stats/breaker?pretty"

執行上面的命令後,返回各個節點的 Circuit breakers 狀態:

"breakers" : {
"request" : {
    "limit_size_in_bytes" : 6442450944,
    "limit_size" : "6gb",
    "estimated_size_in_bytes" : 690875608,
    "estimated_size" : "658.8mb",
    "overhead" : 1.0,
    "tripped" : 0
},
"fielddata" : {
    "limit_size_in_bytes" : 11274289152,
    "limit_size" : "10.5gb",
    "estimated_size_in_bytes" : 236500264,
    "estimated_size" : "225.5mb",
    "overhead" : 1.03,
    "tripped" : 0
},
"in_flight_requests" : {
    "limit_size_in_bytes" : 32212254720,
    "limit_size" : "30gb",
    "estimated_size_in_bytes" : 18001,
    "estimated_size" : "17.5kb",
    "overhead" : 1.0,
    "tripped" : 0
},
"parent" : {
    "limit_size_in_bytes" : 17716740096,
    "limit_size" : "16.5gb",
    "estimated_size_in_bytes" : 927393873,
    "estimated_size" : "884.4mb",
    "overhead" : 1.0,
    "tripped" : 0 //是否發生過熔斷
    }
}

其中重點需要關注的是 limit_size 與 estimated_size 大小是否相近,越接近越有可能觸發熔斷。tripped 數量是否大於 0,如果大於 0 說明已經觸發過熔斷。

3 Circuit breaker 配置原則

Circuit breaker 的目的是防止不當的操作導致進程出現 OutOfMemoryError 問題。不能由於觸發了某個斷路器就盲目調大對應參數的設置,也不能由於節點經常發生 OutOfMemoryError 錯誤就盲目調小各個斷路器的設置。需要結合業務合理評估參數的設置。

3.1 不同版 circuit breakers 區別

Elasticsearch 從 2.0 版本開始,引入 Circuit breaker 功能,而且隨着版本的變化,Circuit breaker 的類型和默認值也有一定的變化,具體如下表所示:

版本

Parent

Fielddata

Request

Inflight

Script

Accounting

2.0-2.3

70%

60%

40%

2.4

70%

60%

40%

100%

5.x-6.1

70%

60%

60%

100%

1分鐘15次

6.2-6.5

70%

60%

60%

100%

1分鐘15次

100%

從上表中可見,Elasticsearch 也在不斷調整和完善 Circuit breaker 相關的默認值,並不斷增加不同類型的 Circuit breaker 來減少 Elasticsearch 節點出現 OOM 的概率。

注: 順便提一下,Elasticsearch 7.0 增加了 indices.breaker.total.use_real_memory 配置項,可以更加精準的分析當前的內存情況,及時防止 OOM 出現。雖然該配置會增加一點性能損耗,但是可以提高 JVM 的內存使用率,增強了節點的保護機制。

3.2 默認值的問題

Elasticsearch 對於 Circuit breaker 的默認值設置的都比較激進、樂觀的,尤其是對於 6.2(不包括 6.2)之前的版本,這些版本中沒有 accounting circuit breaker,節點加載打開的索引後,Lucene 的一些數據結構需要常駐內存,Parent circuit breakeredit 配置成堆的 70%,很容易發生 OOM。

3.3 配置思路

不同的業務場景,不同的數據量,不同的硬件配置,Circuit breaker 的設置應該是有差異的。 配置的過大,節點容易發生 OOM 異常,配置的過小,雖然節點穩定,但是會經常出現觸發斷路的問題,導致一部分合理應用無法完成。這裏我們介紹下在配置時需要考慮的問題。

1. 瞭解 Elasticsearch 內存分佈

Circuit breaker 最主要的作用就是防止節點出現 OOM 異常,所以,掌握 Elasticsearch 中都有哪些組件佔用內存是配置好 Circuit breaker 的第一步。

具體參見本課程中《常見問題之-內存問題》一章。

2. Parent circuit breaker 配置

前面提到 Elasticsearch 6.2 之前的版本是不包含 accounting requests circuit breaker 的,所以需要根據自己的數據特點,評估 Lucene segments 佔用的內存量佔 JVM heap 、index buffer、Query Cache、Request Cache 佔用的內存的百分比,並用 70% 減去評估出的值作爲 parent circuit breaker 的值。 對於 6.2 以後的版本,不需要減掉 Lucene segments 佔用的百分比。

3. Fielddata circuit breaker 配置

在 Elasticsearch 引入 doc_values 後,我們十分不建議繼續使用 fielddata,一是 feilddata 佔用內存過大,二是在 text 字段上排序和聚合沒有意義。Fielddata 佔用的內存應該僅限於 Elasticsearch 在構建 global ordinals 數據結構時佔用的內存。

有一點注意的是,Elasticsearch 只有在單個 shard 包含多個 segments 時,才需要構建 global ordinals,所以對於不再更新的索引,儘量將其 merge 到一個 segments,這樣在配置 Fielddata circuit breaker 時只需要評估還有可能變化的索引佔用的內存即可。

Fielddata circuit breaker 應該略高於 indices.fielddata.cache.size, 防止老數據不能從 Cache 中清除,新的數據不能加載到 Cache。

4. Accounting requests circuit breaker 配置

根據自己的數據的特點,合理評估出 Lucene 佔用的內存百分比,並在此基礎上上浮 5% 左右。

5. Request circuit breaker 配置

大數據量高緯度的聚合查詢十分消耗內存,需要評估業務的數據量和聚合的維度合理設置。建議初始值 20% 左右,然後出現 breaker 觸發時,評估業務的合理性再適當調整配置,或者通過添加物理資源解決。

6. In flight requests circuit breaker 配置

該配置涉及傳輸層的數據傳輸,包括節點間以及節點與客戶端之間的通信,建議保留默認配置 100%。

總結

本章簡要介紹了 Elasticsearch 不同版本中的 breaker 及其配置,並結合我們的經驗,給出了一點配置思路。是否能合理配置 Circuit breaker 是保證 Elasticsearch 能否穩定運行的關鍵, 由於 Elasticsearch 的節點恢復時間成本較高,提前觸發 breaker 要好於節點 OOM

7.x 之前的版本中,大部分的 breaker 在計算內存使用量時都是估算出來的,這就造成很多時候 breaker 不生效,或者過早介入,7.x 之後 breaker 可以根據實際使用量來計算佔用空間,比較精確的控制熔斷。

第 2-5 課:集羣壓測

在業務上線之前,壓力測試是一個十分重要的環節,他不僅能讓你瞭解集羣能夠支撐多大的請求量,以便在業務增長過程中提前擴容,同時在壓力場景下也能提前發現以下不常見的問題。

由於業務數據的千差萬別,除了參考 Elasticsearch 集羣的基準測試指標,每個業務都應該使用自己的數據進行全鏈路的壓力測試。

你可以使用很多工具進行壓力測試,例如編寫 shell 腳本使用 curl、ab 等命令行工具,也可以自己開發壓測工具或者使用 Jmeter 進行壓測,在此我們建議使用官方的壓測工具:esrally 。官方也是使用這個工具進行壓力測試的, 使用 esrally 可以做到:

  • 得到讀寫能力,讀寫 QPS 能達到多少?
  • 對壓測結果進行對比,例如不同版本,不同數據,不同索引設置下的性能差異,例如關閉 _all之後寫入性能可以提高多少?
  • 同時監控 JVM 信息,可以觀察 GC 等指標是否存在異常。

1 esrally 的安裝和配置

esrally 是 Elastic 的開源項目,由 python3 編寫,安裝 esrally 的系統環境需求如下:

  • 爲了避免多個客戶端從磁盤數據成爲性能瓶頸,最好使用 SSD;
  • 操作系統支持 Linux 以及 MacOS,不支持 Windows;
  • Python 3.4 及以上;
  • Python3 頭文件;
  • pip3
  • git 1.9 及以上;
  • JDK 8,並且正確設置了 JAVA_HOME 環境變量;

當上述環境準備就緒後,可以通過 pip3 簡單安裝:

pip3 install esrally

安裝完畢後,esrally 所需的配置文件等已經被安裝到默認位置,你可以運行下面的命令重新生成這些默認配置:

esrally configure

如果想要修改默認的配置文件路徑,可以運行下面的命令進行高級設置:

esrally configure --advanced-config

2 基本概念

壓測工具引用了很多汽車拉力賽中的概念,要學會使用 esrally 必須理解這些術語。

track 賽道,在 esrally 中指測試數據以及對這些測試數據執行哪些操作,esrally 自帶了一些測試數據,執行:

esrally list tracks

命令可以查看目前都有哪些 track

我們以 geonames/track.json 爲例看看一個 trace 都包含了哪些東西:

在這一堆信息中只需要重點關注幾個字段: indices:描述了測試時數據寫入到哪個索引,以及測試數據的 json 文件名稱 challenges:描述了測試過程中都要執行哪些操作

challenge 

在賽道上執行哪些挑戰。此處只對數據執行哪些壓測操作。這些操作的部分截圖如下:

可以看到先執行刪除索引,然後創建索引,檢查集羣健康,然後執行索引寫入操作。

car 賽車,這裏待測試的指 Elasticsearch實例,可以爲每個實例進行不同的配置。通過下面的命令查看都有哪些自帶的 car

esrally list cars

race 進行一次比賽,此處指進行一次壓測,進行一次比賽要指定賽道,賽車,進行什麼挑戰。此處需要指定 track,challenge,car。通過下面的命令可以查看已經執行過的壓測

esrally list races

Tournament 錦標賽,由多次 race 組成一個

3 執行壓測

esrally 可以自行下載指定版本的 Elasticsearch進 行測試,也可以對已有集羣進行測試。如果想要對比不同版本,不同 Elasticsearch 配置,開啓_all 與否等性能差異,那麼建議使用 esrally 管理的 Elasticsearch 實例。

如果只想驗證一下讀寫吞吐量,可以使用外部集羣,運行 esrally 的服務器與 Elasticsearch 集羣獨立部署也可以讓測試結果更準確。現在我們先使用 esrally 自己管理的 Elasticsearch 實例快速執行一個簡單的壓測

esrally --distribution-version=6.5.1   --track=geonames  --challenge=append-no-conflicts --car="4gheap"  --test-mode --user-tag="demo:test"

--distribution-version

esrally 會下載 6.5.1 版本的 Elasticsearch

--track

使用 geonames 這個數據集

--challenge

執行 append-no-conflicts 操作序列

--car 使用 Elasticsearch 實例配置爲 4gheap

--test-mode

由於這個數據集比較大,我們爲了快速完成壓測示例,通過此參數只使用 1000 條數據進行壓測。

--user-tag

參數爲本次壓測指定一個標籤,便於在多次壓測之間進行區分。

壓測開始運行後,正常情況下其輸出信息如下:

壓測完成後會產生詳細的壓測結果信息,部分結果如下:

這些結果包括索引寫入速度,寫入延遲,以及 JVM 的 GC 情況等我們關心的指標。你可以在運行壓測時指定 --report-file=xx 來將壓測結果單獨保存到一個文件中。

如果使用相同的數據集對外部已有集羣進行壓測,則對應的命令如下:

esrally  --pipeline=benchmark-only --target-hosts=hostname:9200 --client-options="basic_auth_user:'elastic',basic_auth_password:'xxxxxx'" --track=geonames  --challenge=append-no-conflicts   --test-mode --user-tag="demo:mycluster"

--pipeline 簡單的理解就是帶測試的 Elasticsearch 集羣來着哪裏,包括直接下載發行版,從源碼編譯,或者使用外部集羣,要對外部已有集羣進行壓測,此處需要設置爲 benchmark-only

--client-options 指定客戶端附加選項,這些選項會設置到發送到 Elasticsearch 集羣的請求中,如果目標集羣開啓了安全認證,我們需要在此處指定用戶名和密碼。

啓動壓測後,esrally 會彈出如下警告,測試外部集羣時,esrally 無法收集目標主機的 CPU 利用率等信息,對這個警告不必緊張。

4 對比壓測結果

現在,我們進行了兩次壓測,可以將兩次壓測結果進行對比,先執行下面的命令列出我們執行過的 race:

esrally list races

輸出信息如下:

Recent races:

 
Race Timestamp    Track     Track Parameters    Challenge            Car       User Tags
----------------  --------  ------------------  -------------------  --------  -----------------------------
20190212T102025Z  geonames                      append-no-conflicts  external  demo=mycluster
20190212T095938Z  geonames                      append-no-conflicts  4gheap    demo=test

User Tags 列可以讓我們容易區分兩次 race,現在我們以 demo=test 爲基準測試,對比 demo=mycluster 的測試結果。在進行對比時,需要指定 race 的時間戳,也就是上面結果中的第一列:

esrally compare --baseline=20190212T095938Z --contender=20190212T102025Z

輸出信息的前幾列如下:

對比結果給出了每個指標相對於基準測試的 diff 量,可以非常方便地看到壓測結果之間的差異。

5 自定義 track

我們通常需要使用自己的數據進行測試,這就需要自己定義 track。esrally 自帶的 track 默認存儲在 ~/.rally/benchmarks/tracks/default 目錄下,我們自己定義的 track 可以放在這個目錄下也可以放到其他目錄,使用的時候通過 --track-path= 參數指定存儲目錄。下面我們來定義一個名爲 demo 的最簡單 track。

1. 準備樣本數據

先創建用於存儲 track 相關文件的目錄,目錄名就是未來的 trace 名稱:

mkdir ~/test/demo

track 所需的樣本數據爲 json 格式,結構與 bulk 所需的類似,在最簡單的例子中,我們在 documents.json 中寫入1000行相同的內容:

cat documents.json |head -n 3
{"name":"zhangchao"}
{"name":"zhangchao"}
{"name":"zhangchao"}

2. 定義索引映射

樣本數據準備好之後,我們需要爲其配置索引映射和設置信息,我們建立 index.json 文件,寫入如下內容:

{
    "settings": {
        "index.refresh_interval": "120s",
        "index.translog.durability" : "async",
        "index.translog.flush_threshold_size" : "5000mb",
        "index.translog.sync_interval" : "120s",
        "index.number_of_replicas": 0,
        "index.number_of_shards": 8
    },
    "mappings": {
        "doc": {
            "dynamic": false,
            "properties": {
                "name":{"type":"keyword"}
            }
        }
    }
}

在 index.json 中添加你自己的索引設置,並在 mappings 中描述字段類型等信息。

3. 編寫 track.json 文件

該配置文件是 track 的核心配置文件,本例中,編寫內容如下:

{% import "rally.helpers" as rally %}
{
  "version": 2,
  "description": "Demo benchmark for Rally",
  "indices": [
    {
      "name": "demo",
      "body": "index.json", //索引的mapping
      "types": [ "doc" ]
    }
  ],
  "corpora": [
    {
      "name": "rally-demo",
      "documents": [
        {
          "source-file": "documents.json", //測試所用的數據
          "document-count": 1000,
          "uncompressed-bytes": 21000
        }
      ]
    }
  ],
"operations": [
    {{ rally.collect(parts="operations/*.json") }}
  ],
  "challenges": [
    {{ rally.collect(parts="challenges/*.json") }}
  ]
}
  • description

此處的描述是 esrally list tracks 命令輸出的 tracks 描述信息。

  • indices

寫入 Elasticsearch 集羣時目標索引信息。 name:索引名稱; body:索引設置信息文件名; types:索引的 type;

  • corpora

指定樣本數據文件及文件信息。 source-file:樣本數據文件名 document-count:樣本數據文件行數,必須與實際文件嚴格一致,可以通過 wc -l documents.json 命令來計算。 uncompressed-bytes:樣本數據文件字節數,必須與實際文件嚴格一致,可以通過 ls -l documents.json 命令來計算

  • operations

用來自定義 operation 名稱,此處我們放到 operations 目錄下獨立的 json 文件中。

  • challenges

描述自定義的 challenges 要執行哪些操作,此處我們放到 challenges 目錄下的獨立文件中。

4. 自定義 operations

這裏自定義某個操作應該如何執行,在我們的例子中,我們定義索引文檔操作以及兩種查詢請求要執行的操作:

 {
      "name": "index-append",
      "operation-type": "bulk",
      "bulk-size": {{bulk_size | default(5000)}},
      "ingest-percentage": {{ingest_percentage | default(100)}}
},
{
      "name": "default",
      "operation-type": "search",
      "body": {
        "query": {
          "match_all": {}
        }
      }
    },
    {
      "name": "term",
      "operation-type": "search",
      "body": {
        "query": {
          "term": {
            "method": "GET"
          }
        }
      }
    },

這個文件不必完全重寫,我們可以從 esrally 自帶的 track 目錄中拷貝 operations/default.json到自己的目錄下進行修改。

5. 自定義 challenges

我們先定義一個寫數據的 challenges,內容如下:

cat challenges/index.json

 
{
  "name": "index",
  "default": false,
  "schedule": [
    {
      "operation": {
        "operation-type": "delete-index"
      }
    },
    {
      "operation": {
        "operation-type": "create-index"
      }
    },
    {
      "operation": {
        "operation-type": "cluster-health",
        "request-params": {
          "wait_for_status": "green"
        }
      }
    },
    {
          "operation": "index-append",
                "warmup-time-period": 120,
          "clients": {{bulk_indexing_clients | default(16)}}
    },
    {
      "operation": {
        "operation-type": "refresh"
      }
    },
    {
      "operation": {
        "operation-type": "force-merge"
      }
    }
  ]
}

name 指定該 challenge 的名稱,在運行 race 的時候, --challenge 參數 指定的就是這個名稱

schedule 指定要執行的操作序列。在我們的例子中,依次執行刪除索引、創建索引、檢查集羣健康,寫入數據,執行刷新、執行 force-merge

接下來,我們再創建一個執行查詢的 challenge,內容如下:

cat operations/default.json

 
{
  "name": "query",
  "default": true,
  "schedule": [
    {
      "operation": {
        "operation-type": "cluster-health",
        "request-params": {
          "wait_for_status": "green"
        }
      }
    },
    {
      "operation": "term",
      "clients": 8,
      "warmup-iterations": 1000,
      "iterations": 10
    },
    {
      "operation": "match",
      "clients": 8,
      "warmup-iterations": 1000,
      "iterations": 10
    }
  ]
}

該 challenge 同樣先檢查集羣狀態,然後依次執行我們預定義的 term 和 match 操作。

你也可以不將自定義 operations 放到單獨目錄中,而是在自定義 challenge 的時候直接合並在一起,但是分開來可以讓自定義 challenge 的文件開起來更清晰一些。

同樣,自定義的 operations 內容也可以直接寫在 track.json 文件中,但是分開更清晰。

到此,我們自定義的 track 已經準備完畢,demo 目錄下的文件結構如下:

tree demo
demo
├── challenges
│   ├── index.json
│   └── query.json
├── documents.json
├── index.json
├── operations
│   └── default.json
└── track.json

執行下面的命令可以看到我們創建完畢的 track:

esrally list tracks --track-path=~/test/demo

輸出信息如下:

現在,我們可以通過下面的命令使用剛剛創建的 track 進行測試:

esrally  --track-path=/home/es/test/demo --pipeline=benchmark-only --target-hosts=hostname:9200 --client-options="basic_auth_user:'elastic',basic_auth_password:'xxxxxxx'"  --challenge=index    --user-tag="demo:mycluster_customtrack_index"

由於已經使用 --track-path 指定 track,因此不再使用 --track 來指定 track 名稱。

總結

使用 esrally 可以很方便的完成我們的壓測需求,但是實際使用過程中可能會因爲 python3 的環境遇到一些問題,因此也可以在 docker 中運行 esrally,將樣本數據放在容器之外,然後將目錄掛載到容器中,不會對性能測試產生多少影響。

現在已經有一些安裝好 esrally 的 docker 鏡像,可以通過 docker search esrally 命令來搜索可用鏡像。

下一節我們介紹集羣監控,Elasticsearch 的監控指標很多,我們將介紹一些需要重點關注的監控項。

第 2-6 課:集羣監控

爲了能夠提前發現問題,以及在出現故障後便於定位問題,我們需要對集羣進行監控,對於一個完整的Elasticsearch 集羣監控系統來說,需要的的指標非常多,這裏我們列出一些比較重要的。

1 集羣級監控指標

1.1 集羣健康

集羣健康是最基礎的指標,他是快速衡量集羣是否正常的依據,當集羣 Yellow 的時候,代表有部分副分片尚未分配,導致未分配的原因很多。

例如節點離線等,從分佈式系統的角度來說意味着數據缺少副本,系統會嘗試將他自動補齊,因此可以不把 Yellow 作爲一種報警狀態。集羣處於 Yellow 狀態時也可以正常執行入庫操作。

另外當創建新索引時,集羣也可能會出現短暫的從 Red 到 Yellow 再到 Green 的狀態,因爲創建索引時,可能需要分配多個分片,在全部的分片分配完畢之前,該索引在集羣狀態中屬於分片不完整的狀態,因此短暫的 Red 也屬於正常現象

集羣健康可以通過 _cluster/health 接口來查看。

1.2 讀寫 QPS(每秒查詢率QPS是對一個特定的查詢服務器在規定時間內所處理流量多少的衡量標準。)

Elasticsearch 沒有提供實時計算出好讀寫 QPS 值,實時計算這些值會對集羣造成比較大的壓力。

他提供了一個接口返回每個節點當前已處理的請求數量,你需要基於此進行計算:發送兩次請求,中間間隔一段時間,用兩次返回的請求數量做差值,得到間隔時間內的增量,再把每個節點的增量累加起來,除以兩次請求的間隔時間,得到整個集羣的 QPS。兩次請求的間隔時間不要太短,建議在 10s 及以上。

節點的請求統計信息通過 _nodes/stats 接口獲取,對於計算讀寫 QPS 來說,我們所需信息如下:

"indexing" : {
  "index_total" : 141310,
}
"search" : {
  "query_total" : 5772,
},

index_total節點收到的索引請求總量;

query_total節點收到的查詢請求總量;

通過這種方式計算出的 QPS 並非業務直接發起的讀寫請求 QPS,而是分片級別的。例如,只有一個索引,索引只有1個分片,那麼我們計算出的 QPS 等於業務發起的請求 QPS,如果索引有5個分片,那麼計算出的 QPS 等於業務發起的 QPS*5。因此,無論是查詢還是索引請求:

計算出的 QPS = 業務發起的 QPS * 分片數量

在 Kibana 的 Monitor 中看到的 Search Rate (/s) 與 Indexing Rate (/s) 的涵義與我們上面的描述相同。

1.3 讀寫延遲

與讀寫 QPS 類似,讀取延遲也可以通過 _nodes/stats 接口返回的信息進行計算,對於讀寫延遲來說,所需信息如下:

"indexing" : {
  "index_time_in_millis" : 54404,
}
"search" : {
  "query_time_in_millis" : 5347,
  "fetch_time_in_millis" : 1465,
},

查詢由兩個階段完成,因此 query 耗時與 fetch 單獨給出,對於整個搜索請求耗時來說需要把它加起來。由於這種方式計算出來的的採樣週期內的平均值,因此只能給監控提供大致的參考,如果需要診斷慢請求需要參考慢查詢或慢索引日誌。

1.4 分片信息

我們還需要關注有多少分片處於異常在狀態,這些信息都在 _cluster/health 的返回結果中,包括:

  • initializing_shards

正在執行初始化的分片數量,當一個分片開始從 UNASSIGNED 狀態變爲 STARTED 時,從分片分配操作一開始,該分片被標記爲 INITIALIZING 狀態。例如創建新索引、恢復現有分片時,都會產生這個狀態。

  • unassigned_shards

待分配的分片數量,包括主分片和副分片。

  • delayed_unassigned_shards

由於一些原因延遲分配的分片數量。例如配置了 index.unassigned.node_left.delayed_timeout,節點離線時會產生延遲分配的分片。

2 節點級別指標

2.1 JVM 指標

JVM 指標也在 _nodes/stats API 的返回結果中,每個節點的信息單獨給出。需要重點關注的指標如下:

1. 堆內存使用率

字段 heap_used_percent 代表堆內存使用率百分比,如果堆內存長期居高則意味着集羣可能需要擴容

JVM 內存使用率過高,且無法 GC 掉時,集羣處於比較危險的狀態,當一個比較大的聚合請求過來,或者短期內讀寫壓力增大時可能會導致節點 OOM。

如果 master 節點的堆內存使用率過高更需要警惕,當重啓集羣時,master 節點執行 gateway 及 recovery 都可能需要比較多的內存,這和分片數量有關,因此可能在重啓集羣的時候內存不足,有時需要關閉一些索引才能讓集羣啓動成功。

2. GC 次數和時長

年輕代和老年代的回收次數與持續時間最好都被監控,如果年輕代 GC 頻繁,可能意味着爲年輕代分配的空間過小,如果老年代 GC 頻繁,可能意味着需要進行擴容。

"gc" : {
  "collectors" : {
    "young" : {
      "collection_count" : 44,
      "collection_time_in_millis" : 2678
    },
    "old" : {
      "collection_count" : 2,
      "collection_time_in_millis" : 493
    }
  }
},

正常情況下,通過 REST API 獲取這些指標不是問題,但是當節點長時間 GC 時,接口無法返回結果,導致無法發現問題,因此建議使用 jstat 等外部工具對 JVM 進行監控。

2.2 線程池

關注線程池信息可以讓我們瞭解到節點負載狀態,有多少個線程正在幹活,Elasticsearch 有很多線程池,一般我們可以重點關注執行搜索和索引的線程信息,可以通過 _nodes/stats API 或 _cat/thread_pool API 來獲取線程池信息,建議使用 _nodes/stats API,你可以在一個請求的結果中得到很多監控指標,我們最好少發一些 stats 之類的請求到 Elasticsearch 集羣。

"bulk" : {
  "active" : 0,
  "rejected" : 0,
},
"search" : {
  "active" : 0,
  "rejected" : 0,
},

active 正在運行任務的線程個數;

rejected 由於線程池隊列已滿,拒絕的請求數量;

客戶端對於被拒絕的請求應該執行延遲重試,更多信息可以參考《Elasticsearch 源碼解析與優化實戰》

3 操作系統及硬件

只監控 Elasticsearch 集羣本身的指標是不夠的,我們必須結合操作系統和硬件信息一起監控。這裏只給出建議重點關注的指標,如何獲取這些指標的方法很多,本文不再過多敘述。

3.1 磁盤利用率

這裏的磁盤利用率不是指使用了多少空間,而是指 iostat 返回的 %util。服務器一般會掛載多個磁盤,你不比經常關心每個磁盤的 %util 有多少,但是需要注意下磁盤 util 長時間處於 100%, 尤其是隻有個別磁盤的 util 長時間處於100%,這可能是分片不均或熱點數據過於集中導致。

3.2 壞盤

目前 Elasticsearch 對磁盤的管理有些不足,因此我們需要外部手段檢查、監控壞盤的產生並及時更換。壞盤對集羣的穩定性有較大影響

3.3 內存利用率

一般不比對操作系統內存進行監控,Elasticsearch 會佔用大量的 page cache,這些都存儲在操作系統的物理內存中。因此發現操作系統的 free 內存很少不必緊張,特別注意不要手工回收 cache,這會對集羣性能產生較嚴重影響。

總結

本章從 Elasticsearch 集羣角度和操作系統角度介紹了需要重點關注的監控項,在設計監控系統的時候,需要注意發起獲取指標的請求頻率不要太高,有些請求需要 master 節點到各個數據節點去收集,頻繁的 _cat/indices 之類請求會對集羣造成比較大的壓力。

第 2-7 課:集羣擴容

隨着時間的推移、業務的發展,你存入 Elasticsearch 的數據會越來越多;隨着用戶量的增加,系統響應時間增加。

總有一天,初始安裝的集羣規模無法滿足日益增長的需求,這時需要考慮對現有的 Elasticsearch 集羣進行擴容。

1 擴容方式

爲了提高系統的處理能力,包括增加系統的 cpu、內存、存儲等資源,通常有兩種擴容方式:垂直擴容和水平擴容。

1.1 垂直擴容

增加單機處理能力,如購買更好的 cpu,增加 cpu 核心數;將機械硬盤換成 SSD,提高 IO 能力;購買更大容量的內存條,提高內存容量滿足計算需求;升級萬兆網卡,提高網絡帶寬等。

1.2 水平擴容

通過增加服務器的數量,將服務器形成分佈式的集羣,以提高整個系統的計算、存儲、IO 能力,滿足業務的需求。水平擴容通常需要軟件在架構層面上的支持。

2 定位硬件瓶頸

我們現在集羣的處理能力能夠滿足業務需求嗎?何時需要擴容?你肯定不希望你的線上業務由於硬件資源不夠掛掉。爲了解答這些問題,我們首先要定位出當前的硬件資源是否存在瓶頸。下面列出我們常用的定位 Elasticsearch 存在硬件資源瓶頸的一些辦法。

2.1 cpu

Elasticsearch 索引和查詢過程都是 cpu 密集型的操作,如果 cpu 存在瓶頸,系統性能會受到很大影響。那採用什麼指標來定位 cpu 瓶頸呢?我們一般通過如下幾個方法來定位:

  • 通過操作系統的監控命令

sar 命令: sarSystem Activity Reporter系統活動情況報告)是目前 Linux 上最爲全面的系統性能分析工具之一

bash-4.2$ sar -u

執行該命令後,輸出如下:

    07:44:02 PM     CPU     %user     %nice   %system   %iowait    %steal     %idle
    07:46:01 PM     all     11.39      0.00      1.64      0.21      0.00     86.76
    07:48:01 PM     all     10.40      0.00      1.50      0.07      0.00     88.03
    07:50:01 PM     all      9.37      0.00      1.34      0.07      0.00     89.23
    07:52:01 PM     all     12.01      0.00      1.55      0.05      0.00     86.40
    07:54:01 PM     all      9.58      0.00      1.41      0.02      0.00     88.98
    07:56:01 PM     all     10.16      0.00      1.49      0.15      0.00     88.20
    07:58:01 PM     all     10.04      0.00      1.44      0.02      0.00     88.50
    08:00:01 PM     all     10.39      0.00      1.51      0.03      0.00     88.07
    08:02:01 PM     all     10.90      0.00      1.55      0.05      0.00     87.50
    08:04:01 PM     all      9.29      0.00      1.43      0.02      0.00     89.26
    08:06:01 PM     all      9.91      0.00      1.40      0.05      0.00     88.64
08:08:01 PM     all     10.56      0.00      1.53      0.08      0.00     87.84

輸出項說明:

CPUall 表示統計信息爲所有 CPU 的平均值。

%user:顯示在用戶級別(application)運行使用 CPU 總時間的百分比。

%nice:顯示在用戶級別,用於nice操作,所佔用 CPU 總時間的百分比。

%system:在覈心級別(kernel)運行所使用 CPU 總時間的百分比。

%iowait:顯示用於等待I/O操作佔用 CPU 總時間的百分比。

%steal:管理程序(hypervisor)爲另一個虛擬進程提供服務而等待虛擬 CPU 的百分比。

%idle:顯示 CPU 空閒時間佔用 CPU 總時間的百分比。

1. %iowait 的值過高,表示硬盤存在I/O瓶頸

2. %idle 的值高但系統響應慢時,有可能是 CPU 等待分配內存,此時應加大內存容量

3. %idle 的值持續低於1,則系統的 CPU 處理能力相對較低,表明系統中最需要解決的資源是 CPU

如果系統 cpu 使用率持續超過 60%,而且後期壓力會隨着業務的發展持續增加,就需要考慮擴容來減輕 cpu 的壓力。

  • 通過查看 Elasticsearch 的 Threadpool
    curl -sXGET 'http://localhost:9200/_cat/thread_pool?v' | grep -E "node_name|search|bulk"

如果 active 一列在一段時間內持續達到了線程數的最大值,或者 rejected 不爲 0,則意味着可能 cpu 資源不足,導致了請求拒絕。也可能瞬時寫入併發過大,mapping 設置不合理等。

2.2 內存

Elasticsearch 高效穩定的運行,十分依賴內存,包括 Java 進程佔用的內存和操作系統 Cache Lucene 索引文件佔用的內存。

  • Java 堆內內存不足

對於 Java 進程來說,我們一般從如下幾個方面判斷是否運行正常:

  1. minor gc 耗時超過 50ms
  2. minor gc 執行很頻繁,10s 以內會執行一次
  3. minor gc 後 eden 還佔用很大比例空間
  4. minor gc 後,survior 容納不下 eden 和另一個 survior 的存活對象,發生了過早提升
  5. fullgc 平均執行時間超過1s
  6. fullgc 10分鐘以內會執行一次

如果調優 GC 參數後,gc 仍然存在問題,則需要適當增加 Java 進程的內存,但是單個節點的內存要小於 32G,繼續觀察,直到運行平穩。

  • 操作系統 Cache 抖動

Elasticsearch 底層基於 Lucene,Lucene 的高效運行需要依賴操作系統 Cache,操作系統頻繁發生換頁,Cache 抖動嚴重,對運行速度會產生很大的影響,產生一系列的問題,導致內存使用效率降低,引發磁盤 IO 升高,cpu 的 IO Wait 增加,從而使系統的整體吞吐量和響應時間收到極大的影響。換頁行爲可以通過操作系統的 sar 命令來定位。

sar -B 

執行該命令後,輸入如下:

07:44:01 PM  pgpgin/s pgpgout/s   fault/s  majflt/s  pgfree/s pgscank/s pgscand/s pgsteal/s    %vmeff
07:46:01 PM  43267.29    389.22 293717.84      0.19  23414.69      0.00      0.00      0.00      0.00
07:48:01 PM  49302.07    982.05 305202.18      0.08  32241.47   3183.44     41.29   3223.85     99.97
07:50:01 PM  49409.41    524.31 297695.93      0.05  40182.40   8982.75    155.12   9126.79     99.88
07:52:01 PM  54349.32    432.02 309908.67      0.02  39272.66   9135.48    567.22   8363.67     86.20
07:54:01 PM  61406.73    348.05 326887.23      0.15  45224.42  20481.31   3296.59  15797.72     66.44
07:56:01 PM  58327.98    130.05 294911.57      0.12  41707.75  15614.09   1915.41  14558.13     83.05
07:58:01 PM  53790.35    442.78 293988.20      0.09  38539.34  13727.40   1719.69  13166.00     85.23
08:00:01 PM  59534.64    279.43 304241.40      0.03  41120.01  19764.74   1896.83  14958.18     69.05
08:02:01 PM  57026.47    204.59 292543.72      0.06  40701.89  18893.40   2479.08  14743.70     68.98
08:04:01 PM  39415.95    447.89 224081.73      0.07  36275.20  10158.59   1542.77  10016.37     85.60
08:06:01 PM  25112.09    668.31 204173.12      0.38  20699.20   3939.11   1371.64   5310.75    100.00
08:08:01 PM  24780.95    656.56 200126.48      0.02  54840.20   1024.04    213.75   1237.79    100.00

輸出項說明:

pgpgin/s:表示每秒從磁盤或SWAP置換到內存的字節數(KB)

pgpgout/s:表示每秒從內存置換到磁盤或SWAP的字節數(KB)

fault/s:每秒鐘系統產生的缺頁數,即主缺頁與次缺頁之和(major + minor)

majflt/s:每秒鐘產生的主缺頁數.

pgfree/s:每秒被放入空閒隊列中的頁個數

pgscank/s:每秒被kswapd掃描的頁個數

pgscand/s:每秒直接被掃描的頁個數

pgsteal/s:每秒鐘從cache中被清除來滿足內存需要的頁個數

%vmeff:每秒清除的頁(pgsteal)佔總掃描頁(pgscank+pgscand)的百分比

如果 %vmeff 一列持續低於 30,同時伴有比較高的 pgpgin/spgpgout/spgscand/s,說明系統內存很緊張了,如果通過優化配置無法有效降低該列的值,需要擴容來緩解系統內存不足的情況。

2.3 磁盤

磁盤瓶頸一般分爲兩種:磁盤空間不足和磁盤讀寫壓力大。

磁盤空間 磁盤間不足,這很容易理解,現有硬件的磁盤容量已經無法滿足業務的需求。

讀寫壓力大 磁盤 IO 讀寫壓力比較大,磁盤使用率長期處於較高狀態,導致系統的 IO Wait 增加,此時需要增加節點,將 shard 分佈到更多的硬件磁盤上,以降低磁盤的 IO 壓力。

2.4 網絡

Elasticsearch 在運行過程中,很多操作都會佔用很大的網絡帶寬,比如大批量的索引操作、scroll 查詢拉取大量的結果、分片恢復拷貝索引文件、分片平衡操作。

如果網絡存在瓶頸,不但會影響這些操作的執行效率,而且影響節點間的內部通信的穩定性,會使集羣出現不穩定的情況,頻繁發生節點離線的情況。網絡瓶頸定位使用 iftopiptrafsar 命令。下面以 sar 命令爲例:

bash-4.2$ sar -n DEV

輸出結果如下:

07:12:01 AM     IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s
07:14:01 AM      eth0   5130.99   8146.84   2933.22   7403.37      0.00      0.00      0.54
07:14:01 AM      eth1      0.00      0.00      0.00      0.00      0.00      0.00      0.00
07:14:01 AM        lo    296.09    296.09    121.74    121.74      0.00      0.00      0.00
07:16:01 AM      eth0   4240.99   7463.91   2490.03   7436.86      0.00      0.00      0.53
07:16:01 AM      eth1      0.00      0.00      0.00      0.00      0.00      0.00      0.00
07:16:01 AM        lo    300.13    300.13    129.30    129.30      0.00      0.00      0.00
07:18:01 AM      eth0   4529.47   7955.89   2639.50   7795.74      0.00      0.00      0.53
07:18:01 AM      eth1      0.00      0.00      0.00      0.00      0.00      0.00      0.00
07:18:01 AM        lo    303.97    303.97    123.36    123.36      0.00      0.00      0.00

重點關注 rxkB/stxkB/s 兩列,如果這兩列的值持續接近網絡帶寬的極限,那就必須提升集羣的網絡配置,比如升級萬兆網卡、交換機,如果集羣跨機房,申請更多的跨機房帶寬。

3 擴容注意事項

當新的服務器準備好之後,在新的 Elasticsearch 節點加入到集羣之前,集羣擴容的過程中有幾點需要注意

3.1 調整最小主節點數

最小主節點數的配置約束爲多數,由於擴容後集羣節點總數增加,有可能導致原來配置的最小主節點數不足多數,因此可能需要對該參數進行調整。

如果不進行調整,集羣可能會腦裂,對該參數的調整非常重要。現在可以通過 REST API 來調整

PUT /_cluster/settings
{
    "persistent" : {
        "discovery.zen.minimum_master_nodes" : $X
    }
}

該配置會立即更新,並且持久化到集羣狀態中,此處的配置會優先於配置文件中的相同配置。也就是說如果配置文件中的值不同,最終會以 REST API 的設置爲準。這樣,你原來的集羣就無需重啓。

3.2 調整節點分片總數

total_shards_per_node 用來限制某個索引的分片在單個節點上最多允許分配多少個。當集羣擴容後,爲了讓分片分佈到更多的節點,利用更多的資源,該值可能需要進行調整,可以通過 REST API 來動態調整。下列參數調整單個索引的配置

index.routing.allocation.total_shards_per_node

或者通過下列參數調整整個集羣的配置,對所有的索引都生效:

cluster.routing.allocation.total_shards_per_node

如果你原來的集羣沒有配置 total_shards_per_node,那麼在擴容之前!!我們強烈建議你先計算好該值設置進去!!,因爲 Elasticsearch 的分片分配策略下會盡量保證節點上的分片數大致相同,而擴容進來的新節點上還沒有任何分片,這會導致新創建的索引集中在擴容進來的新節點,熱點數據過於集中,產生性能問題。

3.3 集羣原有的分片會自動遷移到新節點嗎?

答案是會的,Elasticsearch 會把分片遷移到新增的節點上,最終讓節點間的分片數量大致均衡,這個過程稱爲 rebalance 。默認情況下,執行 rebalance 的併發數爲 2,可以通過下面的參數進行調整:

cluster.routing.allocation.cluster_concurrent_rebalance

Elasticsearch 中,Peer recovery 負責副分片的數據恢復,增加副分片,以及 rebalance 等所有把數據從主分片拷貝到另一個節點的過程。因此 rebalance 期間的流量限速可以通過 Peer recovery 的限速開關進行調整:

indices.recovery.max_bytes_per_sec //每秒的最大字節數量

同理,你也可以使用 _cat/recovery API 查看數據遷移的狀態和進度。

數據均衡策略並不會讓節點間的分片數量分佈完全一致,而是允許存在一定量的差異,有時候我們可能希望集羣自己少做一些 rebalance 的操作,容忍節點間的分片數差異更多一點,可以通過調整一些權重值來實現:

  • cluster.routing.allocation.balance.shard

基於分片數量的權重因子,提高此值使集羣中所有節點之間的分片數量更接近相等,默認值 0.45f

  • cluster.routing.allocation.balance.index

基於某個索引所有分片的權重因子,提高此值使集羣中所有節點上某個索引的分片數量更接近相等,默認值0.55f

  • cluster.routing.allocation.balance.threshold

內部根據權重計算之後的值如果大於 threshold,就執行 rebalance,因此提高此值可以降低執行 rebalance 操作的積極性

總結

本課重點分析了定位系統硬件瓶頸的方法,當從軟件層面不能有效改善系統運行性能時,可以採用本課提供的方式去分析是否存在硬件瓶頸。

對 Elasticsearch 集羣的擴容是平滑的過程,期間不會影響業務使用,但是一定要注意到本文提及的幾個事項,避免線上事故。下一節課我們介紹下集羣數據遷移。

第 2-8 課:集羣遷移

在使用 Elasticsearch 過程中,你會發現你偶爾需要將一個集羣的數據遷移到另一個集羣,或者把索引的數據遷移到另一個具有不同 mapping 或者分片數的索引。本章總結常見的遷移場景和遷移方法。

1. 數據遷移場景

(1) mapping 發生了改變

Elasticsearch 的 scheme 十分靈活,支持給類型動態添加新的字段, 6.x 之前的版本支持給索引動態的添加新的類型。但是不支持修改已有的字段的類型,也不能使用新的分詞器對已有的字段進行分析,因未這會影響到已有數據的搜索。

所以,如果業務中 mapping 發生了變化,而你又必須保留歷史數據,最簡單和直接的辦法就是根據新的 mapping 創建好新索引,然後降歷史數據從歷史索引遷移到新索引。

(2) 調整分片數

默認情況下,Elasticsearch 創建的索引分片數爲 5 個。或許你在創建索引初期也評估了分片數的設置,但是後期仍然需要調大索引的分片數,如果您使用的是 6.1 之後的版本,那可以採用 shard split 功能。否則只能按照合理的分片數,建立好目標索引,然後降索引數據從歷史索引遷移到新索引。

(3) 拆分索引

隨着業務的發展,前期的設計無法滿足目前的性能要求和業務場景。比如索引需要按天劃分而不是按月或按周。索引需要按類型劃分而不是降多個類型存儲到單個索引中等,需要按照合理的方式拆分好目標索引,並將數據從歷史索引遷移到新索引

(4) 機房變化

由於某種原因,數據從一個數據中心遷移到另一個數據中心。涉及集羣數據的整體搬遷。

2. 遷移前需要考慮的問題

(1) _source是否啓用?

Elasticsearch 默認啓用 \_source字段,\_source字段存儲了原始 json 文檔,\_source並不會被索引,它主要用於查詢結果展示。Elasticsearch 的 reindex api依賴該字段;而且在沒有原始數據的情況下,如果_source沒有啓用,有些場景的遷移無法完成。

(2) 版本兼容情況

Elasticsearch 不支持加載跨版本的索引數據,比如 6.x 可以加載 5.x 的索引文件,但是不能加載 1.x 及 2.x 的索引文件。snapshot/restore 功能也是如此,不支持跨版本的備份和恢復。

所以,在跨集羣遷移數據前要明確目標集羣和源集羣的 Elasticsearch 版本。如果包含跨越大版本的索引,這部分索引只能通過 reindex 來遷移。

3. 遷移方法

3.1 snapshot/restore

snapshot/restore 是 Elasticsearch 用於對數據進行備份和恢復的一組 api 接口,可以通過 snapshot/restore 接口進行跨集羣的數據遷移,該方法支持索引級、集羣級的 snapshot/restore。

(1) 前提條件

目的集羣的 Elasticsearch 版本號要大於等於源端集羣索引版本號且不能跨越大版本。

(2) 操作步驟

  • 源集羣配置倉庫路徑 修改源集羣中 Elasticsearch 的配置文件,elasticsearch.yml,添加如下配置:
path.repo: ["/data/to/backup/location"]
  • 源集羣中創建倉庫
 curl -XPUT "http://[source_cluster]:[source_port]/_snapshot/backup"  -d '{
     "type": "fs",
     "settings": {
         "location": "/data/to/backup/location" 
         "compress": true
     }
 }'
  • 源集羣創建 snapshot
 curl -XPUT http://[source_cluster]:[source_port]/_snapshot/backup/indices_snapshot_1?wait_for_completion=true
  • 目標集羣配置和創建倉庫

該步驟與源集羣中步驟類似, 不再贅述。

  • 將 snapshot 從源集羣倉庫移動到目的集羣倉庫 –這裏應該是選用直接複製或者scp的方法
  • 目的集羣執行 restore
curl -XPUT "http://[dest_cluster]:[dest_port]/_snapshot/backup/indices_snapshot/_restore"
  • 檢查恢復狀態
 curl -sXGET "http://[dest_cluster]:[dest_port]/_snapshot/_status"

(3) 適用場景

該遷移方式適合大量數據的遷移,支持增量遷移,但是需要比較大的存儲空間來存放 snapshot。

3.2 reindex

reindex 支持集羣內和集羣間的索引數據遷移。

(1) 前提條件

源端索引要啓用 \_source字段, 如果沒有則不能進行 reindex;reindex 需要事先在目標集羣(源集羣和目標集羣可以是同一個集羣)按照要求建立好目標索引,reindex 過程並不會自動降源端索引的設置拷貝到目標索引,否則 reindex 就失去了意義。所以在 reindex 前,要設置好目標索引的 mapping、分片數。

(2) 集羣內 reindex

集羣內 reindex 比較簡單,按照新的需求創建好目標索引後,執行如下命令即可:

POST _reindex
{
  "source": {
    "index": "twitter"
  },
  "dest": {
    "index": "new_twitter"
  }
}

(3) 跨集羣 reindex

跨集羣 reindex 與集羣內 reindex 的主要區別是源端集羣的信息需要配置到目標集羣的 Elasticsearch 配置文件裏,例如:

reindex.remote.whitelist: "otherhost:9200, another:9200"

由於這是靜態配置,配置完成後需要重啓節點。之後可以通過如下命令進行數據遷移:

POST _reindex
{
  "source": {
    "remote": {
      "host": "http://otherhost:9200",
      "username": "user",
      "password": "pass"
    },
    "index": "source",
    "query": {
      "match": {
        "test": "data"
      }
    }
  },
  "dest": {
    "index": "dest"
  }
}

(4) 控制參數

reindex 提供了很多控制參數,下面介紹幾個常用的配置:

  • size

指定遷移的數據條數。

  • _source

指定需要遷移數據中的哪些字段。

  • size in source

可以指定一次 scroll 的數據條數,用來控制 reindex 對源、目的集羣資源消耗壓力。 默認值爲 1000。由於 index 過程比較消耗 cpu 資源,所以需要根據硬件環境合理配置,可以先配置一個較小的值,如果資源壓力不大,逐步加大到合適的值,然後重新啓動 reindex 過程。

  • connect_timeout

跨集羣 reindex 時,遠端集羣連接超時時間,可以根據網絡情況進行調整。默認值時 30s。

  • socket_timeout

跨集羣 reindex 時,遠端集羣的讀超時時間,可以根據網絡情況進行調整。默認值是 30s。

  • slices

可以控制 reindex 的併發度。應用官方文檔的例子:

POST _reindex?slices=5&refresh
{
  "source": {
    "index": "twitter"
  },
  "dest": {
    "index": "new_twitter"
  }
}

注意:較大的值會對源端和目的端帶來資源壓力。需要逐步加大,觀察源集羣和目的集羣的資源適用情況。

(5) 適用場景

該方法依賴源端索引啓用 _source 字段,能夠提供 query 來遷移索引的一部分數據。適用於遷移數據量和索引數都比較小的場景。

3.3 拷貝索引文件:最方便的拷貝方式

(1) 前提條件

源端集羣和目標集羣索引版本兼容,該方法不適用於集羣內遷移,也不能改變目標索引的相關設置。

(2) 遷移步驟

遷移步驟以單個索引爲例。如果有多個,可以根據步驟實現自動化腳本來完成。

  • 禁止源集羣中索引的分片移動
curl -XPUT "http://[source_cluster]:[source_port]/_cluster/settings" -d
'{
  "persistent": {
    "cluster.routing.allocation.enable": "none"
  }
}'
  • 源集羣停止寫入、更新操作,並在源端集羣執行執行 sync_flush
curl -XPOST "http://[source_cluster]:[source_port]/$index/_flush/synced"
  • 查找主分片目錄

Elasticsearch 5.x 之前,索引數據是存放在以索引名爲目錄的文件夾下,5.x 起是存放在 uuid 目錄下,索引首先確定源端索引的 uuid:

curl -sXGET "http://[source_cluster]:[source_port]/_cat/indices/$index?v"

之後確定所有主分片所在節點:

curl -sXGET "http://[source_cluster]:[source_port]/_cat/shards/$index" | grep " p "

最後一列爲各個主分片所在的節點。這樣就可以根據節點配置的 path.data 及索引的 uuid 找到索引存放的位置。

  • 將索引主分片的所有數據拷貝到目標集羣。

採用 rsync 方式,將索引文件從源集羣數據目錄拷貝到目的集羣中的數據目錄中。Elasticsearch 會加載拷貝的索引文件。

  • 檢查遷移結果

待目的集羣將遷移過去的索引加載完成,集羣狀態恢復成 Green 後,檢查目的集羣中該索引的 doc 數是否與源集羣中對應索引的 doc 數是否相等。

  • 打開副本

遷移過程只遷移了主分片,如果索引在目的集羣中有副本需求,需要根據需求設置合理的副本數量。一般保留一個副本即可:

curl -XPUT "http://[dest_cluster]:[dest_port]/$index/_settings" -d '
{
      "index.number_of_replicas": 1
}'

(3) 注意事項

當刪除一個索引時,Elasticsearch 會在集羣狀態裏保存刪除的索引的名稱,防止被刪除的索引被重新加載到集羣,

cluster.indices.tombstones.size, 默認值500。如果目標集羣刪除的索引列表中包含同名代遷移的索引,則拷貝的索引文件會出現不能加載的情況。檢查方法

curl -sXGET 'http://localhost:9200/_cluster/state/metadata?pretty'

執行以上命令,在 “index-graveyard” 部分查找是否有和要導入索引同名的索引。如果存在,可以減少 cluster.indices.tombstones.size 的配置,或者通過腳本創建刪除索引使該索引名從 index graveyard 裏移除,例如:

for((i=0;i<500;i++>))
do
    curl -XPUT "http://localhost:9200/index_should_not_exists_$i";
    curl -XDELETE "http://localhost:9200/index_should_not_exists_$i;
done

(4) 適用場景

該方法採用直接拷貝索引文件的方式,遷移速度完全取決於帶寬, 對帶寬佔用較高,對 cpu 和內存資源佔用很低。適用於有大量數據需要遷移的場景。源端索引不需要啓用 \_source 字段。我們做過多次數據遷移,優先都是採用此方式。

總結

本課介紹了數據遷移的方法及不同方法適用的場景,希望可以幫助需要遷移數據的同學找到合適的遷移

第 2-9 課:常用配置及注意事項

Elasticsearch 各個模塊提供了很多配置參數,用來滿足不同的業務場景。本篇我們結合我們的經驗對參數進行歸類介紹,方便讀者進行配置。主要包括索引性能參數、查詢參數、穩定性參數、數據安全參數、內存參數、allocation 參數、集羣恢復參數。注意以下大多數配置需要在持久性關鍵字下進行包含配置:

persistent :重啓後依然有效
transient :重啓後無效

所以大致的配置規則如下:

PUT /_cluster/settings
{
    "[ persistent| transient]" : {
        "你想要修改的配置項" : "配置所需的參數"
    }
}

1. 索引性能參數

1. index.translog.durability

爲了保證寫入的可靠性,該值的默認參數值爲 request,即,每一次 bulk、index、delete 請求都會執行 translog 的刷盤,會極大的影響整體入庫性能。

如果在對入庫性能要求較高的場景,系統可以接受一定機率的數據丟失,可以將該參數設置成 “async” 方式,並適當增加 translog 的刷盤週期。

2. index.refresh_interval

數據索引到 Elasticsearch 集羣中後,要經過 refresh 這個刷新過程才能被檢索到。爲了提供近實時搜索,默認情況下,該參數的值爲 1 秒,每次 refresh 都會產生一個新的 Lucene segment,會導致後期索引過程中頻繁的 merge。

對於入庫性能要求較高,實時性要求不太高的業務場景,可以結合indices.memory.index_buffer_size參數的大小,適當增加該值。

3. index.merge.policy.max_merged_segment

索引過程中,隨着新數據不斷加入,Lucene 會根據 merge 策略不斷的對已產生的段進行 merge 操作。index.merge.policy.max_merged_segment 參數控制此 merge 過程中產生的最大的段的大小,默認值爲 5gb。爲了提高入庫性能,可以適當降低配置的大小。

4. threadpool.write.queue_size

index、bulk、delete、updaet 等操作的隊列大小,默認值爲 200。隊列滿時,說明已經沒有足夠的 CPU 資源做寫入操作。加大配置並不能有效提高索引性能,但是會增加節點 OOM 的機率。

有一點要注意的是,Elasticsearch 首先在主分片上進行寫入操作,然後同步到副本。爲了保持主副本一致,在主分片寫入後,副本並不會受該設置的限制,所以一個節點如果堆積了大量的副本寫入操作,會增加節點 OOM 的機率。

2. 查詢參數

1. threadpool.search.queue_size

該配置是在elasticsearch.yml,進行修改或者執行rest api:

curl -XPUT 'localhost:9200/_cluster/settings' -d '{

    "transient": {

        "threadpool.index.type": "fixed",

        "threadpool.index.size": 100,

        "threadpool.index.queue_size": 500

    }

}'

查詢隊列大小,默認值爲1000。不建議將該值設置的過大,如果 search queue 持續有數據,需要通過其他策略提高集羣的併發度,比如增加節點、同樣的數據減少分片數等。

2. search.default_search_timeout

控制全局查詢超時時間,默認沒有全局超時。可用通過 Cluster Update Settings 動態修改。在生產集羣,建議設置該值防止耗時很長的查詢語句長期佔用集羣資源,將集羣拖垮。

PUT /_cluster/settings
{
    "persistent" : {
        "search.default_search_timeout" : "35s"
    }
}

3. cluster.routing.use_adaptive_replica_selection

在查詢過程中,Elasticsearch 默認採用 round robin 方式查詢同一個分片的多個副本,該參數可以考慮多種因素,將查發送到最合適的節點, 比如對於包含查詢分片的多個節點,優先發送到查詢隊列較小的節點。生產環境中,建議打開該配置。

PUT /_cluster/settings

{

    "transient": { / transient –- 短暫的設置

        "cluster.routing.use_adaptive_replica_selection": true

    }

}

4. action.search.max_concurrent_shard_requests

限制一個查詢同時查詢的分片數。防止一個查詢佔用整個集羣的查詢資源。該值需要根據業務場景進行合理設置。

5. search.low_level_cancellation

Elasticsearch 支持結束正在執行的查詢任務,但是在默認情況下,只在 segments 之間有是否結束查詢的檢查點。默認爲 false。將該參數設置成 true 後,會在更多的位置進行是否結束的檢查,這樣會更快的結束查詢

如果集羣中沒有很多大的 segment,不建議修改該值的默認設置,設置後過多的檢查任務是否停止會對查詢性能有很大的影響。

6. execution_hint

Elasticsearch 兩種方式執行 terms 聚合(map和global_ordinals),默認會採用 global_ordinals 動態分配 bucket。大部分情況下,採用 global_ordinals 的方式是最快的。

但是對於查詢命中結果數量比較小的時候,採用 map 方式會極大減少內存的佔用。引用官方文檔的例子,使用方式如下:

GET /_search

{

    "aggs" : {

        "tags" : {

             "terms" : {

                 "field" : "tags",

                 "execution_hint": "map"

             }

         }

    }

}

3. 穩定性參數

1. discovery.zen.minimum_master_nodes

該配置是在elasticsearch.yml

假設一個集羣中的節點數爲n(這裏存在疑問,這裏應該是候選節點的數量n,也就是說集羣中,該值是針對那些node.master=true的來設置的,建議>=num(node.master=true)/2+1.而不是這裏解釋的,集羣機器數量的除以2再加1,當然默認情況下是,因爲默認情況下,discovery.zen.master_election.ignore_non_master_pings爲false),則至少要將該值設置爲n/2 + 1,防止發生腦裂的現象。

2. index.max_result_window

該配置可在elasticsearch.yml或進行動態的rest api配置

在查詢過程中控制 from + size 的大小。查詢過程消耗 CPU 和堆內內存。一個很大大值,比如 1000 萬,很容易導致節點 OOM,默認值爲 10000。

PUT index-name/_settings {

  "max_result_window" : 500000

}

3. action.search.shard_count.limit

用於限制一次操作過多的分片,防止過多的佔用內存和 CPU 資源。建議合理設計分片和索引的大小。儘量查詢少量的大的分片,有利於提高集羣的併發吞吐量。

4. 數據安全參數

1. action.destructive_requires_name

強烈建議在線上系統將該參數設置成 true,禁止對索引進行通配符和 _all 進行刪除,目前 Elasticsearch 還不支持回收站功能,程序bug或者誤操作很可能帶來災難性的結果——數據被清空

2. index.translog.durability

如果對數據的安全性要求高,則該值應該配置成 “request”,保證所有操作及時寫入 translog。

5. 內存參數

1. indices.fielddata.cache.size

限制 Field data 最大佔用的內存,可以按百分比和絕對值進行設置,默認是不限制的, 也就是整個堆內存。 如果業務場景中持續有數據加載到 Fielddata Cache,很容易引起 OOM。所以我建議初始時將該值設置的比較保守一些,當遇到查詢性能瓶頸時再結合軟硬件資源調整。

2. indices.memory.index_buffer_size

在索引過程中,新添加的文檔會先寫入索引緩衝區。默認值爲堆內存的 10%。更大的 index buffer 通常會有更高的索引效率。

但是單個 shard的index buffer 超過 512M 以後,索引性能幾乎就沒有提升了。所以,如果爲了提高索引性能,可以根據節點上執行索引操作的分片數來合理設置整個參數。

3. indices.breaker.total.limit

父級斷路器內存限制,默認值爲堆內存的 70%。 對於內存比較小的集羣,爲了集羣的穩定性,建議該值設置到 50% 以下。

4. indices.breaker.fielddata.limit

防止過多的 Fielddata 加載導致節點 OOM,默認值爲堆內存的 60%。 在生產集羣,建議將該值設置成一個比較保守的值,比如 20%,在性能確實由於該值配置較小出現瓶頸時,合理考慮集羣內存資源後,謹慎調大。

PUT /_cluster/settings {

 "persistent" : { // persistent—持續的設置

"indices.breaker.fielddata.limit" : 40% (1)

}

}

6. allocation 參數

1. cluster.routing.allocation.disk.watermark.low

該參數表示當磁盤使用空間達到該值後,新的分片不會繼續分配到該節點,默認值是磁盤容量的 85%。

2. cluster.routing.allocation.disk.watermark.high

參數表示當磁盤使用空間達到該值後,集羣會嘗試將該節點上的分片移動到其他節點,默認值是磁盤容量的90%。對於索引量比較大的場景,該值不宜設置的過高。可能會導致寫入速度大於移動速度,使磁盤寫滿,引發入庫失敗、集羣狀態異常的問題。

index.routing.allocation.include.{attribute}

index.routing.allocation.require.{attribute};

index.routing.allocation.exclude.{attribute} ;

  • include 表示可以分配到具有指定 attribute 的節點;
  • require 表示必須分配到具有指定 attribute 的節點;
  • exclude 表示不允許分配到具有指定 attribute 的節點。

Elasticsearch 內置了多個 attribute,無需自己定義,包括 _name,_host_ip,_publish_ip_ip_host。attribute 可以自己定義到 Elasticsearch 的配置文件。

3. total shards per node

控制單個索引在一個節點上的最大分片數,默認值是不限制。我們的經驗是,創建索引時,儘量將該值設置的小一些,以使索引的 shard 比較平均的分佈到集羣內的所有節點。

7. 集羣恢復參數

1. indices.recovery.max_bytes_per_sec

該參數控制恢復速度,默認值是 40MB。如果是集羣重啓階段,可以將該值設置大一些。但是如果由於某些節點掉線,過大大值會佔用大量的帶寬北恢復佔用,會影響集羣的查詢、索引及穩定性。

2. cluster.routing.allocation.enable

控制是否可以對分片進行平衡,以及對何種類型的分片進行平衡。可取的值包括:all、primaries、replicas、none,默認值是 all。

  • all 是可以對所有的分片進行平衡;
  • primaries 表示只能對主分片進行平衡;
  • replicas 表示只能對副本進行平衡;
  • none 表示對任何分片都不能平衡,也就是禁用了平衡功能。

有一個小技巧是,在重啓集羣之前,可以將該參數設置成 primaries,由於主分片是從本地磁盤恢復數據,速度比較快,可以使集羣迅速恢復到 Yellow 狀態,之後設置成 all,開始恢復副本數據。

3. cluster.routing.allocation.node_concurrent_recoveries

控制單個節點可以同時恢復的副本的數量,默認值爲 2。副本恢復主要瓶頸在於網絡,對於網絡帶寬不大的環境,不需要修改該值。

4. cluster.routing.allocation.node_initial_primaries_recoveries

控制一個節點可以同時恢復的主分片個數,默認值爲 4。由於主分片是從本地存儲恢復,爲了提高恢復速度,完全可以加大設置。

5. cluster.routing.allocation.cluster_concurrent_rebalance

該參數控制在平衡過程中,同時移動的分片數,加大可以提高平衡的速度。一般在集羣擴容節點、下線節點後,可以加大,使集羣儘快的進行平衡。

總結

本課主要把我們在實踐中常用的參數進行了分類,並對結合我們的經驗對參數的設置及影響做了說明。希望可以給讀者提供一些設置參考。

這部分內容主要介紹在業務正式上線前如何壓測,上線後對於分片和節點層面的管理工作,在集羣運行過程中合適擴容等,第一部分和第二部分組合起來就是一個集羣的生命週期。在此期間,“安全”是無法迴避的話題,下一部分我們介紹下如何使用 X-Pack 進行安全防護。

 

  

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