摘要:基本概念在深入解讀之前,先了解下的幾個(gè)基本概念,以及這幾個(gè)概念背后隱藏的一些東西。如圖是一個(gè)內(nèi)的基本組成,內(nèi)數(shù)據(jù)只是一個(gè)抽象表示,不代表其內(nèi)部真實(shí)數(shù)據(jù)結(jié)構(gòu)。即詞典,是根據(jù)條件查找的基本索引。
前言
Apache Lucene是一個(gè)開(kāi)源的高性能、可擴(kuò)展的信息檢索引擎,提供了強(qiáng)大的數(shù)據(jù)檢索能力。Lucene已經(jīng)發(fā)展了很多年,其功能越來(lái)越強(qiáng)大,架構(gòu)也越來(lái)越精細(xì)。它目前不僅僅能支持全文索引,也能夠提供多種其他類(lèi)型的索引方式,來(lái)滿(mǎn)足不同類(lèi)型的查詢(xún)需求。
基于Lucene的開(kāi)源項(xiàng)目有很多,最知名的要屬Elasticsearch和Solr,如果說(shuō)Elasticsearch和Solr是一輛設(shè)計(jì)精美、性能卓越的跑車(chē),那Lucene就是為其提供強(qiáng)大動(dòng)力的引擎。為了駕馭這輛跑車(chē)讓它跑的更快更穩(wěn)定,我們需要對(duì)它的引擎研究透徹。
在此之前我們?cè)趯?zhuān)欄已經(jīng)發(fā)表了多篇文章來(lái)剖析Elasticsearch的數(shù)據(jù)模型、讀寫(xiě)路徑、分布式架構(gòu)以及Data/Meta一致性等問(wèn)題,這篇文章之后我們會(huì)陸續(xù)發(fā)表一系列的關(guān)于Lucene的原理和源碼解讀,來(lái)全面解析Lucene的數(shù)據(jù)模型和數(shù)據(jù)讀寫(xiě)路徑。
Lucene官方對(duì)自己的優(yōu)勢(shì)總結(jié)為幾點(diǎn):
Scalable, High-Performance Indexing
Powerful, Accurate and Efficient Search Algorithms
希望通過(guò)我們的系列文章,能夠讓讀者理解Lucene是如何達(dá)到這些目標(biāo)的。
整個(gè)分析會(huì)基于Lucene 7.2.1版本,在讀這篇文章之前,需要有一定的知識(shí)基礎(chǔ),例如了解基本的搜索和索引原理,知道什么是倒排、分詞、相關(guān)性等基本概念,了解Lucene的基本使用,例如Directory、IndexWriter、IndexSearcher等。
基本概念在深入解讀Lucene之前,先了解下Lucene的幾個(gè)基本概念,以及這幾個(gè)概念背后隱藏的一些東西。
如圖是一個(gè)Index內(nèi)的基本組成,Segment內(nèi)數(shù)據(jù)只是一個(gè)抽象表示,不代表其內(nèi)部真實(shí)數(shù)據(jù)結(jié)構(gòu)。
Index(索引)
類(lèi)似數(shù)據(jù)庫(kù)的表的概念,但是與傳統(tǒng)表的概念會(huì)有很大的不同。傳統(tǒng)關(guān)系型數(shù)據(jù)庫(kù)或者NoSQL數(shù)據(jù)庫(kù)的表,在創(chuàng)建時(shí)至少要定義表的Scheme,定義表的主鍵或列等,會(huì)有一些明確定義的約束。而Lucene的Index,則完全沒(méi)有約束。Lucene的Index可以理解為一個(gè)文檔收納箱,你可以往內(nèi)部塞入新的文檔,或者從里面拿出文檔,但如果你要修改里面的某個(gè)文檔,則必須先拿出來(lái)修改后再塞回去。這個(gè)收納箱可以塞入各種類(lèi)型的文檔,文檔里的內(nèi)容可以任意定義,Lucene都能對(duì)其進(jìn)行索引。
Document(文檔)
類(lèi)似數(shù)據(jù)庫(kù)內(nèi)的行或者文檔數(shù)據(jù)庫(kù)內(nèi)的文檔的概念,一個(gè)Index內(nèi)會(huì)包含多個(gè)Document。寫(xiě)入Index的Document會(huì)被分配一個(gè)唯一的ID,即Sequence Number(更多被叫做DocId),關(guān)于Sequence Number后面會(huì)再細(xì)說(shuō)。
Field(字段)
一個(gè)Document會(huì)由一個(gè)或多個(gè)Field組成,F(xiàn)ield是Lucene中數(shù)據(jù)索引的最小定義單位。Lucene提供多種不同類(lèi)型的Field,例如StringField、TextField、LongFiled或NumericDocValuesField等,Lucene根據(jù)Field的類(lèi)型(FieldType)來(lái)判斷該數(shù)據(jù)要采用哪種類(lèi)型的索引方式(Invert Index、Store Field、DocValues或N-dimensional等),關(guān)于Field和FieldType后面會(huì)再細(xì)說(shuō)。
Term和Term Dictionary
Lucene中索引和搜索的最小單位,一個(gè)Field會(huì)由一個(gè)或多個(gè)Term組成,Term是由Field經(jīng)過(guò)Analyzer(分詞)產(chǎn)生。Term Dictionary即Term詞典,是根據(jù)條件查找Term的基本索引。
Segment
一個(gè)Index會(huì)由一個(gè)或多個(gè)sub-index構(gòu)成,sub-index被稱(chēng)為Segment。Lucene的Segment設(shè)計(jì)思想,與LSM類(lèi)似但又有些不同,繼承了LSM中數(shù)據(jù)寫(xiě)入的優(yōu)點(diǎn),但是在查詢(xún)上只能提供近實(shí)時(shí)而非實(shí)時(shí)查詢(xún)。
Lucene中的數(shù)據(jù)寫(xiě)入會(huì)先寫(xiě)內(nèi)存的一個(gè)Buffer(類(lèi)似LSM的MemTable,但是不可讀),當(dāng)Buffer內(nèi)數(shù)據(jù)到一定量后會(huì)被flush成一個(gè)Segment,每個(gè)Segment有自己獨(dú)立的索引,可獨(dú)立被查詢(xún),但數(shù)據(jù)永遠(yuǎn)不能被更改。這種模式避免了隨機(jī)寫(xiě),數(shù)據(jù)寫(xiě)入都是Batch和Append,能達(dá)到很高的吞吐量。Segment中寫(xiě)入的文檔不可被修改,但可被刪除,刪除的方式也不是在文件內(nèi)部原地更改,而是會(huì)由另外一個(gè)文件保存需要被刪除的文檔的DocID,保證數(shù)據(jù)文件不可被修改。Index的查詢(xún)需要對(duì)多個(gè)Segment進(jìn)行查詢(xún)并對(duì)結(jié)果進(jìn)行合并,還需要處理被刪除的文檔,為了對(duì)查詢(xún)進(jìn)行優(yōu)化,Lucene會(huì)有策略對(duì)多個(gè)Segment進(jìn)行合并,這點(diǎn)與LSM對(duì)SSTable的Merge類(lèi)似。
Segment在被flush或commit之前,數(shù)據(jù)保存在內(nèi)存中,是不可被搜索的,這也就是為什么Lucene被稱(chēng)為提供近實(shí)時(shí)而非實(shí)時(shí)查詢(xún)的原因。讀了它的代碼后,發(fā)現(xiàn)它并不是不能實(shí)現(xiàn)數(shù)據(jù)寫(xiě)入即可查,只是實(shí)現(xiàn)起來(lái)比較復(fù)雜。原因是Lucene中數(shù)據(jù)搜索依賴(lài)構(gòu)建的索引(例如倒排依賴(lài)Term Dictionary),Lucene中對(duì)數(shù)據(jù)索引的構(gòu)建會(huì)在Segment flush時(shí),而非實(shí)時(shí)構(gòu)建,目的是為了構(gòu)建最高效索引。當(dāng)然它可引入另外一套索引機(jī)制,在數(shù)據(jù)實(shí)時(shí)寫(xiě)入時(shí)即構(gòu)建,但這套索引實(shí)現(xiàn)會(huì)與當(dāng)前Segment內(nèi)索引不同,需要引入額外的寫(xiě)入時(shí)索引以及另外一套查詢(xún)機(jī)制,有一定復(fù)雜度。
Sequence Number
Sequence Number(后面統(tǒng)一叫DocId)是Lucene中一個(gè)很重要的概念,數(shù)據(jù)庫(kù)內(nèi)通過(guò)主鍵來(lái)唯一標(biāo)識(shí)一行,而Lucene的Index通過(guò)DocId來(lái)唯一標(biāo)識(shí)一個(gè)Doc。不過(guò)有幾點(diǎn)要特別注意:
DocId實(shí)際上并不在Index內(nèi)唯一,而是Segment內(nèi)唯一,Lucene這么做主要是為了做寫(xiě)入和壓縮優(yōu)化。那既然在Segment內(nèi)才唯一,又是怎么做到在Index級(jí)別來(lái)唯一標(biāo)識(shí)一個(gè)Doc呢?方案很簡(jiǎn)單,Segment之間是有順序的,舉個(gè)簡(jiǎn)單的例子,一個(gè)Index內(nèi)有兩個(gè)Segment,每個(gè)Segment內(nèi)分別有100個(gè)Doc,在Segment內(nèi)DocId都是0-100,轉(zhuǎn)換到Index級(jí)的DocId,需要將第二個(gè)Segment的DocId范圍轉(zhuǎn)換為100-200。
DocId在Segment內(nèi)唯一,取值從0開(kāi)始遞增。但不代表DocId取值一定是連續(xù)的,如果有Doc被刪除,那可能會(huì)存在空洞。
一個(gè)文檔對(duì)應(yīng)的DocId可能會(huì)發(fā)生變化,主要是發(fā)生在Segment合并時(shí)。
Lucene內(nèi)最核心的倒排索引,本質(zhì)上就是Term到所有包含該Term的文檔的DocId列表的映射。所以Lucene內(nèi)部在搜索的時(shí)候會(huì)是一個(gè)兩階段的查詢(xún),第一階段是通過(guò)給定的Term的條件找到所有Doc的DocId列表,第二階段是根據(jù)DocId查找Doc。Lucene提供基于Term的搜索功能,也提供基于DocId的查詢(xún)功能。
DocId采用一個(gè)從0開(kāi)始底層的Int32值,是一個(gè)比較大的優(yōu)化,同時(shí)體現(xiàn)在數(shù)據(jù)壓縮和查詢(xún)效率上。例如數(shù)據(jù)壓縮上的Delta策略、ZigZag編碼,以及倒排列表上采用的SkipList等,這些優(yōu)化后續(xù)會(huì)詳述。
索引類(lèi)型Lucene中支持豐富的字段類(lèi)型,每種字段類(lèi)型確定了支持的數(shù)據(jù)類(lèi)型以及索引方式,目前支持的字段類(lèi)型包括LongPoint、TextField、StringField、NumericDocValuesField等。
如圖是Lucene中對(duì)于不同類(lèi)型Field定義的一個(gè)基本關(guān)系,所有字段類(lèi)都會(huì)繼承自Field這個(gè)類(lèi),F(xiàn)ield包含3個(gè)重要屬性:name(String)、fieldsData(BytesRef)和type(FieldType)。name即字段的名稱(chēng),fieldsData即字段值,所有類(lèi)型的字段的值最終都會(huì)轉(zhuǎn)換為二進(jìn)制字節(jié)流來(lái)表示。type是字段類(lèi)型,確定了該字段被索引的方式。
FieldType是一個(gè)很重要的類(lèi),包含多個(gè)重要屬性,這些屬性的值決定了該字段被索引的方式。
Lucene提供的多種不同類(lèi)型的Field,本質(zhì)區(qū)別就兩個(gè):一是不同類(lèi)型值到fieldData定義了不同的轉(zhuǎn)換方式;二是定義了FieldType內(nèi)不同屬性不同取值的組合。這種模式下,你也能夠通過(guò)自定義數(shù)據(jù)以及組合FieldType內(nèi)索引參數(shù)來(lái)達(dá)到定制類(lèi)型的目的。
要理解Lucene能夠提供哪些索引方式,只需要理解FieldType內(nèi)每個(gè)屬性的具體含義,我們來(lái)一個(gè)一個(gè)看:
stored: 代表是否需要保存該字段,如果為false,則lucene不會(huì)保存這個(gè)字段的值,而搜索結(jié)果中返回的文檔只會(huì)包含保存了的字段。
tokenized: 代表是否做分詞,在lucene中只有TextField這一個(gè)字段需要做分詞。
termVector: 這篇文章很好的解釋了term vector的概念,簡(jiǎn)單來(lái)說(shuō),term vector保存了一個(gè)文檔內(nèi)所有的term的相關(guān)信息,包括Term值、出現(xiàn)次數(shù)(frequencies)以及位置(positions)等,是一個(gè)per-document inverted index,提供了根據(jù)docid來(lái)查找該文檔內(nèi)所有term信息的能力。對(duì)于長(zhǎng)度較小的字段不建議開(kāi)啟term verctor,因?yàn)橹恍枰匦伦鲆槐榉衷~即可拿到term信息,而針對(duì)長(zhǎng)度較長(zhǎng)或者分詞代價(jià)較大的字段,則建議開(kāi)啟term vector。Term vector的用途主要有兩個(gè),一是關(guān)鍵詞高亮,二是做文檔間的相似度匹配(more-like-this)。
omitNorms: Norms是normalization的縮寫(xiě),lucene允許每個(gè)文檔的每個(gè)字段都存儲(chǔ)一個(gè)normalization factor,是和搜索時(shí)的相關(guān)性計(jì)算有關(guān)的一個(gè)系數(shù)。Norms的存儲(chǔ)只占一個(gè)字節(jié),但是每個(gè)文檔的每個(gè)字段都會(huì)獨(dú)立存儲(chǔ)一份,且Norms數(shù)據(jù)會(huì)全部加載到內(nèi)存。所以若開(kāi)啟了Norms,會(huì)消耗額外的存儲(chǔ)空間和內(nèi)存。但若關(guān)閉了Norms,則無(wú)法做index-time boosting(elasticsearch官方建議使用query-time boosting來(lái)替代)以及l(fā)ength normalization。
indexOptions: Lucene提供倒排索引的5種可選參數(shù)(NONE、DOCS、DOCS_AND_FREQS、DOCS_AND_FREQS_AND_POSITIONS、DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS),用于選擇該字段是否需要被索引,以及索引哪些內(nèi)容。
docValuesType: DocValue是Lucene 4.0引入的一個(gè)正向索引(docid到field的一個(gè)列存),大大優(yōu)化了sorting、faceting或aggregation的效率。DocValues是一個(gè)強(qiáng)schema的存儲(chǔ)結(jié)構(gòu),開(kāi)啟DocValues的字段必須擁有嚴(yán)格一致的類(lèi)型,目前Lucene只提供NUMERIC、BINARY、SORTED、SORTED_NUMERIC和SORTED_SET五種類(lèi)型。
dimension:Lucene支持多維數(shù)據(jù)的索引,采取特殊的索引來(lái)優(yōu)化對(duì)多維數(shù)據(jù)的查詢(xún),這類(lèi)數(shù)據(jù)最典型的應(yīng)用場(chǎng)景是地理位置索引,一般經(jīng)緯度數(shù)據(jù)會(huì)采取這個(gè)索引方式。
來(lái)看下Lucene中對(duì)StringField的一個(gè)定義:
StringFiled有兩種類(lèi)型索引定義,TYPE_NOT_STORED和TYPE_STORED,唯一的區(qū)別是這個(gè)Field是否需要Store。從其他的幾個(gè)屬性也可以解讀出,StringFiled選擇omitNorms,需要進(jìn)行倒排索引并且不需要被分詞。
Elasticsearch數(shù)據(jù)類(lèi)型Elasticsearch內(nèi)對(duì)用戶(hù)輸入文檔內(nèi)Field的索引,也是按照Lucene能提供的幾種模式來(lái)提供。除了用戶(hù)能自定義的Field,Elasticsearch還有自己預(yù)留的系統(tǒng)字段,用作一些特殊的目的。這些字段映射到Lucene本質(zhì)上也是一個(gè)Field,與用戶(hù)自定義的Field無(wú)任何區(qū)別,只不過(guò)Elasticsearch根據(jù)這些系統(tǒng)字段不同的使用目的,定制有不同的索引方式。
舉個(gè)例子,上圖?是Elasticsearch內(nèi)兩個(gè)系統(tǒng)字段_version和_uid的FieldType定義,我們來(lái)解讀下它們的索引方式。Elasticsearch通過(guò)_uid字段唯一標(biāo)識(shí)一個(gè)文檔,通過(guò)_version字段來(lái)記錄該文檔當(dāng)前的版本。從這兩個(gè)字段的FieldType定義上可以看到,_uid字段會(huì)做倒排索引,不需要分詞,需要被Store。而_version字段則不需要被倒排索引,也不需要被Store,但是需要被正排索引。很好理解,因?yàn)開(kāi)uid需要被搜索,而_version不需要。但_version需要通過(guò)docId來(lái)查詢(xún),而且Elasticsearch內(nèi)versionMap內(nèi)需要通過(guò)docId做大量查詢(xún)且只需要查詢(xún)出_version字段,所以_version最合適的是被正排索引。
關(guān)于Elasticsearch內(nèi)系統(tǒng)字段全面的解析,可以看下這篇文章。
總結(jié)這篇文章主要介紹了Lucene的一些基本概念以及提供的索引類(lèi)型。后續(xù)我們會(huì)有一系列文章來(lái)解析Lucene提供的IndexWriter的寫(xiě)入流程,其In-Memory Buffer的結(jié)構(gòu)以及持久化后的索引文件結(jié)構(gòu),來(lái)了解Lucene為何能達(dá)到如此高效的數(shù)據(jù)索引性能。也會(huì)去解析IndexSearcher的查詢(xún)流程,以及一些特殊的查詢(xún)優(yōu)化的數(shù)據(jù)結(jié)構(gòu),來(lái)了解為何Lucene能提供如此高效的搜索和查詢(xún)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/69146.html
摘要:基本概念在深入解讀之前,先了解下的幾個(gè)基本概念,以及這幾個(gè)概念背后隱藏的一些東西。如圖是一個(gè)內(nèi)的基本組成,內(nèi)數(shù)據(jù)只是一個(gè)抽象表示,不代表其內(nèi)部真實(shí)數(shù)據(jù)結(jié)構(gòu)。即詞典,是根據(jù)條件查找的基本索引。 前言 Apache Lucene是一個(gè)開(kāi)源的高性能、可擴(kuò)展的信息檢索引擎,提供了強(qiáng)大的數(shù)據(jù)檢索能力。Lucene已經(jīng)發(fā)展了很多年,其功能越來(lái)越強(qiáng)大,架構(gòu)也越來(lái)越精細(xì)。它目前不僅僅能支持全文索引,也...
摘要:倒排索引是基于詞的搜索。關(guān)于倒排索引要學(xué)習(xí)搜索引擎,就需要了解倒排索引,要更加深刻地理解倒排索引,就要先了解什么是正排索引表。由于不是由記錄來(lái)確定屬性值,而是由屬性值來(lái)確定記錄的位置,因而稱(chēng)為倒排索引。 Lucene是什么? Lucene是apache軟件基金會(huì)4 jakarta項(xiàng)目組的一個(gè)子項(xiàng)目,是一個(gè)開(kāi)放源代碼的全文檢索引擎工具包,但它不是一個(gè)完整的全文檢索引擎,而是一個(gè)全文檢索引...
摘要:介紹如何優(yōu)化數(shù)值類(lèi)范圍查詢(xún)。查詢(xún)過(guò)程在中查詢(xún)是基于。在中為了查詢(xún)的這樣一個(gè)條件,會(huì)建立基于的倒排鏈。在單查詢(xún)上可能相比并沒(méi)有明顯優(yōu)勢(shì),甚至?xí)恍K詾榱酥С指咝У臄?shù)值類(lèi)或者多維度查詢(xún),引入類(lèi)。 前言 Lucene 是一個(gè)基于 Java 的全文信息檢索工具包,目前主流的搜索系統(tǒng)Elasticsearch和solr都是基于lucene的索引和搜索能力進(jìn)行。想要理解搜索系統(tǒng)的實(shí)現(xiàn)原理,就...
閱讀 2932·2023-04-26 01:49
閱讀 2066·2021-10-13 09:39
閱讀 2278·2021-10-11 11:09
閱讀 923·2019-08-30 15:53
閱讀 2817·2019-08-30 15:44
閱讀 916·2019-08-30 11:12
閱讀 2966·2019-08-29 17:17
閱讀 2371·2019-08-29 16:57