Elasticsearch寫入數據的過程是什麼樣的?以及是如何快速更新索引數據的?

前言

最近面試過程中遇到問Elasticsearch的問題不少,這次總結一下,然後順便也瞭解一下Elasticsearch內部是一個什麼樣的結構,畢竟總不能就只瞭解個倒排索引吧。本文標題就是我遇到過的兩個問題,所以此次基本上只是圍繞着這兩個問題來總結。

ES寫入數據

在介紹寫入數據的過程時,先明確一下ES中的一些關鍵性的概念:

  • Clouster:集羣,由一到N個Elasticsearch服務節點組成。
  • Node:節點,組成Elasticsearch集羣的基本單元,單個集羣內節點名稱唯一。通常一個節點中分配一道多個分片。
  • Shards:分片,當ES的索引數據過大時,會進行水平拆分,拆分出來的每一個單元都稱爲分片。在進行寫入數據的時候,會通過路由來確定具體寫到哪個分片上,所以在創建索引的時候就要確定好分片數量,並且一旦確定不可更改。索引數據在經過分片後,在數據管理和性能上都有很大提升,並且每一個分片都是一個Lucende的索引,每個分片都必須有一個主分片和零到多個副分片
  • Replicas:副本或備份,副本是指對主分片的備份分片,無論是主分片還是副本分片都可以對外提供查詢服務。但是寫入操作時是先寫入主分片,然後再分發到副本上。
    當主分片不可用時會在副本分片上選舉一個作爲主分片,因此副本不僅可以保證系統的高可用性,還可以提升搜索時的併發性能(主副分片都可以提供查詢)。但並不是副本越多越好,副本數量過多會導致數據同步的負擔過大。

分片數 (副本數+1)= 所需的最大節點數
舉例:你計劃5個分片和1個副本,那麼所需要的最大的節點數爲:5
(1+1)=10個節點。

  • Index:索引,由一個和多個分片組成,單個集羣內索引名字是唯一的。
  • Type:類型,指索引內部的邏輯分區,一般是通過Type的名字來進行分區,若是查詢條件中沒有該值,則說明在整個索引中執行查詢。
  • Document:文檔,ES索引中的每一條數據都稱爲一個Document,基本上和關係型數據庫中的一個記錄意思相同,通過_id,在Type內進行唯一標識。
  • Settings:對集羣中索引的設定,例如默認的分片數量,副本數等信息。
  • Mapping:這裏的Mapping類似於,關係型數據庫的表結構信息,這裏麪包含了索引中字段的存儲類型,分詞方式,是否分詞等信息。
    Elasticsearch中的Mapping是可以動態識別的,Elasticsearch字段的數據格式識別它的類型,但是若是需要對Filed字段進行特殊設置時,就需要手動創建Mapping了。注意:一個Mapping一旦創建成功後,若是已經存儲了數據了,就不可以修改了。
  • Analyzer:字段的分詞方式的定義,一個Analyzer,通常由一個Tokenizer,零到多個Filter組成。例如默認的標準Analyzer包含一個標準的Analyzer和三個Filter(Standard Token Filter、Lower Case Token Filter、Stop Token Filter)。

Elasticsearch的節點分類

  • Master Node(主節點):主節點主要負責創建索引,刪除索引,分配分片,追蹤集羣中的節點狀態等工作。在配置文件中設置node.master=true 來將該節點設置成候選主節點,在集羣中只有候選主節點纔有選舉權和被選舉權。
  • Data Node(數據節點):數據節點負責數據的存儲和相關具體操作,例如索引數據的創建,更新,搜索,聚合等操作。因此,數據節點對機器的要求比較高無論是在磁盤空間還是CPU、內存、I/O性能等。
    集羣在擴大後,需要增加更多的數據節點來提高可用性,在配置文件中通過node.data=true 來設置當前節點爲數據節點。
  • Client Node(客戶端節點):客戶端節點是既不做候選主節點也不做數據節點的節點,只負責請求的分發、彙總等。若是單獨增加這種節點主要是更多地爲了提高併發性。
  • Tribe Node(部落節點):部落節點可以跨越多個集羣,它可以接收每個集羣的狀態,然後合併成一個全局集羣狀態。
  • Coordinating Node(協調節點):協調節點只是一個角色,並不是指具體的某個集羣節點,也沒法通過配置來指定某個節點爲協調節點,這也就說明集羣中任何一個節點都可以是協調節點。
    例如:節點A接收到用戶的查詢請求,會把查詢語句分發到其他節點,然後合併各個節點返回查詢結果,最後將完成的聚合結果返回給用戶。這個請求中節點A的扮演的就是協調節點的角色。

