elasticsearch document的索引過程分析



點(diǎn)擊上方藍(lán)色“邁莫coding”,選擇“設(shè)為星標(biāo)”
一、預(yù)備知識(shí)
一、預(yù)備知識(shí)
1 1.1、索引不可變
看到這篇文章相信大家都知道es是倒排索引,不了解也沒關(guān)系,在我的另一篇博文中詳細(xì)分析了es的倒排索引機(jī)制。在es的索引過程中為了滿足一下特點(diǎn),落盤的es索引是不可變的。
1 不需要鎖。如果從來不需要更新一個(gè)索引,就不必?fù)?dān)心多個(gè)程序同時(shí)嘗試修改。
2 一旦索引被讀入文件系統(tǒng)的緩存(內(nèi)存),它就一直在那兒,因?yàn)椴粫?huì)改變。只要文件系統(tǒng)緩存有足夠的空間,大部分的讀會(huì)直接訪問內(nèi)存而不是磁盤。這有助于性能提升。
3 在索引的聲明周期內(nèi),所有的其他緩存都可用。它們不需要在每次數(shù)據(jù)變化了都重建,使文本可以被搜索因?yàn)閿?shù)據(jù)不會(huì)變。寫入單個(gè)大的倒排索引,可以壓縮數(shù)據(jù),較少磁盤IO和需要緩存索引的內(nèi)存大小。
當(dāng)然,不可變的索引有它的缺點(diǎn),首先是它不可變!你不能改變它。如果想要搜索一個(gè)新文 檔,必須重建整個(gè)索引。這不僅嚴(yán)重限制了一個(gè)索引所能裝下的數(shù)據(jù),還有一個(gè)索引可以被更新的頻次。所以es引入了動(dòng)態(tài)索引。
下一個(gè)需要解決的問題是如何在保持不可變好處的同時(shí)更新倒排索引。答案是,使用多個(gè)索引。不是重寫整個(gè)倒排索引,而是增加額外的索引反映最近的變化。每個(gè)倒排索引都可以按順序查詢,從最老的開始,最后把結(jié)果聚合。
Elasticsearch底層依賴的Lucene,引入了 per-segment search 的概念。一個(gè)段(segment)是有完整功能的倒排索引,但是現(xiàn)在Lucene中的索引指的是段的集合,再加上提交點(diǎn)(commit point,包括所有段的文件),如圖1所示。新的文檔,在被寫入磁盤的段之前,首先寫入內(nèi)存區(qū)的索引緩存。然后再通過fsync將緩存中的段刷新到磁盤上,該段將被打開即段落盤之后開始能被檢索。

看到這里如果對(duì)分段還是有點(diǎn)迷惑,沒關(guān)系,假如你熟悉java語(yǔ)言,ArrayList這個(gè)集合我們都知道是一個(gè)動(dòng)態(tài)數(shù)組,他的底層數(shù)據(jù)結(jié)構(gòu)其實(shí)就是數(shù)組,我們都知道數(shù)組是不可變的,ArrayList是動(dòng)過擴(kuò)容實(shí)現(xiàn)的動(dòng)態(tài)數(shù)組。
在這里我們就可以將commit point理解成ArrayList,segment就是一個(gè)個(gè)小的數(shù)組。然后將其組合成ArrayList。假如你知道Java1.8的ConcurrentHashMap的分段鎖相信你理解這個(gè)分段就很容易了。
在es中“索引”是分片(shard)的集合,在lucene中“索引”從宏觀上來說就是es中的一個(gè)分片,從微觀上來說就是segment的集合。
“document的索引過程”這句話中的這個(gè)“索引”,我們可以理解成es為document建立索引的過程。
二、document索引過程

