ElasticSearch 5.X 最佳實踐

Author: 袁野
Date: 2018.01.05
Version: 1.0

注意事項:

  • 本文檔所述為通用情況,不可作為特定業務參照;
  • 本文檔所述適用于 ELK 棧中的 ElasticSearch 優化;
  • 本文檔所述基于 elastic 公司的 5.2.1 版本;
  • 本文檔只描述最佳實踐,不包含所需變更步驟;
  • 本文檔針對 CentOS 7.2,其他 Unix 發行版不在討論范圍內;

硬件選擇

目前公司的物理機機型在CPU和內存方面都滿足需求,建議使用SSD機型。原因在于,可以快速把 Lucene 的索引文件加載入內存(這在宕機恢復的情況下尤為明顯),減少 IO 負載和 IO wait以便CPU不總是在等待IO中斷。建議使用多裸盤而非raid,因為 ElasticSearch 本身就支持多目錄,raid 要么犧牲空間要么犧牲可用性。

系統配置

ElasticSearch 理論上必須單獨部署,并且會獨占幾乎所有系統資源,因此需要對系統進行配置,以保證運行 ElasticSearch 的用戶可以使用足夠多的資源。生產集群需要調整的配置如下:

  • 設置 JVM 堆大小;
  • 關閉 swap;
  • 增加文件描述符;
  • 保證足夠的虛存;
  • 保證足夠的線程;
  • 暫時不建議使用G1GC;

設置 JVM 堆大小

ElasticSearch 需要有足夠的 JVM 堆支撐索引數據的加載,對于公司的機型來說,因為都是大于 128GB 的,所以推薦的配置是 32GB(如果 JVM 以不等的初始和最大堆大小啟動,則在系統使用過程中可能會因為 JVM 堆的大小調整而容易中斷。 為了避免這些調整大小的暫停,最好使用初始堆大小等于最大堆大小的 JVM 來啟動),預留足夠的 IO Cache 給 Lucene(官方建議超過一半的內存需要預留)。

設置方法(需要重啟進程):

# Step1. 修改 ${PATH_TO_ElasticSearch_HOME}/config/jvm.options 中的 Xms 和 Xmx
-Xms32g
-Xmx32g
# Step2. 重啟 elasticsearch
sudo sytemctl restart elasticsearch

關閉 swap & 禁用交換

必須要關閉 swap,因為在物理內存不足時,如果發生 FGC,在回收虛擬內存的時候會造成長時間的 stop-the-world,最嚴重的后果是造成集群雪崩。公司的默認模板是關閉的,但是要巡檢一遍,避免有些機器存在問題。

設置方法:

# Step1. root 用戶臨時關閉
sudo swapoff -a
sudo sysctl vm.swappiness=0
# Step2. 修改 /etc/fstab,注釋掉 swap 這行
# Step3. 修改 /etc/sysctl.conf,添加:
vm.swappiness = 0
# Step4. 確認是否生效
sudo sysctl vm.swappiness

也可以通過修改 yml 配置文件的方式從 ElasticSearch 層面禁止物理內存和交換區之間交換內存:

Linux 把它的物理 RAM 分成多個內存塊,稱之為分頁。內存交換(swapping)是這樣一個過程,它把內存分頁復制到預先設定的叫做交換區的硬盤空間上,以此釋放內存分頁。物理內存和交換區加起來的大小就是虛擬內存的可用額度。

內存交換有個缺點,跟內存比起來硬盤非常慢。內存的讀寫速度以納秒來計算,而硬盤是以毫秒來計算,所以訪問硬盤比訪問內存要慢幾萬倍。交換次數越多,進程就越慢,所以應該不惜一切代價避免內存交換的發生。

ElasticSearch 的 memory_lock 屬性允許 Elasticsearch 節點不交換內存。(注意只有Linux/Unix系統可設置。)這個屬性可以在yml文件中設置。

# Step1. 修改 ${PATH_TO_ES_HOME}/config/elasticsearch.yml,添加:
bootstrap.memory_lock: true

增加文件描述符

單個用戶可用的最大進程數量(軟限制)&單個用戶可用的最大進程數量(硬限制),超過軟限制會有警告,但是無法超過硬限制。 ElasticSearch 會使用大量的文件句柄,如果超過限制可能會造成宕機或者數據缺失。

