時隔3年重新開源,這些 ElasticSearch 應(yīng)用技能運維必會?
一、ES 的基本使用
安裝使用
下載和解壓Elasticsearch,解壓后即可用:
安裝目錄下運行 bin/elasticsearch 來啟動 ES。
默認(rèn)在 9200 端口運行,請求 curl http://localhost:9200/ 或者瀏覽器輸入 http://localhost:9200,得到一個 JSON 對象,其中包含當(dāng)前節(jié)點、集群、版本等信息。
{"name" : "U7fp3O9","cluster_name" : "elasticsearch","cluster_uuid" : "-Rj8jGQvRIelGd9ckicUOA","version" : {"number" : "6.8.1","build_flavor" : "default","build_type" : "zip","build_hash" : "1fad4e1","build_date" : "2019-06-18T13:16:52.517138Z","build_snapshot" : false,"lucene_version" : "7.7.0","minimum_wire_compatibility_version" : "5.6.0","minimum_index_compatibility_version" : "5.0.0"},"tagline" : "You Know, for Search"}
集群健康狀態(tài)
{"cluster_name" : "lzj","status" : "yellow","timed_out" : false,"number_of_nodes" : 1,"number_of_data_nodes" : 1,"active_primary_shards" : 9,"active_shards" : 9,"relocating_shards" : 0,"initializing_shards" : 0,"unassigned_shards" : 5,"delayed_unassigned_shards" : 0,"number_of_pending_tasks" : 0,"number_of_in_flight_fetch" : 0,"task_max_waiting_in_queue_millis" : 0,"active_shards_percent_as_number" : 64.28571428571429}
集群狀態(tài)通過 綠,黃,紅 來標(biāo)識:
綠色:集群健康完好,一切功能齊全正常,所有分片和副本都可以正常工作。 黃色:預(yù)警狀態(tài),所有主分片功能正常,但至少有一個副本是不能正常工作的。此時集群是可以正常工作的,但是高可用性在某種程度上會受影響。 紅色:集群不可正常使用。某個或某些分片及其副本異常不可用,這時集群的查詢操作還能執(zhí)行,但是返回的結(jié)果會不準(zhǔn)確。對于分配到這個分片的寫入請求將會報錯,最終會導(dǎo)致數(shù)據(jù)的丟失。當(dāng)集群狀態(tài)為紅色時,它將會繼續(xù)從可用的分片提供搜索請求服務(wù),但是你需要盡快修復(fù)那些未分配的分片。
二、ES 機制原理
ES的基本概念和基本操作介紹完了之后,我們可能還有很多疑惑
它們內(nèi)部是如何運行的?
主分片和副本分片是如何同步的?
創(chuàng)建索引的流程是什么樣的?
ES如何將索引數(shù)據(jù)分配到不同的分片上的?
以及索引數(shù)據(jù)是如何存儲的?
為什么說ES是近實時搜索引擎而文檔的CRUD(創(chuàng)建-讀取-更新-刪除) 操作是實時的?
以及ES是怎樣保證更新被持久化在斷電時也不丟失數(shù)據(jù)?
為什么刪除文檔不會立刻釋放空間?
帶著這些疑問我們進入接下來的內(nèi)容。
寫索引原理
下圖描述了3個節(jié)點的集群,共擁有12個分片,其中有4個主分片(S0、S1、S2、S3和8個副本分片(R0、R1、R2、R3),每個主分片對應(yīng)兩個副本分片,節(jié)點1是主節(jié)點(Master節(jié)點)負(fù)責(zé)整個集群的狀態(tài)。
寫索引是只能寫在主分片上,然后同步到副本分片。這里有四個主分片,一條數(shù)據(jù) ES 是根據(jù)什么規(guī)則寫到特定分片上的呢?
首先這肯定不會是隨機的,否則將來要獲取文檔的時候我們就不知道從何處尋找了。
實際上,這個過程和之前寫的Hash分片的路由方法,實際根據(jù)下面這個公式?jīng)Q定的:
shard = hash(routing) % number_of_primary_shards
Routing是一個可變值,默認(rèn)是文檔的_id,也可以設(shè)置成一個自定義的值。Routing通過Hash函數(shù)生成一個數(shù)字,然后這個數(shù)字再除以number_of_primary_shards(主分片的數(shù)量)后得到余數(shù)。
這個在0到number_of_primary_shards-1之間的余數(shù),就是我們所尋求的文檔所在分片的位置。
這就解釋了為什么我們要在創(chuàng)建索引的時候就確定好主分片的數(shù)量并且永遠(yuǎn)不會改變這個數(shù)量:因為如果數(shù)量變化了,那么所有之前路由的值都會無效,文檔也再也找不到了。
由于在 ES 集群中每個節(jié)點通過上面的計算公式都知道集群中的文檔的存放位置,所以每個節(jié)點都有處理讀寫請求的能力。
在一個寫請求被發(fā)送到某個節(jié)點后,該節(jié)點即為前面說過的協(xié)調(diào)節(jié)點,協(xié)調(diào)節(jié)點會根據(jù)路由公式計算出需要寫到哪個分片上,再將請求轉(zhuǎn)發(fā)到該分片的主分片節(jié)點上。
客戶端向 ES1 節(jié)點(協(xié)調(diào)節(jié)點)發(fā)送寫請求,通過路由計算公式得到值為 0,則當(dāng)前數(shù)據(jù)應(yīng)被寫到主分片 S0 上。 ES1 節(jié)點將請求轉(zhuǎn)發(fā)到 S0 主分片所在的節(jié)點 ES3,ES3 接受請求并寫入到磁盤。 并發(fā)將數(shù)據(jù)復(fù)制到兩個副本分片 R0 上,其中通過樂觀并發(fā)控制數(shù)據(jù)的沖突。 一旦所有的副本分片都報告成功,則節(jié)點 ES3 將向協(xié)調(diào)節(jié)點報告成功,協(xié)調(diào)節(jié)點向客戶端報告成功。
存儲原理
path.data: /path/to/data//索引數(shù)據(jù)path.logs: /path/to/logs//日志記錄
分段存儲
索引文檔以段的形式存儲在磁盤上,何為段?索引文件被拆分為多個子文件,則每個子文件叫作段,每一個段本身都是一個倒排索引,并且段具有不變性,一旦索引的數(shù)據(jù)被寫入硬盤,就不可再修改。
在底層采用了分段的存儲模式,使它在讀寫時幾乎完全避免了鎖的出現(xiàn),大大提升了讀寫性能。
段被寫入到磁盤后會生成一個提交點,提交點是一個用來記錄所有提交后段信息的文件。
一個段一旦擁有了提交點,就說明這個段只有讀的權(quán)限,失去了寫的權(quán)限。相反,當(dāng)段在內(nèi)存中時,就只有寫的權(quán)限,而不具備讀數(shù)據(jù)的權(quán)限,意味著不能被檢索。
如果索引有更新,就需要重新全量創(chuàng)建一個索引來替換原來的索引。這種方式在數(shù)據(jù)量很大時效率很低,并且由于創(chuàng)建一次索引的成本很高,所以對數(shù)據(jù)的更新不能過于頻繁,也就不能保證時效性。
索引文件分段存儲并且不可修改,那么新增、更新和刪除如何處理呢?
新增
很好處理,由于數(shù)據(jù)是新的,所以只需要對當(dāng)前文檔新增一個段就可以。
刪除
由于不可修改,所以對于刪除操作,不會把文檔從舊的段中移除而是通過新增一個 .del 文件,文件中會列出這些被刪除文檔的段信息。這個被標(biāo)記刪除的文檔仍然可以被查詢匹配到, 但它會在最終結(jié)果被返回前從結(jié)果集中移除。
更新
不能修改舊的段來進行反映文檔的更新,其實更新相當(dāng)于是刪除和新增這兩個動作組成。會將舊的文檔在.del文件中標(biāo)記刪除,然后文檔的新版本被索引到一個新的段中。可能兩個版本的文檔都會被一個查詢匹配到,但被刪除的那個舊版本文檔在結(jié)果集返回前就會被移除。
段被設(shè)定為不可修改具有一定的優(yōu)勢也有一定的缺點,優(yōu)勢主要表現(xiàn)在:不需要鎖。如果你從來不更新索引,你就不需要擔(dān)心多進程同時修改數(shù)據(jù)的問題。
一旦索引被讀入內(nèi)核的文件系統(tǒng)緩存,便會留在哪里,由于其不變性。只要文件系統(tǒng)緩存中還有足夠的空間,那么大部分讀請求會直接請求內(nèi)存,而不會命中磁盤。這提供了很大的性能提升。
其它緩存(像Filter緩存),在索引的生命周期內(nèi)始終有效。它們不需要在每次數(shù)據(jù)改變時被重建,因為數(shù)據(jù)不會變化。
寫入單個大的倒排索引允許數(shù)據(jù)被壓縮,減少磁盤 I/O 和需要被緩存到內(nèi)存的索引的使用量。
段的不變性的缺點如下:
當(dāng)對舊數(shù)據(jù)進行刪除時,舊數(shù)據(jù)不會馬上被刪除,而是在.del文件中被標(biāo)記為刪除。 而舊數(shù)據(jù)只能等到段更新時才能被移除,這樣會造成大量的空間浪費。 若有一條數(shù)據(jù)頻繁的更新,每次更新都是新增新的標(biāo)記舊的,則會有大量的空間浪費。 每次新增數(shù)據(jù)時都需要新增一個段來存儲數(shù)據(jù)。 當(dāng)段的數(shù)量太多時,對服務(wù)器的資源例如文件句柄的消耗會非常大。 在查詢的結(jié)果中包含所有的結(jié)果集,需要排除被標(biāo)記刪除的舊數(shù)據(jù),這增加了查詢的負(fù)擔(dān)。
延遲寫策略
介紹完了存儲的形式,那么索引寫入到磁盤的過程是怎樣的?是否是直接調(diào) Fsync物理性地寫入磁盤?
答案是顯而易見的,如果是直接寫入到磁盤上,磁盤的 I/O 消耗上會嚴(yán)重影響性能。那么當(dāng)寫數(shù)據(jù)量大的時候會造成 ES 停頓卡死,查詢也無法做到快速響應(yīng)。如果真是這樣 ES 也就不會稱之為近實時全文搜索引擎了。
為了提升寫的性能,ES并沒有每新增一條數(shù)據(jù)就增加一個段到磁盤上,而是采用延遲寫的策略。每當(dāng)有新增的數(shù)據(jù)時,就將其先寫入到內(nèi)存中,在內(nèi)存和磁盤之間是文件系統(tǒng)緩存。
當(dāng)達(dá)到默認(rèn)的時間(1 秒鐘)或者內(nèi)存的數(shù)據(jù)達(dá)到一定量時,會觸發(fā)一次刷新(Refresh),將內(nèi)存中的數(shù)據(jù)生成到一個新的段上并緩存到文件緩存系統(tǒng)上,稍后再被刷新到磁盤中并生成提交點。
這里的內(nèi)存使用的是ES的JVM內(nèi)存,而文件緩存系統(tǒng)使用的是操作系統(tǒng)的內(nèi)存。
新的數(shù)據(jù)會繼續(xù)的被寫入內(nèi)存,但內(nèi)存中的數(shù)據(jù)并不是以段的形式存儲的,因此不能提供檢索功能。
由內(nèi)存刷新到文件緩存系統(tǒng)的時候會生成新的段,并將段打開以供搜索使用,而不需要等到被刷新到磁盤。
在Elasticsearch中,寫入和打開一個新段的輕量的過程叫做 Refresh(即內(nèi)存刷新到文件緩存系統(tǒng))。
默認(rèn)情況下每個分片會每秒自動刷新一次。這就是為什么我們說Elasticsearch 是近實時搜索,因為文檔的變化并不是立即對搜索可見,但會在一秒之內(nèi)變?yōu)榭梢姟?/span>
我們也可以手動觸發(fā) Refresh:
POST/_refresh //刷新所有索引POST/nba/_refresh //刷新指定的索引
Tips:盡管刷新是比提交輕量很多的操作,它還是會有性能開銷。當(dāng)寫測試的時候,手動刷新很有用,但是不要在生產(chǎn)>環(huán)境下每次索引一個文檔都去手動刷新。而且并不是所有的情況都需要每秒刷新。
refresh_interval = "30s"
的值 , 降低每個索引的刷新頻率,設(shè)值時需要注意后面帶上時間單位,否則默認(rèn)是毫秒。當(dāng)refresh_interval=-1 時表示關(guān)閉索引的自動刷新。索引寫流程
segment合并
六、ES 的性能優(yōu)化
存儲設(shè)備
磁盤在現(xiàn)代服務(wù)器上通常都是瓶頸。Elasticsearch重度使用磁盤,你的磁盤能處理的吞吐量越大,你的節(jié)點就越穩(wěn)定。這里有一些優(yōu)化磁盤 I/O 的技巧:
使用SSD。就像其他地方提過的,他們比機械磁盤優(yōu)秀多了。
使用RAID10/RAID5。條帶化RAID會提高磁盤I/O,同時增加數(shù)據(jù)可靠性
使用多塊硬盤,并允許Elasticsearch通過多個 path.data目錄配置把數(shù)據(jù)條帶化分配到它們上面。
不要使用遠(yuǎn)程掛載的存儲,比如NFS或者SMB/CIFS。這個引入的延遲對性能來說完全是背道而馳的。
如果你用的是 EC2,當(dāng)心EBS。即便是基于SSD的EBS,通常也比本地實例的存儲要慢。
內(nèi)部索引優(yōu)化
索引優(yōu)化
調(diào)整配置參數(shù)
調(diào)整配置參數(shù)建議如下:
給每個文檔指定有序的具有壓縮良好的序列模式ID,避免隨機的UUID-4 這樣的ID,這樣的ID壓縮比很低,會明顯拖慢Lucene。
對于那些不需要聚合和排序的索引字段禁用Doc values。
Doc Values 是有序的、基于document=>field value的映射列表。
不需要做模糊檢索的字段使用Keyword類型代替Text類型,這樣可以避免在建立索引前對這些文本進行分詞。
如果你的搜索結(jié)果不需要近實時的準(zhǔn)確度,考慮把每個索引的index.refresh_interval改到30s。
如果你是在做大批量導(dǎo)入,導(dǎo)入期間你可以通過設(shè)置這個值為-1 關(guān)掉刷新,還可以通過設(shè)置 index.number_of_replicas: 0 關(guān)閉副本。別忘記在完工的時候重新開啟它。
避免深度分頁查詢建議使用Scroll進行分頁查詢。普通分頁查詢時,會創(chuàng)建一個from+size的空優(yōu)先隊列,每個分片會返回from+size條數(shù)據(jù),默認(rèn)只包含文檔ID和得分Score給協(xié)調(diào)節(jié)點。
如果有N個分片,則協(xié)調(diào)節(jié)點再對(from+size)×n條數(shù)據(jù)進行二次排序,然后選擇需要被取回的文檔。當(dāng)from很大時,排序過程會變得很沉重,占用CPU資源嚴(yán)重。減少映射字段,只提供需要檢索,聚合或排序的字段。其他字段可存在其他存儲設(shè)備上,例如HBase,在ES中得到結(jié)果后再去HBase 查詢這些字段。
創(chuàng)建索引和查詢時指定路由Routing值,這樣可以精確到具體的分片查詢,提升查詢效率。路由的選擇需要注意數(shù)據(jù)的分布均衡。
JVM 調(diào)優(yōu)
ES 非常依賴文件系統(tǒng)緩存(Filesystem Cache),快速搜索。一般來說,應(yīng)該至少確保物理上有一半的可用內(nèi)存分配到文件系統(tǒng)緩存。