時隔3年重新開源,這些 ElasticSearch 應用技能運維必會?

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