从根上理解elasticsearch(lucene)查询原理(2)-lucene常见查询类型原理分析
大家好,我是藍(lán)胖子,在上一節(jié)我提到要想徹底搞懂elasticsearch 慢查詢的原因,必須搞懂lucene的查詢原理,所以在上一節(jié)我分析了lucene查詢的整體流程,除此以外,還必須要搞懂各種查詢類型內(nèi)部是如何工作,比如比較復(fù)雜的查詢是將一個大查詢分解成了小查詢,然后通過對小查詢的結(jié)果進(jìn)行合并得到最終結(jié)果。
今天就來看看幾種比較常見的查詢其內(nèi)部的工作原理。
BooleanQuery 查詢分析
首先來看下布爾查詢,拿下面這段代碼舉例,我用lucene寫了一個布爾查詢的例子,布爾查詢由兩個term查詢組成,其中一個term是用must,一個term用的是should。
BooleanQuery.Builder query = new BooleanQuery.Builder();
query.add(new TermQuery(new Term(field1, "w3")), BooleanClause.Occur.MUST);
query.add(new TermQuery(new Term(field2, "xx")), BooleanClause.Occur.SHOULD);
int[] expDocNrs = {2, 3, 1, 0};
queriesTest(query.build(), expDocNrs);
布爾查詢會將兩個term查詢的倒排鏈進(jìn)行合并,得到最終結(jié)果。上一節(jié)有提到,計(jì)分邏輯是通過bulkScore.score方法實(shí)現(xiàn)的。在bulkScore.score方法內(nèi)部 ,需要先遍歷篩選出符合條件的文檔,然后對該文檔進(jìn)行計(jì)分,無論是篩選出符合條件的文檔,還是對文檔計(jì)分,都與weight對象創(chuàng)建的scorer對象有關(guān),遍歷用到的是DocIdSetIterator,計(jì)分用到的是score() 方法,scorer涉及到的方法如下,
其中計(jì)分方法score是在scorer抽象類又繼承的一個Scorable 抽象類中,如下所示
public abstract class Scorer extends Scorable {
...
}
在遍歷倒排列表取出文檔id時(shí),會調(diào)用DocIdSetIterator 的nextDoc 方法取出當(dāng)前文檔id,并將便利指針移動到倒排列表的下一個文檔id處。
但是布爾查詢往往是多個條件的組合查詢,它不可能是只遍歷一個倒排鏈表,所以布爾查詢的實(shí)現(xiàn)中,針對查詢條件生成了特殊的scorer對象,比如ConjunctionScorer 交集scorer,它會將查詢條件組合起來,并且利用子查詢的DocIdSetIterator 構(gòu)造新的DocIdSetIterator 用于遍歷篩選出符合條件的文檔id。ConjunctionScorer 的nextDoc方法就相當(dāng)于是在執(zhí)行多個倒排鏈表合并的過程。
關(guān)于倒排鏈表的合并過程就不在這篇文檔繼續(xù)展開了。除此以外,布爾查詢構(gòu)建的scorer對象還有 并集DisjunctionSumScorer,差集ReqExclScorer,ReqOptSumScorer。它們的nextDoc方法也都是在做遍歷倒排鏈表取出文檔id的操作,不過遍歷合并倒排鏈表的邏輯各有不同。
所以,如果你的布爾查詢命中結(jié)果比較多,并且需要計(jì)分的話, 會導(dǎo)致在進(jìn)行倒排鏈表合并操作時(shí)花費(fèi)比較長的時(shí)間。比如我之前碰到的一個慢查詢,經(jīng)過profile的分析如下,布爾查詢在next_doc操作上耗時(shí)比較長,next_doc對于布爾查詢而言是在進(jìn)行倒排鏈表的合并。
而對于布爾查詢的子查詢term查詢你會發(fā)現(xiàn)耗時(shí)基本是花在了advance操作上。因?yàn)榈古帕斜砗喜⑦^程中會有很多移動遍歷指針的操作也就是advance操作,所以在倒排列表比較長時(shí),要想完整遍歷合并多個倒排列表則會有很多advance操作。
MultiTermQuery 查詢分析
接著看另外一個常見的查詢類型MultiTermQuery,它的查詢重寫分好幾種類型,具體的重寫類型區(qū)別可以查看官方文 https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-term-rewrite.html
這里我拿其中一種 CONSTANT_SCORE_BLENDED_REWRITE 舉例,這也是在復(fù)雜查詢例如
fuzzyprefixquery_stringregexpwildcard
默認(rèn)使用的重寫類型。
wildcardQuery這些模糊匹配,正則匹配差詢首先是構(gòu)建自動狀態(tài)機(jī),然后默認(rèn)會將查詢重寫成為了CONSTANT_SCORE_BLENDED_REWRITE類型的MultiTermQuery查詢。
之后在創(chuàng)建weight的scorer對象時(shí),會將詞典term dictionary中的term與自動狀態(tài)機(jī)做匹配,選出符合條件的term,根據(jù)term的個數(shù)判斷是將查詢重寫為布爾查詢還是直接構(gòu)建bitset用于后續(xù)計(jì)分時(shí)進(jìn)行迭代遍歷。
符合條件的term 大于16個,則會進(jìn)行bitset的構(gòu)建,構(gòu)建過程則是將符合條件的term對應(yīng)的倒排列表取出來加到一個bitset中。這個過程是比較耗時(shí)的,特別是term對應(yīng)的倒排列表過大或者term數(shù)量過多時(shí),耗時(shí)會非常長。注意這個構(gòu)建過程是發(fā)生在scoer對象創(chuàng)建的時(shí)候,即build_scorer階段。拿我之前遇到的一個慢查詢舉例,這是一個匹配到的term數(shù)量比較多的wildcardQuery,
下面是執(zhí)行的DSL語句,
{"size":1000,"query":{"bool":{"filter":[{"term":{"owner_uid":{"value":712377485,"boost":1.0}}},{"term":{"pid":{"value":0,"boost":1.0}}},{"wildcard":{"name":{"wildcard":"*","boost":1.0}}},{"exists":{"field":"vgroup","boost":1.0}}],"adjust_pure_negative":true,"boost":1.0}},"_source":{"includes":["name"],"excludes":[]}}
經(jīng)過profile分析可以看到wildcardQuery已經(jīng)被重寫為了MultiTermQueryConstantScoreWrapper,耗時(shí)過長最大的階段則是在build_scorer階段,對每個階段不太熟悉的話可以翻看我前一篇文章 https://mp.weixin.qq.com/s/Drhs6lKPYy8vDHa2RouiyA
注意像wildcardQuery,前綴匹配這些查詢都會構(gòu)建自動狀態(tài)機(jī),構(gòu)建自動狀態(tài)機(jī)的過程在匹配規(guī)則文本比較長時(shí),非常消耗cpu,生產(chǎn)上注意限制匹配規(guī)則文本長度,并且構(gòu)建自動狀態(tài)機(jī)花費(fèi)的時(shí)長不會體現(xiàn)在profile輸出結(jié)果中。
總結(jié)
以上是生活随笔為你收集整理的从根上理解elasticsearch(lucene)查询原理(2)-lucene常见查询类型原理分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 橙光游戏《步步宫辛之雀成凰》攻略
- 下一篇: Winform 控件库 Materia