文檔被索引的過程如上面所示,大致可以分為 內(nèi)存緩沖區(qū)buffer、translog、filesystem cache、系統(tǒng)磁盤這幾個(gè)部分,接下來我們梳理一下這個(gè)過程。
階段1:
這個(gè)階段很簡(jiǎn)單,一個(gè)document文檔第一步會(huì)同時(shí)被寫進(jìn)內(nèi)存緩沖區(qū)buffer和translog。
階段2:
refresh:內(nèi)存緩沖區(qū)的documents每隔一秒會(huì)被refresh(刷新)到filesystem cache中的一個(gè)新的segment中,segment就是索引的最小單位,此時(shí)segment將會(huì)被打開供檢索。也就是說一旦文檔被刷新到文件系統(tǒng)緩存中,其就能被檢索使用了。這也是es近實(shí)時(shí)性(NRT)的關(guān)鍵。后面會(huì)詳細(xì)介紹。
階段3:
merge:每秒都會(huì)有新的segment生成,這將意味著用不了多久segment的數(shù)量就會(huì)爆炸,每個(gè)段都將十分消耗文件句柄、內(nèi)存、和cpu資源。這將是系統(tǒng)無法忍受的,所以這時(shí),我們急需將零散的segment進(jìn)行合并。ES通過后臺(tái)合并段解決這個(gè)問題。小段被合并成大段,再合并成更大的段。然后將新的segment打開供搜索,舊的segment刪除。
階段4:
flush:經(jīng)過階段3合并后新生成的更大的segment將會(huì)被flush到系統(tǒng)磁盤上。這樣整個(gè)過程就完成了。但是這里留一個(gè)包袱就是flush的時(shí)機(jī)。在后面介紹translog的時(shí)候會(huì)介紹。
不要著急,接下來我們將以上步驟拆分開來詳細(xì)分析一下。
1 2.1、近實(shí)時(shí)化搜索(NRT)
在早起的lucene中,只有當(dāng)segement被寫入到磁盤,該segment才會(huì)被打開供搜索,和我們上面所說的當(dāng)doc被刷新到filesystem cache中生成新的segment就將會(huì)被打開。
因?yàn)?per-segment search 機(jī)制,索引和搜索一個(gè)文檔之間是有延遲的。新的文檔會(huì)在幾分鐘內(nèi)可以搜索,但是這依然不夠快。磁盤是瓶頸。提交一個(gè)新的段到磁盤需要 fsync 操作,確保段被物理地寫入磁盤,即時(shí)電源失效也不會(huì)丟失數(shù)據(jù)。但是 fsync 是昂貴的,它不能在每個(gè)文檔被索引的時(shí)就觸發(fā)。
所以需要一種更輕量級(jí)的方式使新的文檔可以被搜索,這意味這移除 fsync 。
位于Elasticsearch和磁盤間的是文件系統(tǒng)緩存。如前所說,在內(nèi)存索引緩存中的文檔被寫入新的段,但是新的段首先寫入文件系統(tǒng)緩存,這代價(jià)很低,之后會(huì)被同步到磁盤,這個(gè)代價(jià)很大。但是一旦一個(gè)文件被緩存,它也可以被打開和讀取,就像其他文件一樣。
在es中每隔一秒寫入內(nèi)存緩沖區(qū)的文檔就會(huì)被刷新到filesystem cache中的新的segment,也就意味著可以被搜索了。這就是ES的NRT——近實(shí)時(shí)性搜索。
簡(jiǎn)單介紹一下refresh API
如果你遇到過你新增了doc,但是沒檢索到,很可能是因?yàn)檫€未自動(dòng)進(jìn)行refresh,這是你可以嘗試手動(dòng)刷新
POST /student/_refresh性能優(yōu)化
在這里我們需要知道一點(diǎn)refresh過程是很消耗性能的。如果你的系統(tǒng)對(duì)實(shí)時(shí)性要求不高,可以通過API控制refresh的時(shí)間間隔,但是如果你的新系統(tǒng)很要求實(shí)時(shí)性,那你就忍受它吧。
如果你對(duì)系統(tǒng)的實(shí)時(shí)性要求很低,我們可以調(diào)整refresh的時(shí)間間隔,調(diào)大一點(diǎn)將會(huì)在一定程度上提升系統(tǒng)的性能。
PUT /student{"settings": {"refresh_interval": "30s"}}
1 2.2、合并段——merge
通過每秒自動(dòng)刷新創(chuàng)建新的段,用不了多久段的數(shù)量就爆炸了。有太多的段是一個(gè)問題。每個(gè)段消費(fèi)文件句柄,內(nèi)存,cpu資源。更重要的是,每次搜索請(qǐng)求都需要依次檢查每個(gè)段。段越多,查詢?cè)铰?/span>
ES通過后臺(tái)合并段解決這個(gè)問題。小段被合并成大段,再合并成更大的段。
這是舊的文檔從文件系統(tǒng)刪除的時(shí)候。舊的段不會(huì)再?gòu)?fù)制到更大的新段中。這個(gè)過程你不必做什么。當(dāng)你在索引和搜索時(shí)ES會(huì)自動(dòng)處理。
我們?cè)賮砜偨Y(jié)一下段合并的過程。
1 選擇一些有相似大小的segment,merge成一個(gè)大的segment
2 將新的segment flush到磁盤上去
3 寫一個(gè)新的commit point,包括了新的segment,并且排除舊的那些segment
4 將新的segment打開供搜索
5 將舊的segment刪除
optimize API與性能優(yōu)化
optimize API 最好描述為強(qiáng)制合并段API。它強(qiáng)制分片合并段以達(dá)到指定 max_num_segments 參數(shù)。這是為了減少段的數(shù)量(通常為1)達(dá)到提高搜索性能的目的。
在特定的環(huán)境下, optimize API 是有用的。典型的場(chǎng)景是記錄日志,這中情況下日志是按照每天,周,月存入索引。舊的索引一般是只可讀的,它們是不可能修改的。這種情況下,把每個(gè)索引的段降至1是有效的。搜索過程就會(huì)用到更少的資源,性能更好:
POST /logstash-2019-10-01/_optimize?max_num_segments=1一般場(chǎng)景下盡量不要手動(dòng)執(zhí)行,讓它自動(dòng)默認(rèn)執(zhí)行就可以了
1 2.3、容災(zāi)與可靠存儲(chǔ)
沒用 fsync 同步文件系統(tǒng)緩存到磁盤,我們不能確保電源失效,甚至正常退出應(yīng)用后,數(shù)據(jù)的安全。為了ES的可靠性,需要確保變更持久化到磁盤。
我們說過一次全提交同步段到磁盤,寫提交點(diǎn),這會(huì)列出所有的已知的段。在重啟,或重新 打開索引時(shí),ES使用這次提交點(diǎn)決定哪些段屬于當(dāng)前的分片。
當(dāng)我們通過每秒的刷新獲得近實(shí)時(shí)的搜索,我們依然需要定時(shí)地執(zhí)行全提交確保能從失敗中 恢復(fù)。但是提交之間的文檔怎么辦?我們也不想丟失它們。
上面doc索引流程的階段1,doc分別被寫入到內(nèi)存緩沖區(qū)和translog,然后每秒都將會(huì)把內(nèi)存緩沖區(qū)的docs刷新到filesystem cache中的新segment,然后眾多segment會(huì)進(jìn)行不斷的壓縮,小段被合并成大段,再合并成更大的段。每次refresh操作后,內(nèi)存緩沖區(qū)的docs被刷新到filesystem cache中的segemnt中,但是tanslog仍然在持續(xù)的增大增多。當(dāng)translog大到一定程度,將會(huì)發(fā)生一個(gè)commit操作也就是全量提交。
詳細(xì)過程如下:
1、 doc寫入內(nèi)存緩沖區(qū)和translog日志文件2、 每隔一秒鐘,內(nèi)存緩沖區(qū)中的docs被寫入到filesystem cache中新的segment,此時(shí)segment被打開并供檢索使用3、 內(nèi)存緩沖區(qū)被清空4、 重復(fù)1~3,新的segment不斷添加,內(nèi)存緩沖區(qū)不斷被清空,而translog中的數(shù)據(jù)不斷累加5、 當(dāng)translog長(zhǎng)度達(dá)到一定程度的時(shí)候,commit操作發(fā)生5-1、 內(nèi)存緩沖區(qū)中的docs被寫入到filesystem cache中新的segment,打開供檢索使用5-2、 內(nèi)存緩沖區(qū)被清空5-3、 一個(gè)commit ponit被寫入磁盤,標(biāo)明了所有的index segment5-4、 filesystem cache中的所有index segment file緩存數(shù)據(jù),被fsync強(qiáng)行刷到磁盤上5-5、 現(xiàn)有的translog被清空,創(chuàng)建一個(gè)新的translog
其實(shí)到這里我們發(fā)現(xiàn)fsync還是沒有被舍棄的,但是我們通過動(dòng)態(tài)索引和translog技術(shù)減少了其使用頻率,并實(shí)現(xiàn)了近實(shí)時(shí)搜索。其次通過以上步驟我們發(fā)現(xiàn)flush操作包括filesystem cache中的segment通過fsync刷新到硬盤以及translog的清空兩個(gè)過程。es默認(rèn)每30分鐘進(jìn)行一次flush操作,但是當(dāng)translog大到一定程度時(shí)也會(huì)進(jìn)行flush操作。
對(duì)應(yīng)過程圖如下

