Elasticsearch性能優化實踐

1. 前言

Elasticsearch是一款流行的分佈式開源搜索和數據分析引擎,具備高性能、易擴展、容錯性強等特點。它強化了Apache Lucene的搜索能力,把掌控海量數據索引和查詢的方式提升到一個新的層次。本文結合開源社區和阿里雲平臺的實踐經驗,探討如何調優Elasticsearch的性能提高索引和查詢吞吐量。

2. Elasticsearch部署建議

2.1 選擇合理的硬件配置,儘可能使用SSD

Elasticsearch最大的瓶頸往往是磁盤讀寫性能,尤其是隨機讀取性能。使用SSD(PCI-E接口SSD卡/SATA接口SSD盤)通常比機械硬盤(SATA盤/SAS盤)查詢速度快5~10倍,寫入性能提升不明顯。

對於文檔檢索類查詢性能要求較高的場景,建議考慮SSD作爲存儲,同時按照1:10的比例配置內存和硬盤。對於日誌分析類查詢併發要求較低的場景,可以考慮採用機械硬盤作爲存儲,同時按照1:50的比例配置內存和硬盤。單節點存儲數據建議在2TB以內,最大不要超過5TB,避免查詢速度慢、系統不穩定。

在單機存儲1TB數據場景下,SATA盤和SSD盤的全文檢索性能對比(測試環境:Elasticsearch5.5.3,10億條人口戶籍登記信息,單機16核CPU、64GB內存,12塊6TB SATA盤,2塊1.5 TB SSD盤):
在這裏插入圖片描述

2.2 給JVM配置機器一半的內存,但是不建議超過32G

修改conf/jvm.options配置,-Xms和-Xmx設置爲相同的值,推薦設置爲機器內存的一半左右,剩餘一半留給操作系統緩存使用。jvm內存建議不要低於2G,否則有可能因爲內存不足導致ES無法正常啓動或內存溢出,jvm建議不要超過32G,否則jvm會禁用內存對象指針壓縮技術,造成內存浪費。機器內存大於64G內存時,推薦配置-Xms30g -Xmx30g 。

2.3 規模較大的集羣配置專有主節點,避免腦裂問題

Elasticsearch主節點(master節點)負責集羣元信息管理、index的增刪操作、節點的加入剔除,定期將最新的集羣狀態廣播至各個節點。在集羣規模較大時,建議配置專有主節點只負責集羣管理,不存儲數據,不承擔數據讀寫壓力。

# 專有主節點配置(conf/elasticsearch.yml):
node.master:true
node.data: false
node.ingest:false

# 數據節點配置(conf/elasticsearch.yml):
node.master:false
node.data:true
node.ingest:true

Elasticsearch默認每個節點既是候選主節點,又是數據節點。最小主節點數量參數minimum_master_nodes推薦配置爲候選主節點數量一半以上,該配置告訴Elasticsearch當沒有足夠的master候選節點的時候,不進行master節點選舉,等master節點足夠了才進行選舉。

例如對於3節點集羣,最小主節點數量從默認值1改爲2。

# 最小主節點數量配置(conf/elasticsearch.yml):
discovery.zen.minimum_master_nodes: 2

2.4 Linux操作系統調優

關閉交換分區,防止內存置換降低性能。 將/etc/fstab 文件中包含swap的行註釋掉:

sed -i '/swap/s/^/#/' /etc/fstab
swapoff -a

單用戶可以打開的最大文件數量,可以設置爲官方推薦的65536或更大些:

echo "* - nofile 655360" >> /etc/security/limits.conf

單用戶線程數調大:

echo "* - nproc 131072" >> /etc/security/limits.conf

單進程可以使用的最大map內存區域數量:

echo "vm.max_map_count = 655360" >> /etc/sysctl.conf

參數修改立即生效:

sysctl -p

2. 索引性能調優建議

2.1 設置合理的索引分片數和副本數

索引分片數建議設置爲集羣節點的整數倍,初始數據導入時副本數設置爲0,生產環境副本數建議設置爲1(設置1個副本,集羣任意1個節點宕機數據不會丟失;設置更多副本會佔用更多存儲空間,操作系統緩存命中率會下降,檢索性能不一定提升)。單節點索引分片數建議不要超過3個,每個索引分片推薦10-40GB大小。索引分片數設置後不可以修改,副本數設置後可以修改。Elasticsearch6.X及之前的版本默認索引分片數爲5、副本數爲1,從Elasticsearch7.0開始調整爲默認索引分片數爲1、副本數爲1。

不同分片數對寫入性能的影響(測試環境:7節點Elasticsearch6.3集羣,寫入30G新聞數據,單節點56核CPU、380G內存、3TB SSD卡,0副本,20線程寫入,每批次提交10M左右數據。):
在這裏插入圖片描述

索引設置:

curl -XPUT http://localhost:9200/fulltext001?pretty -H 'Content-Type: application/json'   -d '
{
    "settings" : {
      "refresh_interval": "30s",
      "merge.policy.max_merged_segment": "1000mb",
      "translog.durability": "async",
      "translog.flush_threshold_size": "2gb",
      "translog.sync_interval": "100s",
      "index" : {
        "number_of_shards" : "21",
        "number_of_replicas" : "0"
      }
    }
}
'

mapping設置:

curl -XPOST http://localhost:9200/fulltext001/doc/_mapping?pretty  -H 'Content-Type: application/json' -d '
{
    "doc" : {
        "_all" : {
            "enabled" : false
         },
        "properties" : {
          "content" : {
            "type" : "text",
            "analyzer":"ik_max_word"
          },
          "id" : {
            "type" : "keyword"
          }
        }
    }
}
'

寫入數據示例:

curl -XPUT 'http://localhost:9200/fulltext001/doc/1?pretty' -H 'Content-Type: application/json' -d '
{
    "id": "https://www.huxiu.com/article/215169.html",
    "content": "“娃娃機,迷你KTV,VR體驗館,堪稱商場三大標配‘神器’。”一家地處商業中心的大型綜合體負責人告訴懂懂筆記,在過去的這幾個月裏,幾乎所有的綜合體都“標配”了這三種“設備”…"
}'

修改副本數示例:

curl -XPUT "http://localhost:9200/fulltext001/_settings" -H 'Content-Type: application/json' -d'
{
    "number_of_replicas": 1
}'

2.2 使用批量請求

使用批量請求將產生比單文檔索引請求好得多的性能。寫入數據時調用批量提交接口,推薦每批量提交5~15MB數據。例如單條記錄1KB大小,每批次提交10000條左右記錄寫入性能較優;單條記錄5KB大小,每批次提交2000條左右記錄寫入性能較優。

批量請求接口API:

