這篇文章將為大家詳細(xì)講解有關(guān)怎樣解析Lucene查詢原理,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。
創(chuàng)新互聯(lián)是一家專注于成都做網(wǎng)站、網(wǎng)站建設(shè)與策劃設(shè)計(jì),祥符網(wǎng)站建設(shè)哪家好?創(chuàng)新互聯(lián)做網(wǎng)站,專注于網(wǎng)站建設(shè)10多年,網(wǎng)設(shè)計(jì)領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:祥符等地區(qū)。祥符做網(wǎng)站價(jià)格咨詢:028-86922220
Lucene 是一個(gè)基于 Java 的全文信息檢索工具包,目前主流的搜索系統(tǒng)Elasticsearch和solr都是基于lucene的索引和搜索能力進(jìn)行。想要理解搜索系統(tǒng)的實(shí)現(xiàn)原理,就需要深入lucene這一層,看看lucene是如何存儲(chǔ)需要檢索的數(shù)據(jù),以及如何完成高效的數(shù)據(jù)檢索。
在數(shù)據(jù)庫中因?yàn)橛兴饕拇嬖冢部梢灾С趾芏喔咝У牟樵儾僮?。不過對比lucene,數(shù)據(jù)庫的查詢能力還是會(huì)弱很多,本文就將探索下lucene支持哪些查詢,并會(huì)重點(diǎn)選取幾類查詢分析lucene內(nèi)部是如何實(shí)現(xiàn)的。為了方便大家理解,我們會(huì)先簡單介紹下lucene里面的一些基本概念,然后展開lucene中的幾種數(shù)據(jù)存儲(chǔ)結(jié)構(gòu),理解了他們的存儲(chǔ)原理后就可以方便知道如何基于這些存儲(chǔ)結(jié)構(gòu)來實(shí)現(xiàn)高效的搜索。本文重點(diǎn)關(guān)注是lucene如何做到傳統(tǒng)數(shù)據(jù)庫較難做到的查詢,對于分詞,打分等功能不會(huì)展開介紹。
Lucene中包含了四種基本數(shù)據(jù)類型,分別是:
Index:索引,由很多的Document組成。
Document:由很多的Field組成,是Index和Search的最小單位。
Field:由很多的Term組成,包括Field Name和Field Value。
Term:由很多的字節(jié)組成。一般將Text類型的Field Value分詞之后的每個(gè)最小單元叫做Term。
在lucene中,讀寫路徑是分離的。寫入的時(shí)候創(chuàng)建一個(gè)IndexWriter,而讀的時(shí)候會(huì)創(chuàng)建一個(gè)IndexSearcher,
下面是一個(gè)簡單的代碼示例,如何使用lucene的IndexWriter建索引以及如何使用indexSearch進(jìn)行搜索查詢。
Analyzer analyzer = new StandardAnalyzer(); // Store the index in memory: Directory directory = new RAMDirectory(); // To store an index on disk, use this instead: //Directory directory = FSDirectory.open("/tmp/testindex"); IndexWriterConfig config = new IndexWriterConfig(analyzer); IndexWriter iwriter = new IndexWriter(directory, config); Document doc = new Document(); String text = "This is the text to be indexed."; doc.add(new Field("fieldname", text, TextField.TYPE_STORED)); iwriter.addDocument(doc); iwriter.close(); // Now search the index: DirectoryReader ireader = DirectoryReader.open(directory); IndexSearcher isearcher = new IndexSearcher(ireader); // Parse a simple query that searches for "text": QueryParser parser = new QueryParser("fieldname", analyzer); Query query = parser.parse("text"); ScoreDoc[] hits = isearcher.search(query, 1000).scoreDocs; //assertEquals(1, hits.length); // Iterate through the results: for (int i = 0; i < hits.length; i++) { Document hitDoc = isearcher.doc(hits[i].doc); System.out.println(hitDoc.get("fieldname")); } ireader.close(); directory.close();
從這個(gè)示例中可以看出,lucene的讀寫有各自的操作類。本文重點(diǎn)關(guān)注讀邏輯,在使用IndexSearcher類的時(shí)候,需要一個(gè)DirectoryReader和QueryParser,其中DirectoryReader需要對應(yīng)寫入時(shí)候的Directory實(shí)現(xiàn)。QueryParser主要用來解析你的查詢語句,例如你想查 “A and B",lucene內(nèi)部會(huì)有機(jī)制解析出是term A和term B的交集查詢。在具體執(zhí)行Search的時(shí)候指定一個(gè)最大返回的文檔數(shù)目,因?yàn)榭赡軙?huì)有過多命中,我們可以限制單詞返回的最大文檔數(shù),以及做分頁返回。
下面會(huì)詳細(xì)介紹一個(gè)索引查詢會(huì)經(jīng)過幾步,每一步lucene分別做了哪些優(yōu)化實(shí)現(xiàn)。
在lucene中查詢是基于segment。每個(gè)segment可以看做是一個(gè)獨(dú)立的subindex,在建立索引的過程中,lucene會(huì)不斷的flush內(nèi)存中的數(shù)據(jù)持久化形成新的segment。多個(gè)segment也會(huì)不斷的被merge成一個(gè)大的segment,在老的segment還有查詢在讀取的時(shí)候,不會(huì)被刪除,沒有被讀取且被merge的segement會(huì)被刪除。這個(gè)過程類似于LSM數(shù)據(jù)庫的merge過程。下面我們主要看在一個(gè)segment內(nèi)部如何實(shí)現(xiàn)高效的查詢。
為了方便大家理解,我們以人名字,年齡,學(xué)號為例,如何實(shí)現(xiàn)查某個(gè)名字(有重名)的列表。
docid | name | age | id |
---|---|---|---|
1 | Alice | 18 | 101 |
2 | Alice | 20 | 102 |
3 | Alice | 21 | 103 |
4 | Alan | 21 | 104 |
5 | Alan | 18 | 105 |
在lucene中為了查詢name=XXX的這樣一個(gè)條件,會(huì)建立基于name的倒排鏈。以上面的數(shù)據(jù)為例,倒排鏈如下:
姓名
Alice | [1,2,3]
---- | --- |
Alan | [4,5]
如果我們還希望按照年齡查詢,例如想查年齡=18的列表,我們還可以建立另一個(gè)倒排鏈:
18 | [1,5]
---| --- |
20 | [2]
21 | [3,4]
在這里,Alice,Alan,18,這些都是term。所以倒排本質(zhì)上就是基于term的反向列表,方便進(jìn)行屬性查找。到這里我們有個(gè)很自然的問題,如果term非常多,如何快速拿到這個(gè)倒排鏈呢?在lucene里面就引入了term dictonary的概念,也就是term的字典。term字典里我們可以按照term進(jìn)行排序,那么用一個(gè)二分查找就可以定為這個(gè)term所在的地址。這樣的復(fù)雜度是logN,在term很多,內(nèi)存放不下的時(shí)候,效率還是需要進(jìn)一步提升??梢杂靡粋€(gè)hashmap,當(dāng)有一個(gè)term進(jìn)入,hash繼續(xù)查找倒排鏈。這里hashmap的方式可以看做是term dictionary的一個(gè)index。 從lucene4開始,為了方便實(shí)現(xiàn)rangequery或者前綴,后綴等復(fù)雜的查詢語句,lucene使用FST數(shù)據(jù)結(jié)構(gòu)來存儲(chǔ)term字典,下面就詳細(xì)介紹下FST的存儲(chǔ)結(jié)構(gòu)。
我們就用Alice和Alan這兩個(gè)單詞為例,來看下FST的構(gòu)造過程。首先對所有的單詞做一下排序?yàn)椤癆lice”,“Alan”。
插入“Alan”
插入“Alice”
有了這個(gè)SkipList以后比如我們要查找docid=12,原來可能需要一個(gè)個(gè)掃原始鏈表,1,2,3,5,7,8,10,12。有了SkipList以后先訪問第一層看到是然后大于12,進(jìn)入第0層走到3,8,發(fā)現(xiàn)15大于12,然后進(jìn)入原鏈表的8繼續(xù)向下經(jīng)過10和12。
有了FST和SkipList的介紹以后,我們大體上可以畫一個(gè)下面的圖來說明lucene是如何實(shí)現(xiàn)整個(gè)倒排結(jié)構(gòu)的:
在lucene中會(huì)采用下列順序進(jìn)行合并:
在termA開始遍歷,得到第一個(gè)元素docId=1
Set currentDocId=1
在termB中 search(currentDocId) = 1 (返回大于等于currentDocId的一個(gè)doc),
因?yàn)閏urrentDocId ==1,繼續(xù)
如果currentDocId 和返回的不相等,執(zhí)行2,然后繼續(xù)
到termC后依然符合,返回結(jié)果
currentDocId = termC的nextItem
然后繼續(xù)步驟3 依次循環(huán)。直到某個(gè)倒排鏈到末尾。
整個(gè)合并步驟我可以發(fā)現(xiàn),如果某個(gè)鏈很短,會(huì)大幅減少比對次數(shù),并且由于SkipList結(jié)構(gòu)的存在,在某個(gè)倒排中定位某個(gè)docid的速度會(huì)比較快不需要一個(gè)個(gè)遍歷??梢院芸斓姆祷刈罱K的結(jié)果。從倒排的定位,查詢,合并整個(gè)流程組成了lucene的查詢過程,和傳統(tǒng)數(shù)據(jù)庫的索引相比,lucene合并過程中的優(yōu)化減少了讀取數(shù)據(jù)的IO,倒排合并的靈活性也解決了傳統(tǒng)索引較難支持多條件查詢的問題。
在lucene中如果想做范圍查找,根據(jù)上面的FST模型可以看出來,需要遍歷FST找到包含這個(gè)range的一個(gè)點(diǎn)然后進(jìn)入對應(yīng)的倒排鏈,然后進(jìn)行求并集操作。但是如果是數(shù)值類型,比如是浮點(diǎn)數(shù),那么潛在的term可能會(huì)非常多,這樣查詢起來效率會(huì)很低。所以為了支持高效的數(shù)值類或者多維度查詢,lucene引入類BKDTree。BKDTree是基于KDTree,對數(shù)據(jù)進(jìn)行按照維度劃分建立一棵二叉樹確保樹兩邊節(jié)點(diǎn)數(shù)目平衡。在一維的場景下,KDTree就會(huì)退化成一個(gè)二叉搜索樹,在二叉搜索樹中如果我們想查找一個(gè)區(qū)間,logN的復(fù)雜度就會(huì)訪問到葉子結(jié)點(diǎn)得到對應(yīng)的倒排鏈。如下圖所示:
BKDTree是KDTree的變種,因?yàn)榭梢钥闯鰜?,KDTree如果有新的節(jié)點(diǎn)加入,或者節(jié)點(diǎn)修改起來,消耗還是比較大。類似于LSM的merge思路,BKD也是多個(gè)KDTREE,然后持續(xù)merge最終合并成一個(gè)。不過我們可以看到如果你某個(gè)term類型使用了BKDTree的索引類型,那么在和普通倒排鏈merge的時(shí)候就沒那么高效了所以這里要做一個(gè)平衡,一種思路是把另一類term也作為一個(gè)維度加入BKDTree索引中。
通過之前介紹可以看出lucene通過倒排的存儲(chǔ)模型實(shí)現(xiàn)term的搜索,那對于有時(shí)候我們需要拿到另一個(gè)屬性的值進(jìn)行聚合,或者希望返回結(jié)果按照另一個(gè)屬性進(jìn)行排序。在lucene4之前需要把結(jié)果全部拿到再讀取原文進(jìn)行排序,這樣效率較低,還比較占用內(nèi)存,為了加速lucene實(shí)現(xiàn)了fieldcache,把讀過的field放進(jìn)內(nèi)存中。這樣可以減少重復(fù)的IO,但是也會(huì)帶來新的問題,就是占用較多內(nèi)存。新版本的lucene中引入了DocValues,DocValues是一個(gè)基于docid的列式存儲(chǔ)。當(dāng)我們拿到一系列的docid后,進(jìn)行排序就可以使用這個(gè)列式存儲(chǔ),結(jié)合一個(gè)堆排序進(jìn)行。當(dāng)然額外的列式存儲(chǔ)會(huì)占用額外的空間,lucene在建索引的時(shí)候可以自行選擇是否需要DocValue存儲(chǔ)和哪些字段需要存儲(chǔ)。
關(guān)于怎樣解析Lucene查詢原理就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。
分享題目:怎樣解析Lucene查詢原理
文章出自:http://chinadenli.net/article20/gicdco.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供靜態(tài)網(wǎng)站、品牌網(wǎng)站建設(shè)、定制網(wǎng)站、全網(wǎng)營銷推廣、商城網(wǎng)站、網(wǎng)站內(nèi)鏈
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)