文件描述符是用于跟蹤打開“文件”的 Unix 結構體。在Unix中,一切都皆文件。 例如,“文件”可以是物理文件,虛擬文件(例如/proc/loadavg)或網絡套接字。 ElasticSearch 需要大量的文件描述符(例如,每個 shard 由多個 segment 和其他文件組成,以及到其他節點的 socket 連接等)。

設置方法(假設是 admin 用戶啟動的 ElasticSearch 進程):

# Step1. 修改 /etc/security/limits.conf,添加:
admin soft nofile 65536
admin hard nofile 65536
# Step2. 確認是否生效
su - admin
ulimit -n
# Step3. 通過 rest 確認是否生效
GET /_nodes/stats/process?filter_path=**.max_file_descriptors

保證足夠的虛存

單進程最多可以占用的內存區域,默認為 65536。Elasticsearch 默認會使用 mmapfs 去存儲 indices,默認的 65536 過少,會造成 OOM 異常。

設置方法:

# Step1. root 用戶修改臨時參數
sudo sysctl -w vm.max_map_count=262144
# Step2. 修改 /etc/sysctl.conf,在文末添加:
vm.max_map_count = 262144
# Step3. 確認是否生效
sudo sysctl vm.max_map_count

保證足夠的線程

Elasticsearch 通過將請求分成幾個階段,并交給不同的線程池執行(Elasticsearch 中有各種不同的線程池執行器)。 因此,Elasticsearch 需要創建大量線程的能力。進程可創建線程的最大數量確保 Elasticsearch 進程有權在正常使用情況下創建足夠的線程。 這可以通過 /etc/security/limits.conf 使用 nproc 設置來完成。

設置方法(假設是 admin 用戶啟動的 Elasticsearch 進程):

# Step1. 修改 /etc/security/limits.d/90-nproc.conf,添加:
admin soft nproc 2048

暫時不建議使用G1GC

已知 JDK 8 附帶的 HotSpot JVM 的早期版本在啟用 G1GC 收集器時會導致索引損壞。受影響的版本是早于 JDK 8u40 附帶的HotSpot 的版本,出于穩定性的考慮暫時不建議使用。

內存優化

ElasticSearch 自身對內存管理進行了大量優化,但對于持續增長的業務仍需進行一定程度的內存優化(而不是純粹的添加節點和擴展物理內存),以防止 OOM 發生。ElasticSearch 使用的 JVM 堆中主要包括以下幾類內存使用:

  • Segment Memory;
  • Filter Cache;
  • Field Data Cache;
  • Bulk Queue;
  • Indexing Buffer;
  • Cluster State Buffer;
  • 超大搜索聚合結果集的 fetch;

詳細資料可以參閱我寫的這篇博文

減少 Segment Memory

  • 刪除無用的歷史索引

刪除辦法,使用 rest API:

# 刪除指定某個索引
DELETE /${INDEX_NAME}
# 刪除符合 pattern 的某些索引
DELETE /${INDEX_PATTERN}
  • 關閉無需實時查詢的歷史索引,文件仍然存在于磁盤,只是釋放掉內存,需要的時候可以重新打開

關閉辦法,使用 rest API:

# 關閉指定某個索引
POST /${INDEX_NAME}/_close
# 關閉符合 pattern 的某些索引
POST /${INDEX_PATTERN}/_close
  • 定期對不再更新的索引做 force merge(會占用大量 IO,建議業務低峰期觸發

force merge 辦法,使用 rest API:

# Step1. 在合并前需要對合并速度進行合理限制,默認是 20mb,SSD可以適當放寬到 80mb:
PUT /_cluster/settings -d '
{
    "persistent" : {
        "indices.store.throttle.max_bytes_per_sec" : "20mb"
    }
}'

# Step2. 強制合并 API,示例表示的是最終合并為一個 segment file:
# 對某個索引做合并
POST /${INDEX_NAME}/_forcemerge?max_num_segments=1
# 對某些索引做合并
POST /${INDEX_PATTERN}/_forcemerge?max_num_segments=1

Filter Cache

默認的 10% heap 設置工作得夠好,如果實際使用中 heap 沒什么壓力的情況下,才考慮加大這個設置。

Field Data Cache

對需要排序的字段不進行 analyzed,盡量使用 doc values(5.X版本天然支持,不需要特別設置)。對于不參與搜索的字段 ( fields ),將其 index 方法設置為 no,如果對分詞沒有需求,對參與搜索的字段,其 index 方法設置為 not_analyzed

Bulk Queue

一般來說官方默認的 thread pool 設置已經能很好的工作了,建議不要隨意去調優相關的設置,很多時候都是適得其反的效果。

Indexing Buffer

這個參數的默認值是10% heap size。根據經驗,這個默認值也能夠很好的工作,應對很大的索引吞吐量。 但有些用戶認為這個 buffer 越大吞吐量越高,因此見過有用戶將其設置為 40% 的。到了極端的情況,寫入速度很高的時候,40%都被占用,導致OOM。

Cluster State Buffer

在超大規模集群的情況下,可以考慮分集群并通過 tribe node 連接做到對用戶透明,這樣可以保證每個集群里的 state 信息不會膨脹得過大。在單集群情況下,縮減 cluster state buffer 的方法就是減少 shard 數量,shard 數量的確定有以下幾條規則:

  • 避免有非常大的分片,因為大的分片可能會對集群從故障中恢復的能力產生負面影響。 對于多大的分片沒有固定的限制,但是分片大小為 50GB 通常被界定為適用于各種用例的限制;
  • 盡可能使用基于時間的索引來管理數據。根據保留期(retention period,可以理解成有效期)將數據分組。基于時間的索引還可以輕松地隨時間改變主分片和副本分片的數量(以為要生成的下一個索引進行更改)。這簡化了適應不斷變化的數據量和需求;(周期性的通過刪除或者關閉歷史索引以減少分片
  • 小分片會導致小分段(segment),從而增加開銷。目的是保持平均分片大小在幾GB和幾十GB之間。對于具有基于時間的數據的用例,通常看到大小在 20GB40GB 之間的分片;
  • 由于每個分片的開銷取決于分段數和大小,通過強制操作迫使較小的段合并成較大的段可以減少開銷并提高查詢性能。一旦沒有更多的數據被寫入索引,這應該是理想的。請注意,這是一個消耗資源的(昂貴的)操作,較為理想的處理時段應該在非高峰時段執行;(對應使用 force meger 以減少 segment 數量的優化,目的是降低 segment memory 占用
  • 可以在集群節點上保存的分片數量與可用的堆內存大小成正比,但這在 Elasticsearch 中沒有的固定限制。 一個很好的經驗法則是:確保每個節點的分片數量保持在低于每 1GB 堆內存對應集群的分片在 20-25 之間。 因此,具有 32GB 堆內存的節點最多可以有 600-750 個分片;
  • 對于單索引的主分片數,有這么 2 個公式:節點數 <= 主分片數 *(副本數 + 1) 以及 (同一索引 shard 數量 * (1 + 副本數)) < 3 * 數據節點數,比如有 3 個節點全是數據節點,1 個副本,那么主分片數大于等于 1.5,同時同一索引總分片數需要小于 4.5,因為副本數為 1,所以單節點主分片最適為 2,索引總分片數最適為 6,這樣每個節點的總分片為 4;
  • 單分片小于 20GB 的情況下,采用單分片較為合適,請求不存在網絡抖動的顧慮;

小結:分片不超 20GB,且單節點總分片不超 600。比如互聯網區域,每天新建索引(lw-greenbay-online) 1 個分片 1 個副本,3 個月前的歷史索引都關閉,3 節點總共需要扛 90 * 2 = 180 個分片,每個分片大約 6 GB,可謂比較健康的狀態。

超大搜索聚合結果集的 fetch

避免用戶 fetch 超大搜索聚合結果集,確實需要大量拉取數據可以采用 scan & scroll API 來實現。在 ElasticSearch 上搜索數據時,默認只會返回10條文檔,當我們想獲取更多結果,或者只要結果中的一個區間的數據時,可以通過 size 和 from 來指定。

GET /_search?size=3&from=20

如上的查詢語句,會返回排序后的結果中第 20 到第 22 條數據。ElasticSearch 在收到這樣的一個請求之后,每一個分片都會返回一個 top22 的搜索結果,然后將這些結果匯總排序,再選出 top22 ,最后取第 20 到第 22 條數據作為結果返回。
這樣會帶來一個問題,當我們搜索的時候,如果想取出第 10001 條數據,那么就相當于每個一分片都要對數據進行排序,取出前 10001 條文檔,然后 ElasticSearch 再將這些結果匯總再次排序,之后取出第 10001 條數據。這樣對于 ElasticSearch 來說就會產生相當大的資源和性能開銷。如果我們不要求 ElasticSearch 對結果進行排序,那么就會消耗很少的資源,所以針對此種情況,ElasticSearch 提供了scan & scroll的搜索方式。

GET /old_index/_search?search_type=scan&scroll=1m 
{
    "query": { "match_all": {}},
    "size":  1000
}

我們可以首先通過如上的請求發起一個搜索,但是這個請求不會返回任何文檔,它會返回一個 _scroll_id ,接下來我們再通過這個 id 來從 ElasticSearch 中讀取數據:

GET /_search/scroll?scroll=1m 
c2Nhbjs1OzExODpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzExOTpRNV9aY1VyUVM4U0 NMd2pjWlJ3YWlBOzExNjpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzExNzpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzEyMDpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzE7dG90YWxfaGl0czoxOw==

此時除了會返回搜索結果以外,還會再次返回一個 _scroll_id,當我們下次繼續取數據時,需要用最新的 id。

存儲優化

關閉不需要的功能

默認情況下 ElasticSearch 并將會將 indexs 和 doc values 添加到大多數字段中,以便可以搜索和聚合它們。 例如,如果有一個名為 foo 的數字字段,需要運行 histograms 但不需要 filter,則可以安全地禁用映射中此字段的索引:

PUT ${INDEX_NAME}
{
  "mappings": {
    "type": {
      "properties": {
        "foo": {
          "type": "integer",
          "index": false
        }
      }
    }
  }
}

text 字段在索引中存儲規范化因子以便能夠對文檔進行評分。 如果只需要在 text 字段上使用 matching 功能,但不關心生成的 score,則可以命令 ElasticSearch 配置為不將規范寫入索引:

PUT ${INDEX_NAME}
{
  "mappings": {
    "type": {
      "properties": {
        "foo": {
          "type": "text",
          "norms": false
        }
      }
    }
  }
}

text 字段也默認存儲索引中的頻率和位置。 頻率用于計算分數,位置用于運行短語查詢(phrase queries)。 如果不需要運行短語查詢,可以告訴 ElasticSearch 不要索引位置:

PUT ${INDEX_NAME}
{
  "mappings": {
    "type": {
      "properties": {
        "foo": {
          "type": "text",
          "index_options": "freqs"
        }
      }
    }
  }
}

此外,如果不關心計分,則可以配置 ElasticSearch 以僅索引每個 term 的匹配文檔。 這樣做仍然可以在此字段上進行搜索(search),但是短語查詢會引發錯誤,評分將假定 term 在每個文檔中只出現一次。

PUT ${INDEX_NAME}
{
  "mappings": {
    "type": {
      "properties": {
        "foo": {
          "type": "text",
          "norms": false,
          "index_options": "freqs"
        }
      }
    }
  }
}

強制清除已標記刪除的數據

Elasticsearch 是建立在 Apache Lucene 基礎上的實時分布式搜索引擎,Lucene 為了提高搜索的實時性,采用不可再修改(immutable)方式將文檔存儲在一個個 segment 中。也就是說,一個 segment 在寫入到存儲系統之后,將不可以再修改。那么 Lucene 是如何從一個 segment 中刪除一個被索引的文檔呢?簡單的講,當用戶發出命令刪除一個被索引的文檔#ABC 時,該文檔并不會被馬上從相應的存儲它的 segment 中刪除掉,而是通過一個特殊的文件來標記該文檔已被刪除。當用戶再次搜索到 #ABC 時,Elasticsearch 在 segment 中仍能找到 #ABC,但由于 #ABC 文檔已經被標記為刪除,所以Lucene 會從發回給用戶的搜索結果中剔除 #ABC,所以給用戶感覺的是 #ABC 已經被刪除了。

Elasticseach 會有后臺線程根據 Lucene 的合并規則定期進行 segment merging 合并操作,一般不需要用戶擔心或者采取任何行動。被刪除的文檔在 segment 合并時,才會被真正刪除掉。在此之前,它仍然會占用著 JVM heap 和操作系統的文件 cache 等資源。在某些情況下,我們需要強制 Elasticsearch 進行 segment merging,已釋放其占用的大量系統資源。

POST /${INDEX_NAME}/_forcemerge?max_num_segments=1&only_expunge_deletes=true&wait_for_completion=true
POST /${INDEX_PATTERN}/_forcemerge?max_num_segments=1&only_expunge_deletes=true&wait_for_completion=true

Force Merge 命令可強制進行 segment 合并,并刪除所有標記為刪除的文檔。Segment merging 要消耗 CPU,以及大量的 I/O 資源,所以一定要在 ElasticSearch 集群處于維護窗口期間,并且有足夠的 I/O 空間的(如:SSD)的條件下進行;否則很可能造成集群崩潰和數據丟失。

減少副本數

最直接的存儲優化手段是調整副本數,默認 ElasticSearch 是有 1 個副本的,假設對可用性要求不高,允許磁盤損壞情況下可能的數據缺失,可以把副本數調整為 0,具體操作如下:

PUT  /_template/${TEMPLATE_NAME}
{

  "template":"${TEMPLATE_PATTERN}",
  "settings" : {
    "number_of_replicas" : 0
  },
  "version"  : 1
}

其中 ${TEMPLATE_NAME} 表示模板名稱,可以是不存在的,系統會新建。${TEMPLATE_PATTERN} 是用于匹配索引的表達式,比如 lw-greenbay-online-*。

與此相關的一個系統參數為:index.merge.scheduler.max_thread_count,默認值為 Math.max(1, Math.min(4, Runtime.getRuntime().availableProcessors() / 2)),這個值在 SSD 上工作沒問題,但是 SATA 盤上還是使用 1 個線程為好,因為太多也來不及完成。

# SATA 請設置 merge 線程為 1
PUT  /_template/${TEMPLATE_NAME}
{

  "template":"${TEMPLATE_PATTERN}",
  "settings" : {
    "index.merge.scheduler.max_thread_count": 1
  },
  "version"  : 1
}

請勿使用默認的動態字符串映射

默認的動態字符串映射會將字符串字段索引為文本(text)和關鍵字(keyword)。 如果只需要其中的一個,這樣做無疑是浪費的。 通常情況下,一個 id 字段只需要被索引為一個 keyword,而一個 body 字段只需要被索引為一個 text 字段。

可以通過在字符串字段上配置顯式映射或設置將字符串字段映射為文本(text)或關鍵字(keyword)的動態模板來禁用此功能。

例如下面的模板,可以用來將 strings 字段映射為關鍵字:

PUT ${INDEX_NAME}
{
  "mappings": {
    "type": {
      "dynamic_templates": [
        {
          "strings": {
            "match_mapping_type": "string",
            "mapping": {
              "type": "keyword"
            }
          }
        }
      ]
    }
  }
}

禁用 _all 字段

_all 字段是由所有字段拼接成的超級字段,如果在查詢中已知需要查詢的字段,就可以考慮禁用它。

PUT /_template/${TEMPLATE_NAME}
{
  "template": "${TEMPLATE_PATTERN}",
  "settings" : {...},
  "mappings": {
    "type_1": {
      "_all": {
         "enabled": false
       },
      "properties": {...}
   }
  },
  "version"  : 1
}

使用 best_compression

_source 字段和 stored fields 會占用大量存儲,可以考慮使用 best_compression 進行壓縮。默認的壓縮方式為 LZ4,但需要更高壓縮比的話,可以通過 inex.codec 進行設置,修改為 DEFLATE,在 force merge 后生效:

# Step1. 修改壓縮算法為 best_compression
PUT  /_template/${TEMPLATE_NAME}
{

  "template":"${TEMPLATE_PATTERN}",
  "settings" : {
    "index.codec" : "best_compression"
  },
  "version"  : 1
}
# Step2. force merge
POST /${INDEX_NAME}/_forcemerge?max_num_segments=1&wait_for_completion=true
POST /${INDEX_PATTERN}/_forcemerge?max_num_segments=1&wait_for_completion=true

使用最優數據格式

我們為數字數據選擇的類型可能會對磁盤使用量產生重大影響。 首先,應使用整數類型(byte,short,integer或long)來存儲整數,浮點數應該存儲在 scaled_float 中,或者存儲在適合用例的最小類型中:使用 float 而不是 double,使用 half_float 而不是 float。

PUT /_template/${TEMPLATE_NAME}
{
  "template": "${TEMPLATE_PATTERN}",
  "settings" : {...},
  "mappings": {
    "type_1": {
      "${FIELD_NAME}": {
         "type": "integer"
       },
      "properties": {...}
   }
  },
  "version"  : 1
}

搜索速度優化

避免Join和Parent-Child

Join會使查詢慢數倍、 Parent-Child會使查詢慢數百倍,請在進行 query 語句編寫的時候盡量避免。

映射

某些數據本身是數字,但并不意味著它應該總是被映射為一個數字字段。 通常存儲著標識符的字段(如ISBN)或來自另一個數據庫的數字型記錄,可能映射為 keyword 而不是 integer 或者 long 會更好些。

避免使用 Scripts

之前 Groovy 腳本曝出了很大的漏洞,總的來說是需要避免使用的。如果必須要使用,盡量用 5.X 以上版本自帶的 painless 和 expressions 引擎。

根據四舍五入的日期進行查詢

根據 timestamp 字段進行的查詢通常不可緩存,因為匹配的范圍始終在變化。 但就用戶體驗而言,以四舍五入對日期進行轉換通常是可接受的,這樣可以有效利用系統緩存。

舉例說明,有以下查詢:

PUT index/type/1
{
  "my_date": "2016-05-11T16:30:55.328Z"
}

GET index/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "my_date": {
            "gte": "now-1h",
            "lte": "now"
          }
        }
      }
    }
  }
}

