Solr权威指南 下卷(txt+pdf+epub+mobi电子书下载)


发布时间:2020-08-08 09:55:36

点击下载

作者:兰小伟

出版社:机械工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

Solr权威指南 下卷

Solr权威指南 下卷试读:

序言

P R E F A C E

Apache Solr是使用最广泛的全文检索解决方案,大部分网站都在使用Solr来实现搜索功能。然而国内关于Solr的资料太少,无奈我只能一点点地啃Solr官方提供的User Guide PDF文档、Solr Wiki以及一些纯英文的技术书籍,希望能够借由本书将我学习积累的所有经验倾情传授给那些由于学习Solr曲线太陡峭而束手无策的同学们。本书致力于帮助Java开发人员更简单、深入地学习Solr。同时本书还提供了随书源码,其中包含大量可运行的示例代码。本书与随书源码搭配在一起学习会事半功倍!由于目前大数据、云计算的发展如火如荼,各种大数据生态框架如雨后春笋般涌现,给人一种无形的压力。为此,本书也介绍了Solr与大数据框架的集成,如果你正好有这方面的需求,希望本书能够给你带来帮助。为什么写这本书

转眼间,我已经跌跌撞撞走过了5个年头,由起初的那个Java迷途小书童变身为程序员届的一根老油条,不由感慨万千。由于深谙一个非高校毕业的“正规军”一路走来有多么的艰辛,因此我一直秉持爱开源、爱分享的个性。这么多年来帮助过的程序员太多太多,本着一颗乐于助人的心,我不想大家重走我的弯路。从2015年3月中旬开始,我在ITEye技术社区发布与Lucene和Solr相关的技术博客,深受大家喜爱。每天联系、咨询我问题的网友越来越多。疲于应付的我,开始意识到仅靠一个人这样一对一地指导是行不通的。而且刚好Solr这方面的中文技术书籍在中国还是一片空白,于是萌生了写一本Solr中文书籍的想法,希望能够帮助更多的Solr技术爱好者。

2015年8月我联系到了华章的杨福川,向他提出了写这本书的想法,得到了他的大力支持。我深知自己过往没有显赫耀眼的工作经历,在一些前辈面前还只是一个晚辈。因此,在创作本书的过程中,查阅了Solr官网提供的Apache-Solr-Ref-Guide、Solr Wiki,并通读了《Solr in action》《Apache Solr 4Cookbook》《Apache Solr Essentials》《Apache Solr High Performance》等英文技术书籍。为了能够编写Solr与大数据集成相关章节,我又耗费了大量时间通读了《Apache Flume Distributed Log Collection for Hadoop》《Hadoop in Action》《HBase in Action》《Learning Spark》等大数据相关的英文技术书籍。写作本书的过程也成为本人学习提升的过程,为此我花费了整整1年的时间。资历尚浅仍可以通过自身努力来弥补,所以我时时刻刻以严谨缜密的态度对待写进书里的每一段文字,除了怀揣着对技术的一种敬畏之情,我知道我还必须为读者负责。

然而造化弄人,在2016年的2月份,我的颈部莫名其妙长了一个肿瘤,这严重影响了我的身心健康。由于辗转于北京协和医院、解放军总医院等地投医救治,所以这本书的编写工作不得不临时中断。还好我没有放弃,于是在修养了半年之后,又进入了“挑灯夜战”的状态,开始以夜继日地赶稿子。因为已经立下了写书的豪言壮志,所以再苦再累我也是要写完的!由于生病,当初所在的公司要求我立即停薪修养,在看尽了世态炎凉之后,我毅然选择了辞职,打算专职将这本书写好,给读者一个交代。没有了经济来源,只靠自己多年来的积蓄维持生活。我顶着巨大的压力,在大病初愈的情况下,决定倾注全部精力打造这本书。很庆幸我坚持下来了。每天叫醒我的不是闹钟不是鸡汤,也不是其他竞争对手,而是我的决心,因为父母已两鬓白发,快要三十的我还孑然一身。所以我不能虚度光阴,需要为了我爱的人和爱我的人努力奋斗,从而改善他们的生活。这本书也算是给自己30岁生日提前备下的一份礼物,并借以纪念不悔的青春岁月。我知道和我有着类似经历的同学太多太多,因此希望这本书能够为学习Solr的你们带来帮助和鼓励:定好一个Target,就永远不要放弃!准备工作

