ElasticSearch讀寫底層原理及性能調優

##一,讀寫底層原理java

Elasticsearch寫人數據的過程

1)客戶端選擇一個node發送請求過去,這個node就是coordinating node(協調節點) 2)coordinating node,對document進行路由,將請求轉發給對應的node(有primary shard) 3)實際的node上的primary shard處理請求,而後將數據同步到replica node 4)coordinating node,若是發現primary node和全部replica node都搞定以後,就返回響應結果給客戶端node

Elasticsearch讀取數據的過程

1)客戶端發送請求到任意一個node,成爲coordinate node 2)coordinate node對document進行路由,將請求轉發到對應的node,此時會使用round-robin隨機輪詢算法,在primary shard以及其全部replica中隨機選擇一個,讓讀請求負載均衡 3)接收請求的node返回document給coordinate node 4)coordinate node返回document給客戶端算法

1.寫入document時,每一個document會自動分配一個全局惟一的id即doc id,同時也是根據doc id進行hash路由到對應的primary shard上。也能夠手動指定doc id,好比用訂單id,用戶id。bootstrap

2.讀取document時,你能夠經過doc id來查詢,而後會根據doc id進行hash,判斷出來當時把doc id分配到了哪一個shard上面去,從那個shard去查詢api

Elasticsearch搜索數據過程

es最強大的是作全文檢索 1)客戶端發送請求到一個coordinate node 2)協調節點將搜索請求轉發到全部的shard對應的primary shard或replica shard也能夠 3)query phase:每一個shard將本身的搜索結果(其實就是一些doc id),返回給協調節點,由協調節點進行數據的合併、排序、分頁等操做,產出最終結果 4)fetch phase:接着由協調節點,根據doc id去各個節點上拉取實際的document數據,最終返回給客戶端數組

搜索的底層原理:倒排索引緩存

Elasticsearch寫數據的底層原理

1)先寫入buffer,在buffer裏的時候數據是搜索不到的;同時將數據寫入translog日誌文件。 2)若是buffer快滿了,或者到必定時間,就會將buffer數據refresh到一個新的segment file中,可是此時數據不是直接進入segment file的磁盤文件的,而是先進入os cache的。這個過程就是refresh。 每隔1秒鐘,es將buffer中的數據寫入一個新的segment file,每秒鐘會產生一個新的磁盤文件,segment file,這個segment file中就存儲最近1秒內buffer中寫入的數據。 可是若是buffer裏面此時沒有數據,那固然不會執行refresh操做咯,每秒建立換一個空的segment file,若是buffer裏面有數據,默認1秒鐘執行一次refresh操做,刷入一個新的segment file中。 操做系統裏面,磁盤文件其實都有一個東西,叫作os cache,操做系統緩存,就是說數據寫入磁盤文件以前,會先進入os cache,先進入操做系統級別的一個內存緩存中去。 只要buffer中的數據被refresh操做,刷入os cache中,就表明這個數據就能夠被搜索到了。安全

爲何叫es是準實時的?NRT,near real-time,準實時。默認是每隔1秒refresh一次的,因此es是準實時的,由於寫入的數據1秒以後才能被看到。bash

能夠經過es的restful api或者java api,手動執行一次refresh操做,就是手動將buffer中的數據刷入os cache中,讓數據立馬就能夠被搜索到。服務器

只要數據被輸入os cache中,buffer就會被清空了,由於不須要保留buffer了,數據在translog裏面已經持久化到磁盤去一份了

圖片.png

二,性能調優

系統層面的調優

系統層面的調優主要是內存的設定與避免交換內存。 ES 安裝後默認設置的堆內存是 1GB,這很明顯是不夠的,那麼接下來就會有一個問題出現:咱們要設置多少內存給 ES 呢? 其實這是要看咱們集羣節點的內存大小,還取決於咱們是否在服務器節點上仍是否要部署其餘服務。 若是內存相對很大,如 64G 及以上,而且不在 ES 集羣上部署其餘服務,那麼建議 ES 內存能夠設置爲 31G-32G,由於這裏有一個 32G 性能瓶頸問題,直白的說就是即便你給了 ES 集羣大於 32G 的內存,其性能也不必定會更加優良,甚至會不如設置爲 31G-32G 時候的性能。 設置 ES 集羣內存的時候,還有一點就是確保堆內存最小值(Xms)與最大值(Xmx)的大小是相同的,防止程序在運行時改變堆內存大小,這是一個很耗系統資源的過程。

禁止swap,一旦容許內存與磁盤的交換,會引發致命的性能問題。 swap空間是一塊磁盤空間,操做系統使用這塊空間保存從內存中換出的操做系統不經常使用page數據,這樣能夠分配出更多的內存作page cache。這樣一般會提高系統的吞吐量和IO性能,但一樣會產生不少問題。頁面頻繁換入換出會產生IO讀寫、操做系統中斷,這些都很影響系統的性能。這個值越大操做系統就會更加積極的使用swap空間。 經過: 在elasticsearch.yml 中 bootstrap.memory_lock: true, 以保持JVM鎖定內存,保證ES的性能。

分片與副本

分片 (shard):ES 是一個分佈式的搜索引擎, 索引一般都會分解成不一樣部分, 分佈在不一樣節點的部分數據就是分片。ES 自動管理和組織分片, 並在必要的時候對分片數據進行再平衡分配, 因此用戶基本上不用擔憂分片的處理細節。建立索引時默認的分片數爲 5 個,而且一旦建立不能更改。

副本 (replica):ES 默認建立一份副本,就是說在 5 個主分片的基礎上,每一個主分片都相應的有一個副本分片。額外的副本有利有弊,有副本能夠有更強的故障恢復能力,但也佔了相應副本倍數的磁盤空間。

那咱們在建立索引的時候,應該建立多少個分片與副本數呢?

對於副本數,比較好肯定,能夠根據咱們集羣節點的多少與咱們的存儲空間決定,咱們的集羣服務器多,而且有足夠大多存儲空間,能夠多設置副本數,通常是 1-3 個副本數,若是集羣服務器相對較少而且存儲空間沒有那麼寬鬆,則能夠只設定一份副本以保證容災(副本數能夠動態調整)。

對於分片數,是比較難肯定的。由於一個索引分片數一旦肯定,就不能更改,因此咱們在建立索引前,要充分的考慮到,之後咱們建立的索引所存儲的數據量,不然建立了不合適的分片數,會對咱們的性能形成很大的影響。

對於分片數的大小,業界一致認爲分片數的多少與內存掛鉤,認爲 1GB 堆內存對應 20-25 個分片,而一個分片的大小不要超過 50G,這樣的配置有助於集羣的健康。可是我我的認爲這樣的配置方法過於死板,我我的在調優 ES 集羣的過程當中,根據總數據量的大小,設定了相應的分片,保證每個分片的大小沒有超過 50G(大概在 40G 左右),可是相比以前的分片數查詢起來,效果並不明顯。以後又嘗試了增長分片數,發現分片數增多以後,查詢速度有了明顯的提高,每個分片的數據量控制在 10G 左右。

查詢大量小分片使得每一個分片處理數據速度更快了,那是否是分片數越多,咱們的查詢就越快,ES 性能就越好呢?其實也不是,由於在查詢過程當中,有一個分片合併的過程,若是分片數不斷的增長,合併的時間則會增長,並且隨着更多的任務須要按順序排隊和處理,更多的小分片不必定要比查詢較小數量的更大的分片更快。若是有多個併發查詢,則有不少小碎片也會下降查詢吞吐量。

