elasticsearch index過程簡要分析

寫入的數據是如何變成 ES 裡可以被檢索和聚合的索引內容的呢?從單文件的靜態層面看,每個全文索引都是一個詞元的倒排索引。這裡只簡單的介紹一下倒排索引的組成,其它的一些通用知識可以看看Lucene相關的一些書籍。

倒排索引通常由詞彙表和記錄表組成:

詞彙表:文檔集合中所包含的不同單詞的集合。

記錄表:對於詞彙表中的每一個單詞,包含這個單詞的文檔編號構成的一個列表(有可能還會保存些其他信息,例如單詞在文檔中的位置信息)。

從服務的層面看,要做到實時更新條件下數據的可用和可靠,就需要在倒排索引的基礎上,再做一系列更高級的處理。Lucene 的處理辦法,很簡單,就是一句話:新收到的數據寫到新的索引文件裡。ES是基於Lucene的分佈式搜索引擎,底層處理也當然會按照Lucene的處理方式來進行處理。

Lucene 把每次生成的倒排索引,叫做一個段(segment)。然後另外使用一個 commit 文件,記錄索引內所有的 segment。而生成 segment 的數據來源,則是內存中的 buffer。也就是說,動態更新過程如下:

elasticsearch index過程簡要分析

1、 當前索引中有3個段。

2、 外部調用ES API寫入新數據進入內存buffer。

3、 內存 buffer 生成一個新的 segment,刷到文件系統緩存中,Lucene 即可檢索這個新 segment。

4、 文件系統緩存真正同步到磁盤上,commit 文件更新,記錄新生成的segment。

在refresh文件系統緩存的過程中,ES默認設置為1秒間隔(可以在ES的yml配置文件中設修改默認刷新時間,例如每5秒刷新一次,index.refresh_interval: 5s),對於大多數應用來說,幾乎就相當於是實時可搜索了。如果對1秒間隔還不滿意,ES也提供了單獨的/_refresh 接口或在JAVA API中寫入的時候設置setRefresh(true),可以主動調用該接口來保證搜索可見。

上面我們說過Lucene通過新生成文件的方式來讓數據更快的被檢索使用,但另一方面,新開文件會給服務器帶來負載壓力。因為默認1秒refresh一次文件系統緩存,都會有一個新文件產生,每個文件都需要有文件句柄,內存,CPU使用等各種資源。一天有 86400 秒,如果每次請求要掃描一遍 86400 個文件,這樣性能絕對好不了!如果是每次寫入的時候都通過API顯式刷新,產生的文件會更多。

所以應該在滿足業務需求的範圍內,適當的調整refresh的間隔時間;另一方面ES會不斷在後臺運行任務,主動將這些小的segment 做數據歸併,儘量讓索引內只保有少量的,每個都比較大的segment 文件。這個過程是有獨立的線程來進行的,並不影響新 segment 的產生。在歸併過程中,沒有完成的較大的 segment 是不可見的。當歸並完成,較大的這個 segment 刷到磁盤後,commit 文件做出相應變更,刪除之前幾個小 segment,改成新的大 segment。等檢索請求都從小 segment 轉到大 segment 上以後,刪除沒用的小 segment。這時候,索引裡 segment 數量就下降了。從上面可以看出段合併的時候會寫磁盤,因此我們必須對寫磁盤的速度進行控制,啟用限速機制,可以在配置文件中設置indices.store.throttle.max_bytes_per_sec選項的值來進行設置,避免無限速的寫,影響服務器上其它服務的性能。

當然在寫索引的這個過程中還會寫一些translog,來保證數據數據的一致性;默認半個小時文件系統緩存flush一次寫入到磁盤,個人覺得這些默認設置對性能影響不大,所以不做過多探討。

後續會從源代碼層面對ES建索引的過程中主要的類進行解釋,有興趣的一起學習相互討論。


分享到:


相關文章: