solr权威指南上卷读书笔记

第1章 初识Solr
部署Solr至Tomcat
参考原书

第2章 Solr基础
通过第2章,你将可以学习到如下内容:
·理解Solr Core的基本概念;
·掌握Solr Core的基本管理;
·掌握如何使用Solr DataImporter导入数据;
·掌握Solr Cell;
·了解SolrDuplication Detection(索引去重检测);
·了解Luke的基本使用。
2.1 Solr Core
我们的索引数据一般都是基于Core进行组织管理的,自然离不开对Core的一些管理操作,比如新建Core、卸载Core、重新加载Core、重命名Core、Coer热交换、Core优化.导入数据到Core 中、Core之间的索引数据合并等。

Solr是把搜索关键字保存在一个倒排表里,搜索性能提高了N个数量级。但Solr创建索引速度相对较慢。Solr里更新部分字段(域)数据相对较慢,因为Solr里更新只能先删除再新增。而且在新增数据的可见性方面,数据库能立马可见,Solr近实时查询的数据可见性则稍差些

尽量避免使用core join 在导入数据时候将数据关联好,特殊情况可以一个查询一个core,虽然消耗大量空间,但是速度增快了,而且关联core的删除时候也会面临N+1的HTTP连接的问题
Core的基本管理
Core有两个必要的组成元素,即Schema.xml和solrconfig.xml。然后由这两个配置文件衍生出两者依赖的其他配置文件。Core的目录结构也有要求,Schema.xml和solrconfig.xml必须放置在solr_home\core_name\conf目录下。solr_home即你的Solr Core宿主目录,所有Core都存放在solr_home目录下
solr_home可以通过web.xml的元素指定

solr/home</ env-entry-name>
C:/solr_home
java.lang.string
</ env-entry>

在solrhome下创建一个core目录,Core目录名称最好与你的Core名称保持一致。然后在Core目录下面再创建一个conf目录,你的Core配置文件将全部存放在这个conf目录下。然后可以从solr 压缩包的这个目录路径( E:\solr-5.3.1\example\example-DIH\solr\solr\conf)下查找相关配置文件作为模板

currency.xml配置文件主要是各种货币之间的汇率常量配置

protwords.txt可以用来配置那些不需要进行词形还原的英文单词,比如taken、takes还原成take如果你不想它们被还原,请将其添加至protwords.txt字典文件中,一个单词单独占一行,如果你确信不需要text_en这个域类型,那么可以将text_en这个域类型整个定义部分注释,这样protwords.txt这个字典文件也不是必须依赖的

schema.xml主要用来定义你索引数据需要的域和域类型,即你需要在这里声明索引数据中需要哪些域以及每个域的类型

solrconfig.xml是Solr Core必需的一个配置文件,主要用来配置索引创建、查询、Solr缓存以及Solr组件处理器等信息

stopwords.txt为自定义的停用词字典文件主要由停用词过滤器工厂类StopFilterFactory加载。

synonyms.txt即自定义的同义词字典文件,
它主要由同义词过滤器工厂类SynonymFilterFactory加载

lange文件夹下的字典文件,其实也是停用词字典文件,只不过这里是分各国语言进行动态加载,如果当前的操作系统是中文语言包,那就会加载stopwords_zh.txt字典文件,en、zh为各国的国家代号缩写

conf目录以及下面的配置文件准备好了,你还可以在Core目录下创建data目录,这个data目录主要用来存放你的索引数据和Solr日志文件,你当前Core相关的索引数据都将全部存放在data\index目录下,而Solr日志文件将全部存放在tlog目录下,index和tlog目录不需要手动创建,Solr会自动判断是否存在该目录,若不存在会自动新建。在创建Core阶段,data目录不是必须手动创建的,即data目录在你Core添加成功后会自动创建。

一切准备工作做好之后,你就可以访问Solr的Web后台,在Core Admin模块中,单击Add Core按钮来创建Core
其中:
·name:即你的Core名称,注意Core名称必须全局唯一,不可重复;
·instanceDir:表示你的Core根目录,这里是相对你的solr_home;
·dataDir:表示你当前Core的数据目录,这里是相对上面的instanceDir;
·config:表示你的solrconfig.xml存放路径,默认是相对你instanceDir下的conf目录;
·schema:表示你的schema.xml存放路径,默认也是相对你instanceDir下的conf目录。

当你的Core创建成功后,Solr会自动在当前Core的根目录下生成一个core.properties配置文件, 主要是core的name 例如: name=ZT_BQ_RY_RY

reload按钮,请首先选中你需要重新加载的Core,然后单击该按钮触发Core的重新加载操作。reload操作就相当于先按照新修改的配置再配置一个同名的Core,等新Core创建好再把旧Core卸载。

所谓Core的卸载其实就是将Core从Solr中取消注册,这样Core就脱离了Solr管理,该Core目录下的所有文件就与Solr没有任何关系了,你可以任意删除他们。卸载操作只是取消Core与Solr的管理关系,它并不会删除Core根目录下的文件。

rename修改一个Core的名称,但它只是修改Core的名称,影响你访问Core的url,并不会修改该Core在硬盘上的Core根目录名称。

线上的core正在运行中,不可能停掉,这时候swap热替换就派上用场了。swap热交换实际就是Core目录交换。举个例子,假如你有两个Core(core1和core2),你用core2去交换core1,原本core1对应的Core目录为core1,core2对应的Core目录为core2,swap操作之后,core1对象的Core目录变成core2交换前对应的Core目录core2,core2的Core目录变成原core1的Core目录。swap热交换并不影响原运行中的Core,被替换掉的旧Core会一直保持运行状态,这样做是为了便于你随时再swap替换回去做撤销

当你选中一个Core,在界面右侧会显示当前Core的一些状态信息,如instanceDir、DataDir、numDocs等。这其实调用的是STATUS接口,此接口的URL地址为:
http://localhost:8080/solr/admin/cores?action=STATUS&core=core0
不指定Core参数即表示你需要查看所有Core的状态信息。

有时候你可能想要将多个索引目录里的数据合并到一个新的索引目录中,即Merge index。关于索引合并,Solr提供的接口URL为:
http:// localhost:8080/solr/admin/cores?action=mergeindexes&core=core0&indexDir=/
opt/solr/core1/data/index&indexDir=/opt/solr/core2/data/index
参数mergeindexs表示执行的是索引合并操作,Core参数表示索引合并到哪个Core,两个indexDir参数表示两个索引目录,即待合并的索引目录,可以有多个indexDir。

对于一个Core来说,只要名称确定,那该Core的索引目录也是确定的,所以只需要指定Core名称即可实现索引合并,这也是索引合并的另一个用法:
http:// localhost:8080/solr/admin/cores?action=mergeindexes&core=core0&srcCore=core1&srcCore=core2

有索引合并,相对的,自然有索引拆分,即Spilt Index。Solr已经提供了Split接口,该接口用于将一个索引分割成2个或2个以上的索引。它接收以下几个参数:
·Core:要对哪个Core的索引进行分割;
·path:分割后的索引写入到哪个索引目录,这是一个多值参数,即它可以指定多次;
·targetCore:这个参数表示把源Core分割成N个targetCore,这里targetCore指代一个在Solr中已经存在的Core名称。
Core Http接口
1.STATUS
获取指定Core的运行状态信息,若没有指定Core,那么就是返回所有Core的运行状态。
http:// localhost:8983/solr/admin/cores?action=STATUS&core=core0;
http:// localhost:8983/solr/admin/cores?action=STATUS。

2.CREATE
创建一个新Core,前提是Core目录已经提前创建好且Core依赖的solrconfig.xml/schema.xml都创建并配置正确,如果提供了persist=true参数,那么新Core的配置会被保存到solr.xml中。
http:// localhost:8983/solr/admin/cores?action=CREATE&name=coreX&instanceDir=path_
to_instance_directory&config=config_file_name.xml&schema=schema_file_name.xml&
dataDir=data
instanceDir参数是必需的,config、schema以及dataDir参数是可选的,Solr4.3版本之后还提供了如下两个可选参数:
·loadOnstartup表示当Solr启动时是否加载此Core;
·transient表示当瞬态Core数量达到了solr.xml中元素配置的transientCache-Size参数值时,是否需要卸载当前Core。

3.RELOAD
重建加载指定Core,在新Core加载未完成之前,对于该Core的请求还是由旧Core来处理,直到新Core加载完成,那么旧Core就会被卸载,之后的请求将交由新Core来处理
http:// localhost:8983/solr/admin/cores?action=RELOAD&core=core0
当你修改了solrconfig.xml或schema.xml后,通过使用reload功能来重新加载Core可以实现无须停止Core或重启Server容器即可实现Core配置的动态更新。

4.RENAME
重命名一个Core的访问名称,下面是一个将Core的名称由core0重命名为core5的示例:
http:// localhost:8983/solr/admin/cores?action=RENAME&core=core0&other=core5

5.SWAP
自动交换两个已知Core的名称,当你需要使用一个替补Core来替换一个线上活跃中的Core,并且要求替换后原来的旧Core能继续运行以保证你能随时撤销回原先的状态,这个时候,swap热交换就显得很有用。
http:// localhost:8983/solr/admin/cores?action=SWAP&core=core1&other=core0

6.UNLOAD
将指定Core从Solr中移除,在Core被完全移除之前,之前未处理完的Core请求会继续处理,之后发送给该Core的新请求就不再受理。
http:// localhost:8983/solr/admin/cores?action=UNLOAD&core=core0
可选参数如下:
·deleteIndex:移除Core的同时是否需要删除该Core下的索引数据,默认不会删除Core的索引数据;
http:// localhost:8983/solr/admin/cores?action=UNLOAD&core=core0&deleteIndex=true
·deleteDataDir:移除Core的同时是否需要删除当前Core的dataDir目录;
·deleteInstanceDir:移除Core的同时是否需要删除当前Core实例的根目录;
http:// localhost:8983/solr/admin/cores?action=UNLOAD&core=core0&deleteIndex=true

7.LOAD
加载指定Core
http:// localhost:8983/solr/admin/cores?action=LOAD&core=core0&persist=true
persist参数表示是否需要将当前Core的配置保存到solr.xml配置文件中

添加索引至Core
先选择一个Core,然后单击左侧的Documents菜单,打开Solr的索引添加界面。下面简单说明添加索引表单页面几个元素的含义:
·Request-Handler:表示添加索引接口对应的请求URL,只不过这里省去
了固定前缀http://localhost:8080/solr/{coreName};

·Document Type:表示你要添加的Document类型,Solr支持了常见的几种数据格式,如CSV,JSON,XML等;

·Commit Within:表示索引必须在限定的时间内完成提交,否则放弃操作,单位为毫秒,这个设置主要是为了防止索引提交长期阻塞;

·Overwrite:覆盖的意思,即是否需要根据索引数据的uniqueKey来覆盖之前添加的Document,只要uniqueKey值equals,那么就会进行Document覆盖。此参数默认值为true。注意:前提是你有在schema.xml里配置uniqueKey,否则即使你设置Overwrite为true,也不会覆盖;

·Boost:此参数用来设置当前你要添加的索引的权重值,默认值为1.0;

·Documents:这里就是你要填写的索引数据,它根据Document Type值不同,会有不同的编写格式。

Solr DIH
大多数的应用程序将数据存储在关系数据库、xml文件中。对这样的数据进行搜索是很常见的应用。DIH(Data Import Handler)提供了一种可配置的方式向Solr中导入数据,可以一次性全量导入,也可以增量导入。

要开启使用DIH功能,你需要在solrconfig.xml配置文件中配置dataimport请求处理
器,并指定data-config.xml配置文件加载路径:


data-config.xml
</ lst>
</ requestHandler >
.上面的config参数用于指定data-config.xml的加载路径,默认是相对你当前Core的conf
目录,当然你也可以指定为绝对路径,如: :xxxxxx/data-config.xml。

当然如果你还需要配置增量导人,那么你还需要再配置- -个DatalmportHandler,此时name=“deltaimport”,然后增量导人也需要对应一个delta -data config.xml。你需要指定依赖的jar包加载路径:

然后重点是配置我们的data-config.xml了

baseDir表示获取这个文件夹下的文件, fileName支持使用正则表达式来过滤一些baseDir
文件夹下你不想被索引的文件,
processor 是用来生成Entity的处理器,而不同Entity默认会生成不同的Field 域。FileListEntityProcessor 处理器会根据指定的文件夹生成多个Entity ,且生成的Entity会包含fileAbsolutePath, fileSize, fileLastModified, fileName 这几个域。
注意,这几个域是FileListEntityProcessor内置的,不能随意更改。
recursive表示是否递归查找子目录下的文件,
onError表示当出现异常时是否跳过这个条件不处理。

然后我们需要在schema.xml中定义域:

使用DIH可以完成
索引文件夹下的文本文件
索引JSON/XML/CSV文件
使用Tika索引Word/Excel/PDF
索引网络上的远程文件
索引XML文件
以上功能参考原书
从数据库中导入数据至Solr
Solr为此提供了JdbcDataSource。
要读取数据库显然你需要导人数据库驱动jar包,将驱动jar包并复制到当前Core的lib目录下。如果使用的是Oracle数据库,那显然你需要导人Oracle的驱动jar包。
重点还是配置我们的data- config.xml:
<dataSource name= " jdbcDataSource" type= " JdbcDataSource"
driver= " com. mysql . jdbc . Dr iver "
url=“jdbc:mysql 😕/ localhost :3306/ test?useUni code=true&
characterEncoding=utf-8” user="root”password=“123”/>








< / document> ,
name表示给你的数据源起个别名,便于entity元素引用,type就是Solr内置的数据源
的类名,driver 就是驱动类的完整包路径,url 就是你的jdbc连接url,这里需要注意的是,
url不要包含html中的特殊字符,以下5个字符请不要出现在url中,具体如表2-1所示。

entity的name表示给你的entity起个别名,query即需要执行的SQL语句,该SQL语句执
行后返回的结果集都将会被导入Solr。下面的部分就是数据库表字段和Solr的域名称之间的映射,有点类似Hibernate里数据库表字段名与类属性名称之间的映射。

dataSource元素中还有两个比较重要的参数和maxRows,
batchSize表示JDBC从数据库一个batchSize批次提取多少条数据,以防止一个批次返回
数据量过大撑爆内存。maxRows参数表示最多返回多少条数据,

此外dataSource还有个convertType参数,接收布尔值,表示是否需要指定数据库表字段的类型,如果convertType设置为type,那么下面的映射需要添加type参数,例如:

jabcDataSource还支持JNDI数据源

然后重新加载你的Core,最后在dataImport中执行索引导人操作即可。

DIH还可以加载nosql中的数据, 请参考原书

Solr DIH总结
DIH术语

  1. propertyWriter属性写入器
    propertyWriter元素定义了在delta查询时使用哪种日期格式和地区。这是一个可选配
    置。直接在DIH配置文件的dataConfig元素下添加该元素,配置示例如下,可用参数如
    表2-5所示。
    <propertyWriterdateFormat=“yyYy -MM-ad HH :mm:ss” type="SimplePropertiesWriter "
    directory=“data” filename= “my_ dih.properties” locale=“en_US” />

  2. DataSource数据源
    数据源是对Solr索引数据的外部来源进行抽象,你可以通过编写继承了org.apache.solr.
    handler. dataimport.DataSource类来创建自定义数据源。
    数据源定义的必需属性为其名称和类型。名称标识了实体元素对应哪个数据源。
    可用的数据源类型如下:
    ( 1 ) ContentStreamDataSource
    它接收POST数据作为数据源。它可以在任何使用了DataSource 的EntityProcessor上使用。
    ( 2 ) FieldReaderDataSource
    它可用于数据库字段包含了你希望使用XPathEntityProcessor来处理的XML内容时使
    用。你可以在配置中同时设置JDBC和FileReader数据源
    FieldReaderDataSource可以接收一个encoding参数,它默认为"UTF-8"。它必须是语
    言-国家格式,如en-US。
    ( 3 ) JdbcDataSource
    默认的数据源,一般配合SqlEntityProcessor 一起使用。
    该数据源常用于XPathEntityProcessor来从一个底层的file://或http://地址获取内容。

  3. EntityProcessor实体处理器
    实体处理器提取、转换数据并将其添加到Solr索引中。实体一般可以是数据库中的视
    图或表。每个处理器有自己的属性集合,任何实体都通用的属性如表2-7所示。参考原书

( 1 ) SqlEntityProcessor
SqlEntityProcessor是默认的处理器,其关联的数据源应该为一个JDBC URL。该处理
器特有的实体属性如表2-8所示。参考原书

(2 ) XPathEntityProcessor
该处理器用于索引XML格式数据,其数据源通常是URLDataSource或FileDataSource。
Xpath也可用于下面的FileListEntityProcessor, 来从每个文件中生成索引文档。
该处理器特有的实体属性如表2-9所示。

( 3 ) MailEntityProcessor
MailEntityProcessor使用Java Mail API基于IMAP协议索引email消息。
MailEntityProcessor通过使用用户名密码来连接到特定的邮箱,获取每个消息的email
头部,然后获取完整的email内容来构造一个索引文档(每个邮件对应-一个索引文档),使用
示例如下列代码所示:
MailEntityProcessor特有的实体属性如表2-11所示。

(4 ) TikaEntityProcessor
TikaEntityProcessor使用Apache Tika来处理传人的文档,配置示例如下:
该处理器特有的实体属性如表2-12所示。

(5 ) FileListEntityProcessor
该处理器基本就是一个封装器,它设计用于生成一系列满足由属性指定的条件的文件,
然后这些文件可以被传递给另- -个处理器,比如XPathEntityProcessor。该处理器的实体信
息将内嵌在FileListEnitity实体中。它会生成四个隐式的字段: fileAbsolutePath、 fileSize、
fileLastModified、fileName, 它们可用在内嵌的处理器中。该处理器没有使用数据源,该处
理器特有的属性如表2-13所示。

( 6) LineEntityProcessor
LineEntityProcessor会依次逐行读取数据源中的内容,并对每个读取到的行返回名为
rawLine的字段,其内容不会被解析;但是可以添加转换器来操作rawLine字段的数据,或
创建其他额外的字段。

(7 ) PlainTextEntityProcessor
PlainTextEntityProcessor读取数据源中的所有内容作为单个隐式字段plainText,其内容不会
被解析;然而你可以添加转换器来操作plainText 字段的数据,或创建其他额外的字段,例如:

(8)如何自定义EntityProcessor
当Solr内置提供的这些EntityProcessor仍然无法满足你的需求时,你可以通过继承
EntityProcessor抽象类或者继承EntityProcessorBase 类来实现自定义EntityProcessor, 其实EntityProcessorBase就是EntityProcessor 抽象类的一个子类,这里推荐你继承EntityProcessorBase类。继承EntityProcessorBase类后,你需要重写其init ( Context context)、nextRow()、getNext()、destroy() 这几个主要函数,具体如何实现,可以参阅Solr内置的LineEntity-Processor实现类源码。
自定义EntityProcessor代码编写完毕后,你需要将其打包成jar,然后将jar包放置到
core的lib目录下,之后就在data-config.xml里应用你自定义的EntityProcessor啦,具体配
置示例如下:

  1. transformer转换器
    转换器可以操作由实体返回的文档中字段。-一个转换器可以创建新的字段或者修改
    已有的字段。你需要通过在元素上添加transformer属性来告诉实体你的导人操.
    作将使用哪些转换器。transformer 属性可以指定多个,多个属性值使用逗号分割,示例
    如下:
    Solr提供了如表2-15所示的这些内置transformer转换器:

就是将一种类型转换为另一种类型的,详细介绍参考原书

(9)如何自定义Transformer转换器
如果Solr内置提供的这些转换器仍然无法满足你的需求时,你可以自己自定义Transformer转换器。
你不需要继承任何类,你只需要简单编写一个普通类,然后在该类内部定一个transformRow函数,该函数声明必须如上述代码所示。参考原书
当然你也可以继承抽象类org,apache.solr.handler. dataimport.Transformer来实现。
编写完毕后你需要将其打包成jar,将jar包复制到Core的lib目录下,然后就可以在
data-config.xml配置文件中引用你自定义的transformer转换器了,配置示例如下所示:
Solr Full Import全量导人
首先能想到的-一个比较简单的方式就是直接使用SQL的多表连接返回Solr需要的那些field
数据,SQL语句类似这样:
这种导人方式前面章节已经做过说明,这里介绍另一种方式,其实data-config.xml里
的entity元素是可以嵌套的,具体配置如下:

这里的名称为item的Entity为Root Entity,每个Root Entity对应Solr里的一个Document,
但category信息和feature信息需要根据item.id参数在子Entity里再去关联其他表查询返
回,最后映射到field上,其中两个子Entity映射的两个field : cat 和features 最终都会纳人
Root Entity对应的Document的域中。

Solr全量导人接口URL
http:// ip:port/solr/core_ name/dataimport ?command=fu1l-import

Solr Delta-import增量导人
增量导人操作内部是新开辟一个新线程来完成,并且此时Core的dataimport运行状态
为status = “busy”。增量导人耗时时间取决于需要增量导人的数据集合大小。
任何时刻都可以通过http:// localhost : 8983/ solr/dataimport
这个链接来获取到增量导人操作的运行状态。

当增量导人操作被执行,它会读取存储在conf/deltaimport.properties配置文件,利用配置文件里记录的上一次操作时间来运行增量查询,增量导人完成后,会更新conf/ deltaimport.properties 配置文件里的上一次操作时间戳。
首次执行增量导入时,若conf/ deltaimport.properties配置文件不存在,会自动新建。

如果要使用增量导人,前提你的表必须有两个字段:一个是删除标志字段即逻辑删除标志: isdeleted,另一个则是数据创建时间字段: create_ date, 字段名称不一定非得是
isdeleted和create_ date,但必须要包含两个表示该含义的字段。

根据数据创建时间跟上一次增量导人操作时间一比对,就可以通过SQL语句查询出需要增量导人的数据,根据isdeleted字段可以查询出被标记为删除的数据,这些数据的ID主键需要传递给Solr,

这样Solr就能同步删除索引中相关Document,实现数据增量更新。如果你数据表里的数据都是物理删除,没有逻辑删除标志位字段的话,那么找出已经被删除的数据显得比较困难,所以这就是需要逻辑删除标志字段的原因。

例如: 原书93页
要理解上面的data-config.xml增量导人配置,你需要先理解如下几个增量导人参数:

pk:表示当前Entity表示的主键字段名称,这里的主键指的是数据库表中的主键,
而非Solr中的UniqueKey主键域。如果你的SQL语句中使用了as关键字为主键字
段定义了别名,那么这里的pk属性需要相应的修改为主键字段的别名,切记;

query:用于指定全量导人时需要的SQL语句,比如select * from xxxx where isdeleted=0,
查询返回的是未被删除的所有有效数据,这个query参数只对全量导人有效,对增量导人无效;

deltaQuery:查询需要增量导人的记录的主键ID所需的SQL语句。可能是update,
insert,delete 等操作,比如:
deltaQuery=“select ID from xxx where my_ date > '$ {dataimporter.last index_ _time }”"
此参数值对增量导人有效;

deletedPkQuery:查询已经被逻辑删除了的数据所需的SQL语句,所以这里你需要
一个类似isdeleted的逻辑删除标志位字段。Solr通过此参数表示的SQL语句执行后
返回的结果集来删除索引里面对应的数据。使用示例:
select ID from myinfo where isdelete= 1 此参数值对增量导人有效;

deltaImportQuery=“select * from myinfo where ID='$ {dataimporter.delta.ID}”
利用deltaQuery参数返回的所有需要增量导人的数据主键ID,遍历每个主键ID,然
后循环执行deltaImportQuery参数表示的SQL语句返回所有需要增量导人的数据。
其中变量$ { dataimporter.delta.ID }用于获取deltaQuery返回的每个主键ID。

增量导人的接口URL:
http: / /localhost : 8080/so1r/ dataimport ?command=delta- import

我们可以手动在浏览器里输人,上面接口URL或者在Solr Web界面里完成增量导人操
作,但现实系统中,我们的数据库数据可能时时刻刻都在更新,我们不可能每次都依靠人工
去完成增量导人来实现Solr索引更新。此时我们需要-一个定时调度器。

最后介绍下如何使用Solr dataimport scheduler 就是solr定时同步数据的一个模块

Solr 索引
Lucene索弓|原理
Lucene建立索引的过程其实就是创建倒排索引表的过程,为此,Lucene 设计了一个
良好的索引结构,Lucene 索引结构中有几个基本概念:索引Index、文档Document、词
Term、域Field、段Segment。

索引(Index):
在Lucene中一个索引是放在一个文件夹中的,一个索引就是多个Document的集合。
同一个索引目录中的所有的文件构成一个Lucene索引。

文档(Document):
文档是我们构建索引的基本单位,索引中的每个Document就好比数据库表中的每
条记录Record,虽然不是-一个概念,但你可以这样类比去理解。
新添加的文档是单独保存在–个新生成的段文件中。

域(Field): .
一个Document其实就是一个Field的集合,每个Field就好比数据库表中的每个
字段column。不同Field可以分别存储不同信息,以及拥有各自不同的存储方式。

段(Segment):
当添加一个新文档就会生成一个新的段,并且也会触发段文件合并。
一个索引可以包含多个段文件,段与段之间是相互独立的。
合并段文件有助于提升创建索引的性能。
段文件里记录了索引中包含多少个段,每个段包含多少个文档。
索引可以由多个子索引构成,这个子索引便叫做段。

词(Term):
每个Field的域值经过分词器处理后得到的每一项称作Term。它是索引中的最小单元。
在两个不同field中的同一个字符串被认为是不同的term。
Field的域值经过序列化( tokenized) 成Term集合(Terms)。

倒排索引, 分词取词方式, 去掉停用词, 记录词在文档中位置, 频率, 压缩存储只存增量
Lucene中常见术语详解
Indexed:表示是否需要创建索引,即是否需要添加到倒排索引表中

Stored:表示是否需要存储某个域的域值,一般表示是否需要将域值写人到磁盘上进
行持久化,持久化的目的是为了查询的时候能再次获取返回给用户做展示。当然存
储则意味着会增大索引文件的体积。如果你的索引目录是基于内存的,那Stored设
置true还是false都没什么意义;

Tokenized :表示是否需要对某个域的域值进行分词操作,如果你设置为不分词,那
么会把域值全部内容当作一个Term存人倒排索引表;

Norms:即Normalization的缩写,翻译过来就是标准化的意思。其实这里表示是
Lucene评分机制里的标准化因子,使用标准化因子来影响文档的最终评分。这就牵
扯到Lucene的评分机制了。 当一个词在多个文档中出现的频率高,说明本词不是什么关键信息, 如果在少数文档出现较多, 说明是关键信息

multiValued:用于表示指定Field 是否是一一个多值域。我们知道,域值一般是单个值,
但有时候可能我们的域值是-一个List集合或者一个数组,这时候该如何处理域值建
立索引呢?为了解决这个问题,所以设计了多值域;

termVectors :表示是否对指定Field启用空间向量模型,当你需要使用FastVector-
Hightlighter高亮器或者MoreLikeThis功能时,你就需要启用termVectors;

termPositions:表示是否记录Field的域值中的每个Term的位置信息即记录当前是文
档中的第几个term,前提是你必须要先启用termVectors;

termOffsets :表示记录Field的域值中的每个Term的位置偏移量,所谓偏移量,其
实就是Term在文档中的起始位置和结束位置,都是从零开始计算。比如I like Java
这个字符串,其中单词Java的位置偏移量就是[7,10];

DocValues : Lucene 索引的存储一般都是以倒排索引的方式( term-doc),即Term到
Document的一个映射。但是在搜索相关功能处理的时候,如排序、高亮,需要通过文档docid找到相应的term值、term 的位置信息等。
为此,在Lucene4.0中,引人了一个新字段类型DocValue,即在索引的时候建立文档到值( document-to-value)的映射。这个方法保证减轻了一些字段缓存的内存要求,并且使得Sorting、Faceting 、Grouping、Fuction Query的响应速度更快。但开启DocValues需要额外保存索引信息,因此会增大索引体积。DocValues只适用于部分FieldType,这些FieldType底层实际又是使用的Lucene的DocValues Type。DocValues 适用的FieldType如下所示:

Payload :所谓payload其实就是提供用户传入一个额外的自定义信息,而该信息可以
干预文档最终的评分。

创建Solr索引
由于Solr内部提供了/update接口来实现索引数据的提交和创建,这个功能已经集
成到Solr的Web后台中,如图2-45所示。
update接口URL为: http://localhost:8080/solr/core/update。
这个接口可以实现索引文档的添加删除更新, 入参需要符合solr的格式, 参考原书

Solr Cell
一般用户都会需要能够接收二进制的结构化文件(比如word文件,PDF等等)并对其
创建索引。Apache Tika框架内部包装了很多不同格式的转换器,比如PDFBox、POI。Solr
的ExtractingRequestHandler使用Tika允许用户上传二进制文件,Solr 然后从中提取文本建
立索引,这就是Solr Cell。详情参考原书

Solr索引去重检测
其实就是在我们创建索引文档时,就根据指定的一个或多个域的域值按照一定的算法生成一个签名摘要或者指纹,然后将这个签名摘要或者指纹当作一个独立的域添加到当前Document中,这个域被称为SignatureField (签名域),它就可以作为Document的唯-性标识,用户可以将这个签名域在schema.xml中配置为uniqueKey (唯一主键),由于Solr在创建索引时,如果发现document的uniqueKey相同就会执行索引更新操作。

其实如果你的业务数据已经包含了明显的主键,那么通过生成签名域来确保Document的唯一性就显得不那么重要了。生成签名需要时间,而额外添加一个签名域无疑增大了你的索引体积,所以这种Solr索引去重检测可以在你的业务数据没有主键的时候使用。
详细配置方式参考原书

Solr原子更新
Solr 提供了两个方式来完成索引文档的部分更新。

第一种方式就是Atomic Update (原子更新)。这种方式允许你只更新索引文档中的一个
或多个域,而不需要对整个索引进行重建。原子更新使得每次只更新索引文档的部分数据成
为可能。要想使用原子更新,那么要求你的域必须配置stored=“true”,copyField除外。

第二种方式就是optimisticconcurrency(并发乐观锁),它是很多NoSQL数据库的-一个
功能特性,它允许根据版本号有条件的更新索引文档,这种方式包含了如何处理版本匹配的
语义和规则。原子更新和并发乐观锁既可以独立的管理文档的更新,也可以结合一起使用。
但需要注意的是,并发乐观锁只是保证了对于同一个索引文档不会出现两个索引更新操作同
时并发执行,但对索引更新操作本身是不是原子更新并不关心。
这个功能需要在你所有Document上添加一个_ version_ 域,对照_ version域也是索引更新操作的一部分,默认Solr在shcema.xml里已经预定义了一个_ version_ 域,
并且这个域是自动添加到索引文档中的,用户不需要做任何干预。

使用Luke查看索引
Luke是一个基于Lucene实现的一个方便开发和诊断的小工具,使用它你可以很方便地
访问Lucene索引以及允许你显示修改查询索引数据。Luke提供的功能如下:
Luke算是-一个不错的开发测试工具,当然Solr的Web Application也是一个不错的选择。

第3章 Solr配置
通过第3章,你将可以学习到如下内容:
熟悉solr.xml配置;
熟悉solrconfig.xml配置;
熟悉schema.xml配置;
熟悉data-config.xml配置。
solr.xml配置;
solr.xml主要用于配置Core相关的管理参数设置,比如Core的加载线程数、Core的根
目录、Core的共享lib目录等,以及SolrCloud的相关配置。附带的还有Solr日志配置。
Core下的schema配置文件名叫什么等,主要和core配置相关

solr.xml配置文件支持使用变量,格式为: ${ 属性名称:默认值},后面的默认值是可
选的,表示当根据属性名称获取属性值发现找不到时会采用冒号后面配置的默认值,当用户
没有配置默认值也没有关系,Solr程序内部会设置默认值。

注意,Solr程序内部设置的默认值可能会随着版本的升级会发生变化,所以实际各个属性默认值需要以实际版本的源码为准。

冒号前的值有两种设置途径:
1 )通过JVM的-_D 参数设置,比如-DpropertyName=value;
2 )通过core.properties属性配置文件设置
属性配置文件与需要应用属性配置文件里参数的配置文件( 比如solr.xml、solrconfig.xml、 data-config.xml都可以通过properties属性配置文件来获取参数)放置在同级目录即可。如果你想要自定义core.properties配置文件的加载路径,那么你可以通过
-Dsystem.properties= conf\solrcore.properties系统参数来自定义。

下面我们来讲解Core自动发现机制。
当Solr启动时,会在SOLR_HOME(即Solr的配置主目录,同时默认也是SolrCore
的宿主目录)目录下递归查找名称为core.properties配置文件,然后根据core.properties中
的配置去加载Core,也就是说core.properties配置文件只需要放置下SOLR_HOME目录
下即可,并没有要求它必须要放置在每个Core的根目录下,一般一个Core对应一个core.
properties配置文件。
core.properties 中支持如下配置参数:
name: core 的名称,必需参数;
config:用于指定solrconfig.xml配置文件的文件名,默认值是solrconfig.xml;
dataDir:配置core的数据目录,该目录下主要存放索引数据以及事务日志,默认为.data,可选参数;
ulogDir :用于配置事务日志文件的存放目录,默认是存放在dataDir的tlog目录下,
你可以将事务日志目录设置为跟索引数据目录不在同一个硬盘,这样可以减少磁盘
竞争提升I/O性能。默认这两个目录都处在dataDir目录下,会存在I/O竞争;
schema:用于配置Core的schema.xml文件名称,默认是schema.xml ;
shard:配置分片ID,用于Solr Clound模式下;
collection: 配置当前Core属于哪个collection, 用于Solr Clound模式下;
roles: SolrCloud 的role定义,用于Solr Clound模式下;
properties:用于配置core.properties文件的加载路径,已经不推荐使用;
loadOnStartup:是否Solr启动时就加载当前Core并创建-一个 新的IndexSearcher实例;
transient :用来设置当Solr的transient-cacheSize阀值达到限制值的时候,是否自动
卸载当前core;
coreNodeName: 配置Core节点名称,用于Solr Clound模式下。
Solr启动完成后,Solr不再监视Core根目录的任何变化,当根据Core的Http接口创建
一个新 Core时,若Core根目录的core.properties 配置文件不存在, Solr 会自动创建一个core.properties文件,并将当前Core的核心信息写人core.properties,写人内容大致如下所示:

由于Solr必须根据core.properties配置文件来自动发现Core,也就意味着,如果某个
Core的根目录下没有提供core.properties,那么这个Core将会被忽略,并且无法被自动发
现和加载

solrconfig.xml配置
solrconfig.xml配置文件中包含了很多Solr自身配置相关的参数,主要配置各种处理使用什么处理器,也就是指定各种jar包

solrconfig.xml中的配置项主要分以下几大块:
依赖的Lucene版本配置,这决定了你创建的Lucene索引结构,因为Lucene各版本;
之间的索引结构并不是完全兼容的,这个需要引起你的注意;
索引创建相关的配置,如索引目录,Index WriterConfig类中的相关配置(它决定了
你的索引创建性能);
solrconfig.xml中依赖的外部jar包加载路径配置;
JMX相关配置;
缓存相关配置,缓存包括过滤器缓存,查询结果集缓存,Document 缓存,以及自定
义缓存等;
updateHandler配置即索引更新操作相关配置;
RequestHandler相关配置,即接收客户端HTTP请求的处理类配置;
查询组件配置如HightLight, SpellChecker 等等;
ResponseWriter配置即响应数据转换器相关配置,决定了响应数据是以什么样格式返
回给客户端的;
自定义ValueSourceParser配置,用来干预Document的权重、评分,排序。
下面是solrconfig.xml的配置详解:
参考原书

最后需要说明下的是在solrconfig.xml中有大量类似 这样的自定
义标签,下面做个统一的说明, 如图3-1所示。
arr: array的缩写,表示一个数组,name即表示这个数组参数的变量名;
lst:list的缩写,但注意它里面存放的是key-value键值对;
bool :表示一个boolean类型的变量,name表示boolean变量名,同理还有int, long,float, str 等;
Str:string的缩写,唯–要注意的是arr下的str子元素是没有name属性的,而list下的str元素是有name属性的。
schema.xml配置详解
Schame决定了索引是如何创建并存储,同时Solr里的Schema是一种相对松散的结构

你可以通过指定Field(域)的FieldType (域类型)来告诉Solr你数据的类型。FieldType 指示了Solr应该如何去解析Field以及Field是如何被查询的。

域分词
当创建索引时,域分词告诉Solr应该如何处理输入的数据。这个过程更准确的说法是
processing或even digestion,但官方称为analysis。

Solr 的schema文件
Solr在Schema文件里存储域类型( FieldType)和域( Field)的详细信息,Schema 文件的名称以及存储位置取决于你是如何初始化配置你的Solr。
如果你的Schema文件名称为managed-scema,那么Solr默认会支持通过Schema API对此
Schema文件的运行时更新,要想实现Schema文件运行更新还有两种可选的方式:

Schemaless Mode (无Schema模式),或者显式的配置ManagedIndexSchemaFactory ( 在solrconfig.xml中配置)。
如果你的Schema文件名称为Schema,那么Schema文件必须手动修改并重新加载才能应用该方法。
在SolrCloud模式下,你只能通过Schema API来访问Schema文件。

如果你正在使用SolrCloud但并没有使用SchemaAPI,那么需要通过ZooKeeper的
upconfig (上传配置)和downconfig ( 下载配置)命令来获取Schema文件。
Solr的域类型
在Solr内部默认定义了很多域类型,如StringField、TextField 、BoolField。
一个域类型定义包含4种类型信息:
name:域类型的名称,强制性必须指定的,且必须保证同一个schema文件内唯一性;

class :域类型对应的Java类,强制性必须指定的,如果是Solr内置的域类型,使用solr.类名方式指定即可,如果是自定义的域类型,你必须指定类的完整包路径;

如果你配置的域类型为TextField,那么你还需要配置元素来指定分词器;

Field type properties:域类型的属性依赖于具体实现类,某些属性是强制性的。

域类型在schema.xml文件中定义,每个域类型都是通过元素来定义的,
他们还可以选择性的定义在元素内,从而能够对于FieldType 进行分组管理。下 面
是一个名称为text_ general 域类型的定义示例,代码如下所示:

Solr内置提供了如下预定义域类型,它们都在org.apache.solr.schema包下,具体如
表3-2所示。 字符串,数字等等

分词器的配置
在FieldType中,对于TextField,我们还可以通过元素配置分词器,从而影
响TextField的分词行为。下 面是一个分词器配置示例:

元素表示-一个分词器,它有个type属性,可选的属性值有index、query。
index表示索引时进行分词,query表示在查询时对用户输人的关键词进行分词。
元素下又可以配置一个加多个,通过这种tokenizer与filter 的不同组
合实现不同的分词效果,你也可以实现自己的tokenizer或filter。当然也可以把tokenizer

Solr的域
Solr中的域用来表示Document的某一部分信息, 而一个Document可能由多个域组成。
Solr的域需要在schema.xml中定义。只要你定义好了域类型,定义域就变得很简单了。下
面是一个Field的简单配置示例:

Field与FieldType拥有很多相同的配置属性,表3-4列出的属性既可以在某个特定的
Field.上指定也可以在某个特定的FieldType. 上指定,若两者同时指定了同一个配置属性,那么Field会覆盖FieldType的配置,若FieldType没有显式的指定同一个配置属性,Field 仍
然会覆盖FieldType底层隐式设置的同名配置属性。

Solr的复制域
有时候,某个域的域值可能已经在其他域中存储,你可能不希望再重复提取数据并赋
值,而是期望能不能从其他域上复制过来。而Solr的复制域就能解决这类问题。
这是一个CopyField的简单配置示例,代码如下所示:


在这个示例中,text域的域值是从cat和vct这两个域复制而来的,source表示复制的
源域名称,dest表示复制的目标域名称。source和dest的属性值都支持通配符。maxChars
表示最多从source复制多少个字符到dest。正如示例所示,你可以把多个域的值复制到一
个目标域上即多次复制。

CopyField(复制域)的一-个典型应用场景就是创建-一个单一的搜索域来满足用户的各
种查询需求。

对于同一个数据,我们有时候可能需要定义多个不同的域来应付不同的需求,举个例
子,比如你有一个作者域author,作者数据是:
Jack,Hunter, Herbert, Lewis
为了满足查询需求,你可能需要创建一个seachField, seachField 的域值需要从原始的
author域上复制而来,然后对seachField索引并分词。
为了满足排序需求,你可能又需要创建一个sortField,sortField的域值也需要从原始的
author域上复制而来,但sortField不需要分词。
为了满足Facet查询需求,你可能又需要创建一个facetField, facetField 的域值或许只
是author (因为author域可能是-一个多值域,因为- -本书的作者可以有多个)域的部分值,
因为你可以并不希望统计所有的作者,所以facetField的域值可以不用从author域复制。

Solr的动态域
Solr的动态域允许你在为域创建索引时不再需要显示的在schema.xml中定义域,
动态域的出现为Solr 带来了一些灵活性。

动态域的name属性支持模糊匹配,它需要为name属性指定–个通配符表达式,当你
索引文档时,如果一个Field找不到,那么就会尝试根据通配符去匹配动态域。

其他Schema元素
id</ uniqueKey >

uniqueKey元素,它用来配置Document的唯一标识域,即Solr是使用此域来决定增
量导人时是否重复导人,如果id一样,则不会重复导人,或者当你更新索引时,你可以根
据指定的uniqueKey域,来确定唯一个Document,然后对该Document进行更新。总之,
它是用来一个确定唯一Document的,跟数据库表里的主键id概念类似,前提是uniqueKey
里配置的域名称,你需要提前使用field 元素进行定义。

CopyField不能作为uniqueKey。

配置Solr的QueryParser默认使用的操作符为OR,不配置默认就是OR。这个配置已 经过时了,后续版本中可能会被移除,所以不建议使用,推荐你使用q.op请求参数来代替。

Schema API
Schema API为每个Collection或Core提供了读写访问Solr Schema的接口。所有Schema
元素都可以被读取访问,Field、 CopyField、DynamicField、 FieldType 这些元素可以通过
Schema API添加、删除、更新。未来Solr的正式发布版中可能会支持对更多Schema元素
的写人访问。

动态加载修改schema文件
Solr的SchemaAPI允许远程客户端访问Schema信息以及通过REST接口来更新Schema
信息。SchemaAPI支持读取Schema的所有类型数据,但修改Schema信息需要你配置支持
Schema可管理并且Schema可变的SchemaFactory : ManagedIndexSchemaFactory。
在你的solrconfig.xml中配置ManagedIndexSchemaFactory的示例如下所示:
ManagedIndexSchemaFactory有以下两个配置属性:
mutable:控制是否允许运行时修改Schema信息,如果你想要启用SchemaAPI,
mutable必须设置为true,如果设置为false,, 即表示锁住Schema文件禁止修改;

managedSchemaResourceName:定义ManagedIndexSchemaFactory管理的schema配置文
件名称,若不指定默认是managed-schema。若没有在solrconfig.xml
中显式指定,Solr 会隐式的使用ManagedIndexSchemaFactory。

不过,管理Schema的另一种可选方式就是显式的在solrconfig.xml中
配置ClassicIndexSchemaFactory,ClassicIndexSchemaFactory 要求使用schema.xml配置文件,但它不支持运行时修改Schema信息,schema.xml配置文件只能通过手动修改,且想要修改立即生效,需要重新加载Core或Collection,或者重启你的SolrServer。

官方给出的solrconfig.xml示例
文件中默认配置的就是ClassicIndexSchemaFactory即默认Schema API没有启用。
如果你之前使用的是ClassicIndexSchemaFactory,现在你想切换到ManagedIndexSchema-
Factory,你可以修改solrconfig.xml 中的配置,示例上面已经给出。

当Solr被重启,Solr 会检测managedSchemaResourceName属性配置的Schema文件(默认文件名为managed-schema.xml)是否存在,若managed-schema.xml文件不存在,但schema.xml文件存在,那么Solr会将schema.xml文件重命名为schema.xml.bak,
然后新建managed-schema.xm文件并将schema.xml文件内容写人到managed-schema.xml文件中,查看managed-schema.xml文件的头部,你会看到如下信息:
< ! – Solr managed schema - automatically generated - DO NOT EDIT -->

然后你就可以使用SchemaAPI来管理你的Schema配置文件。
有时候可能又想切换回手动修改Schema配置文件,你可能需要以下步骤:
managed-schema.xml重命名为schema.xml;
修改solrconfig.xml配置文件中的 元素的class属性值为ClassicIndex-
SchemaFactory ;
重新加载你的Core。
如果你正在使用SolrCloud,那么可能需要借助Zookeeper来修改配置文件。
对于所有对SchemaAPI的调用,返回的输出模型有两种:JSON和XML。当调用
SchemaAPI去修改Schema信息时,SolrCore会自动重新加载,以便这些Schema修改能
立即对Schema修改之后添加的索引文档有效。对于Schema信息修改之前已经索引的文档,
你只能重建索引。

  1. Schema API入口点
    Schema API接口的baseURL为:
    http://:/solr/<collection_ name>
    ( 1 ) /schema
    查询或更新Schema信息,比如添加删除替换域、动态域、复制域、域类型。
    示例如下:
    GET http:// localhost : 8983/so1r/gettingstarted/ schema?wt= j son
    wt参数可选值有json/xml/schema.xml。
    ( 2 ) /schema/ fields
    获取schema.xml中定义的所有域的信息或者获取指定域的信息。
    示例如下:
    GET http:// localhost : 8983/solr/gett ingstarted/ schema/ fields
    GET http:// localhost : 8983/solr/gett ingstarted/ schema/ fields/ fieldname
    fieldname表示指定的域名称。
    支持的请求参数如表3-5所示。

更多参考172页

修改Schema接口
增删改域

Schemaless Mode
Schemaless Mode (无Schema模式)是Solr 的一个功能集合,当这些功能一起使用时,
用户就可以快速的通过简单的索引数据构造一个高效的Schema,而不需要手动的去修改
Schema文件。这些功能全部通过在solrconfig.xml文件里配置来完成控制。
配置见原书

data-config.xml配置详解
data-config.xml用于使用Solr DIH从外部数据库导人数据,
如上所示, 根元素下有和两种元素,
元素表示数据源,即需要导入到Solr中的数据来自哪里以及应该如何加载数据,数据源可
以配置多个。 元素表示-一个Solr索引,其下可以定义多个元素,每个Entity表示解析出来的一个实体,它可能是数据库表里的一.条记录,也可能是文件里的一行数据等,Entity 元素下可以定义多个。每个Entity元素由多个field 元素组成。Entity元素还可以嵌套,比如你有个book的Entity,其中有个表示书籍分类的field :categoryID,
你可能希望对分类的名称进行索引,而不是对分类的id数字进行索引,此时你就可以定义
一个子Entity,在子Entity里去加载category分类表获取分类名称信息。,

data-config.xml中还可以使用属性参数,举个例子,当你在配置JDBC数据源的时
候,你可能不希望将jdburl、driver.username、password这些JDBC连接参数配置在data-
config.xml文件里,或许更希望能够单独配置在一个properties属性文件里,然后通过参数
名去替换,这样方便统一管理这些参数, 因为这些参数在开发环境、测试环境、产品环境下不尽相同,可能会需要经常来回切换,使用properties 属性文件显然更方便,而Solr默认是支持的,当你运行DIH导人数据时,Solr 默认会在SOLR_ HOME/core/conf 目录下生成一-个dataimport.properties属性文件,当然也可以手动新建它,你可以在其中添加自己需要的配置参数,比如这样:

zoo.cfg配置详解
zoo.cfg配置用于SolrCloud模式下,主要包含Zookeeper的一- 些运行参数,配置示例如
下所示:

第4章 Solr分词
通过第4章,你将可以学习到以下内容:
分词的基本概念;
什么是Analzyer;
什么是Tokenizer;
什么是TokenFilter;
学习并掌握Solr内置的Tokenizer和TokenFilter的使用;
学习并有选择性的掌握各种常见的中文分词器的使用。
接下来的章节将会讲解Solr是如何对原始文本进行分解和处理的,关于Solr分词,这
里有3个主要概念需要理解: Analyzer,Tokenizer, TokenFilter。

分词的基本概念
分词就是将用户输入的一-串文本分割成一个个token,一个个token组成了tokenStream,
然后遍历tokenStream对其进行过滤操作,比如去除停用词、特殊字符、标点符号和统一转换成小写形式等。分词的准确与否,会直接影响搜索结果的相关度排序。从某种程度上来
讲,分词算法的不同,都会影响页面的返回结果。因此,可以说分词是搜索的基础。

理解 Analyzer
Analyzer即分词器,用于将输入文本或文本输人流分解成TokenStream的工具。表面
上看,貌似分词处理工作是由Analyzer 完成的,其实Analyzer只不过是个门面

理解Tokenizer
Tokenizer接收java.io.Reader作为输人,并通过incrementToken函数依次遍历每个
token,同时为每个token添加属性,比如postion、offset、 payload 等。因此Tokenizer才是token的生产者,它首先需要接收-一个java.io.Reader字符输人流,而该Reader对象是通过TokenStreamComponents对象的setReader函数传递给tokenizer,代码如下所示:

理解 TokenFilter
TokenFilter即Token过滤器,它内部维护了一个tokenizer,通过调用tokenizer 的increment-
Token来完成token的遍历,定义过滤规则来完成token过滤。这是一个典型的装饰者模式。
比如Solr 默认自带的OffsetLimitTokenFilter实现,代码如下所示:

Solr 分词器
Lucene中的分词器Analyzer包含两个核心组件,Tokenizer 以及TokenFilter。前者用于
生成Token流,后者用于对tokenizer进行过滤。

在Lucene中使用分词器可以直接创建相应的Analyzer对象,而在Solr中使用分词器,需要在schema.xml中配置元素,然后在元素下配置和,不过,Solr 里不能直接配置tokenizer,而是由tokenizerFactory代替,即Tokenizer是由tokenizerFactory工厂创建的。

当然你也可以直接配置一个<analyzer class=" xxxAnalyzer” >。但这种直接配置Analyzer实现类的方式不推荐使用,建议通过和 元素动态组合的方式进行配置。想要在schema.xml中配置分词器,首先需要了解Lucene提供了哪些Tokenizer和TokenFilter。需要说明的是,Solr 中并没有提供任何Tokenizer和TokenFilter的实现,Solr 中的自带分词器其实都是引用的Lucene项目。

Analyzer
用于生成
通常情况下,只有TextField才需要指定analyzer,配置analyzer最简单的方式就是使用
元素,然后指定一个class属性,class 属性值设置为分词器类的完整包路径即可,
Lucene内置的分词器类都在org. apache.lucene.analysis.Analyzer包下,配置示例如下所示:

但有时候,我们有更复杂的分词需求,通常更期望将分词处理分
解成一个个独立的部件,以及一些相对简单的步骤。正如你看到的那样,Solr 默认已经提供
了大量可选的Tokenizers和Tokenfilter配置示例,如下所示:




.


</ analyzer>
</ fieldType>

分词在两种阶段下可能会发生,-种是index (索引时),当一个Field被创建时,分词
器分词处理后返回的tokenStream会被添加到索引中,
另一.种是query(查询时),用户输人的查询关键词可能会被分词,分词后得到的tokenStream不会添加到索引中,查询时会与Field.上的索引进行匹配从而返回查询结果集。
通常,一个相同的分词器可能会在两种阶段下被应用。当你希望索引阶段和查询阶段
的分词处理有所不同,那么你可以配置两个 : type=“index” 和type=“query”,配
置示例如下所示:

Tokenizer
前面我们已经知道,Tokenzier的职责是用于分解生成tokens的,而Analyzer是由1个
Tokenizer和N个TokenFilter组成的,而且Tokenizer是analyzer处理的第-一个环节,Analyzer必须要有一个Tokenizer,而TokenFilter可以没有。需要注意的是,在Solr中,Tokenizer并不能直接配置使用,需要通过tokenizerFactory工厂类来创建对应的Tokenzier, 即每个Tokenizer必须对应一个TokenizerFactory。这也就意味着,假如你想要自定义–个Tokenizer,除了需要继承Tokenizer 基类,还需要继承TokenizerFactory实现与该Tokenizer对应的Tokenizer厂类。TokenizerFactory 的职责就是创建-一个特定的Tokenizer,与Tokenizer一- -对应。

Lucene中默认提供了很多Tokenizer实现,在Solr中也提供了与之对应的TokenizerFactory
实现,默认都是在该类名称后面加上Factory。

例子
( 3 ) LowerCase Tokenizer
LowerCaseTokenizer继承自LetterTokenzier,所以LowerCaseTokenizer除了拥有Letter-
Tokenzier的特性之外,还会将分成来的每个Token都转换成小写形式。比如“I LOVE YOU"
会被分成“i”“ love"“you”。LowerCaseTokenizer 的工厂类是LowerCase TokenizerFactory。
配置示例如下所示:


</ analyzer>

( 7 ) PathHierarchyTokenizer
PathHierarchyTokenizer会根据给定的路径层次结构生成同义词。它的工厂类是PathHierarchyTokenizerFactory,拥有如下可选参数:
1 ) delimiter:指定你的路径分隔符,比如反斜杠“/”;
2 ) replace:将delimiter字符使用replace表示的字符进行替换。比如:
输入文本: “c:\solrhomelcore1lconf "
delimiter=”" replace=“/”
输出结果:“c:”, "c:/ solrhome " , “c:/solrhomelcore1”, " c:/solrhome/core1/conf"配置示例如下所示:
<fieldType name= “text_path” class= "solr.TextField"positionIncrementGap=“100”>


</ analyzer></ fieldType>

( 8 ) PatternTokenizer
PatternTokenizer使用Java里的正则表达式来分割输人的文本并生成Tokens。
Pattern参数提供的正则表达式既可以理解为token的分隔符,也可以理解为匹配正则表
达式的文本会被提取为Token。它的工厂类是PatternTokenizerFactory。



// 剔除前后的空格



TokenFilter
TokenFilter是Tokenizer的过滤器,它用于对Tokenizer处理后的token进行二次过滤。
由于TokenFilter本身就是Tokenizer的子类,而Analyzer首先需要经过–个Tokenizer,然
后经过N个TokenFilter链条依次过滤。所以,Analyzer 其实可以没有Tokenizer,直接经
过N个TokenFilter也可以的,此时经过的第一个TokenFilter就充当了Tokenizer的角色,
因为TokenFilter本身就是Tokenizer的子类,当然可以看作是–个Tokenizer。只是全部使用TokenFilter不太直观。

在Solr中,TokenFilter都是在schema.xml配置文件中先声明的,交给Solr去创建,用户
不用关心如何创建TokenFilter,何时创建TokenFilter,只需要明白如何正确配置TokenFilter
即可。你只需要在元素下配置-一个 元素, 元素的class属性配置成TokenFilter的工厂类,与Tokenizer类似,每个TokenFilter 都必须要有个TokenFilterFactory

( 1 ) StopFilter
StopFilter会过滤掉在停用词字典里出现的Token,停用词字典文件默认名称为
stopwords.txt,需要放置在core/conf目录下。StopFilter 的工厂类是StopFilterFactory,拥有
如下可选参数:

配置一些小写,停顿,同义词等等
更多参考原书
大概30个

CharFilter
CharFilter是一个对输人文本进行预处理的组件。它也可以像Tokenizer和TokenFilter
那样加入分词处理链条中,它需要配置在Tokenizer之前。CharFilter 可以添加、更新、删除字符,但会保留原始文本的位置偏移量,从而保证对高亮功能的支持。

( 1 ) MappingCharFilter
0 MappingCharFilter用于将-一个字符映射成另一个字符,比如e→e。
它的工厂类是MappingCharFilterFactory,拥有的配置参数如下所示:

更多参考原书

Solr 自定义分词
扩展Tokenizer,你只需要继承Tokenizer 抽象类(字符串级别的分词)或CharTokenizer抽
象类(字符级别的分词),扩展TokenFilter,你只需要继承TokenFilter抽象类。扩展Analyzer,
你需要继承SolrAnalyzer抽象类或Analyzer 抽象类。

自定义TokenFilter
首先需要继承TokenFilter抽象类,重写其相应的方法。在TokenFilter类内部定义了几
个可以重写的方法,如下所示:

自定义Analyzer
首先你需要继承Analyzer抽象类,重写其createComponents方法,在其内部构造分词
的处理链,具体实现代码如下所示:

中文分词器
现有的分词算法有3大类:基于字典,基于理解,基于统计。

IK分词器
IKAnalyzer是-一个开源的,基于java语言开发的轻量级的中文分词工具包。IKAnalyzer
具有如下特性:

同理,你需要将其打包成jar然后放置到core/lib目录下,然后重新加载你的Core。
然后在schema.xml中使用IK分词器的

其他多种中文分词器
更多参考原书

第5章 Solr查询
通过第5章,你将可以学到如下内容:
Solr查询相关理论;
Solr Relevance相关理论;
Solr内置的各种查询解析器(如DisMax、eDisMax);
Solr中Query与Filter Query的区别;
Solr如何返回查询结果集;
Solr如何分页查询;
Solr排序;
Solr查询的调试模式。

Solr查询概述
当用户发起了一个Solr查询请求,这个查询请求会被RequestHanlder接收并处理。
Request Hanlder 是Solr的内置插件,用于定义Solr处理用户请求的逻辑。Solr 支持多种
Request Handler,一些是为了处理查询请求而设计,而另一些Request Hanlder则用于管理

  • -些 Solr任务,比如索引复制。

默认情况下,Solr 会选择一个特定的Request Handler来处理用户的查询请求,除此之
外,Solr还允许用户通过配置的方式来选择一个自己喜好的Request Handler来覆盖Solr的默认选择。用于处理Solr查询请求的RequestHandler被称为QueryParser,它是用于解析
用户传人的查询关键词以及查询参数的。

不同的QueryParser支持不同的查询语法,Solr默认的Query Parser 是StandardQueryParser (即标准查询解析器),它其实是继承自Lucene的Query Parser。

Solr 同时还包含DisMax Query Parser和扩展的DisMax Query Parser。标准查询解析器对于用户传人的查询表达式要求比较严格,如果用户输人的查询表达式不符合规范,可能会抛出异常,而不是返回空结果集。而DisMax Query Parser 则有更高的查询表达式错误容忍度,即便查询表达式语法错误也不会拋出异常。

DisMaxQueryParser设计的目的就是为了提供了一个类似当前那流行的搜索引擎,比如Google的使用体验,而扩展的DisMax Query Parser (即eDisMax Query Parser)则是DisMax Query Parser的功能改进版,它除了兼容Lucene的查询表达式语法以及错误容忍,还包含几个额外的特性。

Query Parser可以接收用户以下输人:
查询搜索关键词:即存在于索引中用于查询的Term;
为了调整查询,在查询的Term之间通过使用boolean运算符,增加某些特定字符
串,增加域的重要性或从查询结果中排除一些内容而传递的参数;
指定控制查询响应表示形式的参数,比如wt参数;用于指定返回结果集顺序的排序
参数,比如order参数;用于限制响应中返回指定的部分Field时传递的参数,比如
fl参数等。

你还可以指定一个filter query参数,作为查询响应的一部分,Filter Query会根据整个
索引数据和缓存的结果集发起-一个查询,因为Solr会为每个FilterQuery都分配一-个独立的缓存,这种策略可以提高Solr的查询性能。

一个Solr查询请求的返回响应中的部分Term可以被高亮突出显示,那些被选中的Term会在带有颜色的CSS盒子模型中显示,以便于用户能一下子被屏幕上的搜索结果所吸引。然而Highlighting功能使得实现在返回的长文本找出相关信息变得非常简单。

Solr 的查询响应支持配置高亮摘要片段,流行的搜索引擎比如Google会在他们的查询结果中返回摘要片段,所谓摘要片段就是使用3~4行的文本描述当前返回的查询结果。

为方便首次进人搜索界面的用户找到他们想要的内容,Solr支持两个特殊方式实现分组
查询,即Faceting (分类)和clustering (聚合),从而引导用户接下来的页面跳转。

Faceting是根据索引的Term对查询结果集分配到不同分类下的一一个过程,在每个分
类中,Solr会统计每个相关Term下的命中结果数量,这个命中结果数量在Facet中被称作
facet constraint。

Clustering会根据结果集的相似度在执行查询时对结果集进行自动分组,而不是在索引
创建时分组。Clustering返回的结果集通常缺乏–种优雅的层级结构来用于在Faceted查询
结果集中查找。尽管如此,Clustering还是很有用的,它可以为用户找出查询结果集之间意
想不到的共性,可以帮助用户排除一些他们不关心的查询结果集。

Solr还支持MoreLikeThis功能,它支持根据给定的查询关键字找出与结果集相似的其
他结果集。它能够为用户提供更多用户可能会关心的相似信息,引导用户继续单击发起一个
新的查询请求。

Solr还提供了ResponseWriter组件用于管理查询响应数据的输出格式,Solr提供了多
种Response Writer,包括XML Response Writer和JSON Response Writer。

Solr查询相关度简述
Relevance是用于表示查询响应满足用户查询需求的一一个程度。查询响应的相关性程度
取决于该查询的执行场景。一个搜索程序不同的使用场景下用户可能会有不同的需求和期望,比如对于一个爱好电影的用户,那么应该尽量给用户返回更多跟电影相关的信息,

评价一个搜索引擎的搜索相关度最好的方法就是计算用户搜索到自己想要的信息时已经浏览了多少文档。但是实际情况是,用户的查询动机是千变万化的,索引数据也是不断更新变化的,所以这种衡量方式不可取。

于是,人们便提出了下面3个概念,并建立了一个评价标准:正确率、精确率、召回率。
正确率(Precision,简称P)定义: P=返回结果中相关文档个数/返回结果的数目;
精确率(Accuracy, 简称A)定义: A =判断结果正确的文档数目/所有文档数目;
召回率(Recall, 简称R)定义: R=返回结果中相关文档数目/所有相关文档数目。

为了返回更有相关性的结果给用户,Solr可以通过Faceting、FilterQuery以及其他查
询组件进行灵活的配置,在正确率和召回率之间获取平衡,以帮助用户调整它们的查询,从
而满足特定用户群体的需求。

Solr 的查询语法解析器
Solr支持多种QueryParser(查询解析器),为搜索应用程序设计者提供了极大的灵活性
以控制如何解析查询。接下来的章节讨论的Query Parser包括:
Standard Query Parser:标准的查询解析器;
DisMax Query Parser: DisMax查询解析器;
Extended DisMax Query Parser:扩展的DisMax查询解析器;
Other Parsers:其他Parser。

Solr的Query Parser插件都继承自QParserPlugin 类,如果你有自定义Query Parser的
需求,那么需要继承QParserPlugin类以创建你自己的Query Parser插件。
QueryParser是–个负责解析查询文本并将其转换成Lucene里的Query对象的组件。
有多种可选的方式来为一个查询请求指定使用哪个Query Parser。

defType参数用于指定查询默认使用什么Query Parser,使用示例:
&q=foo bar&defType= lucene
使用LocalParams语法来指定使用什么Query Parser,使用示例:
&q={ !dismax}foo bar
所谓LocalParams即代表了一-些本地参数,提供了–种将元数据信息添加给特定的参数
类型(比如查询文本)的方式,LocalParams总是以前缀参数的表现形式传递给Solr,比如
你有这样的一个查询:
q=solr rocks
我们可以在查询文本的前面使用LocalParams添加前缀参数来提供更多的信息给Query
Parser,比如改变默认的Boolean操作符为AND以及改变默认的查询域为title:
q={ !q.op=AND df=title}solr rocks
指定LocalParams的语法是,在查询文本的前面添加一-对花括号作为前缀,然后花括号
内部以感叹号“!”开头,然后后面是使用空格分割的多个key=value的键值对,因此假如.
原始的查询文本是foo,那么应用了LocalParams之后的查询文本可能是这样的:
{ !k1=v1 k2=v2 k3=v3} foo
注意:每个查询文本只能指定一个LocalParams,花括号内的key-value键值对中的
value可以使用单引号或双引号进行包裹,比如查询多个域需要使用qf参数,你可以这样
指定:
q={ !type=dismax qf=‘myfield yourfield’ }solr rocks

如果一个LocalParams的key-value键值对缺少key只有value,那么默认隐式的key为
type,这允许用户以简短的形式来指定type参数,所以:
q={ !dismax qf=myfield}solr rocks
等价于
q={ ! type=dismax qf=myfield}solr rocks
LocalParams内部的特定key:“v”,提供了–种可选的方式用来指定查询的文本即用
户输人的查询关键字,因此:
q={ !dismax qf=myfield}solr rocks
等价于
q={ ! type=dismax qf=myfield v=‘solr rocks’}

LocalParams内部的key-value 键值对中的value值可以引用自一个外部变量,而不需要
直接为value指定值,这个特性可以用来简化查询,可以用来分离用户输人的查询参数或者
分离那些来自solrconfig.xml中默认配置的前端界面传递的参数。因此:
q={ !dismax qf=myfield}solr rocks
等价于
q={ ! type=dismax qf=myfield v=KaTeX parse error: Expected 'EOF', got '}' at position 3: qq}̲&qq=solr rocks …qq表示一个变量,它定义在LocalParams的外部,变量名必须以“$”开头,
变量名称遵循命名规范即可,也就是说,你可以任意定义多个外部变量将用户输人的查询文
本进行分离。

Solr提供了很多内置的开箱即用的QueryParser,它们是:
lucene:即默认的Lucene里的Query Parser。
dismax:DisMaxparser允许查询跨多个域并使用不同的权重。
edismax: ExtendedDisMax parser是edismax的升级版,提供了更多特性。
maxscore : .MaxScore Query Parser表示Boolean查询中通过Boolean操作符连接的多
个子查询的最大评分作为最终的得分,默认是使用sum求和。(自Solr4.4版本开始
可用)。
func:即FunctionQParser,通过Solr内置的或自定义的功能函数来干预文档评分。
boost:通过FunctionQuery为–个Query查询添加权重。
frange :即FunctionRangeQParser,它主要用于对Function Query进行范围过滤,
field:用于构造FieldQuery的Query Parser,其实FieldQuery都可以使用类似这样的
查询表达式进行表示: title: foo,而你也可以这样指定: {!field f= title}foo,两者是
等价的,这里的f是固定的key,表示域的名称。
prefix:用于构造PrefixQuery前缀查询的Query Parser。使用语法如下所示:
{!prefix f= myfield}foo,跟Lucene里的myfield: foo*写法是等价的,都是表示在
myfield域上查询以foo开头的索引文档。
raw:用于构造TermQuery的Query Parser。使用语法如下所示:
{!raw f= myfield}Foo Bar,跟Lucene里的TermQuery (Term(“myfield”, “Foo Bar”))
写法是等价的。Solr 4.0版本开始,使用term来代替raw。
term:用于构造TermQuery的Query Parser。用法与raw一样。
surround:即SurroundQParser,用于构造SpanQuery的Query Parser,使用示例如下
simple:即SimpleQueryParser,自Solr 4.7版本开始可用。
complexphrase:即ComplexPhraseQueryParser,自Solr 4.8版本开始可用。
query :表示Nested Query Parser,用于构造Nested Query ( 嵌套查询),它允许用户
通过一-些参数设置来重新定义一个查询的类型,比如将–个Query转换成其他类型
的Query,示例如下:

虽然Solr内置了这么多种QueryParser,但有些QueryParser拥有一套通用的查询参
数。表5-1总结了Solr中的通用查询参数,Standrad、 DisMax、 eDisMax Query Parser 都
支持。

( 5 ) fq
fq参数用于定义一个Filter Query、Filter Query用于对普通的Query查询返回的结果集进行一次过滤,由于Filter Query并不进行文档评分操作,所以它对于提升复杂查询的性能非常有用。Filter Query拥有独立主查询的缓存,当后续的一个查询使用了当前这个相同的Filter Query,那么会直接命中缓存,被过滤的结果集会快速从缓存中返回。
当你使用fq参数时,你需要始终记住以下几点:
fq参数可以在一个query被指定多次,Filter Query与Query 之间的交集部分的索引文档会被过滤掉,下面是一个简单的使用示例:
fq=popularity : [10 TO * ]&fq=section : 0
Filter Query也可以涉及复杂的Boolean查询,上面的示例也可以改写成一个单一的fq,比如下面这样:
fq=+popularity : [10 TO * ] +section : 0
每个Filter Query都有一个独立的缓存区,所以,如果这两个条件是经常一起出现的,你应该使用同一个fq,如果两个条件是相对独立的,那么你应该使用两个fq
但是需要注意的是,如果你将多个条件使用一个fq表示,那么意味着对于这多个条件过滤返回的结果集全部存储在一个缓存区中,有可能你的缓存区大小不够,会导致缓存无法命中,降低了Filter Query的查询性能。虽然你可以通过在solrconfig.xml中配置filterCache调整缓存区的大小,但应该思考的是你的Filter Query缓存区应该尽量充分利用,分配了500M 实际每次只利用了50M,那就是浪费;
在Solr查询请求URL中指定的所有参数涉及的特殊字符都应该进行转义或编码成十六进制。

详细使用参考原书

Lucene 的基本查询语法
Solr的Standard Query Parser继承自Lucene 的Query Parser,因此你需要先了解Lucene
中关于Query Parser支持的查询语法。Lucene 通过Query Parser提供了丰富的查询语法,内部是使用JavaCC来实现查询关键字的语法解析。

在学习使用Query Parser之前,你需要先熟悉以下几点:
如果你通过编程的方式生成了查询表达式并将其传递给Query Parser解析,那么此时你应该认真考虑下是否应该直接通过Lucene Query API来构建你的Query对象,换句话说,设计Query Parser的初衷就是为了避免用户在查询时输入晦涩难懂的查询表达式,直接输人查询文本,然后通过Query Parser将其解析成查询表达式,你自己生成查询表达式再传给Query Parser就本末倒置了;
对于不需要分词的域,应该直接对其构造Query,而不是将查询关键词传递给QueryParser,因为Query Parser 会借组Analyzer分词器对用户的查询关键词进行分词,既然你的域不需要分词还传递给Query Parser就有点多余;
口在查询表单中,普通的查询文本需要传递给Query Parser进行解析,而对于日期范围、关键字等应该直接通过Query API构建Query,而对于类似下拉列表的值不应该添加到查询关键字中,而应该直接通过选择的列表项的某个值构建TermQuery

在构建查询时,你既可以指定-一个在某个特定的域上进行查询,也可以不指定域在默
认域上进行查询,你可以在任何域上进行查询,先指定域的名称,其后紧跟- - 个冒号,冒号之后就是你的查询Term。举个例子,假定你的索引中包含了两个域: title 和text,并且text .
是默认域,

Lucene支持对单个Term进行通配符查询,对单个字符进行模糊那就使用“?”通配符,
对于多个字符进行模糊,那么就使用“”通配符,单个字符的通配符查询你可以这样执行
查询,比如你想要查询“test" 或“text":
te?t
对多个字符进行模糊,你可以这样执行查询:
test
或者t*t

但是你不能将“?”或“*”通配符放置在查询表达式的开头,因为这种前缀模糊查询
性能非常烂,为了防止用户使用,默认已经被禁止使用了。

Lucene还支持基于Levenshtein Distance实现的Fuzzy Query,你可以通过在单个Term
后面添加“~”符号来指定使用FuzzyQuery。比如你想要使用FuzzyQuery返回一个跟
‘roam”拼写相似的单词,那么你可以这样指定查询表达式:
roam~
这个查询会返回包含foam、roams 等单词的索引文档。当然你也可以在“~”符号后
面指定一个相似度数值来限定返回的结果,相似度数值取值范围为[0~ 1 ],默认值为0.5。
示例如下:
roam~0 .8

Lucene支持ProximitySearches(即邻近查询),表示查找两个单词之间间隔指定距
离的索引文档,使用邻近查询你可以在Phrase短语之后添加一个“~”符号,比如查询
apache” 和“jakarta” 之间间隔10个单词的索引文档你可以这样指定查询表达式:
“jakarta apache”~10

Lucene的Range Query (范围查询)允许匹配指定域的域值在特定区间范围的索引文档,
使用中括号“[]”表示包含边界,使用花括号“{}”表示排除边界。默认Lucene中不支持
Date域,所以在Lucene中时间日期类型数据你只能使用字符串或者转成Long类型的毫秒
数,然后你才可以使用RangeQuery。范围查询离不开大小比较,默认大小比较标准是按照
字符的ASCII码值进行比较的。下面是-一个Range Query的查询表达式示例:
mod_ _date: [20020101 TO 20030101]
这个查询表达式表示查询mod_date域的域值在20020101和20030101之间的所有索引
文档。

Lucene还提供了对匹配文档中包含指定Term的相关性级别即Term的权重值,权重值
用数字表示,Term的相关性越高那么它的Boost权重值就应该越大。为一个Term指定权重
值可以使用^符号并在它后面添加一个权重数值。通过为一个Term添加权重值可以方便用
户控制返回的索引文档的相关性。比如你想要查询“jakarta apache”,并且你想要“jakarta
在文档中拥有更高的权重,那你可以在jakarta这个Term后面指定权重值。你可以这样
指定:
jakarta^4 apache
这使得包含“Jakarta”这个Term的索引文档具有更高的相关性,你也可以对短语指定
权重,比如:
“jakarta apache”^4 " Apache Lucene ”.
默认权重值为1,尽管权重值必须是正数,但它可以被指定为小数

Lucene的查询表达式还支持Boolean操作符,它允许多个Term之间通过Boolean逻辑
操作符进行连接。Lucene 支持的Boolean操作符有AND、“+”、OR、NOT、“一”。注意:
所有的Boolean操作符必须大写。
Lucene默认使用的是OR操作符,这意味着如果你没有显式指定Boolean操作符,那
么在两个Term之间默认使用OR操作符进行连接。OR操作符连接两个Term并匹配包含
两个Term中任意-一个的索引文档,这类似于求两个集合的并集。

“+”加号操作符用于匹配文档中必须包含指定的Term,它必须指定在Term的前面。
比如查询一个文档必须包含“jakarta” 并且可能包含“lucene”,你可以这样查询:
+jakarta lucene .

NOT操作符用于排除包含指定Term的索引文档,比如查询一个文档包含“jakarta
apache"但不能包含“Apache Lucene”,你可以这样查询:
“jakarta apache” NOT " Apache Lucene"

同理还有“一”减号操作符,它与NOT作用类似。

Lucene支持使用小圆括号“()”对查询表达式进行分组形成多个子查询表达式,当你
想要控制Booealn查询的逻辑时,它可能会比较有用。比如查询文档包含“jakarta”或者
“apache"并且必须包含“ website”,你可以这样查询:
(jakarta OR apache) AND website

Lucene支持对单个域的多个查询表达式使用圆括号“()”进行分组,比如查询文档中
包含“return” 并且包含短语“pink panther",那么你可以这样查询:
title: (+return +“pink panther” )

另外需要弓|起你注意的是,Lucene 中以下字符需要进行转义:
因为它们在Lucene的查询表达式中拥有特殊的含义,所以如果想要在查询表达式
中使用这些字符,那么你需要对其进行转移,只需要在上述特殊字符的前面添加斜杠字
符“\”即可,比如你想要查询的文档中包含“(1+1=2)”,那么你可以这样查询:

Solr 的标准查询语法解析器
SolrStandardQueryParser是Solr的标准查询解析器,也是Solr默认使用的查询解析
器,它继承自Lucene的QueryParer,因此Lucene的查询表达式语法全部适用于Solr。但
Solr的标准查询解析器与Lucene的查询解析器还是有一些区别的,即两者并不是完全对
等的。
Solr标准查询解析器支持如表5-5所示的参数。

Solr标准查询解析器与Lucene查询解析器之间的区别:
通配符查询中“”星号可以用在范围区间的两端,比如:
●field: [
TO 100]: 查询域值小于等于100的索引文档;
●field: [100 TO*]: 查询域值大于等于100的索引文档;
●field: [* TO ]:查询返回所有索引文档;
●-field:[
TO *]:查询域值为空的索引文档。

支持filter()语法将一个普通查询的结果集缓存到FilterQuery的缓存区,比如q=
features: songs OR filter(inStock: true)。

在Solr中区间范围查询、前缀查询、通配符查询都是常量评分查询,即返回的每个索
引文档的评分都是相同的,评分因素TF、IDF、索引文档的权重、coord 都会被忽略。

TrieDateField域的区间范围查询中支持的日期格式不同,在Solr中支持UTC日期格
式,UTC日期时间格式为YYYY-MM-DDThh: mm: ssZ。比如:
●timestamp: [* TO NOW]:返回从过去截止到目前的所有索引文档;
●createdate: [1976-03-06T23: 59: 59.999ZTO *]:返回从1976-03-06T23: 59: 59.999Z
之后的所有索引文档;
●createdate: [1995-12-31T23: 59: 59.999Z TO 2007-03-06T00: 00: 00Z] :返回两个时
间区间内的索引文档;
●pubdate: [NOW-1YEAR/DAY TO NOW/DAY + 1DAY] :返回从去年的今天到明天
之间的所有索引文档。

Solr DisMax
DisMaxQueryParser设计的初衷是处理用户输人的简单查询短语(不包括复杂的语法)
并且基于每个域的重要性为跨多个域的个别Term添加不同的权重。提供的一些额外参数使
用户能够基于特定的规则去影响评分(不依赖于用户的输人)。
–般来说,DisMaxQueryParser接口看起来更像是Google而不像标准的Solr请求处理
器接口。这种相似性使得DisMax成为能适用于多种应用程序的QueryParser,
,它接收-一个简单的语法,并且几乎不会产生错误信息。也就是说DisMax的查询语法是比较松散的,即便语法不正确,只会不返回结果并不会返回错误信息给客户端用户。

DisMax支持极其简单的语法,它是Lucene QueryParser语法的一一个子集。因为在Lucene
中,引号字符是用来组织Phrase短语,“+”加号用来表示强制要求必须满足,而“一”减
号用来表示可选的条件。Lucene Query Parser里的其他所有特殊字符( AND和OR除外)都
会被转义以简化用户的使用体验。DisMax负责根据用户可能使用了Boolean条件的输人
构建一个跨多个域的DisMaxQuery并指定权重,同时它还为Solr管理者能提供一个额外
的Boosting Query、Boosting Function以及Filter Query,以便于能够人工干预查询结果。
DisMax的所有参数在定义于solrconfig.xml配置文件中的RequestHandler里都赋予了默认
值,你可以通过将参数拼接到Solr的查询URL后面进行默认值覆盖。

不管你理不理解上面的解释,请记住DisMax设计的目的就是提高易用性以及能够接收
任何输人但不返回任何错误信息,提高用户的使用体验。

除了通用的请求参数、高亮参数和facet参数之外,DisMax 查询还支持如表5-7所示的
参数。

(1)mm
默认通过q参数指定的查询条件都是“Should”,在Lucene中Boolean查询的条件满足
有3种类型: MUST (必须满足)、MUST NOT (必须不满足)、SHOULD (应该满足)。当一
个查询包含了这3种类型的查询条件,那么此时你可以通过指定mm参数来要求查询至少
必须匹配几个查询条件。指定mm参数有如表5-8所示几种可选方式。

更多参考原书

Solr eDisMax
eDisMax即Extended DisMax,它是DisMax Query Parser的升级版,它除了支持Lucene
Query Parser的所有参数、DisMax Query Parser的所有参数之外,它还支持:
在Lucene语法模式下,将and、or 解析为AND、OR;
支持- -种“magic field" (魔域)比如_ val_ 和_ query_ 。魔域并不在schema.xml中真实
存在,但是你可以使用它做- - 些- - 些特殊的事情,比如使用_ val 实现function query,
使用_ query_ 实现nested query。如果_ val 域用于 term qeruy或phrase query,那么它的值会被解析成一个Function;
包括改进了部分智能的特殊字符转义以防止语法错误,同时仍然支持FieldQuery,
Phrase Query以及“+”/ “一”加减号操作符;
增强了对Span Query的支持,在对拥有邻近Term的文档加权之前,你不需要在文
档中匹配所有的词;
包括高级停用词处理;
增强了对BoostFunction的支持,在eDisMax模式下,DisMax的bf和bq参数依然
支持;
支持纯否定的nested query (嵌套查询):比如支持+ foo (-foo)查询,它会匹配所有
索引文档,它能够限制终端用户查询哪些域或者拒绝直接的域查询。

除了能够安全地处理用户输人的查询文本以及随心所欲的解释查询语法,eDisMax最有
用的特性就是支持跨多个域查询,而不是强制的将查询文本复制到-一个默认域上进行查询。
eDisMax会自动将查询文本的每部分应用到指定的多个域上进行查询。在Lucene中,你想
根据查询文本“solr in action”在多个域上构造一一个查询可能需要这样写:

更多参考原书

Solr的其他查询语法解析器
除了我们之前讨论的几种主要Queryparser之外,Solr还提供了很多种其他QueryParser
如图5-2所示,它们可以用于实现- -些特殊需求。这一节将具体讲解其他Query Parser 并举例说明如何使用它们。.

参考原书 11个query parser

Query VS Filter Query
还需要好好理解Query和FilterQuery
是如何工作的。比如Query和FilterQuery有何区别?他们直接是如何相互影响的?他们
是如何决定查询的性能已经最终返回结果集的相关性的?Solr查询由两个重要的查询操
作组成,他们匹配用户查询请求参数以及对匹配的结果集进行排序,以便于匹配度较高
的前几个索引文档会被返回。默认索引文档会基于相关性评分进行排序,这意味着在查
询结果集被查询并收集到之后,需要一个额外的操作来计算每个匹配的索引文档的相关性
评分。

fqVSq
通过了解并理解fq和q之间的区别将有助于你更高效地执行查询。
fq只有一项单一的职责:对匹配的索引文档进行过滤限制,不会对索引文档进行相关性评分操作。
而q参数拥有两项职责:
根据用户传人的查询条件匹配符合条件的索引文档;
使用相关性算法根据Term列表对匹配到的索引文档进行相关性评分,这些Term列
表可能是用户传人的,也有可能是对用户输人的查询文本字符串经过分词器处理后得到的

因此,q参数作为一个特殊的过滤,它会告诉Solr在计算相关性评分时什么Term应该
考虑进去。正因为这种差别,人们更倾向于将输人的查询关键字传递给q参数,然后通过
fq参数自动生成FilterQuery。
从q参数构造的主查询中分离出来的FilterQuery,会经常在查询之间被重用,因为
Filter Query会在Filter缓存区缓存Filter Query匹配到的索引文档。由于q参数构造的主查
询需要对匹配到的每个索引文档进行相关性评分,而将查询某些部分分离到FilterQuery中,
那么这些被分离到Filter Query的部分匹配到的索引文档将不会进行相关性评分操作,这将
大量减少了索引文档的相关性评分的计算工作量。

关于Solr查询,你需要注意的最后一点就是你可以在你的查询请求中添加多个fq参.
数,从而构造多个Filter Query,但是q参数只能指定一个。考虑下面的两个查询:
q= keywords : sol r& fq=category : technology& fq=year :2013
q= keywords : solr& fq=category : technology AND year :2013
上面两个查询返回的结果集是相同的,但他们的查询执行效率是不同的,因为Filter
Query不会执行额外的索引文档的相关性评分,以及内部会为每个FilterQuery分配独立的
缓存区来缓存Filter Query匹配到的索引文档,那么第二次以及后续再次执行同样的Filter
Query时,将会直接命中缓冲区中的索引文档,

鉴于FilterQuery内部的这种机制,应该将那些能够过滤掉大部分无效索引文档的
查询条件通过fq参数实现,你也可以将那些用户使用频率比较高的查询条件使用fq参数来
实现,充分利用缓存来提升查询效率。

因为Query和Filter Query都可以用来查找索引文档,那么可能有人就要问了: Query
和Filter Query的执行顺序是怎样的?有人说Filter Query先执行,有人说Query先执行,
也有人说FilterQuery和Query是并行执行,到底是哪种?这看起来是个超级复杂的问题。

问题的答案取决你的使用场景。
从纯技术的角度来讲,它们的执行顺序是这样的:
首先每个fq参数会在Filter缓存区查找索引文档,如果在缓存中存在,那么会返回命中的 DocSet;
如果fq参数在Filter缓存区没有命中且Filter缓存开启了,那么会根据fq参数构造Filter Query 从 Solr索引中加载获取每个索引文档的DocSet,并存入Filter缓存;根据q参数构造主查询从Solr索引中加载匹配的所有索引文档,得到一个索引文档的集合,如果主查询返回的索引文档的内部ID在Filter Query缓存的DocSet中也存在,那么该索引文档就判定为应该返回给用户,然后就对该匹配的索引文档计算相关性评分;
如果主查询还包含其他POST Filter(它会在Query和 Filter Query执行完毕之后才执行),他们还会执行一部分的索引文档收集工作;

鉴于上面的执行流程解释,从技术层面上来讲,在Filter缓存开启的情况下,Filter
Query应该是先于Query执行的,随后Query和Filter Query在索引文档收集阶段是并行执
行的,当两者的索引文档收集工作执行完毕之后,POST Filter 开始执行。

Filter Query缓存
Solr 允许你控制哪些FilterQuery应该被缓存,以及每个Filter Query、Query、POST Filter之间的执行顺序。
当最常用的FilterQuery在任意时刻都保持缓存命中的话,那么你的Solr查询性能是最优的。

为了避免Filter缓存被一些不太重要的FilterQuery过度负载了,你可以显式关闭任意
Filter Query的缓存,具体语法如下所示:

默认Filter Query的Filter 缓存是开启的,即cache参数的默认值是true。
通过查询时候指定参数来关闭

Filter Query执行顺序
如果你的查询请求中包含了多个Filter Query,每个Filter Query的执行顺序会直接影
响你的查询性能,如果一个FilterQuery提前执行并过滤掉一部分结果集缩小了查询匹配范
围,那么接下来执行的FilterQuery会基于更少的Document进行二次查询匹配,其查询速
度自然就更快。
相反的,如果-一个Filter Query必须要执行非常复杂的计算,比如geospatialFilter需要过滤指定半径的范围内的坐标点,那么此时应该给予更少的索引文档去执行昂贵的CPU计算,那么就意味着那些比较昂贵的FilterQuery需要尽量靠后执行。

当你清楚地知道FilterQuery中哪个执行开销是最昂贵的,Solr允许你通过指定一个
cost参数来强制指定Filter Query的执行顺序,cost 参数表示对该Filter Query执行开销的-一个估算数值,数值越大说明该Filter Query的执行开销越大,即意味着它应该越靠后执行。
cost参数的具体使用语法如下所示:
fq={ ! cost=1 }category : technology&
fq={ !cost=2 }date: [NOW/DAY-1YEAR TO *]&
fq={ !geofilt pt=37 .773, -122.419 sfield=location d=50 cost=3}&
fq={ ! frange 1=90 u=100 cache=false cost=100}
scale (query({ !v="content: (solr OR lucene) "}) ,0, 100)
cost参数值没有强制要求必须是连续的数字,默认会按照彼此之间的相对顺序来决定
Filter Query的执行顺序。当cost参数值>= 100 时,此时该Filter Query在Solr中被称为
Post Filter。

Post Filter
有时候,某些Filter Query的执行开销会很昂贵,你期望它等到Query和所有的Filter
Query全部执行完毕之后再执行它。为此,Solr 中实现了一种特殊的Filter :
Post Filter。
当Query和Filter Query并行收集索引文档时,每个索引文档被Query或Filter Query所收集,只有当每个索引文档都被收集后,Post Filter 才会随后被执行。这个特性允许执行开销比较低的FilterQuery先执行去限制总的匹配文档数目。随后执行的开销比较昂贵的PostFilter
会基于一个比较小的索引文档集合进行过滤查询,这在一定程度上也能提升执行开销比较
昂贵的PostFilter的执行性能。cost参数除了能够对FilterQuery指定之外,其实它能够用
于Post Filter, 当你将一个Filter Query的cost参数值设置的大于等于100,那么默认该FilterQuery就会被转换成PostFilter。但前提是该FilterQuery实现了PostFilter接口且cost>=100。你没有必要为所有的Query和Filter Query都实现PostFilter接口,除非确实需要将该Filter Query放置后最后执行。如果想要自定义Post Filter,那么你需要实现PostFilter 接口。

Solr 返回结果
通过对前面章节的学习你已经知道如何使用QueryParser来构造Query。通过Query来
查询返回你期望的索引文档,通常可能还需要指定每个文档应该返回哪些域供客户端页面显
示,基于文档的相关性评分对索引文档进行排序,以及对查询返回的结果集进行分页,甚至
你可能还需要设置返回的响应结果集的输出格式。下面的将会对这些内容进行讲解。

设置响应输出格式
默认Solr查询返回的响应数据输出格式为XML。对于SolrServer端来讲,采用哪种
输出格式都是无关紧要的,至于客户端期望返回什么数据格式,则需要通过指定wt参数来
强制要求SolrServer返回指定的数据格式。

如果需要自定义响应输出格式,那么你需要编写自己的ResponseWriter实现类。为此,需要创建一一个类实现QueryResponseWriter接口,并重写其init、getContentType、write这3个函数,然后,你必须在solrconfig.xml中注册刚刚创建的那个Response Writer 类

选择返回域。
1.返回存储域
当在schema.xml中定义域时,你可以指定-一个可选的属性stored,表示是否是-一个存
储域。通过fl参数返回给用户并没有什么意义,因为只有存储域返回才有意义。
fl参数可以指定多个你期望返回的域,多个域名称之间使用逗号分隔,比如:
/select? … .&fl=id, name

2.返回伪域
除了返回存储域之外,每个索引文档中比较有价值的信息当中的一个就是文档的评分,
你可以在fl参数中指定score这个暗含的“伪域”,比如:
/select?. . .&fl=* , score

3.文档转换器
有时候,在查询匹配的索引文档写人到Response中之前,往索引文档中添加一- 些额外
信息可能会比较有用,比如你可以添加查询计划信息、在分布式搜索中文档属于哪个shard
以及索引文档的内部ID。这类信息在Solr内部是通过DocumentTransformer(文档转换器)
转换而来的,Document Transformer可以按照如下方式被调用:

4.返回域别名
除了可以将动态生成的值作为伪域返回之外,Solr还提供–种在你返回搜索结果集之前
对域定义别名的能力。这有点类似于SQL里的“select tid as id, stu_ name as sname”为返回
表字段定义别名。定义别名语法如下所示:
/select ? . …&fl=id, betterFieldName : actualFieldName
betterFieldName表示定义的别名,actualFieldName表示 schema.xml中实际存在的域的名称

分页查询
需要用到两个请求参数:start和rows。start参数表示查询结果集返回的偏移量,它是从零开始计算的。rows 参数表示一页查询返回多少个索引文档给用户。

Solr排序
Solr中默认是按照文档的相关性评分从高到低进行排序的,但是你其实可以基于任何内容进行排序,

根据域进行排序
当你运行一个关键字查询时,返回的查询结果集默认会按照相关性评分降序排列,即
得分高的会排在前面。
如果两个索引文档的得分相同,那么此时会再按照索引文档的内部ID进行升序排列。如果你没有设置对索引文档进行打分,那么可能索引文档没有相关性评分,这意味着所有索引文档的得分都是相同的,最终索引文档就会默认按照内部的DocID进行升序排序。

默认的这种排序行为可以很轻易地通过在查询请求中添加sort参数来覆盖:
sort=someField desc,someOtherFiela asc
sort=score desc, date desc
sort=date desC, popularity desC, score desc
需要注意的是,任何你想要按照它排序的域必须是在schema.xml中定义了indexd=
true。你应该知道Solr排序是基于索引中Term的顺序来排列的,比如你有一个string类型
的域,索引的值有1、2、3、10、20、30,然后你根据它进行升序排序,那么你得到的顺序
将是1、10、2、20、3、30,而不是1、2、3、10、20,因为对于字符串来说默认比较大小是按照字符的ASCII值的大小来进行比较的

缺失值处理
当你对一个域值缺失的域进行排序时,可能期望它排在最前面后者最后面,所以Solr允许你选择其中任意一种适合的情况。而你只需要在schema.xml中域定义上添加sortMissingLast和sortMissingFirst属性

排序的内存占用
关于Solr排序需要说到的最后一点就是: Solr排序是-一个内存密集的处理。为了实现
对索引文档进行排序,Solr 使用了Lucene 的域缓存,

调试查询结果
Solr 提供了一个特殊的查询组件: DebugComponent ( 调试组件),

返回调试信息
了解Solr查询内部执行原理最简单的方式就是通过传递debug=true请求参数来启用
Debug调试模式,Debug参数设置为true会激活查询请求的DebugComponenton调试组件,该调试组件返回一些调试信息。使用示例如下所示:
. /select?q=* : *&rows= 3&debuq=true
返回的调试信息如下:

querystring表示用户传人的查询表达式,rawquerystring 表示用户传人的原始查询文本
字符串,parsedquery表示用户传人的查询文本经过解析后的内部表示形式,parsedquery_
toString即parsedquery调用toString后的结果,主要是为了方便用户阅读。Explain 中包含
的当前查询的执行计划信息,QParser表示当前使用的是哪个查询解析器,timing中包含的
是查询的各个阶段的耗时情况信息。

开启调试模式
除此之外,你还可以通过debug参数的其他选项来控制只返回调试信息中的某-一部分
信息,除了可以设置debug=true,如果你设置debug=results,那么会看到关于索引文档的
相关性评分的详细解释信息。如果你设置debug = timing,表示只返回查询的各个执行阶段
使用的查询组件的执行耗时信息。

第6章 Solr Facet
Facet查询要求Facet Field必须是indexed = true

http://localhost:8080/solr/restaurants/select?q=:&rows=0&facet=true&facet.field=name

Solr中Facet查询的最基本形式:在指定域上执行Facet查询,
在这种Facet查询中,会对域的域值进行分组统计,当然如果该域配置了分词器,那么就是
对域的域值分词后得到的每个唯一Term进行分组统计。

对于多值域会对每一个值进行facet统计

Facet查询是针对域的唯一值进行分组统计,如果该域是StringField,即不会进行分词处理,那么就直接根据该域的每个域值进行分组统计。
对于TextField,那么就是对分词后得到的每个Token进行分类统计,统计的数字就是每个Token在所有文档中出现的总次数。
对于Solr开发者,我们一般建议单独建一个新域用于Facet查询,将域值复制到新的域,这里你可以使用Solr 中的复制域。

Solr 提供了很多Facet参数允许你对Facet查询进行调整或干预Facet查询行为。
表6-1所示为可以在FieldFacet中指定的参数。

facet.field参数可以指定多次,比如像下面这样:
&facet. field=tags&facet. field=type
有些Facet参数支持对每个域单独设置,基本语法如下所示:
f. . =
其中表示域名称, 表示Facet参数名称, 表示该
参数对应的参数值。

Query Facet
除了能够对任意的索引域执行Facet查询之外,你还可以对任意的子查询统计匹配的索引文
档总个数,而后你能够根据统计的数据进行分析。Solr 提供了这种功能,它被称为Query Facet。

多个子查询可以通过facet.query参数结合成一个查询请求。同理,
也可以将多个价格区间组合成一一个Query请求,这样可以重构我们的查询请求,如下所示:
http:// localhost : 8080/so1r/ restaurants/ select?q=* : &rows=0& facet=true&
facet .query=price: [
TO 5}&
facet . query=price: [5 TO 10}&
facet .query=price: [10 TO 20}&
facet .query=price: [20 TO 50}&
facet . query=price: [50 TO *]

QueryFacet提供了一个不错的选择。它允许在查询时非常灵活的指定或重新定义哪些Facet
Value应该被计算统计和返回。

尽管我们的示例非常简单,但它完全演示了基于任意查询构建FacetQuery的灵活性,
因为Solr提供了很多强大的查询功能,比如NesedQuery(转换查询)、FunctionQuery(函.
数查询)、Facet Query (维度查询)。

Range Facet
表示Facet区间范围查询
http:// localhost:8080/so1r/ restaurants/select?q=*: *&facet=true&
facet . range=price&
facet. range.start=0&
facet. range. end=50&
facet . range.gap=5


“5.0”.5.
“10.0” ,o,
"15.0 “,3,
" 20.0”.2.
“25.0” 2,

上面示例中facet.range参数表示对哪个域执行Facet区间查询,facet.range.start参数表
示区间的上限值,facet.range.end 表示区间的下限值。facet.range.gap 参数按照每个区间分布多少个值进行自动区间划分

这里还有其他的可选的参数,比如facet.range.hardend、facet.range.other 、facet.range.
include。下 面表6-2所示详细描述了每个参数的含义。 见原书

请求
http://localhost:8080/solr/restaurants/select ?q=: &facet=true&facet .field=state&
facet .field=city&
facet.query=price: [ * TO 10} &
facet.query=price: [ 10 TO 25}&
facet.query=price: [ 25 TO 50}&
facet.query=price: [ 50 TO * ]
返回的查询结果如下所示:
“facet_queries” : {
“price: [ TO 10}" :11,
“price: [ 10_TO 25}” : 5,
"price: [ 25TO 50} " : 4,
"price: [ 50 TO
] “: 0},
“facet_fields” : (
" state” : [
“Georgia” , 6,“california” , 4 ,“New York” , 4,” Texas" ,3,“1llinois” ,2,“South carolina” ,1 ],
" city" :[
" Atlanta,GA",6,“New York,NY”,4 ," Austin,Tx",3," San Francisco,CA",3," Chicago,IL",2,“Greenville, sc”, 1 ,“Los Angeles, CA”,1]} ,
FacetFilter
基本上,在Facet查询上应用一个Filter Query相比在Query.上应用一个Filter Query是一样的。就是会过滤掉不符合的数据,对于统计也会有影响, 对于多值域和分词域也是过滤一些剩下一些的
http://localhost:8080/solr/restaurants/select?q=
:
&facet=true&facet.mincount=1
&facet .field=name&facet .field=tags&
fq={ !term f=tags } coffee&fq={ ! term f=tags } hamburgers
Multiselect Faceting
当我们发起一个FacetQuery,Facet返回Facet名称可能并不是我们想要的,为此,
Solr允许你在FacetQuery结果返回之前修改Facet的显示名称,以更友好的名称返回给用
户。

对于那些已经被FilterQuery过滤掉的Document,Solr也允许将其纳人Facet统计之
内,Solr中将这种功能称为MultiselectFaceting。即FilterQuery能过滤查询结果集中将要
返回的索引文档,但并不影响最终统计的索引文档总个数,看起来就像Filter Query并没有
起作用一样。

在本节中,我们将介绍关于key、tag 以及exclude的概念和使用,它们启用了Facet中非常有用的重命名以及多选功能。

key
所有的Facet (维度)都有一个方便开发者区分它们彼此的名称,如果是Field Facet或
Range Facet,那么Facet的名称就是Field的名称,如果是Query Facet,那么Facet的名称
就是query的查询表达式或者Facet Value或Function动态计算的值。通过使用key这个本
地参数,你可以很容易的对任何Facet名称进行重命名,具体请看下面的示例:
http:// localhost : 8080/so1r/ restaurants/ select?q=* : &facet=t rue& facet . mincount=l&
facet. field={ !key=“Location” }city&
facet .query={ !key=“<$10”}price: {
TO 10}&
facet .query={ !key=“$10 - $25” }price: [10 TO 25}&
facet. query={ !key=“$25 - $50” }price: [25 TO 50}&
facet .query={ !key=“>$50”}price: [50 TO *]

tag
当一个Filter Query应用到一个Solr Query请求上时,最终返回的结果集是求Query查询返回的结果集与Filter Query查询返回的结果集之间的交集。默认情况下,Facet Query也
是这种查询机制。不符合FilterQuery的FacetValue已经被排除在外,不会纳入Facet查询
统计,这在大多数情况下是有用的。

想要统计被Filter Query排除在外的数据进入Facet Query时候需要tag和ex这两个本地参数。下面的示例演示如何使用这两个参数实现MultiselectFaceting。

http://localhost:8080/solr/restaurants/select?q=* 😗&facet=true&facet.mincount=1&facet .field= { !ex=tagForstate}state&
facet .field={ !ex=tagForCity}city&
facet .query= { ! ex=tagForPrice }price: [20 TO 50}&
facet .query= { ! ex=tagForPrice}price: [ 50TO * ]&
fq= { ! tag=“tagForstate” } state:California

上面的示例中,重点就是我们为Facet Query定义了一个 Filter Query,即过滤掉不在California州的饭店,同时通过tag参数为Filter Query打上了一个标签,名为 tagForState,标签名称是可以随意取的。然后在每个Query Facet上通过ex参数来应用我们刚刚打的那个标签,应用一个标签的隐含的含义就是当前Facet Query自动忽略该标签指代的Filter Query对索引文档总个数统计阶段的影响,但是Facet Query最终返回的索引文档仍然会进行过滤。其中 ex即 exclude的缩写即排除的意思

返回的统计数据包含全部的统计值, 但是文档信息不会包含fq之外的值

在Solr中,Facet查询大量使用了Solr缓存,所以如果想要最大化的提升Facet查询
性能的话,那么你需要优化Solr的内置缓存。关于Solr缓存将在后续章节详细讲解。除了
Solr性能调优,你可能还希望了解Facet更高级方面的知识,其中一个就是Pivot Faceting。
Solr提供了两个Facet叠加分组统计的功能类似SQL里的Groupby两个字段的意思。即
在某个Facet Query执行后返回的结果集基础之上再执行其他Facet Query。如果没有Pivot
Faceting,要实现这种功能,你可能需要执行多次独立的Facet Query,不过这种做法它没有
很好的系统伸缩性,而且当索引文档数据量不断增长的时候,它很容易导致你必须运行几十
甚至几百个Facet查询,这显然不可取,Solr中的PivotFaceting就是设计用来解决此类问
题的。PivotFaceting允许你跨多个维度在–个查询中进行Facet查询统计。有关PivotFacet
留到后续章节再做讲解。

本章总结
Facet提供了一.种快速的方式使用户能够鸟瞰查询匹配的各种索引文档。你已经很难找
到一个以搜索为准的网站没有提供Facet功能了。通过Facet你可以针对指定域进行Facet
(即FieldFacet),针对任意Query进行Facet(即QueryFacet),针对指定域的给定的区间进
行Facet即Range Facet。你还可以使用Filter Query对Facet Query进行过滤即Facet Filter。
如果想排除Filter Query对Facet Count的影响,那么你可以使用tag和ex参数来实现。最
后可能还需要关注Solr提供的多维度叠加的Facet功能(即PivotFaceting)。此时此刻你应
该已经能够实现一些比较复杂的查询

第7章Solr高亮
对命中的搜索关键字进行高亮标注出来,这就是Solr中的高亮功能,英文术语称为:
Solr Highlighting。
7.2 Solr高亮的工作原理.
在Solr中使用高亮不需要做太多预先的配置工作,它是开箱即用的
下面的代码清单演示了如何在Solr中开启高亮功能:
http:// localhost :8080/solr/ufo/select?
q=blue fireball in the rain&df=sighting_en&wt=json&rows=10&hl=true

从返回的高亮片段结果来看,我们不难发现,高亮片段其实也是按照与搜索关键字的
相关性评分来排序决定最后的显示先后顺序的

如果你想要在多个域上执行高亮,那么你可以通过指定hl.fl参数来实现。
h1.fl = title, body

在Solr高亮器执行高亮之前,它需要首先访问域的原始文本即域值,要想访问域的原
始文本,那么该域必须stored = true。

高亮器会根据域类型分词
7.2.1 Fragmenter
Fragmenter会根据高亮域的原始文本与用户输人关键字生成Fragment,然后交由Scorer
对每个Fragment进行相关性打分。最后评分前N个Fragment会被构造成Snippet,这里
的N由参数hl.snippets决定。在Solr中生成Fragment有两种方式,使用GapFragmenter
和RegexFragmenter。默认实现是使用GapFragmenter, GapFragmenter 是基于一个目标长
度来生成Fragment,默认fragment的长度为100,但是它可以通过hl.fragsize参数进行
修改。但是h.fragsize参数并不表示Fragment的固定长度。GapFragmenter之所以叫做
GapFragmenter,是因为它在为多值域创建Fragment时能够避免生成的Fragment跨越了很
大一个位置间隙,而多值域的两个值之间的间隙可以通过positionIncrementGap属性来控
制,你可以schema.xml中定义field 时为其添加positionIncrementGap属性。
同时Solr还提供了RegexFragmenter 实现,它使用正则表达式的方式来生成Fragment。
比如你可以使用正则表达式[-\w,An"]{20, 200} 来匹配Fragment。由于RegexFragmenter
并不是默认实现,所以如果你想要默认使用RegexFragmenter,那么你需要在solrconfig.xml
中提前配置RegexFragmenter,

Scorer
Scorer用于对Fragmenter生成的每个Fragmemt进行相关性评分,默认Scorer的实
现是QueryScorer。它会统计每个Fragmemt中出现用户输人的查询关键字的次数,出现
次数越频繁说明相关性越高,那么Fragmemt的得分就越高,自然就越优先返回。高亮器
PostingsHighlighter中使用的是另- -种Scorer,它采用的是一种更高级的打分算法来实现对
每个Fragmemt的相关性打分,具体稍候再做讲解。

7.2.3 Encoder & Formatter
hl.formatter参数用于指定采用什么formatter来指定对高亮Term进行格式化,默认实
现是hl.formatter = simple,这种方式对于使用HTML标签来包裹高亮Term比较适用,设置
两头的包裹HTML标签你可以使用hl.simple.pre和hl.simple.post这两个参数,比如你想要
使用css美化高亮Term,那么你在查询请求中如下所示添加2个请求参数:
Facet & Highlighting
当我们应用了Filter Query之后,Highlighter 高亮器并没有对我们的“light”这个Term进行高亮,这是因为“light’是在Filter Query查询中,并不存在于q参数的主查询中。我们需要做的是如何在不改变查询的基础之.上控制高亮器使用的查询Term,在Solr中你可以通过指定hl.q参数来实现。

高亮多值域
就直接显示原始域值,那么此时你需要添加一个hl.preserveMulti参数,并将其设置为true,
此参数默认值为false,它表示在高亮结果中保留多值域的每个值,
高亮参数
364页

上面表格中的参数你都可以单独每个域进行设置,举个例子,假如你有title和body这
两个域,title 通常是一些比较短的域,所以一般一个高亮片段就够了,但是你可能希望为
body域生成3个高亮片段,那么可以这样处理:
f . body .hl . snippets=3
FastVectorHighlighter
使用默认的高亮器最大的问题就是对于大文本域执行高亮查询会非常慢。导致查询速
度慢的主要原因就是它需要在查询时对域值文本进行重新分词。为了解决这个问题,Solr提供了FastVectorHighlighter 快速高亮器,它的执行速度比默认高亮器要快,因为它跳过了在生成Fragment阶段需要重新分词的步骤。
PostingsHighlighter
Solr中还提供了一.种新的高亮器: PostingsHighlighter, 它的执行速度比FastVector-
Highlighter还快。PostingsHighlighter 需要在倒排索引表中存储Term的偏移量,与Fast-
VectorHighlighter相比,FastVectorHighlighter 需要在索引中创建- -个独立的数据结构用于检索Term的位置信息和偏移量。

本章总结
在本章中,我们通过一个简单示例讲解了高亮器在Solr的使用,详细讲解了Solr中高
亮器的工作原理,并详细解释了高亮器支持的请求参数,然后介绍了如何使用FastVector-Highlighter和PostingsHighlighter这两种高级高亮器,以及如何使用它们来提升高亮查询的性能。默认的高亮器简单易用,但对于大文本域或者大规模的索引文档,可能查询会比较慢,在这种情况下你可以考虑使用FastVectorHighlighter,FastVectorHighlighter 高亮器带来了高亮查询的性能提升的同时也是以增大索引体积为代价的。新引人的PostingsHighlighter高亮器,它比FastVectorHighlighter高亮器性能更高,而且索引体积增大幅度也没Fast-VectorHighlighter高亮器大,因此建议使用PostingsHighlighter高亮器,尤其是当你需要对大文本域进行高亮时。PostingsHighlighter高亮器最大的不足就是它依赖于Term精确的位置信息和偏移量信息,然而并不是所有的分词器都正确处理了Term的位置信息和偏移量信息,因为Postings-Highlighter高亮器可能并不兼容所有分词器,比如WordDelimiterFilter。
第8章Solr Query Suggestion查询建议
通过第8章,你将可以学习到如下内容:
掌握Solr中的Spell-Check查询组件基本用法;
掌握Solr中的Autosuggest查询组件基本用法;
学会基于N_ Gram实现Autosuggest查询;
学会基于用户的过去行为来实现Autosuggest查询。

Spell-Checking (拼写检查)和Autosuggest (自动建议)。

Spel-Checking(拼写检查)会定位到拼写错误的查询关键字,而拼写错误的查询关键字可能会导致查询没有返回结果。当你使用搜索引擎的时候或多或少都使用过它,比如当你搜索“soKr”,如图8-1所示,搜索引擎会提示“您是不是要找solr”,

Autosuggest能在用户搜索的时候即时的给予用户一些建议提示,从而帮助用户能够快
速的构建自己的查询。
8.1 Spell-Check
自动化的Spell-Check拼写检查是Solr中的核心查询功能,

8.1.2 Spell-Check 查询组件
首先需要在solrconfig.xml中已经将Spell-Check查询组件集成到/select这个Request Handler中

然后请求中可以传入spellcheck相关的参数,
比如:
spellcheck
表示是否为你的查询请求启用Spell-Check查询组件,默认值false即不启用
spellcheck.q
用于指定spell-check对哪个查询进行拼写检查,如果此参数未指定,那
么默认会对q参数构造的主查询进行拼写检查(前提是spellcheck = true)

然后在SearchHandler 中配置了spellchecker为我们查询请求链条中的最后一个查询组件

DirectSolrSpellChecker是 Solr 中的Spell-Checker查询组件默认实现

Solr Spell-Check中的一个强大功能就是你可以将多个Spell-Check查询组件结合在一起构建成一个链条形成一个组合式Spell-Check

总体来说就是需要在solrconfig.xml中配置处理器,然后查询时候使用参数
8.2 Autosuggest
Autosuggest执行速度必须要快,Autosuggest必须在用户每输人一个字符之后就能立
即更新拼写建议;
然后它必须按照term的出现频率按序排列,出现频率高的应该靠前显示。

首先你需要在solrconfig.xml中注册Autosuggest组件,然后在request handler中启用Autosuggest

在上面的配置示例中我们重新定义了一个Request Handler,且定义了Request Handler包含的查询组件只有suggest这一个,它覆盖了默认的组件栈(比如query , facet , highlighting,debug等),因为Autosuggest要求就是尽可能快的返回拼写建议给用户,因此我们不需要额外执行其他查找组件。这里通过将查询URL:/suggest 与SearchHandler(实际查询请求处理类)进行映射,这样查询客户端需要发送/suggest请求来获取拼写建议

请求
http:ll localhost:8080/solr/solrpedia/suggest ?q=atm&wtjson&indent=true
返回
" suggestion" : [ " atmosphere" ,
" atmospheric" ]}]}}
从上面返回的查询结果中我们可以得知,Autosuggest组件根据用户输入的“atm”返回了两个拼写建议:“atmosphere”和“ atmospheric”。在脑海中想象一下,这个过程就相当

当/suggest请求URL映射的Request Handler接收一个查询请求时,它执行suggest组件来生成拼写建议,而 suggest组件其实是需要提前在solrconfig.xml配置文件中注册的,下面是Autosuggest 组件的注册配置示例:

Solr 内置的Suggester采用的是前缀树的数据结构来实现的,前缀树支持快速的前缀查找。
还需要一些其他的配置,比如匹配一些高频且重要的词,等词库建立等
8.3基于N-Gram实现Autosuggest
Suggester为指定的查询Term自动生成拼写建议提供了一种很好的解决方案,但是对于
短语或者不分词的域比如我们示例中的title域,Suggester就不好用了。

N-Gram,它支持对索引文档中的不分词域的域值提供拼写建议。
8.4基于用户行为实现Autosuggest
之前拼写建议采用的方式虽然实现了需求,但是并没有考虑用户输入一个查询关键字的次数。Solr内置的Suggester适用于对指定的查询Term返回拼写建议,但用户输人的查询关键字不可能都存在于索引中。基于n-gram实现的Suggester适用于对索引文档中的多个域基于查询Term前缀进行suggest,在这两种实现方式中,都没有考虑用户的过去查询行为,比如用户输入次数比较多的查询关键字应该优先返回作为拼写建议。

比较好的实现方式应该是从一个最近查询次数比较多的关键字数据集中返回拼写建议。

我们之前的实现方式都是基于主索引库来进行suggest查询返回拼写建议的,而基于用户行为的拼写建议实现方式需要对用户的查询行为记录、采集、解析用户日志构建另外一个索引库,那么你需要开发一个工具来分析用户的查询行为日志并计算每个用户查询的流行度评分。

每当用户发起了一个查询请求,那么你的日志分析工作也要随之进行保证流行度评分能实时更新。当我们对根据用户行为构建的新索引库进行suggest查询返回拼写建议时,需要按照每个关键字的流行度评分从高到低进行排序返回。

所以需要构建一个新core或者其他的存储设备来保存一些用户行为

8.5本章总结
在本章中,我们学会了如何使用Spell-Check查询组件来处理用户输入搜索关键字时出
现拼写错误的情况,我们还学会了如何实现为查询返回拼写建议来提升搜索程序的可用性
以及用户体验。此外,我们还使用Solr内置的Suggester组件为用户返回拼写建议,Spell-
Check与Autosuggest是互补性的查询组件,两者结合一起构成了强大的拼写建议解决方案。
然后我们尝试使用了另外一种方式来实现对任意域或查询提供拼写建议,这里我们采用的是
在创建索引的同时使用N-Gram处理生成更多的Term,这样就能非常高效的使用前缀字符
来返回拼写建议。最后讨论了如何实现基于用户查询行为来返回拼写建议

第9章Solr Group分组
通过第9章,你将可以学习到如下内容:
如何“删除”查询结果集中的重复索引文档;
如何在单–请求中对查询结果集返回多个分组;
如何实现分组查询的分页和排序;
如何根据指定域、任意查询、Function 函数动态计算值对索引文档进行分组;
如何实现基于Group分组返回的结果集进行Facet查询统计;
如何高效的实现Group分组查询。

但是对于Solr而言,不同颜色不同款式在索引中其实是不同的Document,即是按照多个索引文档来存储的,“Field Collapsing”功能就是用于解决此类问题,它会按照指定值对索引文档进行收缩折叠,对于同一个值会落入同一个分组最终只返回top one,这就是“Field Collapsing
9.1 Result grouping VS Field collapsing
两者有什么区别?
Field Collapsing用于解决对于索引文档中大部分域的域值相同,只有个别域的域值
不同的情况下只返回一个索引文档

而Result Grouping通常表示更常用的结果集分组功能。尽管Field collapsing在指定域的多个重复值上返回单个索引文档时需要Result grouping的支持,但Field collapsing其实也支持对于单个查询返回多个结果集或者多个分组。
Field collapsing 更多是对传统的结果集分组功能的一个扩展。
本章主要目标是Solr 的结果集分组功能即ResultGrouping。
9.2按照指定域分组

执行如下查询示例:
http : ll localhost : 8080 / solr / ecommerce /select ?f l=id,product , format&
sort=popularity asc&a=spider-man&
group=true&
group.field=product&
group . 1imit=1