随书提供了大量的示例代码(本书随书示例源码下载地址:https://github.com/yida-lxw/solr-book),其中涉及MongoDB、ZooKeeper、Hadoop、HBase、Flume、Kafka、Storm、Spark、Scala等知识点,不仅限于Solr,所以对于Java初学者而言会有一定压力。尽管书中提供了部分大数据框架的集群搭建步骤,但是由于篇幅的限制不可能面面俱到,你还是需要另外查阅其他相关书籍或资料来补充大数据这方面的知识。由于随书源码是基于Maven构建的,因此你还需要掌握Maven的基本使用方法。为了尽最大努力满足大部分用户的需求,所以从第14章开始我将以Solr 6.2.1版本为例进行讲解,而Solr6.x是要求JDK 1.8+版本的,那么在学习本书之前,你需要提前安装好JDK 1.7和JDK 1.8。如果你有将Solr部署在Tomcat下的需求,那么你还应安装Tomcat环境。对于企业而言,SolrCloud集群通常会部署在Linux环境下,因此本书SolrCloud部分是以CentOS 6.5为例进行讲解的,或许你还需要掌握Linux操作系统的基础知识以及一些Linux的常用命令。另外,由于Solr是基于Lucene构建的,因此你最好拥有一定的Lucene基础再来学习本书内容会感觉更轻松。因为本书自始至终是以由浅入深的原则进行编写的,尽量细致入微地讲解每一步。当然,Solr源码是使用Java编写的,这也要求你能够熟练掌握Java编程语言的知识,并拥有良好的编码基本功以及编程悟性。而Solr中的数据往往来自于关系型数据库,因此你最好是对关系型数据库有一定的了解。如何阅读本书

全书分为上下两卷,总共16章,涵盖了Solr各个方面的知识点。本书从前到后按内容的难易程度以循序渐进的方式呈现出来。因此你只需要拥有足够的毅力将它阅读完,当然最好是能够边读边上机实践,就可以掌握Solr。此外每章之间都是相互独立的,如果你对于某章的内容已经非常熟悉,那么可以直接跳过选择感兴趣的章节进行学习。当然还是建议大家能够通读本书,系统学习Solr,这样才会对Solr有一个更完整的理解,为你日后从事Solr相关的开发工作打下夯实的基础。本书每章开头部分都列举了该章的主要知识点,可以让你快速了解本章能够学习到的内容。虽然本书中演示的示例代码在随书源码中都可以找到,但是我还是建议大家能够实际动手去敲一遍,毕竟只有亲手实践过,才能将遇到的各种问题真正悟透并彻底解决。这个过程虽是艰辛的,但也是深刻的,因为解决问题对于程序员来说就是积累经验的机会。面向的读者

·Java开发工程师;

·架构师;

·Solr技术爱好者;

·各大高校或IT培训机构的学弟学妹们。勘误与反馈

在编写本书的过程中,尽管我倾注了大量时间与精力,但是由于水平有限,书中难免会存在不足与疏漏之处,还请大家多多批评指正。如果你在阅读本书过程中有任何疑问或者建议需要向我反馈,可直接发送E-mail至736031305@qq.com或者添加个人微信(13476669029)联系我。致谢

不知道你拿到这本书的时间是哪一年哪一个季节,但是对我来说,这都是我在自己30岁之前完成的一个最大的心愿。这是国内真真正正全面介绍Solr技术的第一本中文书籍,很开心我做到了。

想感谢的人很多,首先要谢谢爸妈,在我生病期间无微不至地照顾我,并无条件地支持我。

谢谢一路以来理解并鼓励我的朋友和粉丝们,是你们让我不断坚持前行。

谢谢机械工业出版社华章公司的杨福川、高婧雅、李艺在这一年当中对我写作的信任与帮助,没有你们辛勤的付出,就不可能有这本书的面市。非常开心和幸运能够与你们共同完成这样一本书籍。

谢谢我的Java启蒙老师习晨龙,是您带我进入了Java世界,从此我在汲取知识的路上甘之如饴。

谢谢在这么多年的工作中所有帮助过我的同事,我会一直记得你们。

最后需要感谢的还是我自己,感谢曾经的年少轻狂,感谢一直都存在的梦想,对于梦想我从来没有也永远不会放弃。所以如果你还有梦想,为了你爱的人,为了你自己,请永远不要放弃!第11章Solr高级查询

通过第11章,你将可以学习到以下内容:

·掌握如何使用Function Query以及如何自定义Function Query;

·掌握如何使用Geospatial Query;

·掌握如何使用Pivot Facet和Subfacet;

·掌握如何使用JSON Facet API来实现复杂的数据统计查询;

·掌握如何使用Solr中的其他查询组件,比如Elevation(竞价排名组件);

·掌握如何使用Solr中的Result Clustering组件实现自动结果集聚类分组。

Solr作为一个强大的文本搜索平台,能够根据输入关键字查询并返回索引文档,你可能也已经了解到了Solr的一些核心功能,比如文本分词、关键字高亮、结果集分组等。尽管对于大多数搜索程序来说,将那些与用户查询最佳匹配的索引文档返回给用户是非常重要的,但是Solr还有另外一个比较常见的使用场景:聚集结果集用于数据统计分析。Solr的Pivot Facet支持叠加统计多个Facet(维度),它能够在单个查询中对任意的聚合分类进行计算统计,这使得Solr在提供数据分析报告方面变得很有用并且还十分高效。Solr另一个核心功能就是在查询时能够对数据执行一个Function(函数)进行动态计算,函数计算后的结果可以被用于Filter Query、文档的相关性评分、文档的排序、作为文档的“伪域”被返回。Solr还提供了强大的Geospatial(地理空间)查询功能,Geospatial查询允许你根据一个点或者一个区域进行多边形查询,或以经纬度为圆心在指定半径的圆内进行查询,实现附近的位置查询(比如查询当前用户所处位置附近的酒店或饭店)。有时候,你期望在返回的索引文档的域中引用外部数据源,Solr提供了这个功能。Solr还支持在同一个Solr实例内跨Core在一个外键域上执行Join操作,这类似于SQL里的两个表根据外键进行多表连接查询。上述每个复杂的功能都会在本章中进行讲解。11.1 Solr函数查询

Solr中的Function Query(函数查询)允许你为每个索引文档执行一个函数进行动态计算值。Function Query是一个比较特殊的查询,函数动态计算后得到的值可以作为一个关键字添加到查询中,也可以作为文档的评分,就像是一个普通的关键字查询同时还能生成相关性评分。通过使用Function Query,函数动态计算值可以被用于修改索引文档的相关性评分,以及查询结果集排序,而且函数动态计算值还可以作为一个“伪域”被动态添加到每个匹配的索引文档中并返回给用户。Function Query还支持嵌套,意思就是一个Function的输出可以作为另一个Function的输入,Function支持任意深度的嵌套。11.1.1 Function语法

Solr中标准的Function语法是先指定一个Function名称,后面紧跟着一对小括号,小括号内可以传入零个或多个输入参数,语法使用示例如下:functionName()functionName(input1)functionName(input1, input2)functionName(input1, input2, ..., inputN)

Function的输入参数可以是以下任意一种形式:

·一个常量值(数字或者字符串),语法:100, 1.45, "hello world"

·一个域名称,语法:fieldName, field(fieldName)

·另外一个Function,语法:functionName(...)

·一个变量,语法:q={!func}min($f1,$f2)&f1=sqrt(popularity)&f2=1

尽管Solr Function乍一看让人有点不知所措,其实Solr文档中定义了每个Function的输入参数的类型,大部分的Function都遵循Function的标准语法,但是Constant Function(常量函数)、Field Function(域函数)、Parameter Substitution(替换变量)这些属于特例,它们支持另一种简单语法。Constant Function(常量函数)的语法就是值本身。Field Function(域函数)的语法就是域的名称被一个名称为“field”的函数包裹。Parameter Substitution(替换变量)的语法就是函数的输入变量使用的是一个$开头的变量,该变量引用自请求URL的查询文本中定义的变量。除了这3个特例,其他函数都使用标准的Function语法。

因为Function的所有输入参数可以被看作是一个Function(函数)(常量值可以被看作常量函数),所以Function的标准语法在概念上来讲,就可以理解为functionName(function1,...,functionN)。假设索引文档中有个fieldContainingNumber域,它其中有个值为-99,那么请思考下面几个Function的使用示例:max(2, fieldContainingNumber) // 输出结果:2max(fieldContainingNumber, 2) // 输出结果:2max(2, -99) // 输出结果:2max(-99, 2) // 输出结果:2max(2, field(fieldContainingNumber)) // 输出结果:2max(field(fieldContainingNumber), add(1,1)) // 输出结果:2

从上面示例你会注意到,你可以为Constant Function常量函数(甚至你可以为其他任意标准函数)使用Field Function进行包装,尽管输入的参数顺序以及每个输入参数的含义会有所不同,但是它们最终都是用于计算-99和2之间的最大值。将一个函数的输入参数看作另外一个函数的好处就是它允许你用任意的嵌套函数来实现复杂计算。并不是所有的Function(函数)都支持同样类型的输入参数,有些Function(函数)期望接收字符串类型常量参数,而另外一些Function(函数)可能期望接收Integer或者Float类型的数字。假设fieldContainingString域的域值为"hallo",请思考下面的函数调用示例:strdist("hello", fieldContainingString, edit) // 输出结果:0.8strdist("hello", "hallo", "edit") // 输出结果:0.8

strdist函数用于计算两个字符串之间的相似度,相似度计算是基于一个指定的算法进行计算,使用哪种算法是通过函数的第3个参数进行指定,我们示例中的"edit"表示采用编辑距算法。假如我们将参数的数据类型指定为错误的,函数将会返回什么呢?strdist("hello", 1000, edit) // 输出结果:0strdist(1000, "1000", edit) // 输出结果:1strdist("1001", 1000, edit) // 输出结果:0.75

你可能会觉得函数会抛出异常,然而实际上函数内部会适当地自动进行数据类型转换,比如在示例中,将数字常量1000转换成字符串"1000"。在大多数情况下,你并不能安全地将一个字符串转换成一个数字,此时Solr可能会抛出一个异常。因此,需要谨记:函数嵌套确实很好用,但是并不是所有的函数都可以随意嵌套,你需要考虑每个函数的输入参数类型是否正确。

Solr的Function可以影响相关性评分,可以被用于Filter Query过滤结果集,可以基于函数计算值进行排序,可以将函数计算值作为索引文档的“伪域”并返回,甚至可以基于函数计算值进行Facet查询统计。下一节我们将深入学习这些用法。11.1.2 使用函数查询

为了便于后续的示例讲解,请大家从随书源码中获取Core的相关配置文件、测试数据及导入测试数据的测试类,根据我们前面章节所学的知识将本节测试环境需要的"news"Core搭建好。

在Solr中执行一个典型的关键字查询,需要在倒排索引中查找关键字,同时计算每个匹配索引文档的相关性评分,从而决定哪些索引文档与查询关键字比较相关,最后作为结果集返回。然而查询不仅能基于搜索关键字,你可以在查询中插入一个Function并将其看作另外一个搜索关键字。为了演示Function Query,请建立"news"Core并运行随书源码中的IndexNews类导入测试数据。假如已经成功导入了测试数据,可以执行下面的查询示例:http://localhost:8080/solr/news/select?q="United States" AND France AND President AND _val_:"recip(ms(date),1,100,100)"&indent=true

上面的查询表示查询包含"United States"短语且包含France和President关键字,并且函数计算值在[1,100]区间范围内的索引文档。这里有3个关键点需要引起你的注意:

·_val_语法:用于注入一个Function Query,这里的_val_可以看作主查询中的一个查询Term。

·Function Query并不会改变最终返回结果集中索引文档的总数。

·查询的最后相关性评分一般是查询中每个Term的相关性评分的总和,"United States"、France和President这些Term的相关性评分是基于tf-idf相似度算法进行计算的,但是Function Query的评分计算是函数自身的计算值。

基于上述3点,你可以了解到示例中的Function Query是为了给新添加的索引文档进行加权。最新的索引文档的相关性评分可能是100,而最旧的索引文档的评分可能是1,剩下的索引文档的评分会落在[1,100]之间。注意,每个索引文档的最后评分是标准化的,这意味着每个索引文档的最后评分不会都到达100分,最近添加的索引文档相比之前显示会更靠前。

Function在Solr中无处不在,它可以对用户的q参数进行加权,它还可以在不同的Query Parser中使用,比如在eDisMax Query Parser中通过bf参数指定Function;它还可以作为Filter Query的一部分,用于索引文档排序等。但是最重要的是你需要了解Function Query是如何被执行的。前面的示例中你已经见过了"_val_"这样的语法,你可能还记得我们之前介绍过的Function Query Parser,可以通过一个本地参数!func来构造一个Function Query,比如:{!func}functionName(…)。Function Query本质就是将函数计算值作为构造的函数查询的评分,因此,以下几种查询语法是等价的:q=solr AND _val_:"add(1, boostField)"q=solr AND _query_:"{!func}add(1, boostField)"q=solr AND {!func v="add(1, boostField)"}

为一个查询,添加一个Function看起来非常有用,它能修改查询匹配的索引文档的评分。如果你期望过滤掉函数计算值不在指定范围内的索引文档,可以使用Function Range Query Parser来解决函数计算值范围过滤。

如果你需要根据函数计算值的范围来过滤索引文档,那么Function Range Query Parser(简称frange)会比较适用你的使用场景,Frange过滤器通过执行一个指定的Function Query,过滤掉函数计算值不在指定范围内的索引文档。为了演示这种功能,我们搭建测试环境。这里会用到随书源码中的"salestax"Core,Core相关配置文件和测试数据以及导入数据测试类请从相应章节中查找获取。导入完成之后,请看下面这个查询示例:http://localhost:8080/solr/salestax/select?q=*:*&fq={!frange l=10 u=15}product(basePrice, sum(1, $userSalesTax))&userSalesTax=0.07

以上查询先通过sum函数计算$userSalesTax和1的价格之和,然后将basePrice域的域值与sum函数计算返回值通过product函数求乘积,最后通过frange过滤器的l(即lower表示最小值)和u(即upper表示最大值)参数定义了product函数计算返回值的取值范围,符合这个区间范围限制的索引文档将会被返回。你还可以设置incll(即include lower)和inclu(include upper)参数来指定是否包含两个边界值。

你可能会说,能不能自定义一个Function来灵活地过滤任意查询匹配的结果集?关于自定义Function相关内容会在本章的后续章节讲解。现在你已经知道如何为查询添加Function,并且理解了函数评分是如何计算的,接下来让我们继续学习使用函数动态计算值来代替静态的域值。11.1.3 将函数计算值作为“伪域”返回

在上一节,你已经了解到函数的输入参数可以被看作是一个函数,既然如此,那么似乎我们可以使用Function来替换Field,因为Field和Function最终都是返回一个值。事实也是如此,你不仅可以为每个索引文档动态计算得到一个数值,还可以将这个数值作为一个“伪域”随索引文档一起返回。重新回到我们在上一节中的"salestax"示例,执行下面这个查询:http://localhost:8080/solr/salestax/select?q=*:*&userSalesTax=0.07&fl=id,basePrice,product(basePrice, sum(1, $userSalesTax))

上面这个查询返回的结果会是怎样的呢?正如你看到的那样,你会发现返回的索引文档中多了一个“伪域”,“伪域”的域名称就是我们定义的函数表达式,“伪域”的域值就是函数表达式最终的计算值。之所以称为“伪域”,是因为它并不真正存在于我们的索引数据中,但是它仍然会像其他存储域一样被一起返回。“伪域”的名称使用函数表达式可能会显得冗长难看,不过值得庆幸的是,Solr提供了为“伪域”定义任意你想要的别名的功能,具体如何为“伪域”定义别名,请看下面这个示例:http://localhost:8080/solr/salestax/select?q=*:*&userSalesTax=0.07&fl=id,basePrice,totalPrice:product(basePrice, sum(1, $userSalesTax))

这里我们为"product(basePrice,sum(1,$userSalesTax))"这个伪域定义了一个别名totalPrice,最终返回结果里伪域名称就是我们这里定义的别名了。正因为你可以为“伪域”定义任意的别名,因此也就意味着可以将“伪域”的别名定义为索引文档中真实存在的域的域名称,这样就可以直接使用“伪域”的值来冒充该域的真实域值。当你期望根据用户权限来控制某些用户没有权限访问某个域的真实域值的时候,通过“伪域”别名来冒充真实域会对你很有用。

通过使用Function,你可以在域的域值返回之前对其进行任意操纵,比如经过函数计算变换它的值。你不仅可以通过函数修改文档中任何域的域值,还可以通过函数修改文档的相关性评分,从而影响文档是否应该被返回,或者文档在返回的结果集中的排序。11.1.4 根据函数进行排序

在上一节,你了解了如何将一个函数的动态计算值添加到索引文档中作为一个“伪域”在查询结果集中返回;你也知道了如何根据函数计算值来对查询结果集进行过滤,以及如何使用函数来修改匹配文档的相关性评分。接下来,让我们继续学习如何基于函数动态计算值来对查询结果集进行排序。根据函数动态计算值来对查询结果集进行排序的语法与普通查询中根据某个域排序的语法没什么太大的不同,具体请看下面这个查询示例:http://localhost:8080/solr/salestax/select?q=*:*&userSalesTax=0.07&sort=product(basePrice, sum(1, $userSalesTax)) asc, score desc

上面这个查询,根据product函数计算值升序进行排序,然后再按文档的评分降序排序,你可以结合其他函数构造更为复杂的Function Query,比如:http://localhost:8080/solr/salestax/select?q=_query_:"{!func}recip(ms(date),1,100,100)"&userSalesTax=0.07&totalPriceFunc=product(basePrice, sum(1, $userSalesTax))&fq={!frange l=10 u=15 v=$totalPriceFunc}&fl=*,totalPrice:$totalPriceFunc&sort=$totalPriceFunc asc, score desc

上面这个查询首先根据Function Query Parser对"{!func}recip(ms(date),1,100,100)"查询表达式进行解析,构造成Function Query;然后通过_query_语法将Function Query转换成普通的Query,转换后的查询并没有过滤任何索引文档,它只是用来根据文档的date域的时间远近对索引文档进行加权;然后通过fq对$totalPriceFunc变量表示的函数最终计算值进行区间范围过滤,不在[10,15]区间内的索引文档将会被过滤掉,通过sort参数先按照$totalPriceFunc变量表示的函数计算值进行升序排序;再按照索引文档的评分降序排序;最后通过fl里将函数计算值当作索引文档的“伪域”一并返回。这个示例综合使用了我们前面所讲解的知识点。11.1.5 Solr中的内置函数

到目前为止,你已经知道如何在Solr中应用Function。由于Solr内置的函数非常多,而且还在不断增加中,所以本书这部分内容不可能面面俱到,如果本书有遗漏某个函数没有提及,读者可以自行查阅资料学习。但是我会尽量覆盖Solr中内置的大部分常用函数,并详细解释每个函数的用途以及使用语法。Solr中的内置函数大致分为5类:data transformation(数据转换)、Math(数学计算)、Relevancy(计算相关性评分)、Distance(距离计算)、Boolean(布尔操作)。1.数据转换类函数

Solr中比较常用的函数大都是转换类函数,即将数据通过一个或多个函数计算从一个值转换成另一个值。下面会详细介绍每个转换类函数的用途和用法(如表11-1所示)。表11-1 数据转换类函数表2.数学函数

数学计算是比较常用的数据分析操作。Solr全面支持数学计算,支持包括加减乘除以及三角函数等多种数学函数。表11-2列举了Solr中支持的数学函数。表11-2 Solr中支持的数学函数3.相关性评分函数

Solr的相关性评分默认是基于DefaultSimilarity类进行计算而来的,DefaultSimilarity利用索引的统计信息来决定哪些索引文档与查询匹配。这些相关性评分是针对每个索引文档进行计算得到一个综合的评分,你还可以使用相关性评分函数对个别查询进行部分评分(比如你只想返回Term的出现频率信息)。相关性评分的所有核心统计都包含于相关性评分函数中,比如tf-idf。表11-3列举了Solr中支持的相关性评分函数。表11-3 Solr中支持的相关性评分函数

可以使用上面表中的相关性函数来重写评分计算,请思考下面这个Solr查询示例:http://localhost:8080/solr/salestax/select?fq={!cache=false}text:"microsoft office"&q={!func}sum( product( tf(text, "microsoft"), idf(text, "microsoft") ), product( tf(text, "office"), idf(text, "office") ))

上面这个查询首先分别计算"microsoft"和"office"的tf和idf,然后分别计算tf和idf的乘积,最后返回两个乘积的和作为文档的最后评分。然后根据短语"microsoft office"进行过滤,将不符合fq参数要求的索引文档过滤掉。通过这些相关性评分函数,Solr为你打开了评分模型,可以随心所欲地在查询时通过相关性评分函数来干预文档评分。4.距离计算函数

有时候你希望能够计算两个值之间的距离,比如你要计算地球上两个点(甚至两个向量)之间的空间距离,这在地理空间搜索中会比较有用。你还可以计算两个字符串之间的相似度,表11-4列举了Solr中支持的距离计算函数。表11-4 Solr中支持的距离计算函数

从以上的表你可以了解到,Solr全方位支持距离计算函数。dish函数允许你指定0-norm、1-norm、2-norm、无穷norm用于N维空间内两个点或向量的距离计算,比如计算二维空间内两点的欧几里得距离:dist(2,x1,y1,x2,y2),计算三维空间内两点的曼哈顿距离:dist(1,x1,y1,z1,x2,y2,z2)。

sqedist函数相比欧几里得函数计算执行开销更小,它返回的是欧几里得距离的平方根,对于二维空间里的坐标点,平方欧式距离计算2222的是勾股定理(a+b=c)中的c,因为欧式距离计算必须要额外的2对c进行开平方根从而得到确切的c值,如果你只需要在文档排序或者文档相关性评分时获取到文档的相对顺序,而不关心两点之间的实际准确距离值,那么使用sqedist函数计算性能会更高效。

hsin函数用于计算球面上两点的距离。radiusInKm参数表示球面的半径,如果你想计算地球上两点的距离,地球的近似半径值是6371.01(赤道半径),由于地球并不是一个完美的球体,因此这个近似半径值的精确度还可以再提升0.5%。如果指定的是经纬度,那么isDegrees需要设置为true,如果坐标点指定的是弧度,那么isDegrees参数设置为false。x1、y1表示第一个坐标点,x2、y2表示第二个坐标点。

ghhsin函数与hsin函数类似,但是ghhsin函数接收的不是角度和弧度,而是geohash值。geohash函数可以接收经度和未读值,并将它们进行geohash编码,得到的编码字符串可以作为ghhsin函数的输入参数,如果在索引中你的某个域存储的是geohash函数编码后的字符串,那么可能会用到这些函数。

strdist函数用于计算两个字符串之间的相似度,一般用于相似Term的模糊匹配。如果将一个字符串看作一个多字符的向量,strdist函数计算的是两个字符串(字符向量)之间的距离,最终计算得到的相似度值范围是[0,1],0表示一点也不相似,1表示两个字符串完全相等。strdist函数的s1、s2参数表示输入的两个字符串,distType参数用于指定距离计算的算法,如果distType参数值为ngram,默认使用2个字符串相比较,但是可以通过ngram参数覆盖。

geodist函数用于计算地球上两点之间的空间距离。sfield参数必须是LatLonType域,函数返回的距离单位是千米,lon参数表示经度,lat表示纬度,pt参数即point的缩写,即坐标点(x,y)。geodist函数内部使用hsin函数,简化了使用语法。geodist函数是Solr中最常用的距离计算函数,我们会在下一章节更详细的讲解它在Geospatial Search(地理空间查询)中的应用。5.布尔函数

Boolean操作不仅可以用于关键字查询,它还可以用于构建或连接任意复杂的Function Query(函数查询),通过if、and、or、not、xor、exists等函数,可以检查域值或其他函数计算以及基于这些检查有条件的返回域值,表11-5列举了Solr支持的布尔函数。表11-5 Solr支持的布尔函数

Solr提供了这么多丰富的函数供我们选择,应该能够满足大部分用户的需求,与Solr中的其他功能一样,你也可以自定义Function来扩展Solr的Function,接下来将学习如何创建我们自己的Function。11.1.6 自定义函数

有时候,你可能想要执行某些数据操作,但Solr内置函数并不支持。值得庆幸的是,在Solr中,可以很简单地实现自己的自定义函数。可以从技术上进行内存计算做任何事情,比如访问外部文件或数据源获取数据,甚至运行任意你想要的代码。实现自定义的Function唯一的约束就是你能容忍函数计算完成耗费多长时间。因为自定义的Function代码可能会针对每个索引文档进行计算,它需要在合理的响应时间内快速的作出响应。

在本节,我们将演示如何创建一个自定义Function来将多个域的域值拼接成一个字符串。为了能够在Solr中以插件的方式使用我们的自定义Function,你需要完成以下3个步骤:

1)编写一个类表示你的Function,这个类需要继承ValueSource类,它能够为索引中的每个索引文档返回一个计算值。

2)编写一个ValueSourceParser类,它能够解析自定义Function的语法并将其解析为变量传递给第一步自定义的ValueSource类。

3)在solrconfig.xml中注册你在第二步中定义的ValueSourceParser类,指定它的完整类路径以及函数名称,但自定义Function执行时,会使用你定义的函数名称,并且自定义的ValueSourceParser类解析输入参数并传递给第一步自定义的ValueSource类。

我们将要实现的concatenation函数需要继承Solr中的ValueSource类,并重写其getValues方法,最后返回一个FunctionValues对象,FunctionValues对象可以为索引中每个索引文档返回计算值,以下的代码演示了如何创建一个ConcatenateFunction类:/** * Created by Lanxiaowei * 自定义Concatenate函数 */public class ConcatenateFunction extends ValueSource { protected final ValueSource valueSource1; protected final ValueSource valueSource2; protected final String delimiter; public ConcatenateFunction(ValueSource valueSource1, ValueSource valueSource2, String delimiter) { if (valueSource1 == null || valueSource2 == null){ throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, "One or more inputs missing for concatenate function" ); } this.valueSource1 = valueSource1; this.valueSource2 = valueSource2; if (delimiter != null){ this.delimiter = delimiter; } else{ this.delimiter = ""; } } public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException { final FunctionValues firstValues = valueSource1.getValues( context, readerContext); final FunctionValues secondValues = valueSource2.getValues( context, readerContext); return new StrDocValues(this) { @Override public String strVal(int doc) { return firstValues.strVal(doc) .concat(delimiter) .concat(secondValues.strVal(doc)); } @Override public String toString(int doc) { StringBuilder sb = new StringBuilder(); sb.append("concatenate("); sb.append("\"" + firstValues.toString(doc) + "\"") .append(',') .append("\"" + secondValues.toString(doc) + "\"") .append(',') .append("\"" + delimiter + "\""); sb.append(')'); return sb.toString(); } }; } @Override public boolean equals(Object o) { if (this.getClass() != o.getClass()) return false; ConcatenateFunction other = (ConcatenateFunction) o; return this.valueSource1.equals(other.valueSource1)&& this.valueSource2.equals(other.valueSource2)&& this.delimiter == other.delimiter; } @Override public int hashCode() { long combinedHashes; combinedHashes = (this.valueSource1.hashCode() + this.valueSource2.hashCode() + this.delimiter.hashCode()); return (int) (combinedHashes ^ (combinedHashes >>> 32)); } @Override public String description() { return "Concatenates two values together with an optional delimiter"; }}

