掌握它才說(shuō)明你真正懂Elasticsearch
原創(chuàng)【51CTO.com原創(chuàng)稿件】Elasticsearch 基于 Lucene,隱藏其復(fù)雜性,并提供簡(jiǎn)單易用的 Restful API接口、Java API 接口。所以理解 ES 的關(guān)鍵在于理解 Lucene 的基本原理。
Lucene 簡(jiǎn)介
Lucene 是一種高性能、可伸縮的信息搜索(IR)庫(kù),在 2000 年開(kāi)源,最初由鼎鼎大名的 Doug Cutting 開(kāi)發(fā),是基于 Java 實(shí)現(xiàn)的高性能的開(kāi)源項(xiàng)目。
Lucene 采用了基于倒排表的設(shè)計(jì)原理,可以非常高效地實(shí)現(xiàn)文本查找,在底層采用了分段的存儲(chǔ)模式,使它在讀寫(xiě)時(shí)幾乎完全避免了鎖的出現(xiàn),大大提升了讀寫(xiě)性能。
核心模塊
Lucene 的寫(xiě)流程和讀流程如下圖所示:
圖 1:Lucene 的寫(xiě)流程和讀流程
其中,虛線箭頭(a、b、c、d)表示寫(xiě)索引的主要過(guò)程,實(shí)線箭頭(1-9)表示查詢的主要過(guò)程。
Lucene 中的主要模塊及模塊說(shuō)明如下:
- analysis:主要負(fù)責(zé)詞法分析及語(yǔ)言處理,也就是我們常說(shuō)的分詞,通過(guò)該模塊可最終形成存儲(chǔ)或者搜索的最小單元 Term。
- index 模塊:主要負(fù)責(zé)索引的創(chuàng)建工作。
- store 模塊:主要負(fù)責(zé)索引的讀寫(xiě),主要是對(duì)文件的一些操作,其主要目的是抽象出和平臺(tái)文件系統(tǒng)無(wú)關(guān)的存儲(chǔ)。
- queryParser 模塊:主要負(fù)責(zé)語(yǔ)法分析,把我們的查詢語(yǔ)句生成 Lucene 底層可以識(shí)別的條件。
- search 模塊:主要負(fù)責(zé)對(duì)索引的搜索工作。
- similarity 模塊:主要負(fù)責(zé)相關(guān)性打分和排序的實(shí)現(xiàn)。
核心術(shù)語(yǔ)
下面介紹 Lucene 中的核心術(shù)語(yǔ):
- Term:是索引里最小的存儲(chǔ)和查詢單元,對(duì)于英文來(lái)說(shuō)一般是指一個(gè)單詞,對(duì)于中文來(lái)說(shuō)一般是指一個(gè)分詞后的詞。
- 詞典(Term Dictionary,也叫作字典):是 Term 的集合。詞典的數(shù)據(jù)結(jié)構(gòu)可以有很多種,每種都有自己的優(yōu)缺點(diǎn)。
比如:排序數(shù)組通過(guò)二分查找來(lái)檢索數(shù)據(jù):HashMap(哈希表)比排序數(shù)組的檢索速度更快,但是會(huì)浪費(fèi)存儲(chǔ)空間。
FST(finite-state transducer)有更高的數(shù)據(jù)壓縮率和查詢效率,因?yàn)樵~典是常駐內(nèi)存的,而 FST 有很好的壓縮率,所以 FST 在 Lucene 當(dāng)前版本中有非常多的使用場(chǎng)景,也是默認(rèn)的詞典數(shù)據(jù)結(jié)構(gòu)。
- 倒排序(Posting List):一篇文章通常由多個(gè)詞組成,倒排表記錄的是某個(gè)詞在哪些文章中出現(xiàn)過(guò)。
- 正向信息:原始的文檔信息,可以用來(lái)做排序、聚合、展示等。
- 段(Segment):索引中最小的獨(dú)立存儲(chǔ)單元。一個(gè)索引文件由一個(gè)或者多個(gè)段組成。在 Luence 中的段有不變性,也就是說(shuō)段一旦生成,在其上只能有讀操作,不能有寫(xiě)操作。
Lucene 的底層存儲(chǔ)格式如下圖所示,由詞典和倒排序兩部分組成,其中的詞典就是 Term 的集合:
圖 2:Lucene 的底層存儲(chǔ)格式
詞典中的 Term 指向的文檔鏈表的集合,叫做倒排表。詞典和倒排表是 Lucene 中很重要的兩種數(shù)據(jù)結(jié)構(gòu),是實(shí)現(xiàn)快速檢索的重要基石。
詞典和倒排表是分兩部分存儲(chǔ)的,在倒排序中不但存儲(chǔ)了文檔編號(hào),還存儲(chǔ)了詞頻等信息。
在上圖所示的詞典部分包含三個(gè)詞條(Term):Elasticsearch、Lucene 和 Solr。詞典數(shù)據(jù)是查詢的入口,所以這部分?jǐn)?shù)據(jù)是以 FST 的形式存儲(chǔ)在內(nèi)存中的。
在倒排表中,“Lucene”指向有序鏈表 3,7,15,30,35,67,表示字符串“Lucene”在文檔編號(hào)為3、7、15、30、35、67的文章中出現(xiàn)過(guò),Elasticsearch 和 Solr 同理。
檢索方式
在 Lucene 的查詢過(guò)程中的主要檢索方式有以下四種:
①單個(gè)詞查詢
指對(duì)一個(gè) Term 進(jìn)行查詢。比如,若要查找包含字符串“Lucene”的文檔,則只需在詞典中找到 Term“Lucene”,再獲得在倒排表中對(duì)應(yīng)的文檔鏈表即可。
②AND
指對(duì)多個(gè)集合求交集。比如,若要查找既包含字符串“Lucene”又包含字符串“Solr”的文檔,則查找步驟如下:
- 在詞典中找到 Term “Lucene”,得到“Lucene”對(duì)應(yīng)的文檔鏈表。
- 在詞典中找到 Term “Solr”,得到“Solr”對(duì)應(yīng)的文檔鏈表。
- 合并鏈表,對(duì)兩個(gè)文檔鏈表做交集運(yùn)算,合并后的結(jié)果既包含“Lucene”也包含“Solr”。
③OR
指多個(gè)集合求并集。比如,若要查找包含字符串“Luence”或者包含字符串“Solr”的文檔,則查找步驟如下:
- 在詞典中找到 Term “Lucene”,得到“Lucene”對(duì)應(yīng)的文檔鏈表。
- 在詞典中找到 Term “Solr”,得到“Solr”對(duì)應(yīng)的文檔鏈表。
- 合并鏈表,對(duì)兩個(gè)文檔鏈表做并集運(yùn)算,合并后的結(jié)果包含“Lucene”或者包含“Solr”。
④NOT
指對(duì)多個(gè)集合求差集。比如,若要查找包含字符串“Solr”但不包含字符串“Lucene”的文檔,則查找步驟如下:
- 在詞典中找到 Term “Lucene”,得到“Lucene”對(duì)應(yīng)的文檔鏈表。
- 在詞典中找到 Term “Solr”,得到“Solr”對(duì)應(yīng)的文檔鏈表。
- 合并鏈表,對(duì)兩個(gè)文檔鏈表做差集運(yùn)算,用包含“Solr”的文檔集減去包含“Lucene”的文檔集,運(yùn)算后的結(jié)果就是包含“Solr”但不包含“Lucene”。
通過(guò)上述四種查詢方式,我們不難發(fā)現(xiàn),由于 Lucene 是以倒排表的形式存儲(chǔ)的。
所以在 Lucene 的查找過(guò)程中只需在詞典中找到這些 Term,根據(jù) Term 獲得文檔鏈表,然后根據(jù)具體的查詢條件對(duì)鏈表進(jìn)行交、并、差等操作,就可以準(zhǔn)確地查到我們想要的結(jié)果。
相對(duì)于在關(guān)系型數(shù)據(jù)庫(kù)中的“Like”查找要做全表掃描來(lái)說(shuō),這種思路是非常高效的。
雖然在索引創(chuàng)建時(shí)要做很多工作,但這種一次生成、多次使用的思路也是非常高明的。
分段存儲(chǔ)
在早期的全文檢索中為整個(gè)文檔集合建立了一個(gè)很大的倒排索引,并將其寫(xiě)入磁盤(pán)中,如果索引有更新,就需要重新全量創(chuàng)建一個(gè)索引來(lái)替換原來(lái)的索引。
這種方式在數(shù)據(jù)量很大時(shí)效率很低,并且由于創(chuàng)建一次索引的成本很高,所以對(duì)數(shù)據(jù)的更新不能過(guò)于頻繁,也就不能保證實(shí)效性。
現(xiàn)在,在搜索中引入了段的概念(將一個(gè)索引文件拆分為多個(gè)子文件,則每個(gè)子文件叫做段),每個(gè)段都是一個(gè)獨(dú)立的可被搜索的數(shù)據(jù)集,并且段具有不變性,一旦索引的數(shù)據(jù)被寫(xiě)入硬盤(pán),就不可修改。
在分段的思想下,對(duì)數(shù)據(jù)寫(xiě)操作的過(guò)程如下:
- 新增:當(dāng)有新的數(shù)據(jù)需要?jiǎng)?chuàng)建索引時(shí),由于段段不變性,所以選擇新建一個(gè)段來(lái)存儲(chǔ)新增的數(shù)據(jù)。
- 刪除:當(dāng)需要?jiǎng)h除數(shù)據(jù)時(shí),由于數(shù)據(jù)所在的段只可讀,不可寫(xiě),所以 Lucene 在索引文件新增一個(gè) .del 的文件,用來(lái)專門(mén)存儲(chǔ)被刪除的數(shù)據(jù) id。
當(dāng)查詢時(shí),被刪除的數(shù)據(jù)還是可以被查到的,只是在進(jìn)行文檔鏈表合并時(shí),才把已經(jīng)刪除的數(shù)據(jù)過(guò)濾掉。被刪除的數(shù)據(jù)在進(jìn)行段合并時(shí)才會(huì)被真正被移除。
- 更新:更新的操作其實(shí)就是刪除和新增的組合,先在.del文件中記錄舊數(shù)據(jù),再在新段中添加一條更新后的數(shù)據(jù)。
段不可變性的優(yōu)點(diǎn)如下:
- 不需要鎖:因?yàn)閿?shù)據(jù)不會(huì)更新,所以不用考慮多線程下的讀寫(xiě)不一致情況。
- 可以常駐內(nèi)存:段在被加載到內(nèi)存后,由于具有不變性,所以只要內(nèi)存的空間足夠大,就可以長(zhǎng)時(shí)間駐存,大部分查詢請(qǐng)求會(huì)直接訪問(wèn)內(nèi)存,而不需要訪問(wèn)磁盤(pán),使得查詢的性能有很大的提升。
- 緩存友好:在段的聲明周期內(nèi)始終有效,不需要在每次數(shù)據(jù)更新時(shí)被重建。
- 增量創(chuàng)建:分段可以做到增量創(chuàng)建索引,可以輕量級(jí)地對(duì)數(shù)據(jù)進(jìn)行更新,由于每次創(chuàng)建的成本很低,所以可以頻繁地更新數(shù)據(jù),使系統(tǒng)接近實(shí)時(shí)更新。
段不可變性的缺點(diǎn)如下:
- 刪除:當(dāng)對(duì)數(shù)據(jù)進(jìn)行刪除時(shí),舊數(shù)據(jù)不會(huì)被馬上刪除,而是在 .del 文件中被標(biāo)記為刪除。而舊數(shù)據(jù)只能等到段更新時(shí)才能真正地被移除,這樣會(huì)有大量的空間浪費(fèi)。
- 更新:更新數(shù)據(jù)由刪除和新增這兩個(gè)動(dòng)作組成。若有一條數(shù)據(jù)頻繁更新,則會(huì)有大量的空間浪費(fèi)。
- 新增:由于索引具有不變性,所以每次新增數(shù)據(jù)時(shí),都需要新增一個(gè)段來(lái)存儲(chǔ)數(shù)據(jù)。當(dāng)段段數(shù)量太多時(shí),對(duì)服務(wù)器的資源(如文件句柄)的消耗會(huì)非常大,查詢的性能也會(huì)受到影響。
- 過(guò)濾:在查詢后需要對(duì)已經(jīng)刪除的舊數(shù)據(jù)進(jìn)行過(guò)濾,這增加了查詢的負(fù)擔(dān)。
為了提升寫(xiě)的性能,Lucene 并沒(méi)有每新增一條數(shù)據(jù)就增加一個(gè)段,而是采用延遲寫(xiě)的策略,每當(dāng)有新增的數(shù)據(jù)時(shí),就將其先寫(xiě)入內(nèi)存中,然后批量寫(xiě)入磁盤(pán)中。
若有一個(gè)段被寫(xiě)到硬盤(pán),就會(huì)生成一個(gè)提交點(diǎn),提交點(diǎn)就是一個(gè)用來(lái)記錄所有提交后的段信息的文件。
一個(gè)段一旦擁有了提交點(diǎn),就說(shuō)明這個(gè)段只有讀的權(quán)限,失去了寫(xiě)的權(quán)限;相反,當(dāng)段在內(nèi)存中時(shí),就只有寫(xiě)數(shù)據(jù)的權(quán)限,而不具備讀數(shù)據(jù)的權(quán)限,所以也就不能被檢索了。
從嚴(yán)格意義上來(lái)說(shuō),Lucene 或者 Elasticsearch 并不能被稱為實(shí)時(shí)的搜索引擎,只能被稱為準(zhǔn)實(shí)時(shí)的搜索引擎。
寫(xiě)索引的流程如下:
- 新數(shù)據(jù)被寫(xiě)入時(shí),并沒(méi)有被直接寫(xiě)到硬盤(pán)中,而是被暫時(shí)寫(xiě)到內(nèi)存中。Lucene 默認(rèn)是一秒鐘,或者當(dāng)內(nèi)存中數(shù)據(jù)量達(dá)到一定階段時(shí),再批量提交到磁盤(pán)中。
當(dāng)然,默認(rèn)的時(shí)間和數(shù)據(jù)量的大小是可以通過(guò)參數(shù)控制的。通過(guò)延時(shí)寫(xiě)的策略,可以減少數(shù)據(jù)往磁盤(pán)上寫(xiě)的次數(shù),從而提升整體的寫(xiě)入性能,如圖 3。
- 在達(dá)到出觸發(fā)條件以后,會(huì)將內(nèi)存中緩存的數(shù)據(jù)一次性寫(xiě)入磁盤(pán)中,并生成提交點(diǎn)。
- 清空內(nèi)存,等待新的數(shù)據(jù)寫(xiě)入,如下圖所示。
圖 3:Elasticsearch 寫(xiě)索引
從上述流程可以看出,數(shù)據(jù)先被暫時(shí)緩存在內(nèi)存中,在達(dá)到一定的條件再被一次性寫(xiě)入硬盤(pán)中,這種做法可以大大提升數(shù)據(jù)寫(xiě)入的書(shū)單。
但是數(shù)據(jù)先被暫時(shí)存放在內(nèi)存中,并沒(méi)有真正持久化到磁盤(pán)中,所以如果這時(shí)出現(xiàn)斷電等不可控的情況,就會(huì)丟失數(shù)據(jù),為此,Elasticsearch 添加了事務(wù)日志,來(lái)保證數(shù)據(jù)的安全。
段合并策略
雖然分段比每次都全量創(chuàng)建索引有更高的效率,但是由于在每次新增數(shù)據(jù)時(shí)都會(huì)新增一個(gè)段,所以經(jīng)過(guò)長(zhǎng)時(shí)間的的積累,會(huì)導(dǎo)致在索引中存在大量的段。
當(dāng)索引中段的數(shù)量太多時(shí),不僅會(huì)嚴(yán)重消耗服務(wù)器的資源,還會(huì)影響檢索的性能。
因?yàn)樗饕龣z索的過(guò)程是:查詢所有段中滿足查詢條件的數(shù)據(jù),然后對(duì)每個(gè)段里查詢的結(jié)果集進(jìn)行合并,所以為了控制索引里段的數(shù)量,我們必須定期進(jìn)行段合并操作。
但是如果每次合并全部的段,則會(huì)造成很大的資源浪費(fèi),特別是“大段”的合并。
所以 Lucene 現(xiàn)在的段合并思路是:根據(jù)段的大小將段進(jìn)行分組,再將屬于同一組的段進(jìn)行合并。
但是由于對(duì)于超級(jí)大的段的合并需要消耗更多的資源,所以 Lucene 會(huì)在段的大小達(dá)到一定規(guī)模,或者段里面的數(shù)據(jù)量達(dá)到一定條數(shù)時(shí),不會(huì)再進(jìn)行合并。
所以 Lucene 的段合并主要集中在對(duì)中小段的合并上,這樣既可以避免對(duì)大段進(jìn)行合并時(shí)消耗過(guò)多的服務(wù)器資源,也可以很好地控制索引中段的數(shù)量。
段合并的主要參數(shù)如下:
- mergeFactor:每次合并時(shí)參與合并的最少數(shù)量,當(dāng)同一組的段的數(shù)量達(dá)到此值時(shí)開(kāi)始合并,如果小于此值則不合并,這樣做可以減少段合并的頻率,其默認(rèn)值為 10。
- SegmentSize:指段的實(shí)際大小,單位為字節(jié)。
- minMergeSize:小于這個(gè)值的段會(huì)被分到一組,這樣可以加速小片段的合并。
- maxMergeSize:若有一段的文本數(shù)量大于此值,就不再參與合并,因?yàn)榇蠖魏喜?huì)消耗更多的資源。
段合并相關(guān)的動(dòng)作主要有以下兩個(gè):
- 對(duì)索引中的段進(jìn)行分組,把大小相近的段分到一組,主要由 LogMergePolicy1 類來(lái)處理。
- 將屬于同一分組的段合并成一個(gè)更大的段。
在段合并前對(duì)段的大小進(jìn)行了標(biāo)準(zhǔn)化處理,通過(guò) logMergeFactorSegmentSize 計(jì)算得出。
其中 MergeFactor 表示一次合并的段的數(shù)量,Lucene 默認(rèn)該數(shù)量為 10;SegmentSize 表示段的實(shí)際大小。通過(guò)上面的公式計(jì)算后,段的大小更加緊湊,對(duì)后續(xù)的分組更加友好。
段分組的步驟如下:
①根據(jù)段生成的時(shí)間對(duì)段進(jìn)行排序,然后根據(jù)上述標(biāo)準(zhǔn)化公式計(jì)算每個(gè)段的大小并且存放到段信息中,后面用到的描述段大小的值都是標(biāo)準(zhǔn)化后的值,如圖 4 所示:
圖 4:Lucene 段排序
②在數(shù)組中找到段,然后生成一個(gè)由段的標(biāo)準(zhǔn)化值作為上限,減去 LEVEL_LOG_SPAN(默認(rèn)值為 0.75)后的值作為下限的區(qū)間,小于等于上限并且大于下限的段,都被認(rèn)為是屬于同一組的段,可以合并。
③在確定一個(gè)分組的上下限值后,就需要查找屬于這個(gè)分組的段了,具體過(guò)程是:創(chuàng)建兩個(gè)指針(在這里使用指針的概念是為了更好地理解)start 和 end。
start 指向數(shù)組的第 1 個(gè)段,end 指向第 start+MergeFactor 個(gè)段,然后從 end 逐個(gè)向前查找落在區(qū)間的段。
當(dāng)找到第 1 個(gè)滿足條件的段時(shí),則停止,并把當(dāng)前段到 start 之間的段統(tǒng)一分到一個(gè)組,無(wú)論段的大小是否滿足當(dāng)前分組的條件。
如圖 5 所示,第 2 個(gè)段明顯小于該分組的下限,但還是被分到了這一組。
圖 5:Lucene 段分組
這樣做的好處如下:
- 增加段合并的概率,避免由于段的大小參差不齊導(dǎo)致段難以合并。
- 簡(jiǎn)化了查找的邏輯,使代碼的運(yùn)行效率更高。
④在分組找到后,需要排除不參加合并的“超大”段,然后判斷剩余的段是否滿足合并的條件。
如圖 5 所示,mergeFactor=5,而找到的滿足合并條件的段的個(gè)數(shù)為 4,所以不滿足合并的條件,暫時(shí)不進(jìn)行合并,繼續(xù)找尋下一個(gè)組的上下限。
⑤由于在第 4 步并沒(méi)有找到滿足段合并的段的數(shù)量,所以這一分組的段不滿足合并的條件,繼續(xù)進(jìn)行下一分組段的查找。
具體過(guò)程是:將 start 指向 end,在剩下的段中尋找大的段,在找到大的值后再減去 LEVEL_LOG_SPAN 的值,再生成一下分組的區(qū)間值。
然后把 end 指向數(shù)組的第 start+MergeFactor 個(gè)段,逐個(gè)向前查找第 1 個(gè)滿足條件的段:重復(fù)第 3 步和第 4 步。
⑥如果一直沒(méi)有找到滿足合并條件的段,則一直重復(fù)第 5 步,直到遍歷完整個(gè)數(shù)組,如圖 6 所示:
圖 6:Lucene 段分組二
⑦在找到滿足條件的 mergeFactor 個(gè)段時(shí),就需要開(kāi)始合并了。但是在滿足合并條件的段大于 mergeFactor 時(shí),就需要進(jìn)行多次合并。
也就是說(shuō)每次依然選擇 mergeFactor 個(gè)段進(jìn)行合并,直到該分組的所有段合并完成,再進(jìn)行下一分組的查找合并操作。
⑧通過(guò)上述幾步,如果找到了滿足合并要求的段,則將會(huì)進(jìn)行段的合并操作。
因?yàn)樗饕锩姘苏蛐畔⒑头聪蛐畔ⅲ远魏喜⒌牟僮鞣譃閮刹糠郑?/p>
- 一個(gè)是正向信息合并,例如存儲(chǔ)域、詞向量、標(biāo)準(zhǔn)化因子等。
- 一個(gè)是反向信息的合并,例如詞典、倒排表等。
在段合并時(shí),除了需要對(duì)索引數(shù)據(jù)進(jìn)行合并,還需要移除段中已經(jīng)刪除的數(shù)據(jù)。
Lucene 相似度打分
我們?cè)谇懊媪私獾?,Lucene 的查詢過(guò)程是:首先在詞典中查找每個(gè) Term,根據(jù) Term 獲得每個(gè) Term 所在的文檔鏈表;然后根據(jù)查詢條件對(duì)鏈表做交、并、差等操作,鏈表合并后的結(jié)果集就是我們要查找的數(shù)據(jù)。
這樣做可以完全避免對(duì)關(guān)系型數(shù)據(jù)庫(kù)進(jìn)行全表掃描,可以大大提升查詢效率。
但是,當(dāng)我們一次查詢出很多數(shù)據(jù)時(shí),這些數(shù)據(jù)和我們的查詢條件又有多大關(guān)系呢?其文本相似度是多少?
本節(jié)會(huì)回答這個(gè)問(wèn)題,并介紹 Lucene 最經(jīng)典的兩個(gè)文本相似度算法:基于向量空間模型的算法和基于概率的算法(BM25)。
如果對(duì)此算法不太感興趣,那么只需了解對(duì)文本相似度有影響的因子有哪些,哪些是正向的,哪些是逆向的即可,不需要理解每個(gè)算法的推理過(guò)程。但是這兩個(gè)文本相似度算法有很好的借鑒意義。
Elasticsearch 簡(jiǎn)介
Elasticsearch 是使用 Java 編寫(xiě)的一種開(kāi)源搜索引擎,它在內(nèi)部使用 Luence 做索引與搜索,通過(guò)對(duì) Lucene 的封裝,提供了一套簡(jiǎn)單一致的 RESTful API。
Elasticsearch 也是一種分布式的搜索引擎架構(gòu),可以很簡(jiǎn)單地?cái)U(kuò)展到上百個(gè)服務(wù)節(jié)點(diǎn),并支持 PB 級(jí)別的數(shù)據(jù)查詢,使系統(tǒng)具備高可用和高并發(fā)性。
核心概念
- Elasticsearch 的核心概念如下:
- Cluster:集群,由一個(gè)或多個(gè) Elasticsearch 節(jié)點(diǎn)組成。
- Node:節(jié)點(diǎn),組成 Elasticsearch 集群的服務(wù)單元,同一個(gè)集群內(nèi)節(jié)點(diǎn)的名字不能重復(fù)。通常在一個(gè)節(jié)點(diǎn)上分配一個(gè)或者多個(gè)分片。
- Shards:分片,當(dāng)索引上的數(shù)據(jù)量太大的時(shí)候,我們通常會(huì)將一個(gè)索引上的數(shù)據(jù)進(jìn)行水平拆分,拆分出來(lái)的每個(gè)數(shù)據(jù)庫(kù)叫作一個(gè)分片。
在一個(gè)多分片的索引中寫(xiě)入數(shù)據(jù)時(shí),通過(guò)路由來(lái)確定具體寫(xiě)入那一個(gè)分片中,所以在創(chuàng)建索引時(shí)需要指定分片的數(shù)量,并且分片的數(shù)量一旦確定就不能更改。
分片后的索引帶來(lái)了規(guī)模上(數(shù)據(jù)水平切分)和性能上(并行執(zhí)行)的提升。每個(gè)分片都是 Luence 中的一個(gè)索引文件,每個(gè)分片必須有一個(gè)主分片和零到多個(gè)副本分片。
- Replicas:備份也叫作副本,是指對(duì)主分片的備份。主分片和備份分片都可以對(duì)外提供查詢服務(wù),寫(xiě)操作時(shí)先在主分片上完成,然后分發(fā)到備份上。
當(dāng)主分片不可用時(shí),會(huì)在備份的分片中選舉出一個(gè)作為主分片,所以備份不僅可以提升系統(tǒng)的高可用性能,還可以提升搜索時(shí)的并發(fā)性能。但是若副本太多的話,在寫(xiě)操作時(shí)會(huì)增加數(shù)據(jù)同步的負(fù)擔(dān)。
- Index:索引,由一個(gè)和多個(gè)分片組成,通過(guò)索引的名字在集群內(nèi)進(jìn)行標(biāo)識(shí)。
- Type:類別,指索引內(nèi)部的邏輯分區(qū),通過(guò) Type 的名字在索引內(nèi)進(jìn)行標(biāo)識(shí)。在查詢時(shí)如果沒(méi)有該值,則表示在整個(gè)索引中查詢。
- Document:文檔,索引中的每一條數(shù)據(jù)叫作一個(gè)文檔,類似于關(guān)系型數(shù)據(jù)庫(kù)中的一條數(shù)據(jù)通過(guò) _id 在 Type 內(nèi)進(jìn)行標(biāo)識(shí)。
- Settings:對(duì)集群中索引的定義,比如一個(gè)索引默認(rèn)的分片數(shù)、副本數(shù)等信息。
- Mapping:類似于關(guān)系型數(shù)據(jù)庫(kù)中的表結(jié)構(gòu)信息,用于定義索引中字段(Field)的存儲(chǔ)類型、分詞方式、是否存儲(chǔ)等信息。Elasticsearch 中的 Mapping 是可以動(dòng)態(tài)識(shí)別的。
如果沒(méi)有特殊需求,則不需要手動(dòng)創(chuàng)建 Mapping,因?yàn)?Elasticsearch 會(huì)自動(dòng)根據(jù)數(shù)據(jù)格式識(shí)別它的類型,但是當(dāng)需要對(duì)某些字段添加特殊屬性(比如:定義使用其他分詞器、是否分詞、是否存儲(chǔ)等)時(shí),就需要手動(dòng)設(shè)置 Mapping 了。一個(gè)索引的 Mapping 一旦創(chuàng)建,若已經(jīng)存儲(chǔ)了數(shù)據(jù),就不可修改了。
- Analyzer:字段的分詞方式的定義。一個(gè) Analyzer 通常由一個(gè) Tokenizer、零到多個(gè) Filter 組成。
比如默認(rèn)的標(biāo)準(zhǔn) Analyzer 包含一個(gè)標(biāo)準(zhǔn)的 Tokenizer 和三個(gè) Filter:Standard Token Filter、Lower Case Token Filter、Stop Token Filter。
Elasticsearch 的節(jié)點(diǎn)的分類如下:
①主節(jié)點(diǎn)(Master Node):也叫作主節(jié)點(diǎn),主節(jié)點(diǎn)負(fù)責(zé)創(chuàng)建索引、刪除索引、分配分片、追蹤集群中的節(jié)點(diǎn)狀態(tài)等工作。Elasticsearch 中的主節(jié)點(diǎn)的工作量相對(duì)較輕。
用戶的請(qǐng)求可以發(fā)往任何一個(gè)節(jié)點(diǎn),并由該節(jié)點(diǎn)負(fù)責(zé)分發(fā)請(qǐng)求、收集結(jié)果等操作,而并不需要經(jīng)過(guò)主節(jié)點(diǎn)轉(zhuǎn)發(fā)。
通過(guò)在配置文件中設(shè)置 node.master=true 來(lái)設(shè)置該節(jié)點(diǎn)成為候選主節(jié)點(diǎn)(但該節(jié)點(diǎn)不一定是主節(jié)點(diǎn),主節(jié)點(diǎn)是集群在候選節(jié)點(diǎn)中選舉出來(lái)的),在 Elasticsearch 集群中只有候選節(jié)點(diǎn)才有選舉權(quán)和被選舉權(quán)。其他節(jié)點(diǎn)是不參與選舉工作的。
②數(shù)據(jù)節(jié)點(diǎn)(Data Node):數(shù)據(jù)節(jié)點(diǎn),負(fù)責(zé)數(shù)據(jù)的存儲(chǔ)和相關(guān)具體操作,比如索引數(shù)據(jù)的創(chuàng)建、修改、刪除、搜索、聚合。
所以,數(shù)據(jù)節(jié)點(diǎn)對(duì)機(jī)器配置要求比較高,首先需要有足夠的磁盤(pán)空間來(lái)存儲(chǔ)數(shù)據(jù),其次數(shù)據(jù)操作對(duì)系統(tǒng) CPU、Memory 和 I/O 的性能消耗都很大。
通常隨著集群的擴(kuò)大,需要增加更多的數(shù)據(jù)節(jié)點(diǎn)來(lái)提高可用性。通過(guò)在配置文件中設(shè)置 node.data=true 來(lái)設(shè)置該節(jié)點(diǎn)成為數(shù)據(jù)節(jié)點(diǎn)。
③客戶端節(jié)點(diǎn)(Client Node):就是既不做候選主節(jié)點(diǎn)也不做數(shù)據(jù)節(jié)點(diǎn)的節(jié)點(diǎn),只負(fù)責(zé)請(qǐng)求的分發(fā)、匯總等,也就是下面要說(shuō)到的協(xié)調(diào)節(jié)點(diǎn)的角色。
其實(shí)任何一個(gè)節(jié)點(diǎn)都可以完成這樣的工作,單獨(dú)增加這樣的節(jié)點(diǎn)更多地是為了提高并發(fā)性。
可在配置文件中設(shè)置該節(jié)點(diǎn)成為數(shù)據(jù)節(jié)點(diǎn):
- node.master=false
- node.data=false
④部落節(jié)點(diǎn)(Tribe Node):部落節(jié)點(diǎn)可以跨越多個(gè)集群,它可以接收每個(gè)集群的狀態(tài),然后合并成一個(gè)全局集群的狀態(tài)。
它可以讀寫(xiě)所有集群節(jié)點(diǎn)上的數(shù)據(jù),在配置文件中通過(guò)如下設(shè)置使節(jié)點(diǎn)成為部落節(jié)點(diǎn):
- tribe:
- one:
- cluster.name: cluster_one
- two:
- cluster.name: cluster_two
因?yàn)?Tribe Node 要在 Elasticsearch 7.0 以后移除,所以不建議使用。
⑤協(xié)調(diào)節(jié)點(diǎn)(Coordinating Node):協(xié)調(diào)節(jié)點(diǎn),是一種角色,而不是真實(shí)的 Elasticsearch 的節(jié)點(diǎn),我們沒(méi)有辦法通過(guò)配置項(xiàng)來(lái)配置哪個(gè)節(jié)點(diǎn)為協(xié)調(diào)節(jié)點(diǎn)。集群中的任何節(jié)點(diǎn)都可以充當(dāng)協(xié)調(diào)節(jié)點(diǎn)的角色。
當(dāng)一個(gè)節(jié)點(diǎn) A 收到用戶的查詢請(qǐng)求后,會(huì)把查詢語(yǔ)句分發(fā)到其他的節(jié)點(diǎn),然后合并各個(gè)節(jié)點(diǎn)返回的查詢結(jié)果,返回一個(gè)完整的數(shù)據(jù)集給用戶。
在這個(gè)過(guò)程中,節(jié)點(diǎn) A 扮演的就是協(xié)調(diào)節(jié)點(diǎn)的角色。由此可見(jiàn),協(xié)調(diào)節(jié)點(diǎn)會(huì)對(duì) CPU、Memory 和 I/O 要求比較高。
集群的狀態(tài)有 Green、Yellow 和 Red 三種,如下所述:
- Green:綠色,健康。所有的主分片和副本分片都可正常工作,集群 100% 健康。
- Yellow:預(yù)警。所有的主分片都可以正常工作,但至少有一個(gè)副本分片是不能正常工作的。此時(shí)集群可以正常工作,但是集群的高可用性在某種程度上被弱化。
- Red:紅色,集群不可正常使用。集群中至少有一個(gè)分片的主分片及它的全部副本分片都不可正常工作。
這時(shí)雖然集群的查詢操作還可以進(jìn)行,但是也只能返回部分?jǐn)?shù)據(jù)(其他正常分片的數(shù)據(jù)可以返回),而分配到這個(gè)分片上的寫(xiě)入請(qǐng)求將會(huì)報(bào)錯(cuò),最終會(huì)導(dǎo)致數(shù)據(jù)的丟失。
3C 和腦裂
①共識(shí)性(Consensus)
共識(shí)性是分布式系統(tǒng)中最基礎(chǔ)也最主要的一個(gè)組件,在分布式系統(tǒng)中的所有節(jié)點(diǎn)必須對(duì)給定的數(shù)據(jù)或者節(jié)點(diǎn)的狀態(tài)達(dá)成共識(shí)。
雖然現(xiàn)在有很成熟的共識(shí)算法如 Raft、Paxos 等,也有比較成熟的開(kāi)源軟件如 Zookeeper。
但是 Elasticsearch 并沒(méi)有使用它們,而是自己實(shí)現(xiàn)共識(shí)系統(tǒng) zen discovery。
Elasticsearch 之父 Shay Banon 解釋了其中主要的原因:“zen discovery是 Elasticsearch 的一個(gè)核心的基礎(chǔ)組件,zen discovery 不僅能夠?qū)崿F(xiàn)共識(shí)系統(tǒng)的選擇工作,還能夠很方便地監(jiān)控集群的讀寫(xiě)狀態(tài)是否健康。當(dāng)然,我們也不保證其后期會(huì)使用 Zookeeper 代替現(xiàn)在的 zen discovery”。
zen discovery 模塊以“八卦傳播”(Gossip)的形式實(shí)現(xiàn)了單播(Unicat):?jiǎn)尾ゲ煌诙嗖?Multicast)和廣播(Broadcast)。節(jié)點(diǎn)間的通信方式是一對(duì)一的。
②并發(fā)(Concurrency)
Elasticsearch 是一個(gè)分布式系統(tǒng)。寫(xiě)請(qǐng)求在發(fā)送到主分片時(shí),同時(shí)會(huì)以并行的形式發(fā)送到備份分片,但是這些請(qǐng)求的送達(dá)時(shí)間可能是無(wú)序的。
在這種情況下,Elasticsearch 用樂(lè)觀并發(fā)控制(Optimistic Concurrency Control)來(lái)保證新版本的數(shù)據(jù)不會(huì)被舊版本的數(shù)據(jù)覆蓋。
樂(lè)觀并發(fā)控制是一種樂(lè)觀鎖,另一種常用的樂(lè)觀鎖即多版本并發(fā)控制(Multi-Version Concurrency Control)。
它們的主要區(qū)別如下:
- 樂(lè)觀并發(fā)控制(OCC):是一種用來(lái)解決寫(xiě)-寫(xiě)沖突的無(wú)鎖并發(fā)控制,認(rèn)為事務(wù)間的競(jìng)爭(zhēng)不激烈時(shí),就先進(jìn)行修改,在提交事務(wù)前檢查數(shù)據(jù)有沒(méi)有變化,如果沒(méi)有就提交,如果有就放棄并重試。樂(lè)觀并發(fā)控制類似于自選鎖,適用于低數(shù)據(jù)競(jìng)爭(zhēng)且寫(xiě)沖突比較少的環(huán)境。
- 多版本并發(fā)控制(MVCC):是一種用來(lái)解決讀-寫(xiě)沖突的無(wú)所并發(fā)控制,也就是為事務(wù)分配單向增長(zhǎng)的時(shí)間戳,為每一個(gè)修改保存一個(gè)版本,版本與事務(wù)時(shí)間戳關(guān)聯(lián),讀操作只讀該事務(wù)開(kāi)始前的數(shù)據(jù)庫(kù)的快照。
這樣在讀操作不用阻塞操作且寫(xiě)操作不用阻塞讀操作的同時(shí),避免了臟讀和不可重復(fù)讀。
③一致性(Consistency)
Elasticsearch 集群保證寫(xiě)一致性的方式是在寫(xiě)入前先檢查有多少個(gè)分片可供寫(xiě)入,如果達(dá)到寫(xiě)入條件,則進(jìn)行寫(xiě)操作,否則,Elasticsearch 會(huì)等待更多的分片出現(xiàn),默認(rèn)為一分鐘。
有如下三種設(shè)置來(lái)判斷是否允許寫(xiě)操作:
- One:只要主分片可用,就可以進(jìn)行寫(xiě)操作。
- All:只有當(dāng)主分片和所有副本都可用時(shí),才允許寫(xiě)操作。
- Quorum(k-wu-wo/reng,法定人數(shù)):是 Elasticsearch 的默認(rèn)選項(xiàng)。當(dāng)有大部分的分片可用時(shí)才允許寫(xiě)操作。其中,對(duì)“大部分”的計(jì)算公式為 int((primary+number_of_replicas)/2)+1。
Elasticsearch 集群保證讀寫(xiě)一致性的方式是,為了保證搜索請(qǐng)求的返回結(jié)果是當(dāng)前版本的文檔,備份可以被設(shè)置為 Sync(默認(rèn)值),寫(xiě)操作在主分片和備份分片同時(shí)完成后才會(huì)返回寫(xiě)請(qǐng)求的結(jié)果。
這樣,無(wú)論搜索請(qǐng)求至哪個(gè)分片都會(huì)返回文檔。但是如果我們的應(yīng)用對(duì)寫(xiě)要求很高,就可以通過(guò)設(shè)置 replication=async 來(lái)提升寫(xiě)的效率,如果設(shè)置 replication=async,則只要主分片的寫(xiě)完成,就會(huì)返回寫(xiě)成功。
④腦裂
在 Elasticsearch 集群中主節(jié)點(diǎn)通過(guò) Ping 命令來(lái)檢查集群中的其他節(jié)點(diǎn)是否處于可用狀態(tài),同時(shí)非主節(jié)點(diǎn)也會(huì)通過(guò) Ping 來(lái)檢查主節(jié)點(diǎn)是否處于可用狀態(tài)。
當(dāng)集群網(wǎng)絡(luò)不穩(wěn)定時(shí),有可能會(huì)發(fā)生一個(gè)節(jié)點(diǎn) Ping 不通 Master 節(jié)點(diǎn),則會(huì)認(rèn)為 Master 節(jié)點(diǎn)發(fā)生了故障,然后重新選出一個(gè) Master 節(jié)點(diǎn),這就會(huì)導(dǎo)致在一個(gè)集群內(nèi)出現(xiàn)多個(gè) Master 節(jié)點(diǎn)。
當(dāng)在一個(gè)集群中有多個(gè) Master 節(jié)點(diǎn)時(shí),就有可能會(huì)導(dǎo)致數(shù)據(jù)丟失。我們稱這種現(xiàn)象為腦裂。
事務(wù)日志
我們?cè)谏厦媪私獾剑琇ucene 為了加快寫(xiě)索引的速度,采用了延遲寫(xiě)入的策略。
雖然這種策略提高了寫(xiě)入的效率,但其弊端是,如果數(shù)據(jù)在內(nèi)存中還沒(méi)有持久化到磁盤(pán)上時(shí)發(fā)生了類似斷電等不可控情況,就可能丟失數(shù)據(jù)。
為了避免丟失數(shù)據(jù),Elasticsearch 添加了事務(wù)日志(Translog),事務(wù)日志記錄了所有還沒(méi)有被持久化磁盤(pán)的數(shù)據(jù)。
Elasticsearch 寫(xiě)索引的具體過(guò)程如下:首先,當(dāng)有數(shù)據(jù)寫(xiě)入時(shí),為了提升寫(xiě)入的速度,并沒(méi)有數(shù)據(jù)直接寫(xiě)在磁盤(pán)上,而是先寫(xiě)入到內(nèi)存中,但是為了防止數(shù)據(jù)的丟失,會(huì)追加一份數(shù)據(jù)到事務(wù)日志里。
因?yàn)閮?nèi)存中的數(shù)據(jù)還會(huì)繼續(xù)寫(xiě)入,所以內(nèi)存中的數(shù)據(jù)并不是以段的形式存儲(chǔ)的,是檢索不到的。
總之,Elasticsearch 是一個(gè)準(zhǔn)實(shí)時(shí)的搜索引擎,而不是一個(gè)實(shí)時(shí)的搜索引擎。
此時(shí)的狀態(tài)如圖 7 所示:
圖 7:Elasticsearch 寫(xiě)數(shù)據(jù)的過(guò)程
然后,當(dāng)達(dá)到默認(rèn)的時(shí)間(1 秒鐘)或者內(nèi)存的數(shù)據(jù)達(dá)到一定量時(shí),會(huì)觸發(fā)一次刷新(Refresh)。
刷新的主要步驟如下:
- 將內(nèi)存中的數(shù)據(jù)刷新到一個(gè)新的段中,但是該段并沒(méi)有持久化到硬盤(pán)中,而是緩存在操作系統(tǒng)的文件緩存系統(tǒng)中。雖然數(shù)據(jù)還在內(nèi)存中,但是內(nèi)存里的數(shù)據(jù)和文件緩存系統(tǒng)里的數(shù)據(jù)有以下區(qū)別。
內(nèi)存使用的是 JVM 的內(nèi)存,而文件緩存系統(tǒng)使用的是操作系統(tǒng)的內(nèi)存;內(nèi)存的數(shù)據(jù)不是以段的形式存儲(chǔ)的,并且可以繼續(xù)向內(nèi)存里寫(xiě)數(shù)據(jù)。文件緩存系統(tǒng)中的數(shù)據(jù)是以段的形式存儲(chǔ)的,所以只能讀,不能寫(xiě);內(nèi)存中的數(shù)據(jù)是搜索不到,文件緩存系統(tǒng)中的數(shù)據(jù)是可以搜索的。
- 打開(kāi)保存在文件緩存系統(tǒng)中的段,使其可被搜索。
- 清空內(nèi)存,準(zhǔn)備接收新的數(shù)據(jù)。日志不做清空處理。
此時(shí)的狀態(tài)如圖 8 所示:
圖 8:Elasticsearch 寫(xiě)數(shù)據(jù)的過(guò)程
刷新(Flush)。當(dāng)日志數(shù)據(jù)的大小超過(guò) 512MB 或者時(shí)間超過(guò) 30 分鐘時(shí),需要觸發(fā)一次刷新。
刷新的主要步驟如下:
- 在文件緩存系統(tǒng)中創(chuàng)建一個(gè)新的段,并把內(nèi)存中的數(shù)據(jù)寫(xiě)入,使其可被搜索。
- 清空內(nèi)存,準(zhǔn)備接收新的數(shù)據(jù)。
- 將文件系統(tǒng)緩存中的數(shù)據(jù)通過(guò) Fsync 函數(shù)刷新到硬盤(pán)中。
- 生成提交點(diǎn)。
- 刪除舊的日志,創(chuàng)建一個(gè)空的日志。
此時(shí)的狀態(tài)如圖 9 所示:
圖 9:Elasticsearch 寫(xiě)數(shù)據(jù)的過(guò)程
由上面索引創(chuàng)建的過(guò)程可知,內(nèi)存里面的數(shù)據(jù)并沒(méi)有直接被刷新(Flush)到硬盤(pán)中,而是被刷新(Refresh)到了文件緩存系統(tǒng)中,這主要是因?yàn)槌志没瘮?shù)據(jù)十分耗費(fèi)資源,頻繁地調(diào)用會(huì)使寫(xiě)入的性能急劇下降。
所以 Elasticsearch,為了提高寫(xiě)入的效率,利用了文件緩存系統(tǒng)和內(nèi)存來(lái)加速寫(xiě)入時(shí)的性能,并使用日志來(lái)防止數(shù)據(jù)的丟失。
在需要重啟時(shí),Elasticsearch 不僅要根據(jù)提交點(diǎn)去加載已經(jīng)持久化過(guò)的段,還需要根據(jù) Translog 里的記錄,把未持久化的數(shù)據(jù)重新持久化到磁盤(pán)上。
根據(jù)上面對(duì) Elasticsearch,寫(xiě)操作流程的介紹,我們可以整理出一個(gè)索引數(shù)據(jù)所要經(jīng)歷的幾個(gè)階段,以及每個(gè)階段的數(shù)據(jù)的存儲(chǔ)方式和作用,如圖 10 所示:
圖 10:Elasticsearch 寫(xiě)操作流程
在集群中寫(xiě)索引
假設(shè)我們有如圖 11 所示(圖片來(lái)自官網(wǎng))的一個(gè)集群,該集群由三個(gè)節(jié)點(diǎn)組成(Node 1、Node 2 和 Node 3),包含一個(gè)由兩個(gè)主分片和每個(gè)主分片由兩個(gè)副本分片組成的索引。
圖 11:寫(xiě)索引
其中,標(biāo)星號(hào)的 Node 1 是 Master 節(jié)點(diǎn),負(fù)責(zé)管理整個(gè)集群的狀態(tài);p1 和 p2 是主分片;r0 和 r1 是副本分片。為了達(dá)到高可用,Master 節(jié)點(diǎn)避免將主分片和副本放在同一個(gè)節(jié)點(diǎn)。
將數(shù)據(jù)分片是為了提高可處理數(shù)據(jù)的容量和易于進(jìn)行水平擴(kuò)展,為分片做副本是為了提高集群的穩(wěn)定性和提高并發(fā)量。
在主分片掛掉后,會(huì)從副本分片中選舉出一個(gè)升級(jí)為主分片,當(dāng)副本升級(jí)為主分片后,由于少了一個(gè)副本分片,所以集群狀態(tài)會(huì)從 Green 改變?yōu)?Yellow,但是此時(shí)集群仍然可用。
在一個(gè)集群中有一個(gè)分片的主分片和副本分片都掛掉后,集群狀態(tài)會(huì)由 Yellow 改變?yōu)?Red,集群狀態(tài)為 Red 時(shí)集群不可正常使用。
由上面的步驟可知,副本分片越多,集群的可用性就越高,但是由于每個(gè)分片都相當(dāng)于一個(gè) Lucene 的索引文件,會(huì)占用一定的文件句柄、內(nèi)存及 CPU,并且分片間的數(shù)據(jù)同步也會(huì)占用一定的網(wǎng)絡(luò)帶寬,所以,索引的分片數(shù)和副本數(shù)并不是越多越好。
寫(xiě)索引時(shí)只能寫(xiě)在主分片上,然后同步到副本上,那么,一個(gè)數(shù)據(jù)應(yīng)該被寫(xiě)在哪個(gè)分片上呢?
如圖 10 所示,如何知道一個(gè)數(shù)據(jù)應(yīng)該被寫(xiě)在 p0 還是 p1 上呢答案就是路由(routing),路由公式如下:
- shard = hash(routing)%number_of_primary_shards
其中,Routing 是一個(gè)可選擇的值,默認(rèn)是文檔的 _id(文檔的主鍵,文檔在創(chuàng)建時(shí),如果文檔的 _id 已經(jīng)存在,則進(jìn)行更新,如果不存在則創(chuàng)建)。
后面會(huì)介紹如何通過(guò)自定義 Routing 參數(shù)使查詢落在一個(gè)分片中,而不用查詢所有的分片,從而提升查詢的性能。
Routing 通過(guò) Hash 函數(shù)生成一個(gè)數(shù)字,將這個(gè)數(shù)字除以 number_of_primary_shards(分片的數(shù)量)后得到余數(shù)。
這個(gè)分布在 0 到 number_of_primary_shards - 1 之間的余數(shù),就是我們所尋求的文檔所在分片的位置。
這也就說(shuō)明了一旦分片數(shù)定下來(lái)就不能再改變的原因,因?yàn)榉制瑪?shù)改變之后,所有之前的路由值都會(huì)變得無(wú)效,前期創(chuàng)建的文檔也就找不到了。
由于在 Elasticsearch 集群中每個(gè)節(jié)點(diǎn)都知道集群中的文檔的存放位置(通過(guò)路由公式定位),所以每個(gè)節(jié)點(diǎn)都有處理讀寫(xiě)請(qǐng)求的能力。
在一個(gè)寫(xiě)請(qǐng)求被發(fā)送到集群中的一個(gè)節(jié)點(diǎn)后,此時(shí),該節(jié)點(diǎn)被稱為協(xié)調(diào)點(diǎn)(Coordinating Node),協(xié)調(diào)點(diǎn)會(huì)根據(jù)路由公式計(jì)算出需要寫(xiě)到哪個(gè)分片上,再將請(qǐng)求轉(zhuǎn)發(fā)到該分片的主分片節(jié)點(diǎn)上。
圖 12:寫(xiě)索引
寫(xiě)操作的流程如下(參考圖 11,圖片來(lái)自官網(wǎng)):
- 客戶端向 Node 1(協(xié)調(diào)節(jié)點(diǎn))發(fā)送寫(xiě)請(qǐng)求。
- Node 1 通過(guò)文檔的 _id(默認(rèn)是 _id,但不表示一定是 _id)確定文檔屬于哪個(gè)分片(在本例中是編號(hào)為 0 的分片)。請(qǐng)求會(huì)被轉(zhuǎn)發(fā)到主分片所在的節(jié)點(diǎn) Node 3 上。
- Node 3 在主分片上執(zhí)行請(qǐng)求,如果成功,則將請(qǐng)求并行轉(zhuǎn)發(fā)到 Node 1 和 Node 2 的副本分片上。
一旦所有的副本分片都報(bào)告成功(默認(rèn)),則 Node 3 將向協(xié)調(diào)節(jié)點(diǎn)報(bào)告成功,協(xié)調(diào)節(jié)點(diǎn)向客戶端報(bào)告成功。
集群中的查詢流程
根據(jù) Routing 字段進(jìn)行的單個(gè)文檔的查詢,在 Elasticsearch 集群中可以在主分片或者副本分片上進(jìn)行。
圖 13
查詢字段剛好是 Routing 的分片字段如“_id”的查詢流程如下(見(jiàn)圖 12,圖片來(lái)自官網(wǎng)):
- 客戶端向集群發(fā)送查詢請(qǐng)求,集群再隨機(jī)選擇一個(gè)節(jié)點(diǎn)作為協(xié)調(diào)點(diǎn)(Node 1),負(fù)責(zé)處理這次查詢。
- Node 1 使用文檔的 routing id 來(lái)計(jì)算要查詢的文檔在哪個(gè)分片上(在本例中落在了 0 分片上)分片 0 的副本分片存在所有的三個(gè)節(jié)點(diǎn)上。
在這種情況下,協(xié)調(diào)節(jié)點(diǎn)可以把請(qǐng)求轉(zhuǎn)發(fā)到任意節(jié)點(diǎn),本例將請(qǐng)求轉(zhuǎn)發(fā)到 Node 2 上。
- Node 2 執(zhí)行查找,并將查找結(jié)果返回給協(xié)調(diào)節(jié)點(diǎn) Node 1,Node 1 再將文檔返回給客戶端。
當(dāng)一個(gè)搜索請(qǐng)求被發(fā)送到某個(gè)節(jié)點(diǎn)時(shí),這個(gè)節(jié)點(diǎn)就變成了協(xié)調(diào)節(jié)點(diǎn)(Node 1)。
協(xié)調(diào)節(jié)點(diǎn)的任務(wù)是廣播查詢請(qǐng)求到所有分片(主分片或者副本分片),并將它們的響應(yīng)結(jié)果整合成全局排序后的結(jié)果集合。
由上面步驟 3 所示,默認(rèn)返回給協(xié)調(diào)節(jié)點(diǎn)并不是所有的數(shù)據(jù),而是只有文檔的 id 和得分 score,因?yàn)槲覀冎环祷亟o用戶 size 條數(shù)據(jù),所以這樣做的好處是可以節(jié)省很多帶寬,特別是 from 很大時(shí)。
協(xié)調(diào)節(jié)點(diǎn)對(duì)收集回來(lái)的數(shù)據(jù)進(jìn)行排序后,找到要返回的 size 條數(shù)據(jù)的 id,再根據(jù) id 查詢要返回的數(shù)據(jù),比如 title、content 等。
圖 14
取回?cái)?shù)據(jù)等流程如下(見(jiàn)圖 13,圖片來(lái)自官網(wǎng)):
- Node 3 進(jìn)行二次排序來(lái)找出要返回的文檔 id,并向相關(guān)的分片提交多個(gè)獲得文檔詳情的請(qǐng)求。
- 每個(gè)分片加載文檔,并將文檔返回給 Node 3。
- 一旦所有的文檔都取回了,Node 3 就返回結(jié)果給客戶端。
協(xié)調(diào)節(jié)點(diǎn)收集各個(gè)分片查詢出來(lái)的數(shù)據(jù),再進(jìn)行二次排序,然后選擇需要被取回的文檔。
例如,如果我們的查詢指定了{(lán)"from": 20, "size": 10},那么我們需要在每個(gè)分片中查詢出來(lái)得分較高的 20+10 條數(shù)據(jù),協(xié)調(diào)節(jié)點(diǎn)在收集到 30×n(n 為分片數(shù))條數(shù)據(jù)后再進(jìn)行排序。
排序位置在 0-20 的結(jié)果會(huì)被丟棄,只有從第 21 個(gè)開(kāi)始的 10 個(gè)結(jié)果需要被取回。這些文檔可能來(lái)自多個(gè)甚至全部分片。
由上面的搜索策略可以知道,在查詢時(shí)深翻(Deep Pagination)并不是一種好方法。
因?yàn)樯罘瓡r(shí),from 會(huì)很大,這時(shí)的排序過(guò)程可能會(huì)變得非常沉重,會(huì)占用大量的 CPU、內(nèi)存和帶寬。因?yàn)檫@個(gè)原因,所以強(qiáng)烈建議慎重使用深翻。
分片可以減少每個(gè)片上的數(shù)據(jù)量,加快查詢的速度,但是在查詢時(shí),協(xié)調(diào)節(jié)點(diǎn)要在收集數(shù)(from+size)×n 條數(shù)據(jù)后再做一次全局排序。
若這個(gè)數(shù)據(jù)量很大,則也會(huì)占用大量的 CPU、內(nèi)存、帶寬等,并且分片查詢的速度取決于最慢的分片查詢的速度,所以分片數(shù)并不是越多越好。
作者:錢(qián)丁君
簡(jiǎn)介:就職于永輝云創(chuàng),擔(dān)任基礎(chǔ)架構(gòu)開(kāi)發(fā),有多年基礎(chǔ)架構(gòu)經(jīng)驗(yàn),主要從事電商新零售、互聯(lián)網(wǎng)金融行業(yè)。技術(shù)發(fā)燒友,涉獵廣泛。熟悉 Java 微服務(wù)架構(gòu)搭建、推進(jìn)、衍化;多種中間件搭建、封裝和優(yōu)化;自動(dòng)化測(cè)試開(kāi)發(fā)、代碼規(guī)約插件開(kāi)發(fā)、代碼規(guī)范推進(jìn);容器化技術(shù) Docker、容器化編排技術(shù) Kubernetes,有較為豐富的運(yùn)維經(jīng)驗(yàn)。
【51CTO原創(chuàng)稿件,合作站點(diǎn)轉(zhuǎn)載請(qǐng)注明原文作者和出處為51CTO.com】