curl -XPOST "http://localhost:9200/_bulk" -H 'Content-Type: application/json' -d'
{ "index" : { "_index" : "test", "_type" : "_doc", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_type" : "_doc", "_id" : "2" } }
{ "create" : { "_index" : "test", "_type" : "_doc", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_type" : "_doc", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }
'

2.3 通過多進程/線程發送數據

單線程批量寫入數據往往不能充分利用服務器CPU資源,可以嘗試調整寫入線程數或者在多個客戶端上同時向Elasticsearch服務器提交寫入請求。與批量調整大小請求類似,只有測試才能確定最佳的worker數量。 可以通過逐漸增加工作任務數量來測試,直到集羣上的I / O或CPU飽和。

2.4 調大refresh interval

在 Elasticsearch 中,寫入和打開一個新段的輕量的過程叫做 refresh 。 默認情況下每個分片會每秒自動刷新一次。這就是爲什麼我們說 Elasticsearch 是 近 實時搜索: 文檔的變化並不是立即對搜索可見,但會在一秒之內變爲可見。
並不是所有的情況都需要每秒刷新。可能你正在使用 Elasticsearch 索引大量的日誌文件,你可能想優化索引速度而不是近實時搜索,可以通過設置 refresh_interval,降低每個索引的刷新頻率。

設置refresh interval API:

curl -XPUT "http://localhost:9200/index" -H 'Content-Type: application/json' -d'
{
    "settings" : {
      "refresh_interval": "30s"
    }
}'

refresh_interval 可以在既存索引上進行動態更新。 在生產環境中,當你正在建立一個大的新索引時,可以先關閉自動刷新,待開始使用該索引時,再把它們調回來:

curl -XPUT "http://localhost:9200/index/_settings" -H 'Content-Type: application/json' -d'
{ "refresh_interval": -1 }'

curl -XPUT "http://localhost:9200/index/_settings" -H 'Content-Type: application/json' -d'
{ "refresh_interval": "1s" }'

2.5 設計mapping配置合適的字段類型

Elasticsearch在寫入文檔時,如果請求中指定的索引名不存在,會自動創建新索引,並根據文檔內容猜測可能的字段類型。但這往往不是最高效的,我們可以根據應用場景來設計合理的字段類型。

例如寫入一條記錄:

curl -XPUT "http://localhost:9200/twitter/doc/1?pretty" -H 'Content-Type: application/json' -d'
{
    "user": "kimchy",
    "post_date": "2009-11-15T13:12:00",
    "message": "Trying out Elasticsearch, so far so good?"
}'

查詢Elasticsearch自動創建的索引mapping,會發現將post_date字段自動識別爲date類型,但是message和user字段被設置爲text、keyword冗餘字段,造成寫入速度降低、佔用更多磁盤空間。

curl -XGET "http://localhost:9200/twitter"
{
  "twitter": {
    "mappings": {
      "doc": {
        "properties": {
          "message": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "post_date": {
            "type": "date"
          },
          "user": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          }
        }
      }
    },
    "settings": {
      "index": {
        "number_of_shards": "5",
        "number_of_replicas": "1",
      }
    }
  }
}

根據業務場景設計索引配置合理的分片數、副本數,設置字段類型、分詞器。如果不需要合併全部字段,禁用_all字段,通過copy_to來合併字段。

curl -XPUT "http://localhost:9200/twitter?pretty" -H 'Content-Type: application/json' -d'
{
    "settings" : {
      "index" : {
        "number_of_shards" : "20",
        "number_of_replicas" : "0"
      }
    }
}'
curl -XPOST "http://localhost:9200/twitter/doc/_mapping?pretty" -H 'Content-Type: application/json' -d'
{
    "doc" : {
        "_all" : {
        "enabled" : false
    },
    "properties" : {
          "user" : {
          "type" : "keyword"
          },
          "post_date" : {
            "type" : "date"
          },
          "message" : {
            "type" : "text",
            "analyzer" : "cjk"
          }
        }
    }
}'

3. 查詢性能調優建議

3.1 使用過濾器緩存和分片查詢緩存

默認情況下,Elasticsearch的查詢會計算返回的每條數據與查詢語句的相關度,但對於非全文索引的使用場景,用戶並不關心查詢結果與查詢條件的相關度,只是想精確的查找目標數據。此時,可以通過filter來讓Elasticsearch不計算評分,並且儘可能的緩存filter的結果集,供後續包含相同filter的查詢使用,提高查詢效率。

普通查詢:

curl -XGET "http://localhost:9200/twitter/_search" -H 'Content-Type: application/json' -d'
{
  "query": {
    "match": {
      "user": "kimchy"
    }
  }
}'

過濾器(filter)查詢:

curl -XGET "http://localhost:9200/twitter/_search" -H 'Content-Type: application/json' -d'
{
  "query": {
    "bool": {
      "filter": {
         "match": {
          "user": "kimchy"
        }
      }
    }
  }
}'

分片查詢緩存的目的是緩存聚合、提示詞結果和命中數(它不會緩存返回的文檔,因此,它只在search_type=count時起作用)。

通過下面的參數我們可以設置分片緩存的大小,默認情況下是JVM堆的1%大小,當然我們也可以手動設置在config/elasticsearch.yml文件裏:

indices.requests.cache.size: 1%

查看緩存佔用內存情況(name表示節點名, query_cache表示過濾器緩存,request_cache表示分片緩存,fielddata表示字段數據緩存,segments表示索引段):

curl -XGET "http://localhost:9200/_cat/nodes?h=name,query_cache.memory_size,request_cache.memory_size,fielddata.memory_size,segments.memory&v" 

3.2 使用路由routing

Elasticsearch寫入文檔時,文檔會通過一個公式路由到一個索引中的一個分片上。默認的公式如下:

shard_num = hash(_routing) % num_primary_shards

_routing字段的取值,默認是_id字段,可以根據業務場景設置經常查詢的字段作爲路由字段。例如可以考慮將用戶id、地區作爲路由字段,查詢時可以過濾不必要的分片,加快查詢速度。

寫入時指定路由:

curl -XPUT "http://localhost:9200/my_index/my_type/1?routing=user1" -H 'Content-Type: application/json' -d'
{
  "title": "This is a document",
  "author": "user1"
}'

查詢時不指定路由,需要查詢所有分片:

curl -XGET "http://localhost:9200/my_index/_search" -H 'Content-Type: application/json' -d'
{
  "query": {
    "match": {
      "title": "document"
    }
  }
}'

返回結果:
{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  }
  ......
}

查詢時指定路由,只需要查詢1個分片:

curl -XGET "http://localhost:9200/my_index/_search?routing=user1" -H 'Content-Type: application/json' -d'
{
  "query": {
    "match": {
      "title": "document"
    }
  }
}'

返回結果:
{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  }
  ......
}

3.3 強制合併只讀索引,關閉歷史數據索引

只讀的索引可以從合併成一個單獨的大segment中收益,減少索引碎片,減少JVM堆常駐內存。歷史數據索引如果業務上不再支持查詢請求,可以考慮關閉索引,減少JVM內存佔用。

索引forcemerge API:

curl -XPOST "http://localhost:9200/abc20180923/_forcemerge"

索引關閉API:

curl -XPOST "http://localhost:9200/abc2017*/_close"

3.4 配置查詢聚合節點

查詢聚合節點可以發送粒子查詢請求到其他節點,收集和合並結果,以及響應發出查詢的客戶端。通過給查詢聚合節點配置更高規格的CPU和內存,可以加快查詢運算速度、提升緩存命中率。某客戶使用25臺8核CPU32G內存節點ELasticsearch集羣,查詢QPS在4000左右。增加6臺16核CPU32G內存節點作爲查詢聚合節點,觀察服務器CPU、JVM堆內存使用情況,並調整緩存、分片、副本參數,查詢QPS達到12000。

# 查詢聚合節點配置(conf/elasticsearch.yml):
node.master: false
node.data: false
node.ingest:false

3.5 設置查詢讀取記錄條數和字段

默認的查詢請求通常返回排序後的前10條記錄,最多一次讀取10000條記錄,通過from和size參數控制讀取記錄範圍,避免一次讀取過多的記錄。通過_source參數可以控制返回字段信息,儘量避免讀取大字段。

查詢請求示例:

curl -XGET http://localhost:9200/fulltext001/_search?pretty  -H 'Content-Type: application/json' -d ' 
{
  "from": 0,
  "size": 10,
  "_source": "id",
  "query": {
    "bool": {
      "must": [
        {"match": {"content":"虎嗅"}}
      ]
    }
  },
  "sort": [
    {
      "id": {
        "order": "asc"
      }
    }
  ]
}
'

3.6 避免前綴模糊匹配

Elasticsearch默認支持通過*?正則表達式來做模糊匹配,如果在一個數據量超過10億條的索引上執行模糊匹配,尤其是前綴模糊匹配,通常耗時會比較長,甚至可能導致內存溢出。儘量避免在高併發查詢請求的生產環境執行這類操作。

某客戶需要對車牌號進行模糊查詢,通過查詢請求"車牌號:A8848"查詢時,往往導致整個集羣負載較高。通過對數據預處理,增加冗餘字段"車牌號.keyword",並事先將所有車牌號按照1元、2元、3元…7元分詞後存儲至該字段,字段存儲內容示例:滬,A,8,4,滬A,A8,88,84,48,滬A8…滬A88488。通過查詢"車牌號.keyword:A8848"即可解決原來的性能問題。

3.7 避免索引稀疏

Elasticsearch6.X之前的版本默認允許在一個index下面創建多個type,Elasticsearch6.X及之後的版本只允許創建一個type。在一個type下面創建多個字段不一樣的type,或者將幾百個字段不一樣的索引合併到一個索引中,會導致索引稀疏問題。

建議每個索引下只創建一個type,字段不一樣的數據分別獨立創建index,不要合併成一個大索引。每個查詢請求根據需要去讀取相應的索引,避免查詢大索引掃描全部記錄,加快查詢速度。

3.8 擴容集羣節點個數、升級節點規格

通常服務器節點數越多,服務器硬件配置規格越高,Elasticsearch集羣的處理能力越強。

在不同節點規模下的查詢性能測試(測試環境:Elasticsearch5.5.3集羣,單節點16核CPU、64G內存、2T SSD盤,10億條人口戶籍登記信息,數據大小1TB。):
在這裏插入圖片描述
不同集羣節點規模寫入性能測試(測試環境:Elasticsearch6.3.2集羣,單節點16核CPU、64G內存、2T SSD盤,10億條人口戶籍登記信息,單條記錄1KB,數據集大小1TB,20個併發寫入線程。):
在這裏插入圖片描述
在條件允許的情況下,還是希望您可以通過實際的數據和使用場景測試出適合自己的最佳實踐。得益於阿里雲Elasticsearch提供的彈性擴容功能,您可以在實際使用時根據情況隨時增加磁盤大小、擴容節點個數、升級節點規格。

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