日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯(lián)網營銷解決方案
超詳細的Elasticsearch高性能優(yōu)化實踐

【稿件】之前分享的一篇《掌握它才說明你真正懂 Elasticsearch》相信讓大家對 ES 的原理都有所了解,這篇將從 ES 的 API 應用測試,性能優(yōu)化,開發(fā)使用等方面展開,深入學習 ES。

ES 性能調優(yōu)

ES 的默認配置,是綜合了數(shù)據(jù)可靠性、寫入速度、搜索實時性等因素。實際使用時,我們需要根據(jù)公司要求,進行偏向性的優(yōu)化。

寫優(yōu)化

假設我們的應用場景要求是,每秒 300 萬的寫入速度,每條 500 字節(jié)左右。

針對這種對于搜索性能要求不高,但是對寫入要求較高的場景,我們需要盡可能的選擇恰當寫優(yōu)化策略。

綜合來說,可以考慮以下幾個方面來提升寫索引的性能:

  • 加大 Translog Flush ,目的是降低 Iops、Writeblock。
  • 增加 Index Refresh 間隔,目的是減少 Segment Merge 的次數(shù)。
  • 調整 Bulk 線程池和隊列。
  • 優(yōu)化節(jié)點間的任務分布。
  • 優(yōu)化 Lucene 層的索引建立,目的是降低 CPU 及 IO。

①批量提交

ES 提供了 Bulk API 支持批量操作,當我們有大量的寫任務時,可以使用 Bulk 來進行批量寫入。

每次提交的數(shù)據(jù)量為多少時,能達到很好性能,主要受到文件大小、網絡情況、數(shù)據(jù)類型、集群狀態(tài)等因素影響。

通用的策略如下:Bulk 默認設置批量提交的數(shù)據(jù)量不能超過 100M。數(shù)據(jù)條數(shù)一般是根據(jù)文檔的大小和服務器性能而定的,但是單次批處理的數(shù)據(jù)大小應從 5MB~15MB 逐漸增加。

我們可以跟著,感受一下 Bulk 接口,如下所示:

 
 
 
 
  1. $ vi request 
  2. $ cat request 
  3. { "index" : { "_index" : "chandler","_type": "test", "_id" : "1" } } 
  4. { "name" : "錢丁君","age": "18" } 
  5. $ curl -s -H "Content-Type: application/json" -XPOST localhost:9200/_bulk --data-binary @request; echo 
  6. {"took":214,"errors":false,"items":[{"index":{"_index":"chandler","_type":"test","_id":"1","_version":1,"result":"created","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":0,"_primary_term":1,"status":201}}]} 
  7. $ curl -XGET localhost:9200/chandler/test/1?pretty 
  8.   "_index" : "chandler", 
  9.   "_type" : "test", 
  10.   "_id" : "1", 
  11.   "_version" : 1, 
  12.   "found" : true, 
  13.   "_source" : { 
  14.     "name" : "錢丁君", 
  15.     "age" : "18" 
  16.   } 

Bulk 不支持 Gget 操作,因為沒什么用處。

②優(yōu)化存儲設備

ES 是一種密集使用磁盤的應用,在段合并的時候會頻繁操作磁盤,所以對磁盤要求較高,當磁盤速度提升之后,集群的整體性能會大幅度提高。

磁盤的選擇,提供以下幾點建議:

  • 使用固態(tài)硬盤(Solid State Disk)替代機械硬盤。SSD 與機械磁盤相比,具有高效的讀寫速度和穩(wěn)定性。
  • 使用 RAID 0。RAID 0 條帶化存儲,可以提升磁盤讀寫效率。
  • 在 ES 的服務器上掛載多塊硬盤。使用多塊硬盤同時進行讀寫操作提升效率,在配置文件 ES 中設置多個存儲路徑,如下所示:
 
 
 
 
  1. path.data:/path/to/data1,/path/to/data2。 

避免使用 NFS(Network File System)等遠程存儲設備,網絡的延遲對性能的影響是很大的。

③合理使用合并

Lucene 以段的形式存儲數(shù)據(jù)。當有新的數(shù)據(jù)寫入索引時,Lucene 就會自動創(chuàng)建一個新的段。

隨著數(shù)據(jù)量的變化,段的數(shù)量會越來越多,消耗的多文件句柄數(shù)及 CPU 就越多,查詢效率就會下降。

由于 Lucene 段合并的計算量龐大,會消耗大量的 I/O,所以 ES 默認采用較保守的策略,讓后臺定期進行段合并,如下所述:

  • 索引寫入效率下降:當段合并的速度落后于索引寫入的速度時,ES 會把索引的線程數(shù)量減少到 1。

這樣可以避免出現(xiàn)堆積的段數(shù)量爆發(fā),同時在日志中打印出“now throttling indexing”INFO 級別的“警告”信息。

  • 提升段合并速度:ES 默認對段合并的速度是 20m/s,如果使用了 SSD,我們可以通過以下的命令將這個合并的速度增加到 100m/s。
  •   
      
      
      
    1. PUT /_cluster/settings 
    2.     "persistent" : { 
    3.         "indices.store.throttle.max_bytes_per_sec" : "100mb" 
    4.     } 

④減少 Refresh 的次數(shù)

Lucene 在新增數(shù)據(jù)時,采用了延遲寫入的策略,默認情況下索引的 refresh_interval 為 1 秒。

Lucene 將待寫入的數(shù)據(jù)先寫到內存中,超過 1 秒(默認)時就會觸發(fā)一次 Refresh,然后 Refresh 會把內存中的的數(shù)據(jù)刷新到操作系統(tǒng)的文件緩存系統(tǒng)中。

