本篇內(nèi)容介紹了“Lucene查詢原理是什么”的有關(guān)知識,在實(shí)際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
創(chuàng)新互聯(lián)建站是一家集網(wǎng)站設(shè)計(jì)、做網(wǎng)站、網(wǎng)站頁面設(shè)計(jì)、網(wǎng)站優(yōu)化SEO優(yōu)化為一體的專業(yè)網(wǎng)站設(shè)計(jì)公司,已為成都等多地近百家企業(yè)提供網(wǎng)站建設(shè)服務(wù)。追求良好的瀏覽體驗(yàn),以探求精品塑造與理念升華,設(shè)計(jì)最適合用戶的網(wǎng)站頁面。 合作只是第一步,服務(wù)才是根本,我們始終堅(jiān)持講誠信,負(fù)責(zé)任的原則,為您進(jìn)行細(xì)心、貼心、認(rèn)真的服務(wù),與眾多客戶在蓬勃發(fā)展的市場環(huán)境中,互促共生。本節(jié)主要是一些Lucene的背景知識,了解這些知識的同學(xué)可以略過。
Elasticsearch的底層是Lucene,可以說Lucene的查詢性能就決定了Elasticsearch的查詢性能。
Lucene查詢原理
Lucene中最重要的就是它的幾種數(shù)據(jù)結(jié)構(gòu),這決定了數(shù)據(jù)是如何被檢索的,本文再簡單描述一下幾種數(shù)據(jù)結(jié)構(gòu):
FST:保存term字典,可以在FST上實(shí)現(xiàn)單Term、Term范圍、Term前綴和通配符查詢等。
倒排鏈:保存了每個(gè)term對應(yīng)的docId的列表,采用skipList的結(jié)構(gòu)保存,用于快速跳躍。
BKD-Tree:BKD-Tree是一種保存多維空間點(diǎn)的數(shù)據(jù)結(jié)構(gòu),用于數(shù)值類型(包括空間點(diǎn))的快速查找。
DocValues:基于docId的列式存儲,由于列式存儲的特點(diǎn),可以有效提升排序聚合的性能。
了解了Lucene的數(shù)據(jù)結(jié)構(gòu)和基本查詢原理,我們知道:
對單個(gè)詞條進(jìn)行查詢,Lucene會讀取該詞條的倒排鏈,倒排鏈中是一個(gè)有序的docId列表。
對字符串范圍/前綴/通配符查詢,Lucene會從FST中獲取到符合條件的所有Term,然后就可以根據(jù)這些Term再查找倒排鏈,找到符合條件的doc。
對數(shù)字類型進(jìn)行范圍查找,Lucene會通過BKD-Tree找到符合條件的docId集合,但這個(gè)集合中的docId并非有序的。
現(xiàn)在的問題是,如果給一個(gè)組合查詢條件,Lucene怎么對各個(gè)單條件的結(jié)果進(jìn)行組合,得到最終結(jié)果。簡化的問題就是如何求兩個(gè)集合的交集和并集。
上面Lucene原理分析的文章中講過,N個(gè)倒排鏈求交集,可以采用skipList,有效的跳過無效的doc。
處理方式一:仍然保留多個(gè)有序列表,多個(gè)有序列表的隊(duì)首構(gòu)成一個(gè)優(yōu)先隊(duì)列(最小堆),這樣后續(xù)可以對整個(gè)并集進(jìn)行iterator(堆頂?shù)年?duì)首出堆,隊(duì)列里下一個(gè)docID入堆),也可以通過skipList的方式向后跳躍(各個(gè)子列表分別通過skipList跳)。這種方式適合倒排鏈數(shù)量比較少(N比較小)的場景。
處理方式二:倒排鏈如果比較多(N比較大),采用方式一就不夠劃算,這時(shí)候可以直接把結(jié)果合并成一個(gè)有序的docID數(shù)組。
處理方式三:方式二中,直接保存原始的docID,如果docID非常多,很消耗內(nèi)存,所以當(dāng)doc數(shù)量超過一定值時(shí)(32位docID在BitSet中只需要一個(gè)bit,BitSet的大小取決于segments里的doc總數(shù),所以可以根據(jù)doc總數(shù)和當(dāng)前doc數(shù)估算是否BitSet更加劃算),會采用構(gòu)造BitSet的方式,非常節(jié)約內(nèi)存,而且BitSet可以非常高效的取交/并集。
通過BKD-Tree查找到的docID是無序的,所以要么先轉(zhuǎn)成有序的docID數(shù)組,或者構(gòu)造BitSet,然后再與其他結(jié)果合并。
如果采用多個(gè)條件進(jìn)行查詢,那么先查詢代價(jià)比較小的,再從小結(jié)果集上進(jìn)行迭代,會更優(yōu)一些。Lucene中做了很多這方面的優(yōu)化,在查詢前會先估算每個(gè)查詢的代價(jià),再決定查詢順序。
默認(rèn)情況下,Lucene會按照Score排序,即算分后的分?jǐn)?shù)值,如果指定了其他的Sort字段,就會按照指定的字段排序。那么,排序會非常影響性能嗎?首先,排序并不會對所有命中的doc進(jìn)行排序,而是構(gòu)造一個(gè)堆,保證前(Offset+Size)個(gè)數(shù)的doc是有序的,所以排序的性能取決于(Size+Offset)和命中的文檔數(shù),另外就是讀取docValues的開銷。因?yàn)?Size+Offset)并不會太大,而且docValues的讀取性能很高,所以排序并不會非常的影響性能。
上一節(jié)講了一些查詢相關(guān)的理論知識,那么本節(jié)就是理論結(jié)合實(shí)踐,通過具體的一些測試數(shù)字來分析一下各個(gè)場景的性能。測試采用單機(jī)單Shard、64核機(jī)器、SSD磁盤,主要分析各個(gè)場景的計(jì)算開銷,不考慮操作系統(tǒng)Cache的影響,測試結(jié)果僅供參考。
ES中建立一個(gè)Index,一個(gè)shard,無replica。有1000萬行數(shù)據(jù),每行只有幾個(gè)標(biāo)簽和一個(gè)唯一ID,現(xiàn)在將這些數(shù)據(jù)寫入這個(gè)Index中。其中Tag1這個(gè)標(biāo)簽只有a和b兩個(gè)值,現(xiàn)在要從1000萬行中找到一條Tag1=a的數(shù)據(jù)(約500萬)。給出以下查詢,那么它耗時(shí)如何呢: 請求: { "query": { "constant_score": { "filter": { "term": { "Tag1": "a" } } } }, "size": 1}' 響應(yīng): {"took":233,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":5184867,"max_score":1.0,"hits":...}
這個(gè)請求耗費(fèi)了233ms,并且返回了符合條件的數(shù)據(jù)總數(shù):5184867條。
對于Tag1="a"這個(gè)查詢條件,我們知道是查詢Tag1="a"的倒排鏈,這個(gè)倒排鏈的長度是5184867,是非常長的,主要時(shí)間就花在掃描這個(gè)倒排鏈上。其實(shí)對這個(gè)例子來說,掃描倒排鏈帶來的收益就是拿到了符合條件的記錄總數(shù),因?yàn)闂l件中設(shè)置了constant_score,所以不需要算分,隨便返回一條符合條件的記錄即可。對于要算分的場景,Lucene會根據(jù)詞條在doc中出現(xiàn)的頻率來計(jì)算分值,并取分值排序返回。
目前我們得到一個(gè)結(jié)論,233ms時(shí)間至少可以掃描500萬的倒排鏈,另外考慮到單個(gè)請求是單線程執(zhí)行的,可以粗略估算,一個(gè)CPU核在一秒內(nèi)掃描倒排鏈內(nèi)doc的速度是千萬級的。
我們再換一個(gè)小一點(diǎn)的倒排鏈,長度為1萬,總共耗時(shí)3ms。
{"took":3,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":10478,"max_score":1.0,"hits":...}
首先考慮兩個(gè)Term查詢求交集:
對于一個(gè)Term的組合查詢,兩個(gè)倒排鏈分別為1萬和500萬,合并后符合條件的數(shù)據(jù)為5000,查詢性能如何呢? 請求: { "size": 1, "query": { "constant_score": { "filter": { "bool": { "must": [ { "term": { "Tag1": "a" // 倒排鏈長度500萬 } }, { "term": { "Tag2": "0" // 倒排鏈長度1萬 } } ] } } } } } 響應(yīng): {"took":21,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":5266,"max_score":2.0,"hits":...}
這個(gè)請求耗時(shí)21ms,主要是做兩個(gè)倒排鏈的求交操作,因此我們主要分析skipList的性能。
這個(gè)例子中,倒排鏈長度是1萬、500萬,合并后仍有5000多個(gè)doc符合條件。對于1萬的倒排鏈,基本上不進(jìn)行skip,因?yàn)橐话氲膁oc都是符合條件的,對于500萬的倒排鏈,平均每次skip1000個(gè)doc。因?yàn)榈古沛溤诖鎯r(shí)最小的單位是BLOCK,一個(gè)BLOCK一般是128個(gè)docID,BLOCK內(nèi)不會進(jìn)行skip操作。所以即使能夠skip到某個(gè)BLOCK,BLOCK內(nèi)的docID還是要順序掃描的。所以這個(gè)例子中,實(shí)際掃描的docID數(shù)粗略估計(jì)也有幾十萬,所以總時(shí)間花費(fèi)了20多ms也符合預(yù)期。
對于Term查詢求并集呢,將上面的bool查詢的must改成should,查詢結(jié)果為:
{"took":393,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":5190079,"max_score":1.0,"hits":...}
花費(fèi)時(shí)間393ms,所以求并集的時(shí)間是多于其中單個(gè)條件查詢的時(shí)間。
RecordID是一個(gè)UUID,1000萬條數(shù)據(jù),每個(gè)doc都有一個(gè)唯一的uuid,從中查找0~7開頭的uuid,大概結(jié)果有500多萬個(gè),性能如何呢? 請求: { "query": { "constant_score": { "filter": { "range": { "RecordID": { "gte": "0", "lte": "8" } } } } }, "size": 1 } 響應(yīng): {"took":3001,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":5185663,"max_score":1.0,"hits":...} 查詢a開頭的uuid,結(jié)果大概有60多萬,性能如何呢? 請求: { "query": { "constant_score": { "filter": { "range": { "RecordID": { "gte": "a", "lte": "b" } } } } }, "size": 1 } 響應(yīng): {"took":379,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":648556,"max_score":1.0,"hits":...}
這個(gè)查詢我們主要分析FST的查詢性能,從上面的結(jié)果中我們可以看到,F(xiàn)ST的查詢性能相比掃描倒排鏈要差許多,同樣掃描500萬的數(shù)據(jù),倒排鏈掃描只需要不到300ms,而FST上的掃描花費(fèi)了3秒,基本上是慢十倍的。對于UUID長度的字符串來說,F(xiàn)ST范圍掃描的性能大概是每秒百萬級。
字符串范圍查詢(符合條件500萬),加上兩個(gè)Term查詢(符合條件5000),最終符合條件數(shù)目2600,性能如何? 請求: { "query": { "constant_score": { "filter": { "bool": { "must": [ { "range": { "RecordID": { "gte": "0", "lte": "8" } } }, { "term": { "Tag1": "a" } }, { "term": { "Tag2": "0" } } ] } } } }, "size": 1 } 結(jié)果: {"took":2849,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":2638,"max_score":1.0,"hits":...}
這個(gè)例子中,查詢消耗時(shí)間的大頭還是在掃描FST的部分,通過FST掃描出符合條件的Term,然后讀取每個(gè)Term對應(yīng)的docID列表,構(gòu)造一個(gè)BitSet,再與兩個(gè)TermQuery的倒排鏈求交集。
對于數(shù)字類型,我們同樣從1000萬數(shù)據(jù)中查找500萬呢? 請求: { "query": { "constant_score": { "filter": { "range": { "Number": { "gte": 100000000, "lte": 150000000 } } } } }, "size": 1 } 響應(yīng): {"took":567,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":5183183,"max_score":1.0,"hits":...}
這個(gè)場景我們主要測試BKD-Tree的性能,可以看到BKD-Tree查詢的性能還是不錯(cuò)的,查找500萬個(gè)doc花費(fèi)了500多ms,只比掃描倒排鏈差一倍,相比FST的性能有了很大的提升。地理位置相關(guān)的查詢也是通過BKD-Tree實(shí)現(xiàn)的,性能很高。
這里我們構(gòu)造一個(gè)復(fù)雜的查詢場景,數(shù)字Range范圍數(shù)據(jù)500萬,再加兩個(gè)Term條件,最終符合條件數(shù)據(jù)2600多條,性能如何? 請求: { "query": { "constant_score": { "filter": { "bool": { "must": [ { "range": { "Number": { "gte": 100000000, "lte": 150000000 } } }, { "term": { "Tag1": "a" } }, { "term": { "Tag2": "0" } } ] } } } }, "size": 1 } 響應(yīng): {"took":27,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":2638,"max_score":1.0,"hits":...}
這個(gè)結(jié)果出乎我們的意料,竟然只需要27ms!因?yàn)樵谏弦粋€(gè)例子中,數(shù)字Range查詢耗時(shí)500多ms,而我們增加兩個(gè)Term條件后,時(shí)間竟然變?yōu)?7ms,這是為何呢?
實(shí)際上,Lucene在這里做了一個(gè)優(yōu)化,底層有一個(gè)查詢叫做IndexOrDocValuesQuery,會自動判斷是查詢Index(BKD-Tree)還是DocValues。在這個(gè)例子中,查詢順序是先對兩個(gè)TermQuery求交集,得到5000多個(gè)docID,然后讀取這個(gè)5000多個(gè)docID對應(yīng)的docValues,從中篩選符合數(shù)字Range條件的數(shù)據(jù)。因?yàn)橹恍枰x5000多個(gè)doc的docValues,所以花費(fèi)時(shí)間很少。
“Lucene查詢原理是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)-成都網(wǎng)站建設(shè)公司網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
網(wǎng)站標(biāo)題:Lucene查詢原理是什么-創(chuàng)新互聯(lián)
標(biāo)題來源:http://aaarwkj.com/article22/goicc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站制作、做網(wǎng)站、全網(wǎng)營銷推廣、網(wǎng)站制作、動態(tài)網(wǎng)站、自適應(yīng)網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時(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)
猜你還喜歡下面的內(nèi)容