关于ConcatenateFunction类有两个关键点:输入参数和getValues方法的返回值。Concatenate Function类的输入参数由两个ValueSource对象表示,ConcatenateFunction类会将两个ValueSource对象包含的值使用连接符c进行拼接。回顾我们之前讲解的知识,一个Function的输入参数可以是另一个函数的返回值,通过定义两个ValueSource对象而不是定义两个String字符串,你的函数可以接收任意输入。尽管将输入参数统统采用ValueSource类型来定义带来了很大的灵活性,但ConcatenateFunction类的构造函数的第3个参数delimiter是一个String类型,它也可以定义为ValueSource类型,它还可以是从其他域或者其他函数计算后返回的值,在我们这里,我们假设我们的delimiter参数是显式的在请求中传递的。为了能够理解ConcatenateFunction类的输出,你需要查看getValues方法,这个方法返回一个FunctionValues对象,并且getValues方法必须返回FunctionValues类型,因为我们的concatenation函数的返回值是一个字符串,我们在内部使用StrDocValues类表包含这个返回值。StrDocValues类是FunctionValues类的一个实现类,它能够将Integer、Boolean等类型数据返回成一个String。FunctionValues类有很多子类实现,有些实现可能会使用到特定的缓存,因此如果你需要这方面的优化,那么你需要检出Solr的源码进行验证确认。StrDocValues对象内部包含了一个strVal(docid)方法,当Function被执行时,它会针对每个索引文档调用一次,正因为如此,所以对于一些执行开销比较大的复杂查询,你需要确保strVal方法能够尽快执行。