如果我們對搜索的實效性要求不高,可以將 Refresh 周期延長,例如 30 秒。

這樣還可以有效地減少段刷新次數(shù),但這同時意味著需要消耗更多的Heap內存。

如下所示:

 
 
 
 
  1. index.refresh_interval:30s 

⑤加大 Flush 設置

Flush 的主要目的是把文件緩存系統(tǒng)中的段持久化到硬盤,當 Translog 的數(shù)據(jù)量達到 512MB 或者 30 分鐘時,會觸發(fā)一次 Flush。

index.translog.flush_threshold_size 參數(shù)的默認值是 512MB,我們進行修改。

增加參數(shù)值意味著文件緩存系統(tǒng)中可能需要存儲更多的數(shù)據(jù),所以我們需要為操作系統(tǒng)的文件緩存系統(tǒng)留下足夠的空間。

⑥減少副本的數(shù)量

ES 為了保證集群的可用性,提供了 Replicas(副本)支持,然而每個副本也會執(zhí)行分析、索引及可能的合并過程,所以 Replicas 的數(shù)量會嚴重影響寫索引的效率。

當寫索引時,需要把寫入的數(shù)據(jù)都同步到副本節(jié)點,副本節(jié)點越多,寫索引的效率就越慢。

如果我們需要大批量進行寫入操作,可以先禁止 Replica 復制,設置 index.number_of_replicas: 0 關閉副本。在寫入完成后,Replica 修改回正常的狀態(tài)。

讀優(yōu)化

①避免大結果集和深翻

在上一篇講到了集群中的查詢流程,例如,要查詢從 from 開始的 size 條數(shù)據(jù),則需要在每個分片中查詢打分排名在前面的 from+size 條數(shù)據(jù)。

協(xié)同節(jié)點將收集到的n×(from+size)條數(shù)據(jù)聚合,再進行一次排序,然后從 from+size 開始返回 size 條數(shù)據(jù)。

當 from、size 或者 n 中有一個值很大的時候,需要參加排序的數(shù)量也會增長,這樣的查詢會消耗很多 CPU 資源,從而導致效率的降低。

為了提升查詢效率,ES 提供了 Scroll 和 Scroll-Scan 這兩種查詢模式。

Scroll:是為檢索大量的結果而設計的。例如,我們需要查詢 1~100 頁的數(shù)據(jù),每頁 100 條數(shù)據(jù)。

如果使用 Search 查詢:每次都需要在每個分片上查詢得分較高的 from+100 條數(shù)據(jù),然后協(xié)同節(jié)點把收集到的 n×(from+100)條數(shù)據(jù)聚合起來再進行一次排序。

每次返回 from+1 開始的 100 條數(shù)據(jù),并且要重復執(zhí)行 100 次。

如果使用 Scroll 查詢:在各個分片上查詢 10000 條數(shù)據(jù),協(xié)同節(jié)點聚合 n×10000 條數(shù)據(jù)進行合并、排序,并將排名前 10000 的結果快照起來。這樣做的好處是減少了查詢和排序的次數(shù)。

Scroll 初始查詢的命令是:

 
 
 
 
  1. $ vim scroll 
  2. $ cat scroll 
  3.     "query": { 
  4.         "match": { 
  5.             "name": "錢丁君" 
  6.         } 
  7.     }, 
  8.     "size":20 
  9. $ curl -s -H "Content-Type: application/json; charset=UTF-8" -XGET localhost:9200/chandler/test/_search?scroll=2m --data-binary @scroll; echo 
  10. {"_scroll_id":"DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAAGFlB6Y3QtNk9oUmdpc09Tb21rX2NXQXcAAAAAAAAABxZQemN0LTZPaFJnaXNPU29ta19jV0F3AAAAAAAAAAgWUHpjdC02T2hSZ2lzT1NvbWtfY1dBdwAAAAAAAAAJFlB6Y3QtNk9oUmdpc09Tb21rX2NXQXcAAAAAAAAAChZQemN0LTZPaFJnaXNPU29ta19jV0F3","took":14,"timed_out":false,"_shards":{"total":5,"successful":5,"skipped":0,"failed":0},"hits":{"total":1,"max_score":0.8630463,"hits":[{"_index":"chandler","_type":"test","_id":"1","_score":0.8630463,"_source":{ "name" : "錢丁君","age": "18" }}]}} 

以上查詢語句的含義是,在 chandler 索引的 test type 里查詢字段 name 包含“錢丁君”的數(shù)據(jù)。

scroll=2m 表示下次請求的時間不能超過 2 分鐘,size 表示這次和后續(xù)的每次請求一次返回的數(shù)據(jù)條數(shù)。

在這次查詢的結果中除了返回了查詢到的結果,還返回了一個 scroll_id,可以把它作為下次請求的參數(shù)。

再次請求的命令,如下所示:

因為這次并沒有到分片里查詢數(shù)據(jù),而是直接在生成的快照里面以游標的形式獲取數(shù)據(jù)。

所以這次查詢并沒有包含 index 和 type,也沒有查詢條件:

  1. "scroll": "2m":指本次請求的時間不能超過 2 分鐘。
  2. scroll_id:是上次查詢時返回的 scroll_id。

Scroll-Scan:Scroll 是先做一次初始化搜索把所有符合搜索條件的結果緩存起來生成一個快照,然后持續(xù)地、批量地從快照里拉取數(shù)據(jù)直到沒有數(shù)據(jù)剩下。

而這時對索引數(shù)據(jù)的插入、刪除、更新都不會影響遍歷結果,因此 Scroll 并不適合用來做實時搜索。

其思路和使用方式與 Scroll 非常相似,但是 Scroll-Scan 關閉了 Scroll 中最耗時的文本相似度計算和排序,使得性能更加高效。

為了使用 Scroll-Scan,需要執(zhí)行一個初始化搜索請求,將 search_type 設置成 Scan,告訴 ES 集群不需要文本相似計算和排序,只是按照數(shù)據(jù)在索引中順序返回結果集:

 
 
 
 
  1. $ vi scroll 
  2. $ cat scroll 
  3.     "query": { 
  4.         "match": { 
  5.             "name": "錢丁君" 
  6.         } 
  7.     }, 
  8.     "size":20, 
  9.     "sort": [ 
  10.       "_doc" 
  11.     ] 
  12. $ curl -H "Content-Type: application/json; charset=UTF-8" -XGET 'localhost:9200/chandler/test/_search?scroll=2m&pretty=true' --data-binary @scroll 
  13.   "_scroll_id" : "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAABWFlB6Y3QtNk9oUmdpc09Tb21rX2NXQXcAAAAAAAAAVxZQemN0LTZPaFJnaXNPU29ta19jV0F3AAAAAAAAAFgWUHpjdC02T2hSZ2lzT1NvbWtfY1dBdwAAAAAAAABZFlB6Y3QtNk9oUmdpc09Tb21rX2NXQXcAAAAAAAAAWhZQemN0LTZPaFJnaXNPU29ta19jV0F3", 
  14.   "took" : 3, 
  15.   "timed_out" : false, 
  16.   "_shards" : { 
  17.     "total" : 5, 
  18.     "successful" : 5, 
  19.     "skipped" : 0, 
  20.     "failed" : 0 
  21.   }, 
  22.   "hits" : { 
  23.     "total" : 1, 
  24.     "max_score" : null, 
  25.     "hits" : [ 
  26.       { 
  27.         "_index" : "chandler", 
  28.         "_type" : "test", 
  29.         "_id" : "1", 
  30.         "_score" : null, 
  31.         "_source" : { 
  32.           "name" : "錢丁君", 
  33.           "age" : "18" 
  34.         }, 
  35.         "sort" : [ 
  36.           0 
  37.         ] 
  38.       } 
  39.     ] 
  40.   } 

注意:Elasticsearch 2.1.0 版本之后移除了 search_type=scan,使用 "sort": [ "_doc"] 進行代替。

  • Scroll 和 Scroll-Scan 有一些差別,如下所示:
  • Scroll-Scan不進行文本相似度計算,不排序,按照索引中的數(shù)據(jù)順序返回。
  • Scroll-Scan 不支持聚合操作。

Scroll-Scan 的參數(shù) Size 代表著每個分片上的請求的結果數(shù)量,每次返回 n×size 條數(shù)據(jù)。而 Scroll 每次返回 size 條數(shù)據(jù)。

②選擇合適的路由

ES 中所謂的路由和 IP 網絡不同,是一個類似于 Tag 的東西。在創(chuàng)建文檔的時候,可以通過字段為文檔增加一個路由屬性的 Tag。在多分片的 ES 集群中,對搜索的查詢大致分為如下兩種。

ES 內在機制決定了擁有相同路由屬性的文檔,一定會被分配到同一個分片上,無論是主分片還是副本。

查詢時可以根據(jù) Routing 信息,直接定位到目標分片,避免查詢所有的分片,再經過協(xié)調節(jié)點二次排序。

如圖 1 所示:

圖 1

如果在查詢條件中不包含 Routing,在查詢時就遍歷所有分片,整個查詢主要分為 Scatter、Gather 兩個過程:

  • Scatter(分發(fā)):請求到達協(xié)調節(jié)點之后,協(xié)調節(jié)點將查詢請求分發(fā)給每個分片。
  • Gather(聚合):協(xié)調點在每個分片上完成搜索,再將搜索到的結果集進行排序,將結果數(shù)據(jù)返回給用戶。

如圖 2 所示:

圖 2

通過對比上述兩種查詢流程,我們不難發(fā)現(xiàn),使用 Routing 信息查找的效率很高,避免了多余的查詢。

所以我們在設計 Elasticsearch Mapping 時要合理地利用 Routing 信息,來提升查詢的效率。

例如,在大型的本地分類網站中,可以將城市 ID 作為 Routing 的條件,讓同一個城市的數(shù)據(jù)落在相同的分片中。

默認的公式如下:

 
 
 
 
  1. shard = hash(routing)%number_of_primary_shards 

不過需要注意的是,根據(jù)城市 ID 進行分片時,也會容易出現(xiàn)分片不均勻的情況。

例如,大型城市的數(shù)據(jù)過多,而小城市的數(shù)據(jù)太少,導致分片之間的數(shù)據(jù)量差異很大。

這時就可以進行必要的調整,比如把多個小城市的數(shù)據(jù)合并到一個分片上,把大城市的數(shù)據(jù)按區(qū)域進行拆分到不同分配。

③SearchType

在 Scatter、Gather 的過程中,節(jié)點間的數(shù)據(jù)傳輸和打分(SearchType),可以根據(jù)不同的場景選擇。

如下所示:

  • QUERY_THEN_FETCH:ES 默認的搜索方式。先向所有的分片發(fā)請求,各分片只返回文檔的相似度得分和文檔的 ID,然后協(xié)調節(jié)點按照各分片返回的分數(shù)進行重新排序和排名,再取出需要返回給客戶端的 Size 個文檔 ID。

第 2 步,在相關的分片中取出文檔的詳細信息并返回給用戶。

  • QUERY_AND_FETCH:協(xié)調節(jié)點向所有分片發(fā)送查詢請求,各分片將文檔的相似度得分和文檔的詳細信息一起返回。

然后,協(xié)調節(jié)點進行重新排序,再取出需要返回給客戶端的數(shù)據(jù),將其返回給客戶端。由于只需要在分片中查詢一次,所以性能很好。

  • DFS_QUERY_THEN_FETCH:與 QUERY_THEN_FETCH 類似,但它包含一個額外的階段:在初始查詢中執(zhí)行全局的詞頻計算,以使得更精確地打分,從而讓查詢結果更相關。

QUERY_THEN_FETCH 使用的是分片內部的詞頻信息,而 DFS_QUERY_THEN_FETCH 使用訪問公共的詞頻信息,所以相比 QUERY_THEN_FETCH 性能更低。

  • DFS_QUERY_AND_FETCH:與 QUERY_AND_FETCH 類似,不過使用的是全局的詞頻。

④定期刪除

由于在 Lucene 中段具有不變性,每次進行刪除操作后不會立即從硬盤中進行實際的刪除,而是產生一個 .del 文件記錄刪除動作。

隨著刪除操作的增長,.del 文件會越來也多。當我們進行查詢操作的時候,被刪除的數(shù)據(jù)還會參與檢索中,然后根據(jù) .del 文件進行過濾。.del 文件越多,查詢過濾過程越長,進而影響查詢的效率。

當機器空閑時,我們可以通過如下命令刪除文件,來提升查詢的效率:

 
 
 
 
  1. $ curl -XPOST localhost:9200/chandler/_forcemerge?only_expunge_deletes=true 
  2. {"_shards":{"total":10,"successful":5,"failed":0}} 

定期對不再更新的索引做 optimize (ES 2.0 以后更改為 Force Merge API)。

這 Optimze 的實質是對 Segment File 強制做合并,可以節(jié)省大量的 Segment Memory。

堆大小的設置

ES 默認安裝后設置的內存是 1GB,對于任何一個現(xiàn)實業(yè)務來說,這個設置都太小了。

如果是通過解壓安裝的 ES,則在 ES 安裝文件中包含一個 jvm.option 文件,添加如下命令來設置 ES 的堆大小:

 
 
 
 
  1. -Xms10g 
  2. -Xmx10g 

Xms 表示堆的初始大小,Xmx 表示可分配的內存,都是 10GB。

確保 Xmx 和 Xms 的大小是相同的,其目的是為了能夠在 Java 垃圾回收機制清理完堆區(qū)后不需要重新分隔計算堆區(qū)的大小而浪費資源,可以減輕伸縮堆大小帶來的壓力。

也可以通過設置環(huán)境變量的方式設置堆的大小。服務進程在啟動時候會讀取這個變量,并相應的設置堆的大小。比如:

 
 
 
 
  1. export ES_HEAP_SIEZE=10g 

也可以通過命令行參數(shù)的形式,在程序啟動的時候把內存大小傳遞給 ES,如下所示:

 
 
 
 
  1. ./bin/elasticsearch -Xmx10g -Xms10g 

這種設置方式是一次性的,在每次啟動 ES 時都需要添加。

假設你有一個 64G 內存的機器,按照正常思維思考,你可能會認為把 64G 內存都給 ES 比較好,但現(xiàn)實是這樣嗎, 越大越好?雖然內存對 ES 來說是非常重要的,但是答案是否定的!

因為 ES 堆內存的分配需要滿足以下兩個原則:

  • 不要超過物理內存的 50%:Lucene 的設計目的是把底層 OS 里的數(shù)據(jù)緩存到內存中。

Lucene 的段是分別存儲到單個文件中的,這些文件都是不會變化的,所以很利于緩存,同時操作系統(tǒng)也會把這些段文件緩存起來,以便更快的訪問。

如果我們設置的堆內存過大,Lucene 可用的內存將會減少,就會嚴重影響降低 Lucene 的全文本查詢性能。

  • 堆內存的大小不要超過 32GB:在 Java 中,所有對象都分配在堆上,然后有一個 Klass Pointer 指針指向它的類元數(shù)據(jù)。

這個指針在 64 位的操作系統(tǒng)上為 64 位,64 位的操作系統(tǒng)可以使用更多的內存(2^64)。在 32 位的系統(tǒng)上為 32 位,32 位的操作系統(tǒng)的尋址空間為 4GB(2^32)。

但是 64 位的指針意味著更大的浪費,因為你的指針本身大了。浪費內存不算,更糟糕的是,更大的指針在主內存和緩存器(例如 LLC, L1等)之間移動數(shù)據(jù)的時候,會占用更多的帶寬。

Java 使用內存指針壓縮(Compressed Oops)技術來解決這個問題。它的指針不再表示對象在內存中的精確位置,而是表示偏移量。

這意味著 32 位的指針可以引用 4GB 個 Byte,而不是 4GB 個 bit。也就是說,當堆內存為 32GB 的物理內存時,也可以用 32 位的指針表示。

不過,在越過那個神奇的邊界 32GB 時,指針就會變?yōu)槠胀▽ο蟮闹羔槪總€對象的指針都變長了,就會浪費更多的內存,降低了 CPU 的性能,還要讓 GC 應對更大的內存。

事實上,當內存到達 40~40GB 時,有效的內存才相當于內存對象指針壓縮技術時的 32GB 內存。

所以即便你有足夠的內存,也盡量不要超過 32G,比如我們可以設置為 31GB:

 
 
 
 
  1. -Xms31g 
  2. -Xmx31g 

32GB 是 ES 一個內存設置限制,那如果你的機器有很大的內存怎么辦呢?現(xiàn)在的機器內存普遍增長,甚至可以看到有 300-500GB 內存的機器。

這時我們需要根據(jù)業(yè)務場景,進行恰當內存的分配:

  • 業(yè)務場景是以全文檢索為主:依然可以給 ES 分配小于 32GB 的堆內存,剩下的交給 Lucene 用作操作系統(tǒng)的文件系統(tǒng)緩存,所有的 Segment 都緩存起來,會加快全文檢索。
  • 業(yè)務場景中有很多的排序和聚合:我們可以考慮一臺機器上創(chuàng)建兩個或者更多 ES 節(jié)點,而不要部署一個使用 32+GB 內存的節(jié)點。

仍然要堅持 50% 原則,假設你有個機器有 128G 內存,你可以創(chuàng)建兩個 Node,使用 32G 內存。也就是說 64G 內存給 ES 的堆內存,剩下的 64G 給 Lucene。

服務器配置的選擇

Swapping 是性能的墳墓:在選擇 ES 服務器時,要盡可能地選擇與當前應用場景相匹配的服務器。

如果服務器配置很低,則意味著需要更多的節(jié)點,節(jié)點數(shù)量的增加會導致集群管理的成本大幅度提高。

如果服務器配置很高,而在單機上運行多個節(jié)點時,也會增加邏輯的復雜度。

在計算機中運行的程序均需在內存執(zhí)行,若內存消耗殆盡將導致程序無法進行。為了解決這個問題,操作系統(tǒng)使用一種叫作虛擬內存的技術。

當內存耗盡時,操作系統(tǒng)就會自動把內存中暫時不使用的數(shù)據(jù)交換到硬盤中,需要使用的時候再從硬盤交換到內存。

如果內存交換到磁盤上需要 10 毫秒,從磁盤交換到內存需要 20 毫秒,那么多的操作時延累加起來,將導致幾何級增長。

不難看出 Swapping 對于性能是多么可怕。所以為了使 ES 有更好等性能,強烈建議關閉 Swap。

關閉 Swap 的方式如下:

①暫時禁用。如果我們想要在 Linux 服務器上暫時關閉,可以執(zhí)行如下命令,但在服務器重啟后失效:

 
 
 
 
  1. sudo swapoff -a 

②關閉。我們可以修改 /etc/sysctl.conf(不同的操作系統(tǒng)路徑有可能不同),增加如下參數(shù):

 
 
 
 
  1. vm.swappiness = 1      //0-100,則表示越傾向于使用虛擬內存。 

注意:Swappiness 設置為 1 比設置為 0 要好,因為在一些內核版本,Swappness=0 會引發(fā) OOM(內存溢出)。

Swappiness 默認值為 60,當設置為 0 時,在某些操作系統(tǒng)中有可能會觸發(fā)系統(tǒng)級的 OOM-killer,例如在 Linux 內核的內存不足時,為了防止系統(tǒng)的崩潰,會自動強制 Kill 一個“bad”進程。

③在 ES 中設置。如果上面的方法都不能做到,你需要打開配置文件中的 mlockall 開關,它的作用就是運行 JVM 鎖住內存,禁止 OS 交換出去。

在 elasticsearch.yml 配置如下:

 
 
 
 
  1. bootstrap.mlockall: true 

硬盤的選擇和設置

所以,如果條件允許,則請盡可能地使用 SSD,它的讀寫性能將遠遠超出任何旋轉介質的硬盤(如機械硬盤、磁帶等)?;?SSD 的 ES 集群節(jié)點對于查詢和索引性能都有提升。

另外無論是使用固態(tài)硬盤還是使用機械硬盤,我們都建議將磁盤的陣列模式設置為 RAID 0,以此來提升磁盤的寫性能。

接入方式

ES 提供了 Transport Client(傳輸客戶端)和 Node Client(節(jié)點客戶端)的接入方式,這兩種方式各有利弊,分別對應不同的應用場景。

①Transport Client:作為一個集群和應用程序之間的通信層,和集群是安全解耦的。

由于與集群解耦,所以在連接集群和銷毀連接時更加高效,適合大量的客戶端連接。

②Node Client:把應用程序當作一個集群中的 Client 節(jié)點(非 Data 和 Master 節(jié)點)。

由于它是集群的一個內部節(jié)點,意味著它可以感知整個集群的狀態(tài)、所有節(jié)點的分布情況、分片的分布狀況等。

由于 Node Client 是集群的一部分,所以在接入和退出集群時進行比較復雜操作,并且還會影響整個集群的狀態(tài),所以 Node Client 更適合少量客戶端,能夠提供更好的執(zhí)行效率。

角色隔離和腦裂

①角色隔離

ES 集群中的數(shù)據(jù)節(jié)點負責對數(shù)據(jù)進行增、刪、改、查和聚合等操作,所以對 CPU、內存和 I/O 的消耗很大。

在搭建 ES 集群時,我們應該對 ES 集群中的節(jié)點進行角色劃分和隔離。

候選主節(jié)點:

 
 
 
 
  1. node.master=true 
  2. node.data=false 

數(shù)據(jù)節(jié)點:

 
 
 
 
  1. node.master=false 
  2. node.data=true 

形成如圖 3 所示的邏輯劃分:

圖 3

②避免腦裂

網絡異??赡軙е录褐泄?jié)點劃分出多個區(qū)域,區(qū)域發(fā)現(xiàn)沒有 Master 節(jié)點的時候,會選舉出了自己區(qū)域內 Maste 節(jié)點 r,導致一個集群被分裂為多個集群,使集群之間的數(shù)據(jù)無法同步,我們稱這種現(xiàn)象為腦裂。

為了防止腦裂,我們需要在 Master 節(jié)點的配置文件中添加如下參數(shù):

 
 
 
 
  1. discovery.zen.minimum_master_nodes=(master_eligible_nodes/2)+1        //默認值為1 

其中 master_eligible_nodes 為 Master 集群中的節(jié)點數(shù)。這樣做可以避免腦裂的現(xiàn)象都出現(xiàn),提升集群的高可用性。

只要不少于 discovery.zen.minimum_master_nodes 個候選節(jié)點存活,選舉工作就可以順利進行。

ES 實戰(zhàn)

ES 配置說明

在 ES 安裝目錄下的 Conf 文件夾中包含了一個重要的配置文件:elasticsearch.yaml。

ES 的配置信息有很多種,大部分配置都可以通過 elasticsearch.yaml 和接口的方式進行。

下面我們列出一些比較重要的配置信息:

  • cluster.name:elasticsearch:配置 ES 的集群名稱,默認值是 ES,建議改成與所存數(shù)據(jù)相關的名稱,ES 會自動發(fā)現(xiàn)在同一網段下的集群名稱相同的節(jié)點。
  • node.nam: "node1":集群中的節(jié)點名,在同一個集群中不能重復。節(jié)點的名稱一旦設置,就不能再改變了。當然,也可以設置成服務器的主機名稱,例如 node.name:${HOSTNAME}。
  • noed.master:true:指定該節(jié)點是否有資格被選舉成為 Master 節(jié)點,默認是 True,如果被設置為 True,則只是有資格成為 Master 節(jié)點,具體能否成為 Master 節(jié)點,需要通過選舉產生。
  • node.data:true:指定該節(jié)點是否存儲索引數(shù)據(jù),默認為 True。數(shù)據(jù)的增、刪、改、查都是在 Data 節(jié)點完成的。
  • index.number_of_shards:5:設置都索引分片個數(shù),默認是 5 片。也可以在創(chuàng)建索引時設置該值,具體設置為多大都值要根據(jù)數(shù)據(jù)量的大小來定。如果數(shù)據(jù)量不大,則設置成 1 時效率很高。
  • index.number_of_replicas:1:設置默認的索引副本個數(shù),默認為 1 個。副本數(shù)越多,集群的可用性越好,但是寫索引時需要同步的數(shù)據(jù)越多。
  • path.conf:/path/to/conf:設置配置文件的存儲路徑,默認是 ES 目錄下的 Conf 文件夾。建議使用默認值。
  • path.data:/path/to/data1,/path/to/data2:設置索引數(shù)據(jù)多存儲路徑,默認是 ES 根目錄下的 Data 文件夾。切記不要使用默認值,因為若 ES 進行了升級,則有可能數(shù)據(jù)全部丟失。

可以用半角逗號隔開設置的多個存儲路徑,在多硬盤的服務器上設置多個存儲路徑是很有必要的。

  • path.logs:/path/to/logs:設置日志文件的存儲路徑,默認是 ES 根目錄下的 Logs,建議修改到其他地方。
  • path.plugins:/path/to/plugins:設置第三方插件的存放路徑,默認是 ES 根目錄下的 Plugins 文件夾。
  • bootstrap.mlockall:true:設置為 True 時可鎖住內存。因為當 JVM 開始 Swap 時,ES 的效率會降低,所以要保證它不 Swap。
  • network.bind_host:192.168.0.1:設置本節(jié)點綁定的 IP 地址,IP 地址類型是 IPv4 或 IPv6,默認為 0.0.0.0。
  • network.publish_host:192.168.0.1:設置其他節(jié)點和該節(jié)點交互的 IP 地址,如果不設置,則會進行自我判斷。
  • network.host:192.168.0.1:用于同時設置 bind_host 和 publish_host 這兩個參數(shù)。
  • http.port:9200:設置對外服務的 HTTP 端口,默認為 9200。ES 的節(jié)點需要配置兩個端口號,一個對外提供服務的端口號,一個是集群內部使用的端口號。
  • http.port 設置的是對外提供服務的端口號。注意,如果在一個服務器上配置多個節(jié)點,則切記對端口號進行區(qū)分。
  • transport.tcp.port:9300:設置集群內部的節(jié)點間交互的 TCP 端口,默認是 9300。注意,如果在一個服務器配置多個節(jié)點,則切記對端口號進行區(qū)分。
  • transport.tcp.compress:true:設置在節(jié)點間傳輸數(shù)據(jù)時是否壓縮,默認為 False,不壓縮。
  • discovery.zen.minimum_master_nodes:1:設置在選舉 Master 節(jié)點時需要參與的最少的候選主節(jié)點數(shù),默認為 1。如果使用默認值,則當網絡不穩(wěn)定時有可能會出現(xiàn)腦裂。

合理的數(shù)值為(master_eligible_nodes/2)+1,其中 master_eligible_nodes 表示集群中的候選主節(jié)點數(shù)。

  • discovery.zen.ping.timeout:3s:設置在集群中自動發(fā)現(xiàn)其他節(jié)點時 Ping 連接的超時時間,默認為 3 秒。

在較差的網絡環(huán)境下需要設置得大一點,防止因誤判該節(jié)點的存活狀態(tài)而導致分片的轉移。

常用接口