還有就是集羣節點有三個顏色狀態:綠色、黃色、紅色

  • 綠色:代表健康狀態,所有的主副分片都可正常工作,集羣100%健康。
  • 黃色:預警,所有的主分片都可以正常工作,但是至少有一個副分片是不能正常工作的。雖然集羣能正常工作,但是高可用性已經有所降低。
  • 紅色:異常,集羣不可正常使用。集羣中至少有一個分片的主分片和全部副分片不可用。此時雖然查詢操作可以返回數據,但也只是返回可用分片的那部分數據,並非全部的正確數據。若此時寫請求打到異常分片會造成數據丟失。

寫入過程

Elasticsearch寫入數據到索引的過程大致是這樣的:
寫入過程

  • 首先客戶端會根據配置的連接節點,通過輪詢的方式選擇一個coordinate節點。
  • coordinate節點通過路由函數(shard = hash(routing)%number_of_primary_shards),計算出數據應該落到那個shard中,根據coordinate節點上維護的shard信息,將請求發送到Node1上。
  • Node1先校驗索引數據,然後在主分片上執行請求,執行成功後,將請求並行轉發到副本集存在Node2、Node3。
  • Node2、Node3寫入成功數據成功後,發送ack信息給主分片所在的Node1節點。
  • Node1節點再將ack信息發送給coordinate節點。
  • coordinate 節點 發送ack節點給客戶端。

在主分片上執行寫入請求的過程如下:

寫入分片

  1. 當有數據寫入時,爲了提升寫速度,也是先將數據寫入到內存(Memory Buffer)中的;
  2. 因爲先寫入了內存所以爲了保證內存中的數據不丟失,也會同時寫入Translog到內存中。
  3. 每隔一定時間(可配置)將數據從Memory Buffer中refreshFileSystemCache中,生成segment文件,一旦生成segment文件,就能通過索引查詢到了。
  4. refresh完,memory buffer就清空了。Translog 也是從buffer flush到磁盤中。
  5. 定期/定量從FileSystemCache中,結合Translog內容flush index到磁盤中。做增量flush的。

因爲Elasticsearch的這個刷盤機制,也說明並非是一個實時的搜索引擎。

更新數據

在早期的全文檢索中爲整個文檔建立了很大的倒排索引,並將其寫入到磁盤。如果索引有更新,就需要重新全量創建一個索引來替換原來的索引。

這種方式在數據量很大時效率很低,並且由於創建一次索引的成本很高,所以對數據的更新不能過於頻繁,也就不能保證實效性。

分段存儲

在搜索中引入了段(segment)的概念(將一個索引文件拆分爲多個子文件,則每個子文件叫做段),每個段都是一個獨立的可被搜索的數據集,並且段具有不變性,一旦索引的數據被寫入硬盤,就不可修改

由於Elasticsearch中的每個分片包含多個segment(段),每一個segment都是一個倒排索引;因此在在查詢的時,會把所有的segment查詢結果彙總歸併爲最終的分片查詢結果返回。

那麼在這種分段存儲的模式下Elasticsearch是如何進行數據操作的呢?
  • 新增: 當有新的數據需要插入索引時,由於段的不可變性,會新建一個段來存儲新增數據。
  • 刪除: 也是由於段的不可變,所以刪除的時候會新增一個.del文件,專門用來存儲被刪除的數據id。這樣雖然查詢的時候還是能查到,但是在進程查詢結果彙總的時候會將已刪除的數據id過濾掉。
  • 更新: 更新操作其實就是刪除和新增的組合操作,先在.del文件中積累舊數據,然後在新段中添加一條更新後的數據。
分段存儲的優點和缺點
segment優點
  • 讀寫都不用加鎖,不直接更新數據,所以不用考慮多線程下讀寫不一致的問題。
  • 長駐內存,由於segment的不可變性,這樣只要空間足夠大,數據都是直接存儲在內存中的。因此查詢數據時直接訪問內存,不用頻繁操作磁盤。
  • 增量創建,分段可以做到增量創建索引,即輕量級的對索引進行改變,不用操作整個索引文件,這樣在頻繁更新數據時,使系統接近實時更新。