5-5步驟不難發(fā)現(xiàn)只有內(nèi)存緩沖區(qū)中的docs全部刷新到filesystem cache中并fsync到硬盤,translog才會(huì)被清除,這樣就保證了數(shù)據(jù)不會(huì)丟失,因?yàn)橹灰猼ranslog存在,我們就能根據(jù)translog進(jìn)行數(shù)據(jù)的恢復(fù)。
簡(jiǎn)單介紹一下flush API
手動(dòng)flush如下所示,但是并不建議使用。但是當(dāng)要重啟或關(guān)閉一個(gè)索引,flush該索引是很有用的。當(dāng)ES嘗試恢復(fù)或者重新打開一個(gè)索引時(shí),它必須重放所有事務(wù)日志中的操作,所以日志越小,恢復(fù)速度越快。
POST /student/_flush三、更新和刪除
前面我們說過es的索引是不可變的,那么更新和刪除是如何進(jìn)行的呢?
段是不可變的,所以文檔既不能從舊的段中移除,舊的段也不能更新以反映文檔最新的版本。相反,每一個(gè)提交點(diǎn)包括一個(gè).del文件,包含了段上已經(jīng)被刪除的文檔。當(dāng)一個(gè)文檔被刪除,它實(shí)際上只是在.del文件中被標(biāo)記為刪除,依然可以匹配查詢,但是最終返回之前會(huì)被從結(jié)果中刪除。
文檔的更新操作是類似的:當(dāng)一個(gè)文檔被更新,舊版本的文檔被標(biāo)記為刪除,新版本的文檔在新的段中索引。也許該文檔的不同版本都會(huì)匹配一個(gè)查詢,但是更老版本會(huì)從結(jié)果中刪除。
分割線
往期推薦
文章也會(huì)持續(xù)更新,可以微信搜索「 邁莫coding 」第一時(shí)間閱讀。每天分享優(yōu)質(zhì)文章、大廠經(jīng)驗(yàn)、大廠面經(jīng),助力面試,是每個(gè)程序員值得關(guān)注的平臺(tái)。

你點(diǎn)的每個(gè)贊,我都認(rèn)真當(dāng)成了喜歡
