从根上理解elasticsearch(lucene)查询原理(2)-lucene常见查询类型原理分析
大家好,我是藍胖子,在上一節我提到要想徹底搞懂elasticsearch 慢查詢的原因,必須搞懂lucene的查詢原理,所以在上一節我分析了lucene查詢的整體流程,除此以外,還必須要搞懂各種查詢類型內部是如何工作,比如比較復雜的查詢是將一個大查詢分解成了小查詢,然后通過對小查詢的結果進行合并得到最終結果。
今天就來看看幾種比較常見的查詢其內部的工作原理。
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查詢的倒排鏈進行合并,得到最終結果。上一節有提到,計分邏輯是通過bulkScore.score方法實現的。在bulkScore.score方法內部 ,需要先遍歷篩選出符合條件的文檔,然后對該文檔進行計分,無論是篩選出符合條件的文檔,還是對文檔計分,都與weight對象創建的scorer對象有關,遍歷用到的是DocIdSetIterator,計分用到的是score() 方法,scorer涉及到的方法如下,
其中計分方法score是在scorer抽象類又繼承的一個Scorable 抽象類中,如下所示
public abstract class Scorer extends Scorable {
...
}
在遍歷倒排列表取出文檔id時,會調用DocIdSetIterator 的nextDoc 方法取出當前文檔id,并將便利指針移動到倒排列表的下一個文檔id處。
但是布爾查詢往往是多個條件的組合查詢,它不可能是只遍歷一個倒排鏈表,所以布爾查詢的實現中,針對查詢條件生成了特殊的scorer對象,比如ConjunctionScorer 交集scorer,它會將查詢條件組合起來,并且利用子查詢的DocIdSetIterator 構造新的DocIdSetIterator 用于遍歷篩選出符合條件的文檔id。ConjunctionScorer 的nextDoc方法就相當于是在執行多個倒排鏈表合并的過程。
關于倒排鏈表的合并過程就不在這篇文檔繼續展開了。除此以外,布爾查詢構建的scorer對象還有 并集DisjunctionSumScorer,差集ReqExclScorer,ReqOptSumScorer。它們的nextDoc方法也都是在做遍歷倒排鏈表取出文檔id的操作,不過遍歷合并倒排鏈表的邏輯各有不同。
所以,如果你的布爾查詢命中結果比較多,并且需要計分的話, 會導致在進行倒排鏈表合并操作時花費比較長的時間。比如我之前碰到的一個慢查詢,經過profile的分析如下,布爾查詢在next_doc操作上耗時比較長,next_doc對于布爾查詢而言是在進行倒排鏈表的合并。
而對于布爾查詢的子查詢term查詢你會發現耗時基本是花在了advance操作上。因為倒排列表合并過程中會有很多移動遍歷指針的操作也就是advance操作,所以在倒排列表比較長時,要想完整遍歷合并多個倒排列表則會有很多advance操作。
MultiTermQuery 查詢分析
接著看另外一個常見的查詢類型MultiTermQuery,它的查詢重寫分好幾種類型,具體的重寫類型區別可以查看官方文 https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-term-rewrite.html
這里我拿其中一種 CONSTANT_SCORE_BLENDED_REWRITE 舉例,這也是在復雜查詢例如
fuzzyprefixquery_stringregexpwildcard
默認使用的重寫類型。
wildcardQuery這些模糊匹配,正則匹配差詢首先是構建自動狀態機,然后默認會將查詢重寫成為了CONSTANT_SCORE_BLENDED_REWRITE類型的MultiTermQuery查詢。
之后在創建weight的scorer對象時,會將詞典term dictionary中的term與自動狀態機做匹配,選出符合條件的term,根據term的個數判斷是將查詢重寫為布爾查詢還是直接構建bitset用于后續計分時進行迭代遍歷。
符合條件的term 大于16個,則會進行bitset的構建,構建過程則是將符合條件的term對應的倒排列表取出來加到一個bitset中。這個過程是比較耗時的,特別是term對應的倒排列表過大或者term數量過多時,耗時會非常長。注意這個構建過程是發生在scoer對象創建的時候,即build_scorer階段。拿我之前遇到的一個慢查詢舉例,這是一個匹配到的term數量比較多的wildcardQuery,
下面是執行的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":[]}}
經過profile分析可以看到wildcardQuery已經被重寫為了MultiTermQueryConstantScoreWrapper,耗時過長最大的階段則是在build_scorer階段,對每個階段不太熟悉的話可以翻看我前一篇文章 https://mp.weixin.qq.com/s/Drhs6lKPYy8vDHa2RouiyA
注意像wildcardQuery,前綴匹配這些查詢都會構建自動狀態機,構建自動狀態機的過程在匹配規則文本比較長時,非常消耗cpu,生產上注意限制匹配規則文本長度,并且構建自動狀態機花費的時長不會體現在profile輸出結果中。
總結
以上是生活随笔為你收集整理的从根上理解elasticsearch(lucene)查询原理(2)-lucene常见查询类型原理分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 橙光游戏《步步宫辛之雀成凰》攻略
- 下一篇: Winform 控件库 Materia