袁庭新老師ES系列02節(jié)|Lucene基本概念
前言
上一節(jié)袁老師給大家介紹了全文檢索的基本概念。什么技術(shù)可以實(shí)現(xiàn)全文檢索呢?那就是大名鼎鼎的Lucene。Lucence是一個(gè)很容易上手,純Java語(yǔ)言的全文索引檢索工具包。接下來(lái)就跟著袁老師來(lái)探索Lucence這個(gè)技術(shù)。
一. Apache Lucene概述
1.1 Lucene介紹
Lucene是Apache下的一個(gè)開(kāi)放源代碼的全文檢索引擎工具包。提供了完整的查詢引擎和索引引擎,部分文本分析引擎(英文與德文兩種西方語(yǔ)言)。Lucene的目的是為軟件開(kāi)發(fā)人員提供一個(gè)簡(jiǎn)單易用的工具包,以方便的在目標(biāo)系統(tǒng)中實(shí)現(xiàn)全文檢索的功能??梢允褂肔ucene實(shí)現(xiàn)全文檢索。
1.2 Lucene適用場(chǎng)景
這項(xiàng)技術(shù)幾乎適用于任何需要結(jié)構(gòu)化搜索、全文搜索、分面、跨高維向量的最近鄰搜索、拼寫(xiě)糾正或查詢建議的應(yīng)用程序。
在應(yīng)用中為數(shù)據(jù)庫(kù)中的數(shù)據(jù)提供全文檢索實(shí)現(xiàn)。
開(kāi)發(fā)獨(dú)立的搜索引擎服務(wù)、系統(tǒng)。
對(duì)于數(shù)據(jù)量大、數(shù)據(jù)結(jié)構(gòu)不固定的數(shù)據(jù)可采用全文檢索方式搜索。
1.3 Lucene功能
Lucene通過(guò)一個(gè)簡(jiǎn)單的API提供了強(qiáng)大的功能。
1.可擴(kuò)展的高性能索引
在現(xiàn)代硬件上超過(guò)800GB/小時(shí)
小RAM要求——只有1MB堆
增量索引與批量索引一樣快
索引大小大約為索引文本大小的20-30%
2.強(qiáng)大、準(zhǔn)確、高效的搜索算法
排名搜索——最好的結(jié)果首先返回
許多強(qiáng)大的查詢類型:短語(yǔ)查詢、通配符查詢、鄰近查詢、范圍查詢等
現(xiàn)場(chǎng)搜索(例如標(biāo)題、作者、內(nèi)容)
高維向量的最近鄰搜索
按任何字段排序
合并結(jié)果的多索引搜索
允許同時(shí)更新和搜索
靈活的刻面、突出顯示、連接和結(jié)果分組
快速、節(jié)省內(nèi)存和容錯(cuò)的建議器
可插拔排名模型,包括向量空間模型和Okapi BM25
可配置的存儲(chǔ)引擎(編解碼器)
3.跨平臺(tái)解決方案
可作為Apache許可證下的開(kāi)源軟件,它允許您在商業(yè)和開(kāi)源程序中使用Lucene
100%純Java
其他可用的與索引兼容的編程語(yǔ)言的實(shí)現(xiàn)
1.4 Lucene架構(gòu)
結(jié)構(gòu)化數(shù)據(jù)搜索與非結(jié)構(gòu)化數(shù)據(jù)搜索對(duì)比分析見(jiàn)下圖:

搜索應(yīng)用程序和Lucene之間的關(guān)系,也反映了利用Lucene構(gòu)建搜索應(yīng)用程序的流程:

二. Lucene基本概念
在深入解讀Lucene之前,先了解下Lucene的幾個(gè)基本概念,以及這幾個(gè)概念背后隱藏的一些內(nèi)容。