若是如今你的場景是分片數不合適了,可是又不知道如何調整,那麼有一個好的解決方法就是按照時間建立索引,而後進行通配查詢。若是天天的數據量很大,則能夠按天建立索引,若是是一個月積累起來致使數據量很大,則能夠一個月建立一個索引。若是要對現有索引進行從新分片,則須要重建索引, 對於每一個index的shard數量,能夠根據數據總量、寫入壓力、節點數量等綜合考量後設定,而後根據數據增加狀態按期檢測下shard數量是否合理。

騰訊雲CES技術團隊的推薦方案是: 對於數據量較小(100GB如下)的index,每每寫入壓力查詢壓力相對較低,通常設置3~5個shard,number_of_replicas設置爲1便可(也就是一主一從,共兩副本) 。 對於數據量較大(100GB以上)的index: 通常把單個shard的數據量控制在(20GB~50GB) 讓index壓力分攤至多個節點:可經過index.routing.allocation.total_shards_per_node參數,強制限定一個節點上該index的shard數量,讓shard儘可能分配到不一樣節點上 綜合考慮整個index的shard數量,若是shard數量(不包括副本)超過50個,就極可能引起拒絕率上升的問題,此時可考慮把該index拆分爲多個獨立的index,分攤數據量,同時配合routing使用,下降每一個查詢須要訪問的shard數量。