现在你已经知道了Function是如何计算并返回计算值的,下一步就是理解请求参数是如何传入ConcatenateFunction对象的。以下代码演示了如何解析输入参数并传入我们的自定义Function:public class ConcatenateFunctionParser extends ValueSourceParser { public ValueSource parse(FunctionQParser parser) throws SyntaxError { ValueSource value1 = parser.parseValueSource(); ValueSource value2 = parser.parseValueSource(); String delimiter = null; if (parser.hasMoreArguments()){ delimiter = parser.parseArg(); } return new ConcatenateFunction(value1, value2, delimiter); }}

以上代码演示了如何使用FunctionQParser对象将输入参数进行解析并传入我们自定义的函数中。FunctionQParser按照标准的函数语法进行解析,比如functionName(input1,input2,…),根据请求的函数名称查找合适的ValueSourceParser实现类。可以通过调用FunctionQParser内部的parseValueSource()、parseArg()、parseFloat()等方法来获取传递给我们Function的输入参数。在ConcatenateFunctionParser示例中,我们期望获取两个ValueSource对象(可以是一个域,可以是用户输入的任意字符串或者其他函数的返回值)以及一个delimiter字符串参数,在从请求中读取到这些输入参数之后,我们创建了一个ConcatenateFunction对象并传入输入参数到其构造函数中。

