上廳房,下廚房,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_sizelog_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之前,能夠了解它的一些底層設計結構。這樣,在遇到一些瓶頸的限制以後,能夠了解到它為什麼有這樣或者那樣的反應;另外在做一些解決方案的時候,能夠多給自己一點底氣。

不過話說回來,上得了廳房,下得了廚房的女人,大家還是都喜歡~