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