实现自定义concatenation函数需要把创建的类都完成,剩下就是在solrconfig.xml中的<>元素下进行注册,让Solr知道我们自定义的新函数。

上面的name属性表示注册的函数名称,函数名称怎么定义完全由我们决定。class表示我们自定义的FunctionParser实现类完整包路径。下面是我们自定义的concat函数的几个使用示例:concat("hello","world", "-") // 返回"hello-world"concat("hello","world", ",") // 返回"hello,world"concat(123,456, ".") // 返回"123.456"concat("no", "delimiter") // 返回"nodelimiter"concat("hello","world", "field1") // 返回"hellofield1world"

如果想要在查询中使用我们刚刚自定义的concat函数,那么可以这样使用:http://localhost:8080/solr/yourcore/select?q=*:*&fl=res:concat(concat(field1,field2,","),"!")

上面的查询中我们演示了如何使用concat函数,先将field1和field2这两个域的域值使用逗号连接起来,然后将拼接后的值继续与感叹号!进行拼接。最终concat函数返回值作为“伪域”以别名res的方式返回。11.2 Solr地理空间查询

Geospatial search是Solr中比较流行的一个功能,它支持基于地理位置进行搜索。实现在每个索引文档中添加一个域,该域一个地理位置坐标点即经度和纬度,然后在查询时请求Solr过滤出落在指定坐标点的半径范围内的索引文档。Solr支持两种主要的地理空间搜索实现方式,旧的实现方式是简单的支持基于单个(纬度,经度)的半径范围内搜索,并且按照距离进行排序。新的地理空间搜索实现方式更复杂一些,它不仅能针对单个坐标点进行过滤,还支持对形状(支持任意复杂的多边形)进行过滤。简单实现主要是在查询时计算两个坐标点的距离,过滤掉相距较远的值。而目前的高级实现是以一系列网格坐标盒子模型来索引形状,搜索转变成跨多个被索引网格的查询,而不需要在查询时计算两点之间的距离。简单实现可以在数据量较小时运行,并且不需要索引额外的数据,然而高级实现方式通常适用于大数据量的情况下,而且它非常灵活,支持多种形状的搜索。11.2.1 Solr地理空间简单查询

