01. ElasticSearch 倒排索引是什么?
Elasticsearch的倒排索引是一種數(shù)據(jù)結(jié)構(gòu),它將每個(gè)單詞與包含該單詞的文檔列表相關(guān)聯(lián)。倒排索引的優(yōu)勢(shì)在于它可以快速地進(jìn)行全文搜索和相關(guān)性評(píng)分。
倒排索引由3個(gè)主要部分組成:詞項(xiàng)、詞匯表、倒排列表。其中詞項(xiàng)是索引中最小的存儲(chǔ)和查詢單元。詞匯表是一個(gè)包含所有單詞的列表,每個(gè)單詞都有一個(gè)唯一的詞項(xiàng)ID。倒排列表是一個(gè)包含每個(gè)單詞的文檔列表(一個(gè)單詞出現(xiàn)在哪些文檔中),每個(gè)文檔都有一個(gè)唯一的文檔ID。倒排列表還包含每個(gè)單詞在每個(gè)文檔中出現(xiàn)的位置和頻率的信息。
當(dāng)用戶執(zhí)行全文搜索時(shí),Elasticsearch將查詢中的每個(gè)單詞與詞匯表中的詞項(xiàng)進(jìn)行匹配,并檢索包含這些單詞的文檔列表。然后,Elasticsearch使用倒排列表中的信息來(lái)計(jì)算每個(gè)文檔的相關(guān)性得分,并將結(jié)果返回給用戶。
ElasticSearch索引中索引了3個(gè)文檔:
文檔id | 文檔內(nèi)容 |
---|---|
1 | 谷歌地圖之父跳槽Facebook |
2 | 谷歌地圖之父加盟Facebook |
3 | 谷歌地圖創(chuàng)始人離開谷歌加盟Facebook |
倒排索引中需要記錄的信息:
單詞id | 單詞 | 文檔頻率 | 倒排列表(文檔id;單詞頻率;<單詞在文檔中的位置>) |
---|---|---|---|
1 | 谷歌 | 3 | (1;1<1>),(2;1;<1>),(3;2;<5>) |
2 | 地圖 | 3 | (1;1<2>),(2;1;<2>),(3;1;<2>) |
3 | 之父 | 2 | (1;1<3>),(2;1;<3>) |
4 | 跳槽 | 2 | (1;1<4>) |
5 | 3 | (1;1<5>),(2;1;<5>),(3;1;<7>) | |
6 | 加盟 | 2 | (2;1<4>),(3;1;<6>) |
7 | 創(chuàng)始人 | 1 | (3;1;<3>) |
8 | 離開 | 1 | (3;1;<4>) |
以單詞谷歌為例,其單詞編號(hào)為1,文檔頻率為3,代表有3個(gè)文檔包含這個(gè)單詞,對(duì)應(yīng)的倒排列表為{(1;1;<1>), (2;1;<1>), (3;2;<1>)},其含義為在文檔1、2、3都出現(xiàn)過(guò)這個(gè)單詞,文檔1和文檔2中該單詞出現(xiàn)的頻率都為1,出現(xiàn)位置也都是1,即文檔中第1個(gè)單詞是谷歌。文檔3中該單詞出現(xiàn)的頻率為2次,出現(xiàn)的位置為1和5,即文檔3的第1個(gè)和第5個(gè)單詞是谷歌。
02. ElasticSearch 倒排索引為什么是不可變的?
倒排索引被寫入磁盤后是不可改變的,它永遠(yuǎn)不會(huì)修改。不變性有重要的價(jià)值:
① 不需要鎖。如果你從來(lái)不更新索引,你就不需要擔(dān)心多進(jìn)程同時(shí)修改數(shù)據(jù)的問題。
② 一旦索引被讀入內(nèi)核的文件系統(tǒng)緩存,便會(huì)留在哪里,由于其不變性。只要文件系統(tǒng)緩存中還有足夠的空間,那么大部分讀請(qǐng)求會(huì)直接請(qǐng)求內(nèi)存,而不會(huì)命中磁盤。這提供了很大的性能提升。
③ 其它緩存(像filter緩存),在索引的生命周期內(nèi)始終有效。它們不需要在每次數(shù)據(jù)改變時(shí)被重建,因?yàn)閿?shù)據(jù)不會(huì)變化。
④ 寫入單個(gè)大的倒排索引允許數(shù)據(jù)被壓縮,減少磁盤 I/O 和 需要被緩存到內(nèi)存的索引的使用量。
當(dāng)然,一個(gè)不變的索引也有不好的地方。主要事實(shí)是它是不可變的! 你不能修改它。如果你需要讓一個(gè)新的文檔可被搜索,你需要重建整個(gè)索引。這要么對(duì)一個(gè)索引所能包含的數(shù)據(jù)量造成了很大的限制,要么對(duì)索引可被更新的頻率造成了很大的限制。
03. ElasticSearch 索引文檔原理?
給定一個(gè)文檔集合(第一次索引文檔時(shí)),此時(shí)如何建立一個(gè)索引?首先在內(nèi)存里維護(hù)一個(gè)倒排索引,當(dāng)內(nèi)存占滿后,將內(nèi)存數(shù)據(jù)寫入磁盤的臨時(shí)文件,第二階段對(duì)臨時(shí)文件進(jìn)行合并形成最終索引。
① 從磁盤讀取文檔,對(duì)文檔內(nèi)容進(jìn)行解析,并在內(nèi)存中建立一個(gè)倒排索引,相當(dāng)于對(duì)目前處理的文檔子集單獨(dú)在內(nèi)存中建立起了一整套倒排索引,和最終索引相比,其結(jié)構(gòu)和形式是相同的,區(qū)別是這個(gè)索引只是部分文檔的索引而非全部文檔的索引。
② 當(dāng)內(nèi)存占滿后,為了騰出內(nèi)存空間,將整個(gè)內(nèi)存中建立的倒排索引寫入磁盤臨時(shí)文件中,然后徹底清除所占內(nèi)存,這樣就空出內(nèi)存來(lái)進(jìn)行后續(xù)文檔的處理。
③ 每一輪處理都會(huì)在磁盤產(chǎn)生一個(gè)對(duì)應(yīng)的臨時(shí)文件,當(dāng)所有文檔處理完成后,在磁盤中會(huì)有多個(gè)臨時(shí)文件,為了產(chǎn)生最終的索引,需要將這些臨時(shí)文件合并形成最終索引。
04. ElasticSearch 如何動(dòng)態(tài)更新索引?
倒排索引一旦被寫入磁盤就是不可更改的(沒有寫入磁盤前是可以更改的噢),如果搜索引擎需要處理的文檔集合是靜態(tài)集合,那么在索引建立好之后,就可以一直用建好的索引響應(yīng)用戶查詢請(qǐng)求。但是,在真實(shí)環(huán)境中,搜索引擎需要處理的文檔集合往往是動(dòng)態(tài)集合,即在建好初始的索引后,后續(xù)不斷有新文檔進(jìn)入系統(tǒng),同時(shí)原先的文檔集合內(nèi)有些文檔可能被刪除或者內(nèi)容被更改。問題是怎樣在保留不變性的前提下實(shí)現(xiàn)倒排索引的更新呢?答案是: 用更多的索引,即增加臨時(shí)索引。在動(dòng)態(tài)更新索引中,有3個(gè)關(guān)鍵的索引結(jié)構(gòu):倒排索引、臨時(shí)索引和已刪除文檔列表。
① 倒排索引就是對(duì)初始文檔集合建立好的索引結(jié)構(gòu),該索引存在磁盤中,不可改變。
② 臨時(shí)索引是在內(nèi)存中實(shí)時(shí)建立的倒排索引,該索引存儲(chǔ)在內(nèi)存中,當(dāng)新增文檔進(jìn)入系統(tǒng),解析文檔,之后更新內(nèi)存中維護(hù)的臨時(shí)索引,文檔中出現(xiàn)的每個(gè)單詞,在其倒排列表末尾追加倒排列表項(xiàng),隨著新加入系統(tǒng)的文檔越來(lái)越多,臨時(shí)索引消耗的內(nèi)存也會(huì)隨之增加,一旦臨時(shí)索引將指定的內(nèi)存消耗光,要考慮將臨時(shí)索引的內(nèi)容更新到磁盤索引中,以釋放內(nèi)存空間來(lái)容納后續(xù)的新進(jìn)文檔。
③ 已刪除文檔列表則用來(lái)存儲(chǔ)已被刪除的文檔的相應(yīng)文檔ID,形成一個(gè)文檔ID列表。這里需要注意的是:當(dāng)一篇文檔內(nèi)容被更改,可以認(rèn)為是舊文檔先被刪除,之后向系統(tǒng)內(nèi)增加一篇新的文檔,通過(guò)這種間接方式實(shí)現(xiàn)對(duì)內(nèi)容更改的支持。
當(dāng)系統(tǒng)發(fā)現(xiàn)有新文檔進(jìn)入時(shí),立即將其加入臨時(shí)索引中。有文檔被刪除時(shí),則將其加入刪除文檔隊(duì)列。文檔被更改時(shí),則將原先文檔放入刪除隊(duì)列,解析更改后的文檔內(nèi)容,并將其加入臨時(shí)索引中。通過(guò)這種方式可以滿足實(shí)時(shí)性的要求。
如果用戶輸入查詢請(qǐng)求,則搜索引擎同時(shí)從倒排索引和臨時(shí)索引中讀取用戶查詢單詞的倒排列表,找到包含用戶查詢的文檔集合,并對(duì)兩個(gè)結(jié)果進(jìn)行合并,之后利用刪除文檔列表進(jìn)行過(guò)濾,將搜索結(jié)果中那些已經(jīng)被刪除的文檔從結(jié)果中過(guò)濾,形成最終的搜索結(jié)果,并返回給用戶。這樣就能夠?qū)崿F(xiàn)動(dòng)態(tài)環(huán)境下的準(zhǔn)實(shí)時(shí)搜索功能。
注意:在內(nèi)存中的臨時(shí)索引是不斷變化的,但是這個(gè)臨時(shí)索引一旦寫進(jìn)磁盤,就是不可變的。
05. ElasticSearch 文檔的新增、刪除、更新?
在早期全文檢索中為整個(gè)文檔集合建立了一個(gè)很大的倒排索引,并將其寫入磁盤中,如果需要讓一個(gè)新的文檔可被搜索,你需要重建整個(gè)索引。這種方式在數(shù)據(jù)量很大時(shí)效率很低,并且由于創(chuàng)建一次索引的成本很高,所以對(duì)數(shù)據(jù)的更新不能過(guò)于頻繁,也就不能保證時(shí)效性,因此提出了段的概念,即將一個(gè)索引文件拆分為多個(gè)文件,每個(gè)子文件叫做段,每個(gè)段都是一個(gè)被寫入磁盤的倒排索引,因此段具有不變性。
Elasticsearch 基于 Lucene, 這個(gè)java庫(kù)引入了按段搜索的概念。 每一段本身都是一個(gè)倒排索引, 但索引在 Lucene 中除表示所有段的集合外, 還增加了提交點(diǎn)的概念,提交點(diǎn)是一個(gè)列出了所有已知段的文件。
① 當(dāng)新增一個(gè)文檔時(shí),這個(gè)文檔會(huì)被添加到內(nèi)存索引緩存中,隨后解析文檔,更新內(nèi)存中維護(hù)的臨時(shí)索引,文檔中出現(xiàn)的每個(gè)單詞,在其倒排列表末尾追加倒排列表項(xiàng)。
② 隨著內(nèi)存索引緩存中的的文檔越來(lái)越多,臨時(shí)索引消耗的內(nèi)存也會(huì)隨之增加,一旦臨時(shí)索引將指定的內(nèi)存消耗光,要考慮將臨時(shí)索引的內(nèi)容更新到磁盤索引中,以釋放內(nèi)存空間來(lái)容納后續(xù)的新進(jìn)文檔。
當(dāng)緩存被提交時(shí):
(1) 一個(gè)新的段被寫入磁盤。(這個(gè)段就是緩存中維護(hù)的臨時(shí)倒排索引)
(2) 一個(gè)新的包含新段名字的提交點(diǎn)被寫入磁盤。
(3) 磁盤進(jìn)行同步。(所有在文件系統(tǒng)緩存中等待的寫入都刷新到磁盤,以確保它們被寫入物理文件)
(4) 新的段被開啟,讓它包含的文檔可見以被搜索。
(5) 內(nèi)存緩存被清空,等待接收新的文檔。
每一個(gè)倒排索引都會(huì)被輪流查詢到,詢完后再對(duì)結(jié)果進(jìn)行合并。當(dāng)一個(gè)查詢被觸發(fā),所有已知的段按順序被查詢。詞項(xiàng)統(tǒng)計(jì)會(huì)對(duì)所有段的結(jié)果進(jìn)行聚合,以保證每個(gè)詞和每個(gè)文檔的關(guān)聯(lián)都被準(zhǔn)確計(jì)算。 這種方式可以用相對(duì)較低的成本將新文檔添加到索引。
刪除和更新文檔:
段是不可改變的,所以既不能從把文檔從舊的段中移除,也不能修改舊的段來(lái)進(jìn)行反映文檔的更新。 取而代之的是,每個(gè)提交點(diǎn)會(huì)包含一個(gè) .del 文件,文件中會(huì)列出這些被刪除文檔的段信息。
當(dāng)一個(gè)文檔被 “刪除” 時(shí),它實(shí)際上只是在 .del 文件中被 標(biāo)記 刪除。一個(gè)被標(biāo)記刪除的文檔仍然可以被查詢匹配到, 但它會(huì)在最終結(jié)果被返回前從結(jié)果集中移除。
文檔更新也是類似的操作方式:當(dāng)一個(gè)文檔被更新時(shí),舊版本文檔被標(biāo)記刪除,文檔的新版本被索引到一個(gè)新的段中。 可能兩個(gè)版本的文檔都會(huì)被一個(gè)查詢匹配到,但被刪除的那個(gè)舊版本文檔在結(jié)果集返回前就已經(jīng)被移除。
06. ElasticSearch 搜索為什么是近實(shí)時(shí)的?
新增的文檔被收集到內(nèi)存緩沖區(qū),隨后解析這個(gè)文檔追加到臨時(shí)索引的單詞詞典和倒排表中,隨著加入的文檔越來(lái)越多,最初分配的內(nèi)存緩沖區(qū)被用完,就會(huì)將內(nèi)存緩沖區(qū)的內(nèi)容寫入磁盤的段中,此時(shí)文檔便可被檢索了,因此一個(gè)新的文檔從索引到可被搜索的時(shí)間取決于該文檔多久能從內(nèi)存中寫入到磁盤中,當(dāng)文檔被寫入磁盤就可被檢索了。
隨著按段搜索的發(fā)展,一個(gè)新的文檔從索引到可被搜索的時(shí)間延遲顯著降低了,新文檔在幾分鐘之內(nèi)即可被檢索,但這樣還是不夠快。那怎樣才能更快呢?可不可以把內(nèi)存緩沖區(qū)大小設(shè)置的小一點(diǎn),這樣一個(gè)新的文檔從索引到寫入磁盤的時(shí)間延遲就降低了,新增的文檔就能更快的被搜索到了?
原理是沒錯(cuò)的,但是不可行!原因是磁盤在這里成為了瓶頸,當(dāng)你把內(nèi)存緩沖區(qū)大小設(shè)置的更小時(shí),磁盤IO的次數(shù)就會(huì)增加,我們知道提交一個(gè)新的段到磁盤需要一個(gè) fsync 來(lái)確保段被物理性地寫入磁盤,這樣在斷電的時(shí)候就不會(huì)丟失數(shù)據(jù)。但是 fsync 操作代價(jià)很大,如果每次索引一個(gè)文檔都去執(zhí)行一次 fsync的話會(huì)造成很大的性能問題,因此我們不能頻繁的 fsync ,即無(wú)法通過(guò)提高磁盤IO次數(shù)的方式來(lái)讓新增的文檔更快的被用戶搜索到。
那到底怎樣才能更快呢?其實(shí)我們需要的是一個(gè)更輕量的方式來(lái)使一個(gè)文檔可被搜索,不能讓內(nèi)存中維護(hù)的倒排索引直接寫入磁盤中,這意味著 fsync
要從整個(gè)過(guò)程中被移除。可是文檔不寫入磁盤,又怎么被檢索到呢?
在Elasticsearch和磁盤之間是文件系統(tǒng)緩存,許多人沒有意識(shí)到文件系統(tǒng)緩存對(duì)于性能的影響,其實(shí)Linux系統(tǒng)默認(rèn)的設(shè)置傾向于把內(nèi)存盡可能的用于文緩存,所以在一臺(tái)大內(nèi)存機(jī)器上,往往我們可能發(fā)現(xiàn)沒有多少剩余內(nèi)存。文件系統(tǒng)緩存可以加速磁盤操作,使系統(tǒng)有更好的IO性能,代價(jià)只是把一些空閑的內(nèi)存利用起來(lái)了。
在內(nèi)存緩沖區(qū)中的文檔會(huì)被寫入一個(gè)新的段中,但是這個(gè)新段會(huì)被先寫入到內(nèi)存的文件系統(tǒng)緩存中,這一步相對(duì)于 fsync
更輕量,代價(jià)更小,當(dāng)段被提交到文件系統(tǒng)緩存中后,新增的文檔便可被檢索了。
Lucene
允許新段被寫入和打開,使其包含的文檔在未進(jìn)行一次完整提交時(shí)便對(duì)搜索可見(完整提交是指將新段寫入磁盤), 這種方式比進(jìn)行一次提交代價(jià) fsync
要小得多,并且在不影響性能的前提下可以被頻繁地執(zhí)行。
07. ElasticSearch 在什么情況下使用 refresh api ?
在Elasticsearch中,寫入和打開一個(gè)新段的輕量的過(guò)程叫做refresh,即refresh是指將內(nèi)存緩沖區(qū)的內(nèi)容寫入到文件系統(tǒng)緩存的一個(gè)段中,使其包含的文檔便可以被搜索,默認(rèn)情況下每個(gè)分片會(huì)每秒自動(dòng)刷新(refresh)一次。這就是為什么我們說(shuō)Elasticsearch是近實(shí)時(shí)搜索: 文檔的變化并不是立即對(duì)搜索可見,但會(huì)在一秒之內(nèi)變?yōu)榭梢姟?/p>
這些行為可能會(huì)對(duì)新用戶造成困惑,索引了一個(gè)文檔然后嘗試搜索它,但卻沒有搜到。這個(gè)問題的解決辦法是用 refresh
API 執(zhí)行一次手動(dòng)刷新:
// 刷新(Refresh)所有的索引
POST /_refresh
// 只刷新(Refresh) blogs 索引
POST /blogs/_refresh
盡管刷新是比提交輕量很多的操作,它還是會(huì)有性能開銷。當(dāng)寫測(cè)試的時(shí)候, 手動(dòng)刷新很有用,但是不要在生產(chǎn)環(huán)境下每次索引一個(gè)文檔都去手動(dòng)刷新。 相反,你的應(yīng)用需要意識(shí)到 Elasticsearch 的近實(shí)時(shí)的性質(zhì),并接受它的不足。
這個(gè)問題的解決辦法是執(zhí)行一次手動(dòng)刷新:并不是所有的情況都需要每秒刷新。可能你正在使用 Elasticsearch 索引大量的日志文件, 你可能想優(yōu)化索引速度而不是近實(shí)時(shí)搜索, 可以通過(guò)設(shè)置 refresh_interval
, 降低每個(gè)索引的刷新頻率:
//每30秒刷新 my_logs 索引。
PUT /my_logs
{
"settings": {
"refresh_interval": "30s"
}
}
refresh_interval
可以在既存索引上進(jìn)行動(dòng)態(tài)更新。 在生產(chǎn)環(huán)境中,當(dāng)你正在建立一個(gè)大的新索引時(shí),可以先關(guān)閉自動(dòng)刷新,待開始使用該索引時(shí),再把它們調(diào)回來(lái):
//關(guān)閉自動(dòng)刷新
PUT /my_logs/_settings
{ "refresh_interval": -1 }
//每秒自動(dòng)刷新
PUT /my_logs/_settings
{ "refresh_interval": "1s" }
refresh_interval
需要一個(gè) 持續(xù)時(shí)間 值, 例如 1s
(1 秒) 或 2m
(2 分鐘)。 一個(gè)絕對(duì)值 1 表示的是 1毫秒 ,這無(wú)疑會(huì)使你的集群陷入癱瘓。
08. Elasticsearch 是怎樣保證更新被持久化在斷電時(shí)也不丟失數(shù)據(jù)?
為了動(dòng)態(tài)更新索引,我們?cè)黾恿硕蔚母拍睿瑢⑿略龅奈臋n收集到內(nèi)存緩沖區(qū)并維護(hù)一個(gè)倒排索引,待最初分配的內(nèi)存緩沖區(qū)空間用完時(shí),將內(nèi)存緩沖區(qū)的內(nèi)容寫入文件系統(tǒng)緩存的段中(refresh)
,這一步相對(duì) fsync
會(huì)更加輕量,代價(jià)更小。
但是如果沒有用 fsync
把數(shù)據(jù)從文件系統(tǒng)緩存刷(flush)
到硬盤,我們不能保證數(shù)據(jù)在斷電甚至是程序正常退出之后依然存在。為了保證Elasticsearch
的可靠性,需要確保數(shù)據(jù)變化被持久化到磁盤。
如果將段從內(nèi)存緩存刷新(refresh)到文件系統(tǒng)緩存而未刷新到磁盤中,那么只是讓段包含的文檔可被檢索了,但是這個(gè)段并未提交。在動(dòng)態(tài)更新索引,我們說(shuō)一次完整的提交會(huì)將段刷到磁盤,并寫入一個(gè)包含所有段列表的提交點(diǎn)。Elasticsearch 在啟動(dòng)或重新打開一個(gè)索引的過(guò)程中使用這個(gè)提交點(diǎn)來(lái)判斷哪些段隸屬于當(dāng)前分片。一個(gè)分片就是一個(gè)Lucene索引,一個(gè)Lucene索引包含多個(gè)段和一個(gè)包含所有段列表的提交點(diǎn)。
即使通過(guò)每秒刷新(refresh)實(shí)現(xiàn)了近實(shí)時(shí)搜索,我們?nèi)匀恍枰?jīng)常進(jìn)行完整提交來(lái)確保能從失敗中恢復(fù)。但在兩次提交之間發(fā)生變化的文檔怎么辦?如果在下一次完整提交之前,機(jī)器斷電,那么存在文件系統(tǒng)緩存中的數(shù)據(jù)就沒了,我們也不希望丟失掉這些數(shù)據(jù)。
Elasticsearch 增加了一個(gè)translog,或者叫事務(wù)日志,在每一次對(duì) Elasticsearch進(jìn)行操作時(shí)均進(jìn)行了日志記錄。通過(guò)translog ,整個(gè)流程看起來(lái)是下面這樣:
① 一個(gè)文檔被索引之后,就會(huì)被添加到內(nèi)存緩沖區(qū),并在內(nèi)存中寫入translog
日志,后寫入日志的原因是數(shù)據(jù)在建立索引時(shí),需要經(jīng)過(guò)分詞等一系列復(fù)雜的操作,有可能寫入失敗,為了保證日志中都是成功的數(shù)據(jù),所以后寫入:
② 分片每秒被刷新(refresh)
一次,刷新完成后, 緩存被清空但是事務(wù)日志不會(huì):
(1) 這些在內(nèi)存緩沖區(qū)的文檔被寫入到一個(gè)新的段中,且沒有進(jìn)行
fsync
操作。
(2) 這個(gè)段被打開,使其可被搜索。
(3) 內(nèi)存緩沖區(qū)被清空。
③ 這個(gè)進(jìn)程繼續(xù)工作,更多的文檔被添加到內(nèi)存緩沖區(qū)和追加到事務(wù)日志,事務(wù)日志不斷積累文檔 :
④ 在刷新(flush)之后,段被全量提交,并且事務(wù)日志被清空:
(1) 所有在內(nèi)存緩沖區(qū)的文檔都被寫入一個(gè)新的段
(2) 緩沖區(qū)被清空
(3) 一個(gè)提交點(diǎn)被寫入硬盤
(4) 文件系統(tǒng)緩存通過(guò) fsync 被刷新(flush)
(5) 內(nèi)存中的translog寫入磁盤并清空老的translog日志文件
translog 提供所有還沒有被刷到磁盤的操作的一個(gè)持久化紀(jì)錄。當(dāng) Elasticsearch 啟動(dòng)的時(shí)候, 它會(huì)從磁盤中使用最后一個(gè)提交點(diǎn)去恢復(fù)已知的段,并且會(huì)重放 translog 中所有在最后一次提交后發(fā)生的變更操作。
translog 也被用來(lái)提供實(shí)時(shí) CRUD 。當(dāng)你試著通過(guò)ID查詢、更新、刪除一個(gè)文檔,它會(huì)在嘗試從相應(yīng)的段中檢索之前, 首先檢查 translog 任何最近的變更。這意味著它總是能夠?qū)崟r(shí)地獲取到文檔的最新版本。
09. ElasticSearch 在什么情況下使用 flush api?
這個(gè)執(zhí)行一個(gè)提交并且截?cái)?translog 的行為在 ES被稱作一次flush , 分片每30分鐘被自動(dòng)刷新flush,或者在 translog 太大的時(shí)候也會(huì)刷新。
flush
API 可以被用來(lái)執(zhí)行一個(gè)手工的刷新(flush):
// 刷新(flush) blogs索引。
POST /blogs/_flush
// 刷新(flush)所有的索引并且并且等待所有刷新在返回前完成。
POST /_flush?wait_for_ongoing
你很少需要自己手動(dòng)執(zhí)行 flush
操作;通常情況下,自動(dòng)刷新就足夠了。這就是說(shuō),在重啟節(jié)點(diǎn)或關(guān)閉索引之前執(zhí)行 flush有益于你的索引。當(dāng) ES 嘗試恢復(fù)或重新打開一個(gè)索引, 它需要重放 translog 中所有的操作,所以如果日志越短,恢復(fù)越快。
10. ElasticSearch 為什么需要段合并?
當(dāng)我們往 ElasticSearch新增文檔時(shí),文檔先加入內(nèi)存緩沖區(qū)并更新倒排索引,然后每隔1秒將內(nèi)存緩沖區(qū)的內(nèi)容寫入一個(gè)可被檢索的新段,這個(gè)過(guò)程叫refresh。由于自動(dòng)刷新流程每秒會(huì)創(chuàng)建一個(gè)新的段 ,這樣會(huì)導(dǎo)致短時(shí)間內(nèi)的段數(shù)量暴增。而段數(shù)目太多會(huì)帶來(lái)較大的麻煩。 每一個(gè)段都會(huì)消耗文件句柄、內(nèi)存和cpu運(yùn)行周期。更重要的是,每個(gè)搜索請(qǐng)求都必須輪流檢查每個(gè)段,所以段越多,搜索也就越慢。
Elasticsearch通過(guò)在后臺(tái)進(jìn)行段合并來(lái)解決這個(gè)問題。小的段被合并到大的段,然后這些大的段再被合并到更大的段。段合并的時(shí)候會(huì)將那些舊的已刪除文檔從文件系統(tǒng)中清除。被刪除的文檔(或被更新文檔的舊版本)不會(huì)被拷貝到新的大段中。啟動(dòng)段合并不需要你做任何事。進(jìn)行索引和搜索時(shí)會(huì)自動(dòng)進(jìn)行。
① 兩個(gè)提交了的段和一個(gè)未提交的段正在被合并到一個(gè)更大的段:
(1) 當(dāng)索引的時(shí)候,刷新(refresh)操作會(huì)創(chuàng)建新的段并將段打開以供搜索使用。
(2) 合并進(jìn)程選擇一小部分大小相似的段,并且在后臺(tái)將它們合并到更大的段中。這并不會(huì)中斷索引和搜索。
② 合并完成時(shí)的活動(dòng):
(1) 新的段被刷新(flush)到了磁盤。寫入一個(gè)包含新段且排除舊的和較小的段的新提交點(diǎn)。
(2) 新的段被打開用來(lái)搜索。
(3) 老的段被刪除。
合并大的段需要消耗大量的I/O和CPU資源,如果任其發(fā)展會(huì)影響搜索性能。ES
在默認(rèn)情況下會(huì)對(duì)合并流程進(jìn)行資源限制,所以搜索仍然有足夠的資源很好地執(zhí)行。
11. ElasticSearch 在什么情況下使用 optimize api?
optimize
API大可看做是強(qiáng)制合并 API。它會(huì)將一個(gè)分片強(qiáng)制合并到 max_num_segments
參數(shù)指定大小的段數(shù)目。 這樣做的意圖是減少段的數(shù)量(通常減少到一個(gè)),來(lái)提升搜索性能。
在特定情況下,使用 optimize
API 頗有益處。例如在日志這種用例下,每天、每周、每月的日志被存儲(chǔ)在一個(gè)索引中。 老的索引實(shí)質(zhì)上是只讀的;它們也并不太可能會(huì)發(fā)生變化。在這種情況下,使用optimize優(yōu)化老的索引,將每一個(gè)分片合并為一個(gè)單獨(dú)的段就很有用了;這樣既可以節(jié)省資源,也可以使搜索更加快速:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-752454.html
POST /logstash-2014-10/_optimize?max_num_segments=1
使用 optimize
API 觸發(fā)段合并的操作不會(huì)受到任何資源上的限制。這可能會(huì)消耗掉你節(jié)點(diǎn)上全部的I/O資源, 使其沒有余裕來(lái)處理搜索請(qǐng)求,從而有可能使集群失去響應(yīng)。 如果你想要對(duì)索引執(zhí)行 optimize
,你需要先使用分片分配把索引移到一個(gè)安全的節(jié)點(diǎn),再執(zhí)行。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-752454.html
-
refresh
,flush
, 和optimize
API 都做了什么, 你什么情況下應(yīng)該使用他們?
12. 分片內(nèi)部原理
1. 索引的不可變性
倒排索引被寫入磁盤后是不可改變的,它永遠(yuǎn)不會(huì)修改。不變性有重要的價(jià)值:
① 不需要鎖。如果你從來(lái)不更新索引,你就不需要擔(dān)心多進(jìn)程同時(shí)修改數(shù)據(jù)的問題。
② 一旦索引被讀入內(nèi)核的文件系統(tǒng)緩存,便會(huì)留在哪里,由于其不變性。只要文件系統(tǒng)緩存中還有足夠的空間,那么大部分讀請(qǐng)求會(huì)直接請(qǐng)求內(nèi)存,而不會(huì)命中磁盤。這提供了很大的性能提升。
③ 其它緩存(像filter緩存),在索引的生命周期內(nèi)始終有效。它們不需要在每次數(shù)據(jù)改變時(shí)被重建,因?yàn)閿?shù)據(jù)不會(huì)變化。
④ 寫入單個(gè)大的倒排索引允許數(shù)據(jù)被壓縮,減少磁盤 I/O 和 需要被緩存到內(nèi)存的索引的使用量。
當(dāng)然,一個(gè)不變的索引也有不好的地方。主要事實(shí)是它是不可變的! 你不能修改它。如果你需要讓一個(gè)新的文檔 可被搜索,你需要重建整個(gè)索引。這要么對(duì)一個(gè)索引所能包含的數(shù)據(jù)量造成了很大的限制,要么對(duì)索引可被更新的頻率造成了很大的限制。
2. 動(dòng)態(tài)更新索引
1、索引文檔
下一個(gè)需要被解決的問題是怎樣在保留不變性的前提下實(shí)現(xiàn)倒排索引的更新?答案是: 用更多的索引。
通過(guò)增加新的補(bǔ)充索引來(lái)反映新近的修改,而不是直接重寫整個(gè)倒排索引。每一個(gè)倒排索引都會(huì)被輪流查詢到,查詢完后再對(duì)結(jié)果進(jìn)行合并。Elasticsearch 基于 Lucene, 這個(gè) java 庫(kù)引入了按段搜索的概念, 每一段本身都是一個(gè)倒排索引。 但索引在 Lucene 中除表示所有段的集合外, 還增加了提交點(diǎn)( 一個(gè)列出了所有已知段的文件)的概念 。如圖:一個(gè) Lucene 索引包含一個(gè)提交點(diǎn)和三個(gè)段:
逐段搜索會(huì)以如下流程進(jìn)行工作:
① 新文檔被收集到內(nèi)存索引緩存, 如圖:一個(gè)在內(nèi)存緩存中包含新文檔的 Lucene 索引,新的文檔首先被添加到內(nèi)存索引緩存中,然后寫入到一個(gè)基于磁盤的段。
② 不時(shí)地,緩存被提交:
- 一個(gè)新的段(一個(gè)追加的倒排索引)被寫入磁盤。
- 一個(gè)新的包含新段名字的提交點(diǎn)被寫入磁盤。
- 磁盤進(jìn)行同步:所有在文件系統(tǒng)緩存中等待的寫入都刷新到磁盤,以確保它們被寫入物理文件。
③ 新的段被開啟,讓它包含的文檔可見以被搜索。
④ 內(nèi)存緩存被清空,等待接收新的文檔。如圖:在一次提交后,一個(gè)新的段被添加到提交點(diǎn)而且緩存被清空
當(dāng)一個(gè)查詢被觸發(fā),所有已知的段按順序被查詢。詞項(xiàng)統(tǒng)計(jì)會(huì)對(duì)所有段的結(jié)果進(jìn)行聚合,以保證每個(gè)詞和每個(gè)文檔的關(guān)聯(lián)都被準(zhǔn)確計(jì)算。 這種方式可以用相對(duì)較低的成本將新文檔添加到索引。
2、刪除和更新文檔
段是不可改變的,所以既不能從把文檔從舊的段中移除,也不能修改舊的段來(lái)進(jìn)行反映文檔的更新。 取而代之的是,每個(gè)提交點(diǎn)會(huì)包含一個(gè) .del
文件,文件中會(huì)列出這些被刪除文檔的段信息。
當(dāng)一個(gè)文檔被 “刪除” 時(shí),它實(shí)際上只是在 .del
文件中被標(biāo)記刪除。一個(gè)被標(biāo)記刪除的文檔仍然可以被查詢匹配到, 但它會(huì)在最終結(jié)果被返回前從結(jié)果集中移除。
文檔更新也是類似的操作方式:當(dāng)一個(gè)文檔被更新時(shí),舊版本文檔被標(biāo)記刪除,文檔的新版本被索引到一個(gè)新的段中。 可能兩個(gè)版本的文檔都會(huì)被一個(gè)查詢匹配到,但被刪除的那個(gè)舊版本文檔在結(jié)果集返回前就已經(jīng)被移除。
3. 近實(shí)時(shí)搜索
隨著按段搜索的發(fā)展,一個(gè)新的文檔從索引到可被搜索的延遲顯著降低了。新文檔在幾分鐘之內(nèi)即可被檢索,但這樣還是不夠快。磁盤在這里成為了瓶頸。提交一個(gè)新的段到磁盤需要一個(gè) fsync
來(lái)確保段被物理性地寫入磁盤,這樣在斷電的時(shí)候就不會(huì)丟失數(shù)據(jù)。 但是 fsync
操作代價(jià)很大; 如果每次索引一個(gè)文檔都去執(zhí)行一次的話會(huì)造成很大的性能問題。我們需要的是一個(gè)更輕量的方式來(lái)使一個(gè)文檔可被搜索,這意味著 fsync
要從整個(gè)過(guò)程中被移除。
在Elasticsearch和磁盤之間是文件系統(tǒng)緩存。 像之前描述的一樣, 在內(nèi)存索引緩沖區(qū)中的文檔會(huì)被寫入到一個(gè)新的段中。 但是這里新段會(huì)被先寫入到文件系統(tǒng)緩存(這一步代價(jià)會(huì)比較低),稍后再被刷新到磁盤(這一步代價(jià)比較高)。不過(guò)只要文件已經(jīng)在緩存中, 就可以像其它文件一樣被打開和讀取了。
在內(nèi)存緩沖區(qū)中包含了新文檔的 Lucene 索引:
Lucene 允許新段被寫入和打開,使其包含的文檔在未進(jìn)行一次完整提交時(shí)便對(duì)搜索可見。 這種方式比進(jìn)行一次提交代價(jià)要小得多,并且在不影響性能的前提下可以被頻繁地執(zhí)行。
緩沖區(qū)的內(nèi)容已經(jīng)被寫入一個(gè)可被搜索的段中,但還沒有進(jìn)行提交:
在 Elasticsearch 中,寫入和打開一個(gè)新段的輕量的過(guò)程叫做 refresh 。 默認(rèn)情況下每個(gè)分片會(huì)每秒自動(dòng)刷新一次。這就是為什么我們說(shuō) Elasticsearch 是 近 實(shí)時(shí)搜索: 文檔的變化并不是立即對(duì)搜索可見,但會(huì)在一秒之內(nèi)變?yōu)榭梢?。這些行為可能會(huì)對(duì)新用戶造成困惑: 他們索引了一個(gè)文檔然后嘗試搜索它,但卻沒有搜到。這個(gè)問題的解決辦法是用 refresh
API 執(zhí)行一次手動(dòng)刷新:
POST /_refresh # 刷新(Refresh)所有的索引。
POST /blogs/_refresh # 只刷新(Refresh) blogs 索引。
盡管刷新是比提交輕量很多的操作,它還是會(huì)有性能開銷。當(dāng)寫測(cè)試的時(shí)候, 手動(dòng)刷新很有用,但是不要在生產(chǎn)環(huán)境下每次索引一個(gè)文檔都去手動(dòng)刷新。 相反,你的應(yīng)用需要意識(shí)到 Elasticsearch 的近實(shí)時(shí)的性質(zhì),并接受它的不足。
并不是所有的情況都需要每秒刷新??赡苣阏谑褂?Elasticsearch 索引大量的日志文件, 你可能想優(yōu)化索引速度而不是近實(shí)時(shí)搜索, 可以通過(guò)設(shè)置 refresh_interval
, 降低每個(gè)索引的刷新頻率:
# 每30秒刷新 my_logs 索引。
PUT /my_logs
{
"settings": {
"refresh_interval": "30s"
}
}
refresh_interval
可以在既存索引上進(jìn)行動(dòng)態(tài)更新。 在生產(chǎn)環(huán)境中,當(dāng)你正在建立一個(gè)大的新索引時(shí),可以先關(guān)閉自動(dòng)刷新,待開始使用該索引時(shí),再把它們調(diào)回來(lái):
# 關(guān)閉自動(dòng)刷新
PUT /my_logs/_settings
{ "refresh_interval": -1 }
# 每秒自動(dòng)刷新
PUT /my_logs/_settings
{ "refresh_interval": "1s" }
refresh_interval
需要一個(gè) 持續(xù)時(shí)間 值, 例如 1s
(1 秒) 或 2m
(2 分鐘)。 一個(gè)絕對(duì)值 1 表示的是 1毫秒 ,這無(wú)疑會(huì)使你的集群陷入癱瘓。
4. 持久化變更
如果沒有用 fsync
把數(shù)據(jù)從文件系統(tǒng)緩存刷(flush)到硬盤,我們不能保證數(shù)據(jù)在斷電甚至是程序正常退出之后依然存在。為了保證 Elasticsearch 的可靠性,需要確保數(shù)據(jù)變化被持久化到磁盤。在動(dòng)態(tài)更新索引,我們說(shuō)一次完整的提交會(huì)將段刷到磁盤,并寫入一個(gè)包含所有段列表的提交點(diǎn)。Elasticsearch 在啟動(dòng)或重新打開一個(gè)索引的過(guò)程中使用這個(gè)提交點(diǎn)來(lái)判斷哪些段隸屬于當(dāng)前分片。
即使通過(guò)每秒刷新(refresh)實(shí)現(xiàn)了近實(shí)時(shí)搜索,我們?nèi)匀恍枰?jīng)常進(jìn)行完整提交來(lái)確保能從失敗中恢復(fù)。但在兩次提交之間發(fā)生變化的文檔怎么辦?我們也不希望丟失掉這些數(shù)據(jù)。Elasticsearch 增加了一個(gè) translog ,或者叫事務(wù)日志,在每一次對(duì) Elasticsearch 進(jìn)行操作時(shí)均進(jìn)行了日志記錄。通過(guò) translog ,整個(gè)流程看起來(lái)是下面這樣:
① 一個(gè)文檔被索引之后,就會(huì)被添加到內(nèi)存緩沖區(qū),并且追加到了 translog ,如圖:新的文檔被添加到內(nèi)存緩沖區(qū)并且被追加到了事務(wù)日志
② 刷新(refresh)完成后, 緩存被清空但是事務(wù)日志不會(huì),分片每秒被刷新(refresh)一次:
- 這些在內(nèi)存緩沖區(qū)的文檔被寫入到一個(gè)新的段中,且沒有進(jìn)行
fsync
操作。 - 這個(gè)段被打開,使其可被搜索。
- 內(nèi)存緩沖區(qū)被清空。
③ 這個(gè)進(jìn)程繼續(xù)工作,更多的文檔被添加到內(nèi)存緩沖區(qū)和追加到事務(wù)日志,如圖:事務(wù)日志不斷積累文檔
④ 每隔一段時(shí)間(translog 變得越來(lái)越大)索引被刷新(flush);一個(gè)新的 translog 被創(chuàng)建,并且一個(gè)全量提交被執(zhí)行,如圖:在刷新(flush)之后,段被全量提交,并且事務(wù)日志被清空:
- 所有在內(nèi)存緩沖區(qū)的文檔都被寫入一個(gè)新的段。
- 緩沖區(qū)被清空。
- 一個(gè)提交點(diǎn)被寫入硬盤。
- 文件系統(tǒng)緩存通過(guò)
fsync
被刷新(flush)。 - 老的 translog 被刪除。
translog 提供所有還沒有被刷到磁盤的操作的一個(gè)持久化紀(jì)錄。當(dāng) Elasticsearch 啟動(dòng)的時(shí)候, 它會(huì)從磁盤中使用最后一個(gè)提交點(diǎn)去恢復(fù)已知的段,并且會(huì)重放 translog 中所有在最后一次提交后發(fā)生的變更操作。
translog 也被用來(lái)提供實(shí)時(shí) CRUD 。當(dāng)你試著通過(guò)ID查詢、更新、刪除一個(gè)文檔,它會(huì)在嘗試從相應(yīng)的段中檢索之前, 首先檢查 translog 任何最近的變更。這意味著它總是能夠?qū)崟r(shí)地獲取到文檔的最新版本。
這個(gè)執(zhí)行一個(gè)提交并且截?cái)?translog 的行為在 Elasticsearch 被稱作一次 flush 。 分片每30分鐘被自動(dòng)刷新(flush),或者在 translog 太大的時(shí)候也會(huì)刷新。
flush
API 可以被用來(lái)執(zhí)行一個(gè)手工的刷新(flush):
# 刷新(flush) blogs索引。
POST /blogs/_flush
# 刷新(flush)所有的索引并且并且等待所有刷新在返回前完成。
POST /_flush?wait_for_ongoing
你很少需要自己手動(dòng)執(zhí)行 flush
操作;通常情況下,自動(dòng)刷新就足夠了。這就是說(shuō),在重啟節(jié)點(diǎn)或關(guān)閉索引之前執(zhí)行 flush 有益于你的索引。當(dāng) Elasticsearch 嘗試恢復(fù)或重新打開一個(gè)索引, 它需要重放 translog 中所有的操作,所以如果日志越短,恢復(fù)越快。
5. 段合并
由于自動(dòng)刷新流程每秒會(huì)創(chuàng)建一個(gè)新的段 ,這樣會(huì)導(dǎo)致短時(shí)間內(nèi)的段數(shù)量暴增。而段數(shù)目太多會(huì)帶來(lái)較大的麻煩。 每一個(gè)段都會(huì)消耗文件句柄、內(nèi)存和cpu運(yùn)行周期。更重要的是,每個(gè)搜索請(qǐng)求都必須輪流檢查每個(gè)段;所以段越多,搜索也就越慢。Elasticsearch通過(guò)在后臺(tái)進(jìn)行段合并來(lái)解決這個(gè)問題。小的段被合并到大的段,然后這些大的段再被合并到更大的段。段合并的時(shí)候會(huì)將那些舊的已刪除文檔從文件系統(tǒng)中清除。被刪除的文檔(或被更新文檔的舊版本)不會(huì)被拷貝到新的大段中。啟動(dòng)段合并不需要你做任何事。進(jìn)行索引和搜索時(shí)會(huì)自動(dòng)進(jìn)行。
① 當(dāng)索引的時(shí)候,刷新(refresh)操作會(huì)創(chuàng)建新的段并將段打開以供搜索使用。
② 合并進(jìn)程選擇一小部分大小相似的段,并且在后臺(tái)將它們合并到更大的段中。這并不會(huì)中斷索引和搜索。如圖:兩個(gè)提交了的段和一個(gè)未提交的段正在被合并到一個(gè)更大的段
③ 一旦合并結(jié)束,老的段被刪除:
- 新的段被刷新(flush)到了磁盤, 寫入一個(gè)包含新段且排除舊的和較小的段的新提交點(diǎn)。
- 新的段被打開用來(lái)搜索。
- 老的段被刪除。
合并大的段需要消耗大量的I/O和CPU資源,如果任其發(fā)展會(huì)影響搜索性能。Elasticsearch在默認(rèn)情況下會(huì)對(duì)合并流程進(jìn)行資源限制,所以搜索仍然 有足夠的資源很好地執(zhí)行。
optimize
API大可看做是 強(qiáng)制合并 API。它會(huì)將一個(gè)分片強(qiáng)制合并到 max_num_segments
參數(shù)指定大小的段數(shù)目。 這樣做的意圖是減少段的數(shù)量(通常減少到一個(gè)),來(lái)提升搜索性能。
在特定情況下,使用 optimize
API 頗有益處。例如在日志這種用例下,每天、每周、每月的日志被存儲(chǔ)在一個(gè)索引中。 老的索引實(shí)質(zhì)上是只讀的;它們也并不太可能會(huì)發(fā)生變化。在這種情況下,使用optimize優(yōu)化老的索引,將每一個(gè)分片合并為一個(gè)單獨(dú)的段就很有用了;這樣既可以節(jié)省資源,也可以使搜索更加快速:
POST /logstash-2014-10/_optimize?max_num_segments=1
使用 optimize
API 觸發(fā)段合并的操作不會(huì)受到任何資源上的限制。這可能會(huì)消耗掉你節(jié)點(diǎn)上全部的I/O資源, 使其沒有余裕來(lái)處理搜索請(qǐng)求,從而有可能使集群失去響應(yīng)。 如果你想要對(duì)索引執(zhí)行 optimize
,你需要先使用分片分配把索引移到一個(gè)安全的節(jié)點(diǎn),再執(zhí)行。
到了這里,關(guān)于ElasticSearch系列 - 分片內(nèi)部原理之動(dòng)態(tài)更新索引、近實(shí)時(shí)搜索、持久化變更、段合并的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!