segment缺點
  • 刪除,對段數據進行刪除時,舊數據在.del文件中並不會馬上刪除。而舊數據只有等到段合併時纔會被刪除,這樣會造成大量的空間浪費。
  • 更新,因爲更新操作是有刪除和新增組合而成,若是頻繁的更新也會造成大量的空間浪費。
  • 新增,由於每次新增數據都是新建一個段,當段的數量過多時,對服務器的資源的消耗會非常大,查詢性能也會受到影響。
  • 過濾,查詢後的結果再彙總時需要對已刪除的數據進行過濾,增加了系統的處理負擔。

這裏要注意一點,並不是每新增一條記錄(Document)就創建一個段(segment)的,而是數據先落到Memory Buffer中後,批量刷到Segment中的。

段合併

由於Memory Buffer中的數據在很短的時間(1s)內不斷的進行刷盤,這樣就會造成短時間內段的數量暴增,當索引中的段數量太多時不僅會嚴重消耗服務器資源,還會影響檢索性能。

所以必須進行定期段合併操作,小的段被合併到大的段,然後這些大的段再被合併到更大的段。

段合併的主要動作有兩個:

  • 對索引中的段進行分組,把大小相近的段分到一組。
  • 將屬於同一分組的段合併成一個更大的段。

還有一個邏輯是段合併的時候會將那些舊的已刪除文檔從文件系統中清除。

段合併

段合併的好處和問題
segment合併的好處
  • 減少索引段的數量,提升的內存空間,從而提高了檢索速度。
  • 減少索引的容量(文檔數)——段合併會移除被標記爲已刪除的那些文檔。提高了全文檢索的速度,並移除了舊版本的數據。
segment合併帶來的問題
  • 磁盤IO操作的代價;因爲段合併操作是非常耗I/O的,因爲需要從舊的索引段中讀取數據然後合併到新的索引段。
  • 查詢性能有一定影響;雖然說索引段合併的操作是異步進行的,但由於合併操作非常耗I/O,若合併時,正好也在進行大量的查詢操作,在那些I/O處理速度慢的系統中,系統性能會受到影響。

如何快速更新索引數據?

通過對上面索引的分段存儲和索引段合併的介紹,已經可以清楚的知道,在更新索引數據的時候,其實都是在操作索引段,對一段的索引數據進程操作,這樣就能實現快速更新索引數據了。

Elasticsearch 的併發處理和數據一致性處理

併發處理(Concurrency)

Elasticsearch在接收到寫請求時,是先將數據寫入到主分片的,然後再將寫請求同步到各個副本分片,但是同步這些副本分片的時間是無序的。

這個時候,Elasticsearch 就會採用樂觀併發控制(Optimistic Concurrency Control)來保證新版本的數據不會被舊版本的數據覆蓋。

這個樂觀併發控制,就類似於Java的CAS機制,就是比較交換的意思。

樂觀併發控制(OCC)認爲事務間的競爭並不激烈,所以任何事務來了就先執行,等到提交的時候再 檢查一下數據有沒有變化,若沒有變化就直接提交,如果有變化就直接重試再提交。這種適用於寫衝突比較少的場景。

一致性(Consistency)

寫一致性

Elasticsearch 集羣保證寫一致性的方式是在寫之前檢查有多少分片可供寫入,若符合條件則執行寫操作,否則會進行等待,等待符合條件,默認等待時間1分鐘。

滿足寫入分片的條件有如下三個配置

  • One,代表只要主分片可用,就執行寫操作。
  • All,代表只有當主分片和所有副本分片可用時,才執行寫操作。
  • Quorum(k-wu-wo/reng,法定人數):這是 Elasticsearch 的默認選項。當有大部分的分片可用時才允許寫操作。其中,對“大部分”的計算公式爲 int((primary+number_of_replicas)/2)+1。 意思是大於主副分片之和的二分之一纔可以執行寫操作。
讀寫一致性

Elasticsearch 集羣保證讀寫一致性的方式是,將副本分片的同步方式設置爲replication=Sync(默認值),指的是隻有主分片和所有副本分片都寫入成功後才返回請求結果。

這種方式,可以保證後面的讀請求無論請求到那個分片,返回的數據都是最新的版本。

但是爲了提升寫的效率也可以通過設置replication=async,這種模式是指,只要寫入主分片成功,就返回寫請求結果,降低了數據一致性提升了寫的效率。

參考:
掌握它才說明你真正懂 Elasticsearch
《深入理解Elasticsearch》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章