2.1 Index(索引)
類似數(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è)收納箱可以塞入各種類型的文檔,文檔里的內(nèi)容可以任意定義,Lucene都能對(duì)其進(jìn)行索引。
2.2 Document(文檔)
用戶提供的源是一條條記錄,它們可以是文本文件、字符串或者數(shù)據(jù)庫(kù)表的一條記錄等等。一條記錄經(jīng)過(guò)索引之后,就是以一個(gè)Document的形式存儲(chǔ)在索引文件中的。用戶進(jìn)行搜索,也是以Document列表的形式返回。
一個(gè)Index內(nèi)會(huì)包含多個(gè)Document。寫(xiě)入Index的Document會(huì)被分配一個(gè)唯一的ID,即Sequence Number(序列號(hào),更多被叫做DocId)。
2.3 Field(字段)
一個(gè)Document會(huì)由一個(gè)或多個(gè)Field組成,F(xiàn)ield是Lucene中數(shù)據(jù)索引的最小定義單位。Lucene提供多種不同類型的Field,例如StringField、TextField、LongFiled或NumericDocValuesField等,Lucene根據(jù)Field的類型(FieldType)來(lái)判斷該數(shù)據(jù)要采用哪種類型的索引方式(Invert Index、Store Field、DocValues或N-dimensional等)。
例如,一篇文章可以包含“標(biāo)題”、“正文”、“最后修改時(shí)間”等信息域,這些信息域就是通過(guò)Field在Document中存儲(chǔ)的。
Field有兩個(gè)屬性可選:存儲(chǔ)和索引。通過(guò)存儲(chǔ)屬性你可以控制是否對(duì)這個(gè)Field進(jìn)行存儲(chǔ);通過(guò)索引屬性你可以控制是否對(duì)該Field進(jìn)行索引。
如果對(duì)標(biāo)題和正文進(jìn)行全文搜索,所以我們要把索引屬性設(shè)置為真,同時(shí)我們希望能直接從搜索結(jié)果中提取文章標(biāo)題,所以我們把標(biāo)題域的存儲(chǔ)屬性設(shè)置為真。但是由于正文域太大了,我們?yōu)榱丝s小索引文件大小,將正文域的存儲(chǔ)屬性設(shè)置為假,當(dāng)需要時(shí)再直接讀取文件;我們只是希望能從搜索解果中提取最后修改時(shí)間,不需要對(duì)它進(jìn)行搜索,所以我們把最后修改時(shí)間域的存儲(chǔ)屬性設(shè)置為真,索引屬性設(shè)置為假。上面的三個(gè)域涵蓋了兩個(gè)屬性的三種組合,還有一種全為假的沒(méi)有用到,事實(shí)上Field不允許你那么設(shè)置,因?yàn)榧炔淮鎯?chǔ)又不索引的域是沒(méi)有意義的。
2.4 Term和Term Dictionary
Lucene中索引和搜索的最小單位,一個(gè)Field會(huì)由一個(gè)或多個(gè)Term組成,Term是由Field經(jīng)過(guò)Analyzer(分詞)產(chǎn)生。Term Dictionary即Term詞典,是根據(jù)條件查找Term的基本索引。
Term由兩部分組成:它表示的詞語(yǔ)和這個(gè)詞語(yǔ)所出現(xiàn)的Field的名稱。
2.5 Segment(段)
一個(gè)Index會(huì)由一個(gè)或多個(gè)sub-index構(gòu)成,sub-index被稱為Segment。Lucene的Segment設(shè)計(jì)思想,與LSM類似但又有些不同,繼承了LSM中數(shù)據(jù)寫(xiě)入的優(yōu)點(diǎn),但是在查詢上只能提供近實(shí)時(shí)而非實(shí)時(shí)查詢。
Lucene中的數(shù)據(jù)寫(xiě)入會(huì)先寫(xiě)內(nèi)存的一個(gè)Buffer(類似LSM的MemTable,但是不可讀),當(dāng)Buffer內(nèi)數(shù)據(jù)到一定量后會(huì)被Flush成一個(gè)Segment,每個(gè)Segment有自己獨(dú)立的索引,可獨(dú)立被查詢,但數(shù)據(jù)永遠(yuǎn)不能被更改。這種模式避免了隨機(jī)寫(xiě),數(shù)據(jù)寫(xiě)入都是Batch和Append,能達(dá)到很高的吞吐量。Segment中寫(xiě)入的文檔不可被修改,但可被刪除,刪除的方式也不是在文件內(nèi)部原地更改,而是會(huì)由另外一個(gè)文件保存需要被刪除的文檔的DocID,保證數(shù)據(jù)文件不可被修改。Index的查詢需要對(duì)多個(gè)Segment進(jìn)行查詢并對(duì)結(jié)果進(jìn)行合并,還需要處理被刪除的文檔,為了對(duì)查詢進(jìn)行優(yōu)化,Lucene會(huì)有策略對(duì)多個(gè)Segment進(jìn)行合并,這點(diǎn)與LSM對(duì)SSTable的Merge類似。
Segment在被Flush或Commit之前,數(shù)據(jù)保存在內(nèi)存中,是不可被搜索的,這也就是為什么Lucene被稱為提供近實(shí)時(shí)而非實(shí)時(shí)查詢的原因。讀了它的代碼后,發(fā)現(xiàn)它并不是不能實(shí)現(xiàn)數(shù)據(jù)寫(xiě)入即可查,只是實(shí)現(xiàn)起來(lái)比較復(fù)雜。原因是Lucene中數(shù)據(jù)搜索依賴構(gòu)建的索引(例如倒排依賴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í)索引以及另外一套查詢機(jī)制,有一定復(fù)雜度。
2.6 Sequence Number(序列號(hào))
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è)兩階段的查詢,第一階段是通過(guò)給定的Term的條件找到所有Doc的DocId列表,第二階段是根據(jù)DocId查找Doc。Lucene提供基于Term的搜索功能,也提供基于DocId的查詢功能。
DocId采用一個(gè)從0開(kāi)始底層的Int32值,是一個(gè)比較大的優(yōu)化,同時(shí)體現(xiàn)在數(shù)據(jù)壓縮和查詢效率上。例如數(shù)據(jù)壓縮上的Delta策略、ZigZag編碼,以及倒排列表上采用的SkipList等,這些優(yōu)化后續(xù)會(huì)詳述。
三. 結(jié)語(yǔ)
我們來(lái)總結(jié)下,這一節(jié)的學(xué)習(xí)內(nèi)容。袁老師給大家介紹了Lucene技術(shù),以及Lucene適用場(chǎng)景和Lucene功能,然后又介紹了Lucene架構(gòu)。第二個(gè)模塊介紹了Lucene基本概念,其中重點(diǎn)的概念包括:Index(索引)、Document(文檔)、Field(字段)、Term和Term Dictionary、Segment(段)、Sequence Number(序列號(hào))等。這些概念是我們繼續(xù)去探索新技術(shù)的基礎(chǔ),你學(xué)會(huì)了嘛?