可以對時間范圍進行替換:

GET index/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "my_date": {
            "gte": "now-1h/m",
            "lte": "now/m"
          }
        }
      }
    }
  }
}

在這種情況下,我們四舍五入到分鐘,所以如果當前時間是 16:31:29 ,范圍查詢將匹配 my_date 字段的值在 15:31:00 和16:31:59 之間的所有內容。 如果多個用戶在同一分鐘內運行包含這個范圍的查詢,查詢緩存可以幫助加快速度。 用于四舍五入的時間間隔越長,查詢緩存可以提供的幫助就越多,但要注意過于積極的舍入也可能會傷害用戶體驗。

為了能夠利用查詢緩存,建議將范圍分割成大的可緩存部分和更小的不可緩存的部分,如下所示:

GET index/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "bool": {
          "should": [
            {
              "range": {
                "my_date": {
                  "gte": "now-1h",
                  "lte": "now-1h/m"
                }
              }
            },
            {
              "range": {
                "my_date": {
                  "gt": "now-1h/m",
                  "lt": "now/m"
                }
              }
            },
            {
              "range": {
                "my_date": {
                  "gte": "now/m",
                  "lte": "now"
                }
              }
            }
          ]
        }
      }
    }
  }
}

然而,這種做法可能會使查詢在某些情況下運行速度較慢,因為由 bool 查詢引入的開銷可能會因更好地利用查詢緩存而失敗。