Solr地理空间查询的简单实现支持基于单个坐标点(通常是提供经纬度)进行查询,它能够通过简单的语法来实现圆或正方形范围过滤。

要实现半径范围内搜索,首先你需要在schema.xml中定义一个域,如下所示:

LatLonType类用于地理位置域定义,它包含一对(纬度,经度)坐标值,最后分割两个坐标值映射到单独的域。为了能够成功映射到单独的域,这些域也需要在schema.xml中存在。通过添加一个以指定后缀结尾的动态域来实现这些单独域的定义,且动态域的域名称后缀需要与域类型的subFieldSuffix属性值保持一致,以下是配置示例:

当测试数据导入完成之后,你可以采用多种方式来进行位置查询,可以这样查询city:"San Francisco,CA"。即按照城市来查询,但是这种方式并不能查询离你附近位置的索引文档,甚至潜在的还可能会返回不在你查询的城市范围内的文档。

Solr提供了一种特定的Query Parser(即geofilt),它能解析(纬度,经度)并生成两个单独域同时计算与给定坐标点之间的距离(单位:千米),最后匹配距离指定坐标点指定地理区域内的所有索引文档。比如我们想要查询离"San Francisco,CA"半径20千米范围内的索引文档,那么查询语法如下所示:http://localhost:8080/solr/geospatial/select?q=*:*&fq={!geofilt sfield=location pt=37.775,-122.419 d=20}

sfield参数用于指定表示地理位置的域,即在schema.xml中定义的LatLonType域,pt(即point的缩写)参数表示纬度、经度的坐标点,d(即distance的缩写)参数用于多少半径范围内(单位:千米),指定为20即表示以20千米为半径画圆,落在圆内的索引文档将会被返回,如图11-1所示。

除了可以实现在指定半径范围内的位置进行查询,还可以通过bbox filter来实现正方形范围内的位置过滤,bbox filter和geofilt有点类似,而bbox filter是根据给定的半径画圆,然后再确定该圆的外切正方形,如图11-2所示,bbox filter需要传递的参数与geofilt一样,但是bbox filter是以正方形为边界进行过滤的,而确定正方形边界比确定圆计算速度要快,因此通常当要求范围不需要太精确时可以采用正方形边界来过滤,同时性能上稍微比geofilt好一些。如果你期望是在圆内过滤,但是又希望计算速度快,此时可以使用RPT域并且尝试调大distErrPct值比,如0.1。示例如下:

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载