查询DSL进阶
在上一章,咱们了解了什么是Apache Lucene,它的总体架构,以及文本分析过程是如何完成的。以后,咱们还介绍了Lucene的查询语言及其用法。除此以外,咱们也讨论了Elasticsearch,讨论了它的架构,以及一些核心概念。在本章,咱们将深刻研究Elasticsearch的查询DSL(Domain Specific Language)。在了解那些高级查询以前,咱们将先了解Lucene评分公式的工做原理。到本章结束,将涵盖如下内容:html
评分是Apache Lucene查询处理过程的一个重要环节。评分是指针对给定查询计算某个文档的score属性的过程。什么是文档得分?它是一个刻画文档与查询匹配程度的参数。在本节,咱们将了解Apache Lucene的默认评分机制:TF/IDF(词频/逆文档频率)算法以及它是如何影响文档查询结果的。了解评分公式的工做原理对构造复杂查询以及分析查询中因子的重要性都是颇有价值的。同时,掌握Lucene评分机制的基础知识有助于咱们更好地优化查询来获取符合咱们使用场景的结果。算法
一个文档被Lucene返回,意味着该文档与用户提交的查询是匹配的。在这种状况下,每一个被返回文档会有一个得分。在某些场景下,全部文档的得分都同样(好比使用constant_score查询),不过通常状况下,各个文档的得分是不同的。得分越高,文档更相关,至少从Apache Lucene及其评分公式的角度来看是这样的。得分还取决于匹配的文档、查询和索引内容,所以,很显然同一个文档对不一样查询的得分是不一样的。读者须要注意,同一文档在不一样查询中的得分不具有可比较性,不一样查询返回文档中的最高得分也不具有可比较性。这是由于文档得分依赖多个因子,除了权重和查询自己的结构,还依赖被匹配的词项数目、词项所在字段,以及用于查询规范化的匹配类型,如此等等。在一些比较极端的状况下,同一个文档在类似查询中的得分很是悬殊,仅仅是由于使用了自定义得分查询或者命中词项数的急剧变化。
如今,让咱们再回到评分过程。为了计算文档得分,咱们须要考虑如下这些因子。apache
从Lucene 4.0版本起,Lucene引入了多种不一样的打分公式,这一点或许你已经有所了解了。不过,咱们仍是但愿在此探索一下默认的TF/IDF打分公式的一些细节。请记住,为了调节查询相关性,你并不须要深刻理解这个公式的前因后果,可是了解它的工做原理却很是重要,由于这有助于简化相关度调优过程。
1. Lucene的理论评分公式
TF/IDF公式的理论形式以下:
缓存
上面的公式融合了布尔检索模型和向量空间检索模型。咱们不打算在此讨论理论评分公式,而是直接跳到实践中使用的评分公式,看看Lucene内部是如何实现和使用评分公式的。
关于布尔检索模型和向量空间检索模型的知识远远超出了本书的讨论范围,想了解更多相关知识,请参考http://en.wikipedia.org/wiki/Standard_Boolean_model 和http://en.wikipedia.org/ wiki/Vector_Space_Model。
2. Lucene的实际评分公式
如今让咱们看看Lucene实际使用的评分公式:
架构
也许你已经看到了,评分公式是一个关于查询q和文档d的函数,正如咱们以前提到的同样。有两个因子并不直接依赖查询词项,它们是coord和queryNorm,这两个因子与查询词项的一个求和公式相乘。
求和公式中每一个加数由如下因子连乘所得:词频,逆文档频率,词项权重,范数。范数就是以前咱们提到过的长度范数。
这个公式听起来很复杂。请别担忧,你并不用记住全部的细节,你只须要意识到哪些因素是与评分有关的便可。从前面的公式咱们能够导出一些基本的规则:分布式
总而言之,Elasticsearch使用了Lucene的评分功能,幸运的是Elasticsearch容许咱们挑选可用的similarity类实现,或者自定义similarity类,来替换默认的评分算法。不过请记住,Elasticsearch不只仅是Lucene的简单封装,由于它虽然使用了Lucene的评分功能,但不只限于Lucene的评分功能。
用户可使用各类不一样的查询类型,以精确控制文档评分的计算。例如使用function_score查询时,能够经过使用脚本(scripting)来改变文档得分,也可使用Elasticsearch 0.90中出现的二次评分功能,经过在返回文档集之上执行另一个查询,从新计算top-N文档的得分。
想了解更多Apache Lucene查询类型,请参考http://lucene.apache.org/core/4_9_0/queries/org/apache/lucene/queries/package-summary.html上的相关文档。函数
如今,咱们已经了解评分的工做原理。接下来咱们看一个在现实生活中应用评分的简单例子。首先咱们须要建立一个名为scoring的新索引。使用以下命令建立这个索引:
性能
简单起见,咱们使用了只有一个物理分片和0个副本的索引(咱们不须要在这个例子中关心分布式文档频率)。咱们须要索引一个简单的文档,代码以下:
优化
接着咱们执行一个简单的匹配(match)查询,查询的词项是“document”。
spa
显然,刚才索引的这个文档被匹配上了,而且被赋予了得分。咱们能够经过下面这条命令来查看得分的计算过程:
能够看出,Elasticsearch给出了针对给定文档和查询的详细的得分计算过程。同时能够看出,得分等于词项频率(本例中是1)和逆文档频率(0.30685282)以及字段范数(0.625)的乘积。
如今,咱们再把另外一个文档加入索引。
如今,能够对比一下TF/IDF评分公式在现实场景中的工做了。在把第2个文档索引到相同分片后(请记住咱们建立的索引只有一个分片且没有副本),得分发生了变化,尽管此时的查询和刚才的同样。这是由于一些影响得分的因子已经改变了。好比,逆文档频率变了,所以得分也会跟着改变。咱们还须要注意对比一下两个文档的得分。咱们查询了一个单词“document”,查询匹配上了两个文档的相同字段的相同词项。第2个文档的得分为何较低,是由于和第1个文档相比,它的name字段多了一个词项。根据先前的知识储备,咱们知道,文档越短,Lucene给出的得分越高。
但愿这个简短的介绍会让你对评分工做机制认识得更清楚,在你须要优化查询时理解目标查询的工做过程。
http://blog.csdn.net/molong1208/article/details/50623948
以前咱们探讨了评分机制,这些知识很是珍贵,特别是当你尝试改进查询相关性时。咱们还认为,在对查询进行调试时,也颇有必要搞清楚查询是如何执行的。所以咱们决定在本节介绍一下查询改写是如何工做的,为何须要查询改写,以及咱们应该如何控制它。
若是你以前使用过诸如前缀查询或通配符查询之类的查询类型,那么你会了解这些都是基于多词项的查询,它们都涉及查询改写。Elasticsearch使用查询改写是出于对性能的考虑。从Lucene的角度来看,所谓的查询改写操做,就是把费时的原始查询类型实例改写成一组性能更高的查询类型实例,从而加快查询执行速度。查询改写过程对客户端不可见,不过最好可以知道咱们能够修改查询改写过程。举个例子,让咱们看看Elasticsearch是如何处理前缀查询的。
演示查询改写过程的最好方式莫过于经过范例深刻了解该过程的内部实现机制,尤为是要去了解原始查询中的词项是如何被改写成目标查询中那些词项的。假设咱们索引了下面这些文档中的数据:
如今咱们想找出索引中全部name字段以字母j开头的文档。简单起见,咱们在clients索引中执行如下查询:
这里使用了一个简单的前缀查询,想检索出全部name字段以字母j开头的文档。咱们同时也设置了查询改写属性以肯定执行查询改写的具体方法,不过如今咱们跳过该参数,具体的参数值将在本章的后续部分讨论。
执行前面的查询之后,咱们将获得下面的结果:
如你所见,返回结果中有3个文档,这些文档的name字段以字母j开头。咱们并无显式设置待查询索引的映射,所以Elasticsearch探测出了name字段的映射,并将其设置为字符串类型并进行文本分析。可以使用下面的命令进行检查:
如今咱们回到Lucene。若是你还记得Lucene倒排索引是如何构建的,你会指出倒排索引中包含了词项、词频以及文档指针(若是忘了,请从新阅读1.1节)。如今咱们看看以前存储到clients索引中的数据大概是如何组织的。
Term这一列很是重要。若是咱们去探究Elasticsearch和Lucene的内部实现,将会发现前缀查询被改写为下面这种查询:
咱们能够用Elasticsearch API来检查重写片断。首先,使用Explain API执行以下命令:
能够看到,Elasticsearch对name字段使用了一个词项是joe的constant_score查询。固然,这一步发生在Lucene中,Elasticsearch实际上只是从缓存中获取这些词项。这一点能够用Validate查询API来验证。
固然,多词项查询的rewrite属性也能够支持除了“constant_score_boolean”以外的其余取值。咱们能够经过这个属性来控制查询在Lucene内部的改写方式。咱们能够将rewrite参数存放在表明实际查询的JSON对象中,例如,像下面的代码这样:
如今让咱们来看看rewrite参数有哪些选项能够配置。
scoring_boolean:该选项将每一个生成的词项转化为布尔查询中的一个或从句(Boolean should clause)。这种改写方法须要针对每一个文档都计算得分。所以,这种方法比较耗费CPU(由于要计算和保存每一个词项的得分),并且有些查询生成了太多的词项,以致于超出了布尔查询默认的1024个从句的限制。默认的布尔查询限制能够经过设置Elasticsearch.yml文件的index.query.bool.max_clause_count属性来修改。用户需谨记,改写后的布尔查询的从句数越多,查询性能越低。
constant_score_boolean:该选项与前面提到过的scoring_boolean相似,可是CPU耗费更少,这是由于并不计算每一个从句的得分,而是每一个从句获得一个与查询权重相同的一个常数得分,默认状况下等于1,咱们也能够经过设置查询权重来改变这个默认值。与scoring_boolean相似,该选项也有布尔从句数的限制。
constant_score_filter:正如Lucene的Javadocs描述的那样,该选项按以下方式改写原始查询—经过顺序遍历每一个词项来建立一个私有的过滤器,标记全部包含这个词项的文档。命中的文档被赋予一个与查询权重相同的常量得分。当命中词项数或文档数较大时,该方法比scoring_boolean 和constant_score_boolean执行速度更快。
top_terms_N:该选项将每一个生成的词项转化为布尔查询中的一个或从句,并保存计算出来的查询得分。与scoring_boolean不一样之处在于,该方法只保留最佳的N个词项,以免触及布尔从句数的限制,并提高查询总体性能。
top_terms_boost_N:该选项与top_terms_N相似,不一样之处在于它的文档得分不是经过计算得出的,而是被设置为跟查询权重(boost)一致,默认值为1。
当rewrite属性设置为constant_score_auto或者没有设置时,Elasticsearch会根据查询的类型及其构造方式来决定是使用constant_score_filter仍是constant_score_boolean。
如今,让咱们再看一个例子。若是咱们想在范例查询中使用top_terms_N选项,而且N的值设置为2,那么查询看起来与下面的代码相似:
从Elasticsearch返回的结果中能够看出,和咱们以前使用的查询不一样,这里的文档得分都不等于1.0。
这是由于top_terms_N须要保留得分最高的N个词项。 结束本节以前,读者应该会产生一个疑问,咱们如何决定什么时候采用何种查询改写方法?该问题的答案更多地取决于您的应用场景。简单来讲,若是您能接受较低的精度和相关性(可是追求更高的性能),那么能够采用top-N查询改写方法。若是您须要更高的查询精度和更好的相关性(同时能够接受较低的性能),那么应该采用布尔方法。