雖然現(xiàn)在有很多開源軟件對 ES 的接口進行了封裝,使我們可以很方便、直觀地監(jiān)控集群的狀況,但是在 ES 5 以后,很多軟件開始收費。

了解常用的接口有助于我們在程序或者腳本中查看我們的集群情況,以下接口適用于 ES 6.5.2 版本。

①索引類接口

通過下面的接口創(chuàng)建一個索引名稱為 indexname 且包含 3 個分片、1 個副本的索引:

 
 
 
 
  1. PUT http://localhost:9200/indexname?pretty 
  2. content-type →application/json; charset=UTF-8 
  3.     "settings":{ 
  4.         "number_of_shards" : 3, 
  5.         "number_of_replicas" : 1 
  6.     } 

通過下面都接口刪除索引:

 
 
 
 
  1. DELETE http://localhost:9200/indexname 

通過該接口就可以刪除索引名稱為 indexname 的索引,通過下面的接口可以刪除多個索引:

 
 
 
 
  1. DELETE http://localhost:9200/indexname1,indexname2 
  2. DELETE http://localhost:9200/indexname* 

通過下面的接口可以刪除集群下的全部索引:

 
 
 
 
  1. DELETE http://localhost:9200/_all 
  2. DELETE http://localhost:9200/* 

進行全部索引刪除是很危險的,我們可以通過在配置文件中添加下面的配置信息,來關閉使用 _all 和使用通配符刪除索引的接口,使用刪除索引職能通過索引的全稱進行。

 
 
 
 
  1. action.destructive_requires_name: true 

通過下面的接口獲取索引的信息,其中,Pretty 參數(shù)用語格式化輸出結構,以便更容易閱讀:

 
 
 
 
  1. GET http://localhost:9200/indexname?pretty 

通過下面的接口關閉、打開索引:

 
 
 
 
  1. POST http://localhost:9200/indexname/_close 
  2. POST http://localhost:9200/indexname/_open 

通過下面的接口獲取一個索引中具體 Type 的 Mapping 映射:

 
 
 
 
  1. GET http://localhost:9200/indexname/typename/_mapping?pretty 

當一個索引中有多個 Type 時,獲得 Mapping 時要加上 Typename。

②Document 操作

安裝 ES 和 Kibana 之后,進入 Kibana 操作頁面,然后進去的 DevTools 執(zhí)行下面操作:

 
 
 
 
  1. #添加一條document 
  2. PUT /test_index/test_type/1 
  3.     "test_content":"test test" 
  4. #查詢 
  5. GET /test_index/test_type/1 
  6. #返回 
  7.   "_index" : "test_index", 
  8.   "_type" : "test_type", 
  9.   "_id" : "1", 
  10.   "_version" : 2, 
  11.   "found" : true, 
  12.   "_source" : { 
  13.     "test_content" : "test test" 
  14.   } 

put/index/type/id 說明如下:

  • _index 元數(shù)據(jù):代表這個 Document 存放在哪個 Idnex 中,類似的數(shù)據(jù)放在一個索引,非類似的數(shù)據(jù)放不同索引,Index 中包含了很多類似的 Document。
  • _type 元數(shù)據(jù):代表 Document 屬于 Index 中的哪個類別(type),一個索引通常會劃分多個 Type,邏輯上對 Index 中有些許不同的幾類數(shù)據(jù)進行分類。
  • _id 元數(shù)據(jù):代表 Document 的標識,id 與 Index 和 Type 一起,可以標識和定位一個 Document,可以理解為數(shù)據(jù)庫中主鍵。我們可以指定 Document 的 id,也可以不指定,由ES自動為我們創(chuàng)建一個 id。

接口應用

①Search 接口

Search 是我們最常用的 API,ES 給我提供了豐富的查詢條件,比如模糊匹配 Match,字段判空 Exists,精準匹配 Term 和 Terms,范圍匹配 Range:

 
 
 
 
  1. GET /_search 
  2.   "query": {  
  3.     "bool": {  
  4.       "must": [     //must_not 
  5.         { "match": { "title":   "Search"        }},  
  6.         { "match": { "content": "Elasticsearch" }}, 
  7.         {"exists":{"field":"字段名"}}   //判斷字段是否為空 
  8.       ], 
  9.       "filter": [  
  10.         { "term":  { "status": "published" }}, 
  11.         { "terms":  { "status": [0,1,2,3] }},//范圍 
  12.         { "range": { "publish_date": { "gte": "2015-01-01" }}} //范圍gte:大于等于;gt:大于;lte:小于等于;lt:小于 
  13.       ] 
  14.     } 
  15.   } 

查詢索引為 test_index,doc 類型為 test_type 的數(shù)據(jù):

 
 
 
 
  1. GET /test_index/test_type/_search 

查詢索引為 test_index,doc 類型為 test_type,docment 字段 num10 為 4 的數(shù)據(jù):

 
 
 
 
  1. GET /test_index/test_type/_search?pretty=true 
  2.   "query": {  
  3.     "bool": {  
  4.       "filter": [  
  5.         { "term":  { "num10": 4 }} 
  6.       ] 
  7.     } 
  8.   } 

更多查詢條件的組合,大家可以自行測試。

②修改 Mapping

 
 
 
 
  1. PUT /my_index/_mapping/my_type 
  2.   "properties": { 
  3.        "new_field_name": { 
  4.            "type":     "string"         //字段類型,string、long、boolean、ip 
  5.        } 
  6.    } 

如上是修改 Mapping 結構,然后利用腳本 Script 給字段賦值:

 
 
 
 
  1. POST my_index/_update_by_query 
  2.   "script": { 
  3.     "lang": "painless", 
  4.     "inline": "ctx._source.new_field_name= '02'" 
  5.   } 