對只讀 indices 進行 force merge

建議將只讀索引被合并到一個單獨的分段中。 基于時間的索引通常就是這種情況:只有當前時間索引會寫入數據,而舊索引是只讀索引。

預熱 global ordinals

全局序號(global ordinals)是用于在關鍵字(keyword)字段上運行 terms aggregations 的數據結構。 由于 ElasticSearch 不知道聚合使用哪些字段、哪些字段不使用,所以它們在內存中被加載得很慢。 我們可以通過下面的 API 來告訴 ElasticSearch 通過配置映射來在 refresh 的時候加載全局序號:

PUT index
{
  "mappings": {
    "type": {
      "properties": {
        "foo": {
          "type": "keyword",
          "eager_global_ordinals": true
        }
      }
    }
  }
}

寫入性能優化

之前描述了 ElasticSearch 在內存管理方面的優化,接下來梳理下如何對寫入性能進行優化,寫入性能的優化也和 HBase 類似,無非就是增加吞吐,而增加吞吐的方法就是增大刷寫間隔、合理設置線程數量、開啟異步刷寫(允許數據丟失的情況下)。

增大刷寫間隔

通過修改主配置文件 elasticsearch.yml 或者 Rest API 都可以對 index.refresh_interval進行修改,增大該屬性可以提升寫入吞吐。

