1. ES基本概念及原理
1.1 索引結構
ES是面向文檔的,所有文本數據都是以文檔的形式存儲在ES中。數據以JSON作為存儲格式,由_index、_type、_id標識唯一的文檔數據。文檔中可以有許多字段,每個字段都是固定的數據類型來描述各種數據信息。
_index:
指向一個或多個物理分片的邏輯命名空間(為同一類型文檔的集合索引)
_type:
ES7版本中默認為_doc。
_id:
文檔標記符由系統自動生成且唯一。
1.2 分片
在分布式系統中,單機無法存儲規模巨大的數據。因此,需要將數據分成若干小塊分配到各個機器上。然后通過某種路由策略找到某個數據塊所在的位置除了將數據分片以提高水平擴展能力,分布式存儲中還會把數據復制成多個副本,放置到不同的機器中,同時數據副本還可以使讀操作並發執行。
為了應對並發更新問題,ES將數據副本分為主從兩部分,即主分片
(primary shard)和副分片
(replica shard)主數據作為權威數據,寫過程中先寫主分片,成功后再寫副分片,恢復階段以主分片為准
數據分片和數據副本的關系如下圖所示:
分片(shard)是底層的基本讀寫單元,分片的目的是分割巨大索引,讓讀寫可以並行操作,由多台機器共同完成。讀寫請求最終落到某個分片上,分片可以獨立執行讀寫工作。ES利用分片將數據分發到集群內各處。分片是數據的容器,文檔保存在分片內,不會跨分片存儲。分片又被分配到集群內的各個節點里。當集群規模擴大或縮小時,ES會自動在各節點中遷移分片,使數據仍然均勻分布在集群里。
索引與分片的關系圖如下:
一個ES索引包含很多分片,一個分片是一個 Lucene的索引,它本身就是一個完整的搜索引擎,可以獨立執行建立索引和搜索任務。Lucene索引又由很多分段
組成,每個分段都是一個倒排索引。ES每次“refresh”都會生成一個新的分段,其中包含若干文檔的數據。
在每個分段內部,文檔的不同字段被單獨建立索引。每個字段的值由若干詞(Term)組成,Term是原文內容經過分詞器處理和語言處理后的最終結果(例如,去除標點符號和轉換為詞根)。
分片與數據
在創建索引時,需要確定好分片數。現在(5x~6x版本之后),ES在一定條件限制下,對某個索引的主分片進行拆分(Splt)或縮小( Shrink)。但是,我們仍然需要在一開始就盡量規划好主分片數量:先依據硬件情況定好單個分片容量,然后依據業務場景預估數據量和增長量,再除以單個分片容量。
分片數不夠時,可以考慮新建索引,搜索1個有着50個分片的索引與搜索50個每個都有1個分片的索引完全等價,或者使用 split AP來拆分索引(6版本開始支持)
在實際應用中,我們不應該向單個索引持續寫數據,直到它的分片巨大無比。巨大的索引會在數據老化后難以刪除,以id為單位刪除文檔不會立刻釋放空間,刪除的doc只在 Lucene分段合並時才會真正從磁盤中刪除。即使手工觸發分段合並,仍然會引起較高的IO壓力,並且可能因為分段巨大導致在合並過程中磁盤空間不足(分段大小大於磁盤可用空間的一半)。因此,我們建議周期性地創建新索引。例如,每天創建一個。假如有一個索引 website,可以將它命名為 website20180319。然后創建一個名為 website的索引別名來關聯這些索引。這樣,對於業務方來說,讀取時使用的名稱不變,當需要刪除數據的時候,可以直接刪除整個索引。
索引別名就像一個快捷方式或軟鏈接,不同的是它可以指向一個或多個索引。可以用於實現索引分組,或者索引間的無縫切換
現在我們已經確定好了主分片數量,並且保證單個索引的數據量不會太大,周期性創建新索引帶來的一個新問題是集群整體分片數量較多,集群管理的總分片數越多壓力就越大。在每天生成一個新索引的場景中,可能某天產生的數據量很小,實際上不需要這么多分片,甚至個就夠。這時,可以使用 shrink apl來縮減主分片數量,降低集群負載
1.3 什么是倒排序
原數據如下:
ID | Name | Age | Sex |
---|---|---|---|
1 | Kate | 24 | Female |
2 | John | 24 | Male |
3 | Bill | 29 | Male |
ID是Elasticsearch自建的文檔id,那么Elasticsearch建立的索引如下:
Name:
Term | Posting List |
---|---|
Kate | 1 |
John | 2 |
Bill | 3 |
Age:
Term | Posting List |
---|---|
24 | [1,2] |
29 | 3 |
Sex:
Term | Posting List |
---|---|
Female | 1 |
Male | [2,3] |
Elasticsearch分別為每個Field
都建立了一個倒排索引,Kate, John, 24, Female這些叫term
,而[1,2]就是Posting List。Posting list就是一個int的數組,存儲了所有符合某個term的文檔id。
倒排序就是:可以根據文檔的信息,反向檢索出所有符合條件的文檔集合。比如:查找Male
的信息,就會檢索出2,3 兩個人的信息。
1.4 動態更新索引
為文檔建立索引,使其每個字段都可以被搜索,通過關鍵詞檢索文檔內容,會使用倒排素引的數據結構。
倒排索引一旦被寫入文件后就具有不變性,不變性具有許多好處:對文件的訪問不需要加鎖,讀取索引時可以被文件系統緩存等。
那么索引如何更新,讓新添加的文檔可以被搜索到?
新增的內容首先會內存中緩存,當達到一定條件后,會觸發一次refresh操作,刷新操作將緩存數據寫入到文件系統並生成一個新的倒排序索引(新的Lucene段),此時處於文件系統緩存中的數據可以被檢索。查詢時,每個倒排索引都被輪流查詢,查詢完再對結果進行合並——也就是所有條件的倒排序索引都會被檢索出來,然后對檢索結果進行合並處理。
-
在緩存中的數據並不是以段的形式存儲,因此不能被檢索。只要生成新段,即使還沒有寫入磁盤,還是可以被被檢索出。
-
由於段的不變性,更新、刪除等操作實際上是將數據標記為刪除,記錄到單獨的位置,這種方式稱為標記刪除。因此刪除部分數據不會釋放磁盤空間。
1.5 近實時搜索
在寫操作中,一般會先在內存中緩沖一段數據,再將這些數據寫入硬盤,每次寫入硬盤的這批數據稱為一個分段
。一般情況下,操作系統write接口寫到磁盤的數據先到達系統緩存
(內存),write函數返回成功時,數據未必被刷到磁盤。通過手工調用fush,或者操作系統通過一定策略將系統緩存刷到磁盤。從wite函數返回成功開始,無論數據有沒有被刷到磁盤,該數據已經對讀取可見。
ES每秒會對緩存中的數據進行一次刷新,正是利用這種特性實現了近實時搜索。ES將內存緩沖數據,先寫入文件系統緩存,並產生一個新的分段,一旦寫操作完成,基於段的倒排序索引就具備了可檢索性。稍后再執行fush刷盤操作寫入磁盤。
由於系統先緩沖一段數據才寫,且新段不會立即刷入磁盤,這兩個過程中如果出現某些意外情況(如主機斷電),則會存在丟失數據的風險。通用的做法是記錄事務日志,每次對ES進行操作時均記錄事務日志,當ES啟動的時候,重放 translog中所有在最后一次提交后發生的變更操作。比如 HBase等都有自己的事務日志。
1.6 段合並
在ES中,每秒清空一次寫緩沖,將這些數據寫入文件,這個過程稱為 refresh,每次 refresh會創建一個新的 Lucene段。但是分段數量太多會帶來較大的麻煩,每個段都會消耗文件句柄、內存。
每個搜索請求都需要輪流檢查每個段,查詢完再對結果進行合並;所以段越多,搜索也就越慢。因此需要通過一定的策略將這些較小的段合並為大的段,常用的方案是選擇大小相似的分段進行合並。在合並過程中,標記為刪除的數據不會寫入新分段,當合並過程結束,舊的分段數據被刪除,標記刪除的數據才從磁盤刪除。
HBase、Cassandra等系統都有類似的分段機制,寫過程中先在內存緩沖一批數據,不時地將這些數據寫入文件作為一個分段,分段具有不變性,再通過一些策略合並分段。分段合並過程中,新段的產生需要一定的磁盤空間,我們要保證系統有足夠的剩余可用空間。
Cassandra系統在段合並過程中的一個問題就是,當持續地向一個表中寫入數據,如果段文件大小沒有上限,當巨大的段達到磁盤空間的一半時,剩余空間不足以進行新的段合並過程。如果段文件設置定上限不再合並,則對表中部分數據無法實現真正的物理刪除。ES存在同樣的問題。
2. 集群內部原理
2.1 集群中的節點
1. 主節點(Master node)
主節點負責創建索引、刪除索引、追蹤集群中節點的狀態。
通過配置 node.master:true(默認)使節點具有被選舉為Master的資格,主節點是全局唯一的,將從有資格成為 Master的節點中進行選舉。
主節點也可以作為數據節點,但盡可能做少量的工作,因此生產環境應盡量分離主節點和數據節點,創建獨立主節點的配置:node.master:true
node.data:false
為了防止數據丟失,每個主節點應該知道有資格成為主節點的數量,默認為1,為避免網絡分區時出現多主的情況,配置是discovery.zen.minimum_master_nodes原則上最小值應該 (master_eligible_nodes/2)+1
2. 數據節點
負責保存數據、執行數據相關操作:CRUD、搜索、聚合等。數據節點對CPU、內存、1O要求較高。
一般情況下,數據讀寫流程只和數據節點交互,不會和主節點打交道(異常情況除外)。
通過配置node.data:true
(默認)來使一個節點成為數據節點。
3.預處理節點
預處理操作允許在索引文檔之前,即寫入數據之前,通過事先定義好的一系列的 processors(處理器)和 pipeline(管道),對數據進行某種轉換、富化。
processors和 pipeline攔截buk和 index請求,在應用相關操作后將文檔傳回給 index或 bulk Api。
配默認情況下,在所有的節點上啟用 ingest,如果想在某個節點上禁用 Ingest,則可以添加配置node.ingest:false
也可以通過下面的配置創建一個僅用於預處理的節點:
node.master:false
node data:false
node.ingest:true
4. 協調節點
客戶端請求可以發送到集群的任何節點,每個節點都知道任意文檔所處的位置,然后轉發這些請求,收集數據並返回給客戶端,處理客戶端請求的節點稱為協調節點。
協調節點將請求轉發給保存數據的數據節點。每個數據節點在本地執行請求,並將結果返回協調節點。協調節點收集完數據后,將每個數據節點的結果合並為單個全局結果。對結果收集和排序的過程可能需要很多CPU和內存資源。
通過下面的配置創建一個僅用於協調的節點
node.master:false
node data:false
node.ingest:false
5. 部落節點
tribes(部落)功能允許部落節點在多個集群之間充當聯合客戶端。
在ES5.0之前還有一個客戶端節點( Node Client)的角色,客戶端節點有以下屬性
node.master:false
node data:false
它不做主節點,也不做數據節點,僅用於路由請求,本質上是一個智能負載均衡器(從負載均衡器的定義來說,智能和非智能的區別在於是否知道訪問的內容存在於哪個節點),從5.0版本開始,這個角色被協調節點取代。
2.2 集群健康狀態
從數據完整性的角度划分,集群健康狀態分為三種
-
Green,所有的主分片和副分片都正常運行。
-
Yellow,所有的主分片都正常運行,但不是所有的副分片都正常運行。這意味着存在單點故障風險。
-
Red,有主分片沒能正常運行。
每個索引也有上述三種狀態,假設丟失了一個副分片,該分片所屬的索引和整個集群變為Yeow狀態,其他索引仍為 Green