
Elasticsearch的默認配置項是比較全面的,在不做太多配置的情況下可以使用es的全文檢索,高亮顯示,聚合,和數據的索引。但是在比較了解es的情況下,可以對很對配置進行優化。
一、一般建議
(相關資料圖)
1、不要返回太大的結果集
Es的本質是搜索引擎,所以它的工作機制是查詢文件的匹配度,而不是像數據庫那些的完全匹配,若需要使用類似于此的查詢方式,請使用Scroll API。
2 、避免過大的文檔
Es索引的單個文檔默認最大值為100M,但是可以使用http.max_context_length進行調整,只是es底層的lucene的最大限制為2G。即使不考慮es或lucene的限制,但是大文檔的索引查詢等代價都是比較大的,可以考慮使用分解的方式進行索引(比如不需要講一整本書的內容進行索引,而是將其拆分為章節索引)。
3 、盡量避免稀疏字段(出現次數少)
Es底層的lucene數據結構最適合于密集的數據(即所有文檔都具有相同的字段,反之稱為稀疏字段),特別是默認的text類型和doc value的numerics(數字)、date、ip、keyword類型。原因在于lucene內部為每一個document都會創建一個整數的doc id,例如使用match進行查詢時會產生迭代器,并使用該id檢索并計算文檔的得分。即若某一索引中有n條數據,則即使某一字段出現的次數非常少也會創建n個(若為空,默認需要預留1個字節)的存儲空間。雖然稀疏的字段對于存儲影響是最大的,但是也會影響到索引和查詢的速度,因素索引時也會有寫操作,查詢時候需要跳過。并且對于norms 和doc values的影響最大。基于該原因,應該從以下幾點盡量避免:
1)、避免將不相關的數據放在同一個索引中
綜上所述,盡量不要把數據結構不同的數據放到同一索引中,并且可以考慮將數據量較少的集合創建較少的分片存儲。當然若數據之間存在父子關系則例外,畢竟父子關系的數據不能存儲在不同的索引當中。
2)、規范化文檔結構
即使由于各種原因需要將不同的數據存儲到同一索引中,也可以采用某些手段減少稀疏字段的可能。比如同一Index的兩個type中都包含日期字段而名稱各不相同,那么完全可以將名稱進行統一。
3)、避免同一索引中的Type的字段相似度較低
盡管同一索引中對于type的字段相似度沒有要求,但是由于以上原因,盡量避免將字段相似度(或者說映射相似度)較低的Type放到同一索引中。
4)、在稀疏的字段中禁用norms 和doc_values
在避免了以上的情況后,需要考慮,若字段不需要用分數計算匹配的程度,而僅僅是使用filter(filter本身還可以進行緩存)查詢是否匹配,則可以禁用稀疏字段(或非稀疏字段,但是畢竟對稀疏字段的影響最大)。若不需要進行排序或聚合的字段可以禁用doc_value。但是需要注意的是若需要取消禁用則只能重新創建映射和索引數據。
二、索引速度
1、盡量使用批量索引
批量索引的效率會比單個索引高很多(這一點試一下就知道很顯著),知道批量索引效果顯著,那多少是效率最高的呢。可以從200開始翻倍的往上疊加,以存在最佳數據。
2、客戶端盡量使用多線程批量索引
使用單線程的批量索引并不能最大限度的使用es的資源,所有最好使用多線程或多進程(內的單個或多個線程)索引數據。而使用多少線程數合適及每個線程內的buik的數量也需要測試,遞增測試知道IO或者CPU飽和。而當java api報EsRejectedExecutionException錯TOO_MANY_REQUESTS (429)時,說明需要重試并且需要指數級的遞減。
3、增加刷新機制的間隔
Es從數據索引到能不查詢整個過程默認為1s,使用index.refresh_interval參數控制。若對數據的實時性要求不高的話,可適當調整該參數到業務系統可接受的范圍。在該間隔時間內es會強制創建一個新的segment(段),時間間隔越大則創建的段也會越大,也減小了后續字段合并段的壓力(段其實的lucene底層的數據結構,詳細可查詢lucene與segment的關系)。
4、數據初始索引時禁用刷新和副本機制
若有一大批數據需要索引的時候(前提條件),由于刷新和副本機制對數據索引性能影響較大,可以將index.refresh_interval設置為-1,將index.number_of_replicas設置為0以禁用該兩機制。直到本次數據全部索引完成后再將這兩個參數調整至合理的值,然而應該明白性能與數據安全總是不能同時得到滿足,完全看業務數據的重要性。
5、禁止內存交換
什么是內存交換,怎樣禁止(或設置一個較低的值),可以參見生產環境配置一節。這對于es來說非常重要,必須完全禁用。
6、文件系統緩存的內存不能低于服務器的一半
文件系統緩存將被用來緩沖輸入/輸出操作,這對于es來說非常的重要,要求內存不能少于服務器內存的一半,即es的jvm heap的值設置應該小于服務器內存的一半。
7、盡量使用es自動生成的id
作為es的document id應該知道其作用第一是確定文檔的唯一性,第二默認情況下使用id作為route值計算文檔應該被分配到哪個shard,同時也是生成uid的成員。若索引時制定id的情況下,會先檢測其唯一性,其代價是比較大的,并且隨著索引文檔數的增加消耗會越來越大,若沒有業務需要最好使用自動生成的id(會跳過檢查過程)。
8、磁盤選擇
在保證文件系統緩存需要的內存外持久化(存儲)的硬件選擇也是很重要的,若本地存儲的話可以選擇ssd替換普通的磁盤。并且盡量避免使用遠程文件系統存儲,如NFS 或 SMB(當然一般會選擇本地的ssd存儲)。若可以活需要選擇多個ssd進行存儲的情況下,需要知道其個數越多則不可用的風險越大,需要做好es的災備和恢復機制。
9、索引緩存的大小
當jvm.options通過-Xms4g -Xmx4g為es設置heap(堆)內存后,默認情況下es會為所有活躍的節點配置堆內存的10%分配給索引緩存。但是在重索引的情況下每個分片的索引緩存應該不小于512M,可以使用indices.memory.index_buffer_size進行設置。所以一定要清楚自己的節點數據,設置的節點堆內存大小,以及節點的分片數。
10、其他控制項
除了以上的因素,很多為磁盤設置的配置項對于數據的索引也有影響,了解更多需要參見下面的磁盤使用。
三、查詢速度
1、文件系統緩存的內存
文件系統緩存的內存設置要求不能少于服務器內存的一半,這不僅影響索引的效率,同時也會影響存放在內存中的熱數據,直接影響到查詢的速度。
2、硬件配置
查詢從消耗上來說可分為io型和Cpu型,若io型查詢與磁盤是否SSD或者是否遠程文件系統有關,而cpu型查詢則可以適當增加其配置。
3、文檔建模
適當的情況下應該為文檔建模,以提高查詢。并且應該盡量避免nested(嵌套)的join查詢,會比正常查詢慢幾倍。而使用父子關系則會慢數百倍。
4、根據查詢索引數據結構
應該盡量根據查詢預判對數據結構進行合理的索引。比如需要根據某一只值按照分段統計(比如價格分為幾個段統計)則完全可以在索引合適的數據結構。比如我們項目中會按照用戶行為數據發生小時進行統計,可以基于Script使用es時間字段的doc["orderTime"].date.hourOfDay進行處理,當然最好在索引時單獨索引一個字段。
5、Mapping(映射)
并不是索引的數組都應該被映射為es的numeric類型,而應該根據情況決定。這就應該考慮mapping中是否開啟"numeric_detection": true配置。
6、盡量不要使用Script(腳本)
一般情況下盡量不要使用Script,若需要則盡量使用painless或expressions語言。
7、關于date類型查詢
Es的date類型由于查詢時間的不確定性,一般情況下是不會進行緩存的,但是若在需求運行的范圍內可以將查詢的時間設置為整數的日志,則可以進行緩存操作,如下:
"range": {
"my_date": {
"gte": "now-1h",
"lte": "now"
}
}
可優化為:
"range": {
"my_date": {
"gte": "now-1h/m",
"lte": "now/m"
}
}
8、將只讀的索引進行強制合并
將所有只讀的索引強制合并到一個統一的megment(段)的索引中。若一個索引是按照時間進行添加,則只有最近時間段的數據需要進行修改,之前的歷史數據一般為只讀狀態可以將移動到一個只讀管理的索引中。
9、global ordinals(全局順序)的設置
全局序數是一種數據結構,用于keyword字段terms聚合。但是由于es不清晰那些需要進行聚合操作,所以會將所以的字段加載進內存中(有些字段永遠也不需要進行terms聚合),所以可以在mapping設置時通過eager_global_ordinals屬性告知es,mapping如下:
PUT index
{
"mappings": {
"type": {
"properties": {
"foo": {
"type": "keyword",
"eager_global_ordinals": true
}
}
}
}
}
10、預加載文件系統緩存
若對es進行重啟操作(前提條件),則文件系統緩存中將是空的。而操作系統將索引中是熱區域數據加載到內存需要一定的時間,則可以使用index.store.preload顯示的告知es需要預加載的內容。
四、磁盤使用
1、禁用某些不需要使用的es特性
一般情況下es索引的同時會將數據添加到doc values中,以便進行查詢和聚合。但是若某一numeric字段只需要用于histograms而不需要使用filter進行操作,則可以在mapping時候設置如下:
PUT index
{
"mappings": {
"type": {
"properties": {
"foo": {
"type": "integer",
"index": false
}
}
}
}
}
默認清下es會將text類型的字段存儲norms 操作,以便使用得分匹配查詢的程度。但是若后續只需要對其進行是否匹配的操作而不需要使用得分則可以進行如下mapping設置:
PUT index
{
"mappings": {
"type": {
"properties": {
"foo": {
"type": "text",
"norms": false
}
}
}
}
}
并且text類型字段還會存儲frequencies和positions ,以便可以進行phrase (短語)查詢操作,若不需要進行phrase 查詢操作可以進行以下mapping操作:
PUT index
{
"mappings": {
"type": {
"properties": {
"foo": {
"type": "text",
"index_options": "freqs"
}
}
}
}
}
若確定只需要進行是否匹配的查詢操作而不會關心score(即不需要查詢的匹配程度),則可以進行如下mapping操作(可以進行查詢操作但是使用phrase查詢操作會報錯,score只能設置一次):
PUT index
{
"mappings": {
"type": {
"properties": {
"foo": {
"type": "text",
"norms": false,
"index_options": "freqs"
}
}
}
}
}
2、關閉String類型的動態映射
String類型的字段使用動態映射會創建text和keyword兩種類型的字段(具體查詢兩種字段類型的作用),而在很多情況下這是浪費的,則需要使用動態映射模板進行映射,或直接對確認的字段進行mapping設置。如下模板只允許String類型動態設置為keyword:
PUT index
{
"mappings": {
"type": {
"dynamic_templates": [
{
"strings": {
"match_mapping_type": "string",
"mapping": {
"type": "keyword"
}
}
}
]
}
}
}
3、禁用_all字段
默認情況下es會將所有的字段的值全部映射到一個叫_all的字段中,若不需要對所有字段同時進行查詢或全文檢索,則完全可以禁用該字段。
4、使用best_compression壓縮
默認情況es對數據進行_source的存儲操作,并且一般情況下這是有必要的,具體需要很清楚其作用。但是我們可以使用best_compression對數據進行壓縮,在方便功能使用的同時盡量少的占用磁盤空間。
5、使用合適的numeric類型
整數和小數類型在使用的使用盡量使用合適的字段類型以節省存儲空間。整數類型可以使用byte, short, integer 或long;小數類型可以使用float優于double, half_float優于float。