PUT  /_template/{TEMPLATE_NAME}
{
  "template":"{INDEX_PATTERN}",
  "settings" : {
    "index.refresh_interval" : "30s"
  }
}
PUT {INDEX_PAATERN}/_settings
{
    "index.refresh_interval" : "30s"
}

合理設置線程數量

調整 elasticsearch.yml ,對 bulk/flush 線程池進行調優,根據本機實際配置:

threadpool.bulk.type:fixed
threadpool.bulk.size:8 #(CPU核數)
threadpool.flush.type:fixed
threadpool.flush.size:8 #(CPU核數)

開啟異步刷寫

如果允許數據丟失,可以對特定 index 開啟異步刷寫:

PUT  /_template/{TEMPLATE_NAME}
{
  "template":"{INDEX_PATTERN}",
  "settings" : {
    "index.translog.durability": "async"
  }
}

PUT  {INDEX_PAATERN}/_settings
{
  "index.translog.durability": "async"
}

審計優化

開啟慢查詢日志

不論是數據庫還是搜索引擎,對于問題的排查,開啟慢查詢日志是十分必要的,ElasticSearch 開啟慢查詢的方式有多種,但是最常用的是調用模板 API 進行全局設置:

PUT  /_template/{TEMPLATE_NAME}
{

  "template":"{INDEX_PATTERN}",
  "settings" : {
    "index.indexing.slowlog.level": "INFO",
    "index.indexing.slowlog.threshold.index.warn": "10s",
    "index.indexing.slowlog.threshold.index.info": "5s",
    "index.indexing.slowlog.threshold.index.debug": "2s",
    "index.indexing.slowlog.threshold.index.trace": "500ms",
    "index.indexing.slowlog.source": "1000",
    "index.search.slowlog.level": "INFO",
    "index.search.slowlog.threshold.query.warn": "10s",
    "index.search.slowlog.threshold.query.info": "5s",
    "index.search.slowlog.threshold.query.debug": "2s",
    "index.search.slowlog.threshold.query.trace": "500ms",
    "index.search.slowlog.threshold.fetch.warn": "1s",
    "index.search.slowlog.threshold.fetch.info": "800ms",
    "index.search.slowlog.threshold.fetch.debug": "500ms",
    "index.search.slowlog.threshold.fetch.trace": "200ms"
  },
  "version"  : 1
}