返回结果部分显示如下所示:
“product” : {
" matches" : 18," groups" :[{
“groupvalue” : " The Amazing spider Man - 2012",
" doclist " : { “numFound” : 2," start" : 0, " docs" : [
{
“id”:" 4",
" format " : " dvd" ,
“product " : " The Amazing Spider Man - 2012”} ]
} },
…l/其他省略

关于Solr中的Group有几个关键点值得注意一下:首先,需要明白Solr中的Group功
能必须通过指定group= true 参数来显式开启。然后你需要指定group.field 参数,表示对
哪个域进行分组。根据指定域分组后,该域的域值相同的索引文档会落人同一分组内,而
group.limit参数用于指定同一分组内最多返回多少个索引文档 当你想移除所有“重复”的索引文档时,group.limit参数设置为1对你来说可能会有意义。

返回的 groupValue属性表示每个分组,值为分组域的每个唯一域值,“doclist”下的numFound属性表示当前分组下有多少个索引文档。
如果你仅仅只想要移除“重复”的索引文档,并不需要其他额外的分组信息,比如每个分组下匹配的索引文档总数,那么你可以通过设置group.main = true参数来合并每个分组的结果形成一个扁平化的列表并最后在主结果集展示区“docs”部分显示,下面的查询示例演示了group.main参数的使用:
http:ll localhost : 8080 / solr/ecommerce/select ?
f l=id, product , format&
sort=popularity asc&
q=spider-man&
group=true&
group.field=product&
group.main=true
返回的查询结果集部分如下所示:
" response" : { " numFound" : 18," start" : 0 , " docs" : [
{
“id” : " 4",
" format " : " dvd" ,
“product” : " The Amazing Spider Man - 2012"},
{“id”:“6”,
“format” : " dvd" ,
“product " : " Spider Man - 2002”},
这样只返回分组的每个值

Solr Group还支持对多个域进行分组,比如group.field = type&group.field = format,如果同时还指定了group.main = true,那么Solr会只返回最后一个分组。
你还可以指定grouping.format参数来设置分组结果集的输出格式,group.format = simple会类似group.main那样以扁平化的列表形式展示分组内的每个索引文档,但此时不是在主结果集展示区“docs”部分显示,具体请大家执行如下查询

//group . format参数的使用
http:ll localhost :8080 / solr / ecommerce/ select ?f l=id, product , format&
sort=popularity asc&
q=spider-man&
group=true&
group.field=product&
group.format=simple

//group.main参数的使用以及多个域分组
http :ll localhost : 8080/ solr / ecommerce/select ?fl=id,product , format&
sort=popularity asc&
q=spider-man&
group=true&
group.field=type&
group.field=format&
group.main=true

//默认多个域分组的结果集输出格式测试
http:ll localhost :8080 / solr / ecommerce / select ?f l=id, product , format&
sort=popularity asc&
q=spider-man&
group=true&
group. field=type&
group.field=format

到此,你已经了解了如何通过group分组查询来收缩折叠结果集,你也了解了分组查询
结果集的3种输出格式:默认格式、group.format=simple表示的简单分组输出格式以及group.amin=true表示的显示在主查询结果集厮示区的单一收缩组
9.3每个分组返回多个文档
Solr的Group分组查询实际上并不仅仅只是用来对每个域的唯一值只返回单个索引文
档(前提是group.limit= 1 )。当分组数目很多时,我们可以通过rows参数来限制返回的分组总个数,start表示分组返回的起始索引位置(从零开始计算),这里跟普通的Query查询分页有点类似,这里是对返回的所有分组进行分页。具体请看下这个查询示例:
http:// localhost : 8080/ solr / ecommerce/select ?
q= spider -man&
fl=id, product, format&
sort=popularity asc&
group=true&
group. field=type&
group. limit=3&
rows=5&
start=0&
group.offset=0

从上面的查询示例可以得知以下几点:
首先你需要注意group.limit参数表示限制每个分组返回的索引文档最大个数。
第二点,你需要注意的是rows =5参数控制的不是有多少索引文档返回,而是最多有几个分组被返回,在默认的分组输出格式中,rows和start参数是应用于每个分组,而不是每个分组内的索引文档。这两个参数搭配在一起使用其实就是对返回的所有分组进行分页。group.offset参数用于控制每个分组内的索引文档的起始偏移量,比如某个分组下有10个索引文档,但group.limit设置为3,那么默认该分组下只会返回0,1,2这3个索引文档,如果group.offset设置为2,即表示每个分组从第2个位置开始返回索引文档。注意,这里的offset (偏移量)是从零开始计算的,所以此时返回的3个索引文档就是第2,3,4这3个索引文档,而且还要注意,group.offset参数值不能大于等于每个分组下所有索引文档的总个数。

在每个分组内部,索引文档也会进行排序,默认是按照id域从小到大进行排序。当你
将group.limit参数设置为1且group.main = true或者group.format = simple 时,那么默认就是按照id域从小到大进行排序。

9.4按照 Function动态计算值分组
除了能够按照指定域的唯一性域值进行分组之外,Solr还支持两种分组方式。第一种有点类似于按照指定域进行分组,但是它允许你对指定域应用Function Query动态计算值,最后按照动态计算值进行分组。第二种就是按照Query进行分组,它允许同时执行多个Query并返回独立的结果集。
按照Function进行分组你需要指定group.func参数来完成,这里暂时不全面介绍Solr中所有的Function,会留到后面专门讲解。下面这个查询示例演示了如何按照Function进行分组:
http:ll localhost : 8080 / solr / ecommerce/ select ?f l=id,product , format&
sort=popularity asc&q=spider-man&
group=true&group . limit= 3&rows=5&
group.func=map(map(map(popularity,1,5,1),6,10,2 ),11,100, 3 )

在上面这个示例中,group.func参数指定的函数尝试按照popularity(流行度)将索引文档分3个等级,你会发现返回的分组结果集跟按照指定域进行分组是类似的,唯一的区别就是按照Function分组作用对象是Function(函数)动态计算得到的值,而按照指定域进行分组的作用对象是分组域的每个唯一性的域值。这就有点类似于SQL语句里的按照count()/sum()/avg()这些函数动态计算值进行分组和按照指定字段分组的区别。这里的map (popularity,1,5,1)其实就是对popularity域的域值进行一个映射,表示popularity域值在〔1,5 〕这个区间内的落入第1组,同理map (map (popularity,1,5,1),6,10,2)表示popularity域值在[6,10]这个区间内的落入第2组,依此类推。
Function是可以嵌套的,正如你在上面示例中所看到的那样,我们对map函数嵌套了3次,这意味着你可以结合多个函数灵活控制函数的动态计算值。如果按照函数分组对于你来说,还有太多限制的话,你还可以按照Query进行分组,这样你就可以将任意你指定的值进行分组。
9.5按照任意 Query分组
你除了可以对提前指定域的域值进行分组之外,还可以动态的对任意查询进行分组,你可以定义多个Query,同时对多个Query进行分组,就好比对多个域进行分组。
http:// localhost :8080/ solr/ ecommerce/select ?
sort=popularity asc&
fl=id, type, format, product&
group. limit=2&
q=*: *&
group=true&
group. query=type :Movies&
group . query =games&
group . query="The Hunger Games

从上面的查询示例我们可以获取到以下3点心得:
对于任意的分组查询,不管是group.field或group.func 还是group.query ,
你都可以返回多个分组;
你可以在原查询基础之上执行多个分组子查询,这里的多个分组都是一个分类上分
组,并不是多个分组的叠加,这跟SQL里的group by a,b不同。多个分组之间是独
立的,之间并没有联系;
按照某个域分组,那么某个索引文档必定只可能属于一个分组,但是如果按照多个
Query分组,那么某个索引文档可能属于多个分组。
9.6 Group的分页与排序
在Group分组查询中,rows 参数用于限制返回的分组总个数,start 参数用于控制对分
组进行分页时第-一个分组的起始位置(从零开始计算)。因此在Group分组查询中,将start
参数.与rows参数搭配使用,可以实现对分组的分页查询。sort参数用于控制如何对每个分组进行排序,默认是基于当前分组下评分最高的索引文档的分数从高到低排序,而不是控制
分组内每个索引文档的排序。

group.limit参数用于指定每个分组内最多返回多少个索引文档,group.offset 参数用于
分组内返回的索引文档的起始位置(从零开始计算),将此参数与group.limit参数结合使用,
你可以实现对分组内的索引文档进行分页。而group.sort参数允许你对分组内部的每个索引
文档进行排序。

也许理解分组查询中的分页、排序最简单的方式就是将分组看作Solr中普通的索引文
档,你可以在标准的Solr查询中对返回的索引文档进行分页、排序,也可以对分组查询中
的所有分组以及每个分组内部的索引文档进行分页、排序,为此SolrGroup提供了group.
limit、group.offset以及group.sort等参数帮助你控制如何显示分组以及每个分组内部的索引文档。
9.7 Group& Facet
默认Facet查询统计是基于q参数的查询结果集的,而不是Group分组查询返回的结果
集,这意味着不管你是否开启分组查询,Facet查询统计返回的结果都是一样的。

Group 分组查询和Facet查询统计都是基于q和fq参数的,两者没有任何联系。

此时可以设置group.facet=true开启group之后的facet,换句话说,此时Facet是基于Group查询后返回的结果集进行统计。类比到关系型数据里SQL Group By语法,group.facet = true其实就相当于Group By后返回的结果集条数,

group.facet=true不开时候统计的是返回的结果总数,开了返回的是组的总数
默认通过group.facet = true设置是全局生效的,如果不想全局生效,你只想单独对某个域进行设置,那么可以这样指定,语法如下所示:
f ..group. facet=true
遗憾的是,当你按照多个域进行分组时,Facet查询统计并不会统计每个分组域返回的分组总个数,只会统计第一个分组域Group之后返回的分组总个数
9.8 Group 分布式查询
Group分组查询不能完全在分布式模式下工作,更准确地说,它是运行在伪分布式模式下。结果集聚合是在分布式模式下运行的,但是聚合需要的每个结果集却是分别在每个本地Core 上单独进行Group计算而来的。

这样为什么会有问题﹖因为你分组查询依赖的值可能是随机分布在多个Solr Core 上,这样你Group分组统计的数字可能会不准确,比如你对产品按照制造商进行分组查询,那么得到的总分组个数可能会约等于实际每个Core上统计的分组总个数。当且仅当,你的索引文档按照制造商这个域进行分区时,你会得到正确的分组个数。因为每个分组只会存在于一个唯一的shard 上。当在分布式模式下使用Solr Group功能时,你需要时刻记住这点,你需要设置group.ngroups = true,以保证能够返回分组的总个数。如果你的索引数据没有按照你分组的域进行分区,并且你执行的是分布式搜索,那么返回的分组总个数可能仅仅为粗略值。
除了需要考虑数据分区的问题之外,某些group参数在分布式模式下也不支持,比如group.truncate、group.func。group.truncate参数如果设置为true,则表示Facet查询统计会基于当前查询匹配的每个分组中相关性最高的文档进行统计数量。group.func参数表示基于FunctionQuery动态计算值进行Facet查询统计,参数值一般是表示Function Query 的查询表达式。
最后你需要注意的是,Solr Group分组查询不支持多值域,即你不能在一个多值域(即
multiValue = true)上进行分组查询。虽然Solr Group分组查询支持分词域(即域类型为TextField),但Solr并不会将分词后得到的每个Token作为GroupValue,你也不能选择按照哪个Token来分组,而且Solr也不保证会按照分词后得到的每个Token进行分组,有可能会丢弃部分Token。因此对分词域进行分组查询没有太大意义,尽管对分词域进行分组查询不会报错,但最后得到的结果并不可靠,因此,一般建议你将需要分组的域设置为不分词的单值域。
9.9 Group 缓存
尽管Solr Group查询功能很强大,跟Solr标准查询相比,查询速度那是相当的慢。对
大数据量的索引文档按照指定域进行分组查询比不分组查询要花费更多的时间。
为了提升SolrGroup分组查询的性能,你可以通过指定group.cache.percent参数来为
分组查询启用缓存。group.cache.percent 参数的默认值为零,取值范围为[ 1, 100]则表
示为Group启用缓存。SolrGroup内部会分两次查询来执行,group.cache.percent参数表示第一次查询匹配的索引文档应该被缓存的百分比,同时,Group缓存还会附带缓存索引文
档的相关性评分,缓存第- -次查询是为了提升第二次查询的性能。group.cache.percent 参数值设置的越大,那么你的Group查询占用的内存将会更多,所以你应该设置不同的百分比来测试出以尽量少的内存占用来获取最快的执行速度。

9.10使用Collapsing Query Parser实现高效的Field Collapsing
如果你只需要返回每个分组,并不需要返回每个分组下包含的所有索引文档,而且你也不需要对分组进行排序,那么你可以采用更高效的方式来实现“field collapsing”(只返回每个分组即域折叠)

Solr中提供了一种Collapsing Query Parser (对应CollapsingQParserPlugin类),它允许
你将查询结果集为每个唯一值折叠收缩成一一个单–的结果,而且使用起来还不复杂。使用语法如下所示:
/select?q=* : *&fq={ !collapse field= fieldToCollapseOn }
这个查询会为field属性指定的域下每个唯一值只返回一个索引文档,而且返回的那个
索引文档是包含该域值的所有索引文档中评分最高的。你还可以通过设置min/max属性来控制返回的索引文档

9.11 Solr Group VS SQL Group by
在本章中,我们已经学习了如何使用Solr中的Result Grouping (结果集分组)和Field
Collapsing (域折叠收缩)功能,Result Grouping功能有点类似于SQL里的Group By,但
是Solr中的Result Grouping (结果集分组)不仅仅支持对某个域进行分组,还是对任意的
Query、任意的Function 在查询时动态计算值进行分组。通过将group.imit 参数设置为1可以实现Field Collapsing (域折叠收缩)功能,看起来像是将域值重复的索引文档“删除”了。
你还可以通过指定多个group.field参数支持在单个查询请求中执行多个域的分组查询,但是注意,这里并没有实现分组嵌套效果。

Solr中你指定多个group.field进行分组,并不会嵌套分组查询,而是单独指定的每个域进行分组查询,就好比SQL里执行2次Group By :

因此在SolrGroup中不支持类似SQL中的嵌套分组查询

9.1 2本章总结
在本章中,我们主要学习了Solr Group的结果集分组,其中按照指定域、任意查询、
函数动态计算值等进行分组是本章重点。如果想要更高效的使用Solr Group,你还需要掌握
Solr中的Group缓存。最后顺带提一提Solr中的Group分组与SQL中的Groupby之间的
区别,明白了使用SolrGroup可能会有哪些坑,会让你在使用SolrGroup之前能够心知肚
明,提前做好应付的对策。

第10章Solr企业级应用
通过第10 章,你将可以学习到如下内容:
如何构建和发布自己的Solr版本;
如何监控Solr;
在大规模数据量下如何扩展Solr索引和查询的负载能力;
如何管理你的Solr Core以及Solr集群。

10.1 Solr 源码编译与补丁应用
10.2 部署Solr
Solr提供了-一个war包文件,你可以直接将其部署到任意的Servlet容器中

10.2.2 Embedded Solr
除了可以将solr.war包部署到Servlet容器,Solr 还支持将Solr嵌人到其他Java Project
中通过Solr API方式来直接访问,而不是通过Http请求方式来访问交互即Embedded Solr。

当你想要为你的Java Project 添加搜索功能,但是又想要Solr 能够包含在你当前项目中且只有当前项目能够访问Solr,而不是暴露成-一个搜索服务,其他项目也能访问到,这个时候采用嵌入式Solr可能会比较适合你。你可以通过SolrJ来访问嵌人式Solr服务。

10.3 Solr 硬件要求与系统配置
10.3.1内存和SSD
在Solr的核心操作中,Solr缓存是最消耗内存的,比如Facet、Sort、索引创建等这些操作都需要使用缓存。你必须确保你有足够的内存来支撑这些操作。你可以在Solr的 Web后台界面的主页上看到当前服务器的所有内存大小以及已经占用了多少。此外你还需要确保你的服务器剩余的未分配给JVM的可用内存是否足够在查询时缓存索引数据,假如你的索引数据大小比你服务器的剩余可用内存还大,这也就意味着Solr会尝试一定的磁盘IO来保证查询的吞吐量。

10.3.2 JVM配置
符合实际情况设置
10.3.3 思考Solr索弓|与查询性能
索引的增加和更新会影响到性能
10.4 Solr 数据批量导人
批量提交增加吞吐量
10.5 Solr Shard与Replication
Solr允许你创建多个查询索引,每个索引由一个Solr Core表示。你可以将你的索
引数据进行分片(或者说分区),每个分片中的数据可能会跨多个Core,这就叫做“Solr
Shard”。复制某个分片的索引数据作为一个副本,这就叫做“Solr Replication”。

如何确定shard数量,不停的测试

10.5.1 Shard
Solr索引数据分片并不能解决自动故障容错问题,索引分片能有效缓解你的索引数据不
断增长带来的索引和查询压力。在开始决定索引分片数量之前,你应该考虑以下5个因素:
索引文档的总数量;
每个文档的大小;
要求的索引吞吐量;
查询的复杂度;
预期索引的增长量。

10.5.2 Replicate
利用Relication机制,你可以构建多个冗余的副本节点来保证系统不会出现单点故障问题
10.6 Core 管理
使用两种方式来管理core, 一种是动态增加(core的自动发现机制,可以直接在web页面上创建core, 但是会在内存中,重启之后就没有了,),一种是手动增加,
其他的core的管理操作参考书籍
10.7 Solr 集群管理
10.7.1Solr Ping健康检测
为了实现节点的健康检查,Solr 提供了Ping Request Handler,它需要在solrconfig.xml中稍作配置进行启用,它可以提供给一些负载均衡器用于测试当前实例是否存活,从而决定是否将此实例排除在外
10.7.2Solr配置文件管理
集群中指定同一个配置文件,
使用动态域来匹配不同的域
10.8如何与 Solr交互
10.8.1 使用REST API与Solr交互,
10.8.2 使用SolrJ与Solr进行交互
使用spring data solr进行交互
10.9监控你的Solr
10.9.2Solr的缓存性能
Solr的缓存决定了Solr的性能。
10.9.4Solr日志
配置log4j

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值