上廳房,下廚房,ElasticSearch有的忙
- 2019 年 10 月 6 日
- 筆記
強烈建議先讀一下本公眾號《也淺談下分散式存儲要點》,對ES會有更好的認識。ES融合了倒排索引、行存、列存的諸多特點,已經不再是一個簡單的全文搜索引擎。
ES是從廚房裡走出來的,原型是Shay Banon
給妻子做的食譜搜索引擎,一開始便充滿了愛的味道。
兒女情長什麼的,最影響程式設計師發揮了!
最終,ElasticSearch
斬斷情愫,成為一個基於Lucene
的分散式存儲。安裝後訪問其頁面,會顯示:」You Know, for Search「
,這句平凡的語句無意中透露的自信,深於一切懷疑和平淡。
幾點說明
幾點說明 |
---|
索引中的Type定義會引起諸多歧義,已經在6.0版本廢棄 |
作為搜索時,是類實時性系統,寫入到讀取之間存在延遲,一般為1秒 ;但當作為存儲時,是RT的 |
ES索引的分片數一旦確定不可改變,既成事實的Mapping也是 |
你優化了寫入,就可能干擾了查詢和可靠性,大多時候不可兼得 |
你會經常碰到OOM,顯然ES是記憶體大戶,缺乏這方面的保護 |
ES沒有事務 |
基礎概念
ES是典型的分散式系統。包含一到多個記憶體型節點。有分片,也有副本,可靠性高,自適應能力強。ES採用多機並行能力來進行擴展,是建立在一系列層級數據結構上的。這些抽象作為一個全局的路由表,存在於每個運行的實例上,給了ES強大的功能和擴展能力。
集群,有著統一的名稱。包含一個master節點,和落干其他類型節點。一般為對等節點
索引類型
倒排索引
很多同學有一個誤解,以為ES是一個全文搜索引擎,那麼就只有倒排索引這一種索引類型,那是錯的。數據在寫入es時,會產生多份數據用於不同查詢方式,使用的索引結構也不相同。
ES默認是對所有欄位進行索引的(也就是倒排索引),如果不需要,可以在mapping中將index
屬性設置為no
;如果欄位需要精確查找,則設置為not_analyzed
。
為了增加倒排索引的Term查找速度,ES還專門做了Term index
,它的本質是一棵Trie(前綴)樹(使用FST技術壓縮)。
_all
是一個特殊的欄位,可以根據某個關鍵詞,搜索整個文檔內容(而不是某個欄位),這個默認是關閉的。
列式存儲
按照以上的倒排索引結構,查找包含某個term的文檔是非常迅捷的。如果要對這個欄位進行排序的話,倒排索引就捉襟見肘了,需要使用其他的存儲結構進行索引。
ES使用冗餘的方式進行解決這個問題,它存儲了另一份數據,也就是Doc Values
。可以說Doc Values
是一個列式存儲結構,適合排序、聚合操作等。放在記憶體中的fielddata
功能和它類似,但沒有記憶體容量的限制,大數據量優先使用。
到此為止,ES已經默認按照不同的結構存儲了兩份數據了。但如果你不需要,還是可以禁用的。同樣是在mapping映射中,給欄位賦予屬性"doc_values":false
即可。
假如你用的是ELKB系列,倒排索引根本就沒用到。
行
式存儲
而作為行存
的_source
欄位,以json方式存儲了原始文檔。一般是不需要關閉的。但如果你的文檔欄位比較多,根據搜索後查出列表,再根據列表的數據到其他存儲獲取,那麼就可以將_source
關掉。類似的,設置"_source"{"enabled":false}
即可。
如果只想要幾個欄位被存儲,可以使用include。
"_source":{ "includes":["field1","field2"] }
ES有太多的這種細化的自定義,不再詳敘。
寫入過程
找到分片
某個分片具體在哪個節點上,由ES自行決定。每個節點都快取了這些路由資訊,所以,你的請求發送到任何一個ES節點上,都可以執行。
ES選擇的分片路由演算法是Hash,這決定了它的分片數一旦確定,不可更改。因為一旦變了,路由的數據就完全不正確了。
如果Request中指定了路由條件,則直接使用Request中的Routing,否則使用Mapping中配置的,如果Mapping中無配置,則使用默認的_id欄位值。
默認參與Hash計算的欄位是_id
,使用ES自帶的生成器能較好的平均數據,使用自定義的id可能會產生數據傾斜。
shard = hash(routing) % number_of_primary_shards
剩下的,就是單機索引的事了。
單機Shard的寫入過程
ES的寫入性能可以很高(尤其是批量寫入),取決於你的配置。一個文檔要寫入索引,直到讀可見,要經過一系列的緩衝和合併。我們拿ES官方部落格的一張圖來說明。
ES的底層存儲是Lucene,包含一系列的反向索引。這樣的一批索引的資訊就是上面提到的段(segment)。但記錄不會直接寫入段,而是先寫入一個緩衝區。
當緩衝區滿了,或者在緩衝區呆的夠久,達到了刷新時間(劃重點),會一次性將緩衝區的內容寫進段中。這也是為什麼refresh_interval
屬性的配置會嚴重的影響性能。如果你不要很高的實時性,不妨將其配置的大一點。
緩衝區默認使用堆空間的10%,最小值為48mb(針對於分片的)。如果你的索引多且寫入重,這部分記憶體的佔用是可觀的,可以適當加大。
問題是Segment是不可變的,刪除、更新操作,並不能在原來的段上進行。ES對待所有的操作都是相似的,並不區別對待,刪除和更新,有著和寫入一樣的mege過程。這會生成大量的段,每個段會佔用一個文件句柄,會浪費大量資源。ES有專門的進程負責段的自動合併,我們不需要手動干涉。
段的合併會浪費大量的I/O和CPU資源,有tiered
(默認)、log_byte_size
、 log_doc
三種合併策略,每種策略都有各自的配置參數。可惜的是,索引一旦確定,策略就不能更改了。調整這些參數,大多情況下效果顯著。
常用的配置參數是執行歸併的執行緒數,max_bytes_per_sec已經不再使用了。
index.merge.scheduler.max_thread_count
如果你的I/O過重,可以適量減少此值的大小。
即然是先寫到緩衝區,就有丟的可能,比如突然斷電。為了解決此問題,ES在寫Buffer的同時,也將數據寫入一個叫做translog的文件。這個文件是順序寫,所以速度比較快。translog是故障恢復時,回放故障發生前夕數據的唯一途徑。這些數據,沒有機會能夠寫入到Lucene中。
將translog的寫入周期改成async的,而不是基於請求的,會顯著減少I/O佔用。
作業系統在將磁碟寫入文件時,也會有相應的buffer cache,這與其他DB如MySQL、PG的工作方式是一樣的,使用fsync保證文件能夠刷到磁碟上,不多描述。
節點類型
ES按照不同的用途和場景,劃分了不同的節點類型。
Master Node 有資格被選擇為主節點,然後控制整個集群
Data Node 該節點能夠保存數據和執行操作
Tribe Node 部落節點,可以連接多個集群,對外提供統一的入口
Ingest Node 定義一個pipeline來處理數據,可以替代logstash中的某些功能
客戶端節點 不保存數據也不協調集群,僅響應用戶請求,將其發送到其他節點
End
ES通過冗餘多份數據達到不同的用途,開箱即用。開箱即用的意思也就是認識成本低,學習成本大,先把你吸引進門再說。
ES不是一匹好馴服的野馬。通常,它並不像官方和其他PPT宣傳的那樣無所不能,你可能經常在OOM和分片移動中度日。
希望在使用ES之前,能夠了解它的一些底層設計結構。這樣,在遇到一些瓶頸的限制以後,能夠了解到它為什麼有這樣或者那樣的反應;另外在做一些解決方案的時候,能夠多給自己一點底氣。
不過話說回來,上得了廳房,下得了廚房的女人,大家還是都喜歡~