對于已經存在的 index 使用 settings API:

PUT {INDEX_PAATERN}/_settings
{
    "index.indexing.slowlog.level": "INFO",
    "index.indexing.slowlog.threshold.index.warn": "10s",
    "index.indexing.slowlog.threshold.index.info": "5s",
    "index.indexing.slowlog.threshold.index.debug": "2s",
    "index.indexing.slowlog.threshold.index.trace": "500ms",
    "index.indexing.slowlog.source": "1000",
    "index.search.slowlog.level": "INFO",
    "index.search.slowlog.threshold.query.warn": "10s",
    "index.search.slowlog.threshold.query.info": "5s",
    "index.search.slowlog.threshold.query.debug": "2s",
    "index.search.slowlog.threshold.query.trace": "500ms",
    "index.search.slowlog.threshold.fetch.warn": "1s",
    "index.search.slowlog.threshold.fetch.info": "800ms",
    "index.search.slowlog.threshold.fetch.debug": "500ms",
    "index.search.slowlog.threshold.fetch.trace": "200ms"
}

這樣,在日志目錄下的慢查詢日志就會有輸出記錄必要的信息了。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,401評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,011評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,263評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,543評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,323評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,874評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,968評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,095評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,605評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,551評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,720評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,242評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,961評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,358評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,612評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,330評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,690評論 2 370

推薦閱讀更多精彩內容