③修改別名

如下給 Index 為 test_index 的數(shù)據(jù)綁定 Alias 為 test_alias:

 
 
 
 
  1. POST /_aliases 
  2.   "actions": [ 
  3.     { 
  4.       "add": {      //add,remove 
  5.         "index": "test_index", 
  6.         "alias": "test_alias" 
  7.       } 
  8.     } 
  9.   ] 

驗證別名關聯(lián),根據(jù)別名來進行數(shù)據(jù)查詢,如下:

 
 
 
 
  1. GET /test_alias/test_type/3 

④定制返回內容

_source 元數(shù)據(jù):就是說,我們在創(chuàng)建一個 Document 的時候,使用的那個放在 Request Body 中的 Json 串(所有的 Field),默認情況下,在 Get 的時候,會原封不動的給我們返回回來。

定制返回的結果,指定 _source 中,返回哪些 Field:

 
 
 
 
  1. #語法: 
  2. GET /test_index/test_type/1?_source=test_field2 
  3. #返回 
  4.   "_index" : "test_index", 
  5.   "_type" : "test_type", 
  6.   "_id" : "1", 
  7.   "_version" : 3, 
  8.   "found" : true, 
  9.   "_source" : { 
  10.     "test_field2" : "test field2" 
  11.   } 
  12. #也可返回多個field使用都好分割 
  13. GET /test_index/test_type/1?_source=test_field2,test_field1 

Java 封裝

組件 elasticsearch.jar 提供了豐富 API,不過不利于我們理解和學習,現(xiàn)在我們自己來進行封裝。

組件 API 使用 RestClient 封裝 Document 查詢接口:

 
 
 
 
  1. /** 
  2.  * @param index 
  3.  * @param type 
  4.  * @param id 
  5.  * @param fields 
  6.  *            查詢返回字段,可空 
  7.  * @return 
  8.  * @throws Exception 
  9.  * @Description: 
  10.  * @create date 2019年4月3日下午3:12:40 
  11.  */ 
  12. public String document(String index, String type, String id, List fields) throws Exception { 
  13.     Map paramsMap = new HashMap<>(); 
  14.     paramsMap.put("pretty", "true"); 
  15.     if (null != fields && fields.size() != 0) { 
  16.         String fieldValue = ""; 
  17.         for (String field : fields) { 
  18.             fieldValue += field + ","; 
  19.         } 
  20.         if (!"".equals(fieldValue)) { 
  21.             paramsMap.put("_source", fieldValue); 
  22.         } 
  23.     } 
  24.     return CommonUtils.toString(es.getRestClient() 
  25.             .performRequest("GET", "/" + index + "/" + type + "/" + id, paramsMap).getEntity().getContent()); 

工程使用,封裝:

 
 
 
 
  1. public String searchDocument(String index, String type, String id, List fields) { 
  2.     try { 
  3.         return doc.document(index, type, id, fields); 
  4.     } catch (Exception e) { 
  5.         log.error(e.getMessage()); 
  6.         ExceptionLogger.log(e); 
  7.         throw new RuntimeException("ES查詢失敗"); 
  8.     } 

測試用例,代碼如下:

 
 
 
 
  1. /** 
  2.  * ES交互驗證-查詢、更新等等操作 
  3.  * 
  4.  * @version 
  5.  * @author 錢丁君-chandler 2019年4月3日上午10:27:28 
  6.  * @since 1.8 
  7.  */ 
  8. @RunWith(SpringRunner.class) 
  9. @SpringBootTest(classes = Bootstrap.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 
  10. public class ESManagerTest { 
  11.     @Autowired 
  12.     private ESBasicManager esBasicManager; 
  13.     @Test 
  14.     public void query() { 
  15.         String result = esBasicManager.searchDocument(ESTagMetadata.INDEX_ALIAS, ESTagMetadata.DOC_TYPE, 
  16.                 "188787665220752824", ImmutableList.of("signup_time", "tag_days_no_visit_after_1_order")); 
  17.         System.out.println("----------->" + result); 
  18.     } 

控臺輸出:

 
 
 
 
  1. ----------->{ 
  2.   "_index" : "crm_tag_idx_20181218_708672", 
  3.   "_type" : "crm_tag_type", 
  4.   "_id" : "188787665220752824", 
  5.   "_version" : 1, 
  6.   "found" : true, 
  7.   "_source" : { 
  8.     "signup_time" : "2017-12-24", 
  9.     "tag_days_no_visit_after_1_order" : "339" 
  10.   } 

我只是拋磚引玉,大家可以自行進行各種操作的封裝,不管對于理解 ES 的使用,還是對代碼質量提升都有很多幫助。

作者:錢丁君

簡介:就職于永輝云創(chuàng),擔任基礎架構開發(fā),有多年基礎架構經驗,主要從事電商新零售、互聯(lián)網金融行業(yè)。技術發(fā)燒友,涉獵廣泛。熟悉 Java 微服務架構搭建、推進、衍化;多種中間件搭建、封裝和優(yōu)化;自動化測試開發(fā)、代碼規(guī)約插件開發(fā)、代碼規(guī)范推進;容器化技術 Docker、容器化編排技術 Kubernetes,有較為豐富的運維經驗。

【原創(chuàng)稿件,合作站點轉載請注明原文作者和出處為.com】


文章名稱:超詳細的Elasticsearch高性能優(yōu)化實踐
轉載源于:http://www.5511xx.com/article/dphsoeg.html