下面我會介紹一些 ES 關鍵參數的調優。 有不少場景是,咱們的 ES 集羣佔用了多大的 cpu 使用率,該如何調節呢。cpu 使用率高,有多是寫入致使的,也有多是查詢致使的,那要怎麼查看呢? 能夠先經過 GET _nodes/{node}/hot_threads 查看線程棧,查看是哪一個線程佔用 cpu 高,若是是 elasticsearch[{node}][search][T#10] 則是查詢致使的,若是是 elasticsearch[{node}][bulk][T#1] 則是數據寫入致使的。 在實際調優中,cpu 使用率很高,使用固態硬盤(Solid State Disk)替代機械硬盤。SSD 與機械磁盤相比,具備高效的讀寫速度和穩定性。若是不是 SSD,建議把 index.merge.scheduler.max_thread_count: 1 索引 merge 最大線程數設置爲 1 個,該參數能夠有效調節寫入的性能。由於在存儲介質上併發寫,因爲尋址的緣由,寫入性能不會提高,只會下降。

還有幾個重要參數能夠進行設置,各位同窗能夠視本身的集羣狀況與數據狀況而定。

index.refresh_interval:這個參數的意思是數據寫入後幾秒能夠被搜索到,默認是 1s。每次索引的 refresh 會產生一個新的 lucene 段, 這會致使頻繁的合併行爲,若是業務需求對實時性要求沒那麼高,能夠將此參數調大,實際調優告訴我,該參數確實很給力,cpu 使用率直線降低。

indices.memory.index_buffer_size:若是咱們要進行很是重的高併發寫入操做,那麼最好將 indices.memory.index_buffer_size 調大一些,index buffer 的大小是全部的 shard 公用的,對於每一個 shard 來講,最多給 512mb,由於再大性能就沒什麼提高了。ES 會將這個設置做爲每一個 shard 共享的 index buffer,那些特別活躍的 shard 會更多的使用這個 buffer。默認這個參數的值是 10%,也就是 jvm heap 的 10%。

translog:ES 爲了保證數據不丟失,每次 index、bulk、delete、update 完成的時候,必定會觸發刷新 translog 到磁盤上。在提升數據安全性的同時固然也下降了一點性能。若是你不在乎這點可能性,仍是但願性能優先,能夠設置以下參數:

"index.translog": {
 "sync_interval": "120s",     #sync間隔調高
 "durability": "async",      # 異步更新
 "flush_threshold_size":"1g" #log文件大小
        }
複製代碼

這樣設定的意思是開啓異步寫入磁盤,並設定寫入的時間間隔與大小,有助於寫入性能的提高。 replica數目

爲了讓建立的es index在每臺datanode上均勻分佈,同一個datanode上同一個index的shard數目不該超過3個。 計算公式: (number_of_shard * (1+number_of_replicas)) < 3*number_of_datanodes 每臺機器上分配的shard數目 "index.routing.allocation.total_shards_per_node": "2

磁盤緩存相關參數

vm.dirty_background_ratio 這個參數指定了當文件系統緩存髒頁數量達到系統內存百分之多少時(如5%)就會觸發pdflush/flush/kdmflush等後臺回寫進程運行,將必定緩存的髒頁異步地刷入外存;

vm.dirty_ratio

該參數則指定了當文件系統緩存髒頁數量達到系統內存百分之多少時(如10%),系統不得不開始處理緩存髒頁(由於此時髒頁數量已經比較多,爲了不數據丟失須要將必定髒頁刷入外存);在此過程當中不少應用進程可能會由於系統轉而處理文件IO而阻塞。

把該參數適當調小,原理通(1)相似。若是cached的髒數據所佔比例(這裏是佔MemTotal的比例)超過這個設置,系統會中止全部的應用層的IO寫操做,等待刷完數據後恢復IO。因此萬一觸發了系統的這個操做,對於用戶來講影響很是大的。

sysctl -w vm.dirty_ratio=10
sysctl -w vm.dirty_background_ratio=5
複製代碼

爲了將設置永久保存,將上述配置項寫入/etc/sysctl.conf文件中

vm.dirty_ratio = 10
vm.dirty_background_ratio = 5
複製代碼
merge相關參數
"index.merge.policy.floor_segment": "100mb",
 "index.merge.scheduler.max_thread_count": "1",
 "index.merge.policy.min_merge_size": "10mb"
複製代碼
還有一些超時參數的設置:
discovery.zen.ping_timeout 判斷 master 選舉過程當中,發現其餘 node 存活的超時設置
discovery.zen.fd.ping_interval 節點被 ping 的頻率,檢測節點是否存活
discovery.zen.fd.ping_timeout 節點存活響應的時間,默認爲 30s,若是網絡可能存在隱患,能夠適當調大
discovery.zen.fd.ping_retries ping 失敗/超時多少致使節點被視爲失敗,默認爲 3
複製代碼
Linux系統參數配置
文件句柄

Linux中,每一個進程默認打開的最大文件句柄數是1000,對於服務器進程來講,顯然過小,經過修改/etc/security/limits.conf來增大打開最大句柄數

* - nofile 65535
複製代碼
讀優化

①避免大結果集和深翻 在上一篇講到了集羣中的查詢流程,例如,要查詢從 from 開始的 size 條數據,則須要在每一個分片中查詢打分排名在前面的 from+size 條數據。 協同節點將收集到的n×(from+size)條數據聚合,再進行一次排序,而後從 from+size 開始返回 size 條數據。 當 from、size 或者 n 中有一個值很大的時候,須要參加排序的數量也會增加,這樣的查詢會消耗不少 CPU 資源,從而致使效率的下降。 爲了提高查詢效率,ES 提供了 Scroll 和 Scroll-Scan 這兩種查詢模式。 Scroll:是爲檢索大量的結果而設計的。例如,咱們須要查詢 1~100 頁的數據,每頁 100 條數據。 若是使用 Search 查詢:每次都須要在每一個分片上查詢得分最高的 from+100 條數據,而後協同節點把收集到的 n×(from+100)條數據聚合起來再進行一次排序。 每次返回 from+1 開始的 100 條數據,而且要重複執行 100 次。 若是使用 Scroll 查詢:在各個分片上查詢 10000 條數據,協同節點聚合 n×10000 條數據進行合併、排序,並將排名前 10000 的結果快照起來。這樣作的好處是減小了查詢和排序的次數。

其餘建議

插入索引自動生成 id:當寫入端使用特定的 id 將數據寫入 ES 時,ES 會檢查對應的索引下是否存在相同的 id,這個操做會隨着文檔數量的增長使消耗愈來愈大,因此若是業務上沒有硬性需求建議使用 ES 自動生成的 id,加快寫入速率。

避免稀疏索引:索引稀疏以後,會致使索引文件增大。ES 的 keyword,數組類型採用 doc_values 結構,即便字段是空值,每一個文檔也會佔用必定的空間,因此稀疏索引會形成磁盤增大,致使查詢和寫入效率下降。

參數調優
index.merge.scheduler.max_thread_count:1 # 索引 merge 最大線程數
indices.memory.index_buffer_size:30% # 內存
index.translog.durability:async # 這個能夠異步寫硬盤,增大寫的速度
index.translog.sync_interval:120s #translog 間隔時間
discovery.zen.ping_timeout:120s # 心跳超時時間
discovery.zen.fd.ping_interval:120s     # 節點檢測時間
discovery.zen.fd.ping_timeout:120s     #ping 超時時間
discovery.zen.fd.ping_retries:6 # 心跳重試次數
thread_pool.bulk.size:20 # 寫入線程個數 因爲咱們查詢線程都是在代碼裏設定好的,我這裏只調節了寫入的線程數
thread_pool.bulk.queue_size:1000 # 寫入線程隊列大小
index.refresh_interval:300s #index 刷新間隔
bootstrap.memory_lock: true#以保持JVM鎖定內存,保證ES的性能。 
複製代碼
關於重建索引

在重建索引以前,首先要考慮一下重建索引的必要性,由於重建索引是很是耗時的。 ES 的 reindex api 不會去嘗試設置目標索引,不會複製源索引的設置,因此咱們應該在運行_reindex 操做以前設置目標索引,包括設置映射(mapping),分片,副本等。

第一步,和建立普通索引同樣建立新索引。當數據量很大的時候,須要設置刷新時間間隔,把 refresh_intervals 設置爲-1,即不刷新,number_of_replicas 副本數設置爲 0(由於副本數能夠動態調整,這樣有助於提高速度)。

{ 
"settings": {
 "number_of_shards": "50",
 "number_of_replicas": "0", 
 "index": { "refresh_interval": "-1" }
              } 

"mappings":
 {
    }
}
複製代碼

第二步,調用 reindex 接口,建議加上 wait_for_completion=false 的參數條件,這樣 reindex 將直接返回 taskId。

POST _reindex?wait_for_completion=false { "source": { "index": "old_index",   //原有索引
  "size": 5000            //一個批次處理的數據量
}, "dest": { "index": "new_index",   //目標索引
}
}
複製代碼

第三步,等待。能夠經過 GET _tasks?detailed=true&actions=*reindex 來查詢重建的進度。若是要取消 task 則調用_tasks/node_id:task_id/_cancel

第四步,刪除舊索引,釋放磁盤空間。重建索引的時候,在參數里加上上一次重建索引的時間戳,直白的說就是,好比咱們的數據是 100G,這時候咱們重建索引了,可是這個 100G 在增長,那麼咱們重建索引的時候,須要記錄好重建索引的時間戳,記錄時間戳的目的是下一次重建索引跑任務的時候不用所有重建,只須要在此時間戳以後的重建就能夠,如此迭代,直到新老索引數據量基本一致,把數據流向切換到新索引的名字。

POST /_reindex
{ 
"conflicts": "proceed",          //意思是衝突以舊索引爲準,直接跳過沖突,不然會拋出異常,中止task
    "source": { "index": "old_index"         //舊索引
        "query": { "constant_score" : 
                      { "filter" : { 
                          "range" : { "data_update_time" : 
                                          { "gte" : 123456789   //reindex開始時刻前的毫秒時間戳
                                              }
                        }
                    }
                }
            }
        }, 
"dest": { "index": "new_index",       //新索引
        "version_type": "external"  //以舊索引的數據爲準
 }
}
複製代碼
相關文章
相關標籤/搜索