1. 程式人生 > >ElasticSearch內部分片處理機制

ElasticSearch內部分片處理機制

逆向索引:

與傳統的資料庫不同,在es中,每個欄位裡面的每個單詞都是可以被搜尋的。如hobby:”dance,sing,swim,run”,我們在搜尋關鍵字swim時,所有包含swim的文件都會被匹配到,es的這個特性也叫做全文搜尋。

為了支援這個特性,es中會維護一個叫做“invertedindex”(也叫逆向索引)的表,表內包含了所有文件中出現的所有單詞,同時記錄了這個單詞在哪個文件中出現過。

例:

當前有3個文件

Doc1:”brown,fox,quick,the”

Doc2:”fox,quick”

Doc3:”brown,fox,the”

那麼es會維護如下一個資料結構:

這裡寫圖片描述

這樣我們隨意搜尋任意一個單詞,es只要遍歷一下這個表,就可以知道有些文件被匹配到了。

逆向索引裡面不止記錄了單詞與文件的對應關係,它還維護了很多其他有用的資料。如:每個文件一共包含了多少個單詞,單詞在不同文件中的出現頻率,每個文件的長度,所有文件的總長度等等。這些資料用來給搜尋結果進行打分,如搜尋單詞apple時,那麼出現apple這個單詞次數最多的文件會被優先返回,因為它匹配的次數最多,和我們的搜尋條件關聯性最大,因此得分也最多。

逆向索引是不可更改的,一旦它被建立了,裡面的資料就不會再進行更改。這樣做就帶來了以下幾個好處:

  1. 沒有必要給逆向索引加鎖,因為不允許被更改,只有讀操作,所以就不用考慮多執行緒導致互斥等問題。

  2. 索引一旦被載入到了快取中,大部分訪問操作都是對記憶體的讀操作,省去了訪問磁碟帶來的io開銷。

  3. 因為逆向索引的不可變性,所有基於該索引而產生的快取也不需要更改,因為沒有資料變更。

  4. 使用逆向索引可以壓縮資料,減少磁碟io及對記憶體的消耗。

Segment:

既然逆向索引是不可更改的,那麼如何新增新的資料,刪除資料以及更新資料?為了解決這個問題,lucene將一個大的逆向索引拆分成了多個小的段segment。每個segment本質上就是一個逆向索引。在lucene中,同時還會維護一個檔案commit point,用來記錄當前所有可用的segment,當我們在這個commit point上進行搜尋時,就相當於在它下面的segment中進行搜尋,每個segment返回自己的搜尋結果,然後進行彙總返回給使用者。

引入了segment和commit point的概念之後,資料的更新流程如下圖:
這裡寫圖片描述

  1. 新增的文件首先會被存放在記憶體的快取中

  2. 當文件數足夠多或者到達一定時間點時,就會對快取進行commit

    a. 生成一個新的segment,並寫入磁碟

    b. 生成一個新的commit point,記錄當前所有可用的segment

    c. 等待所有資料都已寫入磁碟

  3. 開啟新增的segment,這樣我們就可以對新增的文件進行搜尋了

  4. 清空快取,準備接收新的文件

文件的更新與刪除:

segment是不能更改的,那麼如何刪除或者更新文件?

每個commit point都會維護一個.del檔案,檔案內記錄了在某個segment內某個文件已經被刪除。在segment中,被刪除的文件依舊是能夠被搜尋到的,不過在返回搜尋結果前,會根據.del把那些已經刪除的文件從搜尋結果中過濾掉。

對於文件的更新,採用和刪除文件類似的實現方式。當一個文件發生更新時,首先會在.del中宣告這個文件已經被刪除,同時新的文件會被存放到一個新的segment中。這樣在搜尋時,雖然新的文件和老的文件都會被匹配到,但是.del會把老的文件過濾掉,返回的結果中只包含更新後的文件。

Refresh:

ES的一個特性就是提供實時搜尋,新增加的文件可以在很短的時間內就被搜尋到。在建立一個commit point時,為了確保所有的資料都已經成功寫入磁碟,避免因為斷電等原因導致快取中的資料丟失,在建立segment時需要一個fsync的操作來確保磁碟寫入成功。但是如果每次新增一個文件都要執行一次fsync就會產生很大的效能影響。在文件被寫入segment之後,segment首先被寫入了檔案系統的快取中,這個過程僅使用很少的資源。之後segment會從檔案系統的快取中逐漸flush到磁碟,這個過程時間消耗較大。但是實際上存放在檔案快取中的檔案同樣可以被開啟讀取。ES利用這個特性,在segment被commit到磁碟之前,就開啟對應的segment,這樣存放在這個segment中的文件就可以立即被搜尋到了。

這裡寫圖片描述

上圖中灰色部分即存放在快取中,還沒有被commit到磁碟的segment。此時這個segment已經可以進行搜尋。

在ES中,將快取中的文件寫入segment,並開啟segment使之可以被搜尋的過程叫做refresh。預設情況下,分片的refresh頻率是每秒1次。這就解釋了為什麼es聲稱提供實時搜尋功能,新增加的文件會在1s內就可以進行搜尋了。

Refresh的頻率通過index.refresh_interval:100s引數控制,一條新寫入es的日誌,在進行refresh之前,是在es中不能立即搜尋不到的。

通過執行curl -XPOST127.0.0.1:9200/_refresh,可以手動觸發refresh行為。

flush與translog

前面講到,refresh行為會立即把快取中的文件寫入segment中,但是此時新建立的segment是寫在檔案系統的快取中的。如果出現斷電等異常,那麼這部分資料就丟失了。所以es會定期執行flush操作,將快取中的segment全部寫入磁碟並確保寫入成功,同時建立一個commit point,整個過程就是一個完整的commit過程。

但是如果斷電的時候,快取中的segment還沒有來得及被commit到磁碟,那麼資料依舊會產生丟失。為了防止這個問題,es中又引入了translog檔案。

  1. 每當es接收一個文件時,在把文件放在buffer的同時,都會把文件記錄在translog中。
    這裡寫圖片描述
  2. 執行refresh操作時,會將快取中的文件寫入segment中,但是此時segment是放在快取中的,並沒有落入磁碟,此時新建立的segment是可以進行搜尋的。
    這裡寫圖片描述
  3. 按照如上的流程,新的segment繼續被建立,同時這期間新增的文件會一直被寫到translog中。
    這裡寫圖片描述
  4. 當達到一定的時間間隔,或者translog足夠大時,就會執行commit行為,將所有快取中的segment寫入磁碟。確保寫入成功後,translog就會被清空。
    這裡寫圖片描述

執行commit並清空translog的行為,在es中可以通過_flush api進行手動觸發。

如:

curl -XPOST127.0.0.1:9200/tcpflow-2015.06.17/_flush?v

通常這個flush行為不需要人工干預,交給es自動執行就好了。同時,在重啟es或者關閉索引之間,建議先執行flush行為,確保所有資料都被寫入磁碟,避免照成資料丟失。通過呼叫sh service.sh start/restart,會自動完成flush操作。

Segment的合併

前面講到es會定期的將收到的文件寫入新的segment中,這樣經過一段時間之後,就會出現很多segment。但是每個segment都會佔用獨立的檔案控制代碼/記憶體/消耗cpu資源,而且,在查詢的時候,需要在每個segment上都執行一次查詢,這樣是很消耗效能的。

為了解決這個問題,es會自動定期的將多個小segment合併為一個大的segment。前面講到刪除文件的時候,並沒有真正從segment中將文件刪除,而是維護了一個.del檔案,但是當segment合併的過程中,就會自動將.del中的文件丟掉,從而實現真正意義上的刪除操作。

當新合併後的segment完全寫入磁碟之後,es就會自動刪除掉那些零碎的segment,之後的查詢都在新合併的segment上執行。Segment的合併會消耗大量的IO和cpu資源,這會影響查詢效能。

在es中,可以使用optimize介面,來控制segment的合併。

如:

POST/logstash-2014-10/_optimize?max_num_segments=1

這樣,es就會將logstash-2014-10中的segment合併為1個。但是對於那些更新比較頻繁的索引,不建議使用optimize去執行分片合併,交給後臺的es自己處理就好了。