Solr——企业级搜索应用服务器

1. Solr简介

前言

​ 学习Solr需要一些和java相关的储备知识,在此之前,假设您已经:

  • 拥有 Java 开发环境以及相应 IDE(eclipse idea)
  • 熟悉 Spring Boot
  • 熟悉 Maven
  • 熟悉 Lucene

1.1 Solr是什么

  • ​ Solr是Apache旗下基于Lucene开发的全文检索的服务。用户可以通过http请求,向Solr服务器提交一定格式的数据(XML,JSON),完成索引库的索引。也可以通过Http请求查询索引库获取返回结果(XML,JSON)。

​ Solr和Lucene的区别

  • ​ Lucene是一个开放源代码的全文检索引擎工具包,它不是一个完整的全文检索引擎,Lucene提供了完整的查询引擎和索引引擎,目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者以Lucene为基础构建全文检索引擎。

Solr的目标是打造一款企业级的搜索引擎系统,它是一个搜索引擎服务,可以独立运行,通过Solr可以非常快速的构建企业的搜索引擎,通过Solr也可以高效的完成站内搜索功能。

在这里插入图片描述

1.2 Solr的发展历程

  • ​ 2004年,CNET NetWorks公司的Yonik Seeley工程师为公司网站开发搜索功能时完成了Solr的雏形。

起初Solr知识CNET公司的内部项目。

  • ​ 2006 年1月,CNET公司决定将Solr源码捐赠给Apache软件基金会。

  • ​ 2008 年9月,Solr1.3发布了新功能,其他包括分布式搜索和性能增强等功能。

  • ​ 2009 年11月,Solr1.4版本发布,此版本对索引,搜索,Facet等方面进行优化,提高了对PDF,HTML等富文本文件处理能力,还推出了许多额外的插件;

  • ​ 2010 年3月,Lucene和Solr项目合并,自此,Solr称为了Lucene的子项目,产品现在由双方的参与者共同开发。

  • ​ 2011年,Solr改变了版本编号方案,以便与Lucene匹配。为了使Solr和Lucene有相同的版本号,Solr1.4下一版的版本变为3.1。

  • ​ 2012年10月,Solr4.0版本发布,新功能Solr Cloud也随之发布。

  • ​ 目前Solr最新版本8.4.1

1.3 Solr的功能优势

  • ​ 灵活的查询语法;
  • ​ 支持各种格式文件(Word,PDF)导入索引库;
  • ​ 支持数据库数据导入索引库;
  • ​ 分页查询和排序
  • ​ Facet维度查询;
  • ​ 自动完成功能;
  • ​ 拼写检查;
  • ​ 搜索关键字高亮显示;
  • ​ Geo地理位置查询;
  • ​ Group 分组查询;
  • ​ Solr Cloud;

2. 下载和安装

2.1 下载

​ solr的下载地址:Solr Downloads - Apache Solr

​ Solr最新版本是8.4.1的版本,由于8属于比较新的版本,可能有一些未知的Bug,出现问题后可能不好解决,所以我们使用Solr7.

因为Solr是基于java语言开发,且Solr7.x要求的JDK版本最少是JDK8.所以我们在安装solr之前,首先必须安装JDK。

在这里插入图片描述

2.2 Solr学习资源

​ 为了方便学习,Solr官方也提供了很多学习资料。可以在官方的Resources中查看;

在这里插入图片描述
在这里插入图片描述

2.3 Window下安装Solr

​ window系统是我们平时开发和学习使用的一个平台,我们首先先来学习如何在window系统中安装Solr;

2.3.1运行环境

​ solr 需要运行在一个Servlet容器中,Solr7.x 要求jdk最少使用1.8以上,Solr默认提供Jetty(java写的Servlet容器),本教程使用Tocmat作为Servlet容器,环境如下:

​ Solr:Solr7.x

​ Jdk:jdk1.8

​ Tomcat:tomcat8.5

2.3.2 安装步骤

  • ​ 下载solr-7.zip并解压;

    ​ 在这里插入图片描述

    ​ bin:官方提供的一些solr的运行脚本。

    ​ contrib:社区的一些贡献软件/插件,用于增强solr的功能。

    ​ dist:Solr的核心JAR包和扩展JAR包

    ​ docs:solr的API文档

    ​ example:官方提供的一些solr的demo代码

    ​ licenses:solr遵守的一些开源协议文件

    ​ server:这个目录有点意思,取名为sever,有点迷惑人,其实就是一个jetty.官方为了方便部署Solr,在安装包中内置了一个Jetty; 我们直接就可以利用内置的jetty部署solr;

    ​ server/solr-webapp/webapp:进入到sever目录中有一个webapp的目录,这个目录下部署的就是solr的war包(solr的服务);
    在这里插入图片描述

    在实际开发中通常我们需要将solr部署到tomcat服务器中;接下来我们要讲解的就是如何将solr部署到tomcat;

  • ​ 部署solr到tomcat;

    ​ 1.解压一个新的tomcat

    ​ 2.将安装包下server/solr-webapp/webapp下的solr服务打war包;

    ​ 进入server/solr-webapp/webapp目录

    ​ 使用cmd窗口

    ​ jar cvf solr.war ./*

    ​ 3.将solr.war复制到tomcat/webapps目录中;
    在这里插入图片描述
    4.启动tomcat,解压war包;

    5.修改webapp/solr/WEB-INF/web.xml的配置solr_home的位置;

    <env-entry>
       	<env-entry-name>solr/home</env-entry-name>
       	<env-entry-value>“你的solrhome位置”</env-entry-value>
       	<env-entry-type>java.lang.String</env-entry-type>
     </env-entry>
    

    Solr home目录,SolrHome是Solr运行的主目录,将来solr产生的数据就存储在SolrHOME中;

    SolrHOME可以含多个SolrCore;

    SolrCore即Solr实例每个SolrCore可以对外单独提供全文检索的服务.

    理解为关系型数据库中的数据库. 关于solrCore的创建我们在后面的课程中专门来讲解;

    6.取消安全配置

    <!--
      <security-constraint>
      <web-resource-collection>
          <web-resource-name>Disable TRACE</web-resource-name>
        <url-pattern>/</url-pattern>
          <http-method>TRACE</http-method>
        </web-resource-collection>
        <auth-constraint/>
      </security-constraint>
      <security-constraint>
        <web-resource-collection>
          <web-resource-name>Enable everything but TRACE</web-resource-name>
          <url-pattern>/</url-pattern>
          <http-method-omission>TRACE</http-method-omission>
        </web-resource-collection>
      </security-constraint>-->
    

    7.将solr-7.7.2/server/solr中所有的文件复制到solrHome

    8.拷贝日志工具相关jar包:将solr-7.7.2/server/lib/ext下的jar包拷贝至上面Tomcat下Solr的/WEB-INF/lib/目录下

    9.拷贝metrics相关jar包:将solr-7.7.2/server/lib下metrics相关jar包也拷贝至/WEB-INF/lib/目录下

    10.拷贝dataimport相关jar包:solr-7.7.2/dist下dataimport相关jar包也拷贝至/WEB-INF/lib/目录下

    11.拷贝log4j2配置文件:将solr-7.7.2/server/resources目录中的log4j配置文件拷入web工程目录WEB-INF/classes(自行创建目录) ,并且修改日志文件的路径

    12.重启tomcat,看到welcome,说明solr就安装完毕
    在这里插入图片描述
    13.访问后台管理系统进行测试

    http://localhost:8080/solr/index.html

    14.在tomcat的bin\catalina.bat中配置日志文件的环境参数

    set "JAVA_OPTS=%JAVA_OPTS% -Dsolr.log.dir=D:\live\solr\logs"
    

在这里插入图片描述

2.4 Linux下安装Solr

​ 在实际的生产环境中,通常我们都需要将solr安装到linux服务器中;由于我们目前属于学习阶段。我们就使用虚拟机来模拟一个Linux服务器安装solr;

环境准备

​ Cent0S 7.0 linux系统;

​ Jdk1.8 linux安装包

​ Java Downloads | Oracle

​ tomcat:tomcat8.5

​ Apache Tomcat® - Welcome!

​ solr7.x 安装包

​ http://mirror.bit.edu.cn/apache/lucene/solr/7.7.2/solr-7.7.2.tgz

​ 这一块的安装包在资料中已经准备好了;

将linux中相关的安装包上传到linux

​ sftp上传 Jdk1.8 tomcat8.5 solr7.x 安装包到linux;

  1. ​ 使用CRT连接到Linux

  2. ​ alt+p打开sftp,上传相关的软件安装到到linux
    在这里插入图片描述

安装jdk
在这里插入图片描述

1.解压jdk

tar -xzvf jdk18 -C /usr/local

在这里插入图片描述
2.配置环境变量

vi /etc/profile
export JAVA_HOME=/usr/local/jdk1.8.0_171
export PATH=$JAVA_HOME/bin:$PATH

3.重新加载profile文件,让配置文件生效;
在这里插入图片描述
4.测试
在这里插入图片描述
安装tomcat,tomcat的安装比较简单,只需要解压即可;
在这里插入图片描述
安装solr,安装solr的过程和windows系统过程完全相同。只不过通过linux命令来操作而已;
1.解压solr安装包,直接解压在宿主目录即可;
在这里插入图片描述
2.将server/solr-webapp/webapp下的solr服务打war包;
2.1 进入到webapp目录
cd server/solr-webapp/webapp

2.2 将webapp中的代码打成war包
jar -cvf solr.war ./*
在这里插入图片描述
3.将war包部署到tomcat的webapps目录
在这里插入图片描述
4.启动tomcat,解压solr.war
4.1进入到tomcat的bin目录
cd /usr/local/apache-tomcat-8.5.50

4.2 启动tomcat
在这里插入图片描述
4.3 进入到webapp目录中查看
在这里插入图片描述
5. 修改webapp/solr/WEB-INF/web.xml的配置solrhome的位置;

<env-entry>
   	<env-entry-name>solr/home</env-entry-name>
	<env-entry-value>“你的solrhome位置”</env-entry-value>
   	<env-entry-type>java.lang.String</env-entry-type>
 </env-entry>

在这里插入图片描述
6.取消安全配置(和window相同)

7.将solr-7.7.2/server/solr中所有的文件复制到solrHome
7.1进入到solr-7.7.2/server/solr
在这里插入图片描述
7.2将所有的文件复制到solrHome
在这里插入图片描述
8.拷贝日志工具相关jar包:将solr-7.7.2/server/lib/ext下的jar包拷贝至上面Tomcat下Solr的/WEB-INF/lib/目录下
8.1 进入solr-7.7.2/server/lib/ext

cd solr-7.7.2/server/lib/ext

8.2 将所有文件复制到Tomcat下Solr的/WEB-INF/lib/
在这里插入图片描述
9.拷贝 metrics相关jar包:将solr-7.7.2/server/lib下metrics相关jar包也拷贝至/WEB-INF/lib/目录下
9.1 进入solr-7.7.2/server/lib

cd solr-7.7.2/server/lib

在这里插入图片描述
9.2 将metrics-开始的所有文件复制到Tomcat下Solr的/WEB-INF/lib/
在这里插入图片描述
10. 将solr安装包中dist目录中和数据导入相关的2个包,复制到tomcat/webapps/solr/WEB-INF/lib
在这里插入图片描述
11.拷贝log4j2配置文件:将solr-7.7.2/server/resource目录中的log4j配置文件拷入web工程目录WEB-INF/classes(自行创建目录) ,并且修改日志文件的路径
11.1 进入到solr-7.7.2/server/resource目录中
在这里插入图片描述
11.2 将log4j2的配置文件复制到solr 的WEB-INF/classes目录;
在这里插入图片描述
在这里插入图片描述

3. Solr基础

3.1 SolrCore

​ solr部署启动成功之后,需要创建core才可以使用的,才可以使用Solr;类似于我们安装完毕MySQL以后,需要创建数据库一样;

3.1.1 什么是SolrCore

​ 在Solr中、每一个Core、代表一个索引库、里面包含索引数据及其配置信息。

​ Solr中可以拥有多个Core、也就是可以同时管理多个索引库、就像mysql中可以有多个数据库一样。

​ 所以SolrCore可以理解成MySQL中的数据库;

3.1.2 SolrCore维护(windows)

​ 简单认识SolrCore以后,接下来要完成的是SolrCore的创建。在创建solrCore之前,我们首先认识一下SolrCore目录结构:

1.SolrCore目录结构

​ Core中有二个重要目录:conf和data
在这里插入图片描述
​ conf:存储SolrCore相关的配置文件;

​ data:SolrCore的索引数据;

​ core.properties:SolrCore的名称,name=SolrCore名称;

​ 所以搭建一个SolrCore只需要创建 2个目录和一个properties文件即可;

2.SolrHome中搭建SolrCore

​ 2.1 solrCore的目录结构搞清楚以后,接下来就是关于SolrCore在哪里进行创建呢?

​ 在之前搭建Solr的时候,我们说一个solr_home是由多个solrCore构成,所以solrCore是搭建在solrHome中;
在这里插入图片描述
​ 2.2 将solr安装包中的配置文件复制到conf目录;

​ 搭建好solrCore以后,conf目录还没没有配置文件,我们需要将solr安装包中提供的示例配置文件复制到conf目录

​ solr安装包中配置文件的位置:solr-7.7.2\example\example-DIH\solr\solr\conf

2.3 重启solr

2.4 在solr的管理后台来查看
在这里插入图片描述

3.如何创建多个solrCore;

​ 只需要复制SolrCore一份,重启solr;

在这里插入图片描述

3.1.3 SolrCore维护(linux)

进入到SolrHome

 cd /usr/local/solr_home/

在这里插入图片描述
创建SolrCore

	 mkdir -p collection1/data
	 mkdir -p collection1/conf
	 cd collection1
	 touch core.properties

在这里插入图片描述
将solr安装包中提供的示例配置文件复制到conf目录

cd solr-8.3.1/example/example-DIH/solr/solr/conf/

cp -r *  /usr/local/solr_home/collection1/conf/

重启tomcat

访问后台管理系统
在这里插入图片描述

3.2 Solr后台管理系统的使用

​ 上一个章节我们已经学习完毕如何在solr中创建SolrCore。有了SolrCore以后我们就可以进行索引和搜索的操作,在进行索引和搜索操作之前,首先学习一下Solr后台管理系统的使用;

​ 1.DashBoard:solr的版本信息、jvm的相关信息还有一些内存信息。

​ 2.Logging:日志信息,也有日志级别,刚进入查看的时候肯定是有几个警告(warn)信息的,因为复制solr的时候路径发生了变化导致找不到文件,但是并不影响。
​ 3.Core Admin:SolrCore的管理页面。可以使用该管理界面完成SolrCore的卸载。也可以完成SolrCore的添加

能添加的前提,SolrCore在solr_home中目录结构是完整的。

4.Java Properties:顾名思义,java的相关配置,比如类路径,文件编码等。

5.Thread Dump:solr服务器当前活跃的一些线程的相关信息。
以上的5个了解一下就行。

6.当我们选择某一个solrCore以后,又会出现一些菜单,这些菜单就是对选择的SolrCore进行操作的,接下来我们重点要讲解的就是这些菜单的使用;

3.2.1 Documents

​ 首先我们先来讲解第一个菜单:Documents

​ 作用:向SolrCore中添加数据,删除数据,更新数据(索引)。

​ 在讲解如何使用Documents菜单向Solr中添加数据之前,我们首先回顾一下我们之前在Lucene中学习的一些概念;

​ 1. 概念介绍:

​ 文档:document是lucene进行索引创建以及搜索的基本单元,我们要把数据添加到Lucene的索引库中,数据结构就是document,如果我从Lucene的索引库中进行数据的搜索, 搜索出来的结果的数据结构也document;

​ 文档的结构:学习过Lucene的同学都知道,一个文档是由多个域(Field)组成,每个域包含了域名和域值;

如果数据库进行类比,文档相单于数据库中的一行记录,域(Field)则为一条记录的字段。
在这里插入图片描述
​ 索引:通常把添加数据这个操作也成为创建索引;

​ 搜索:通常把搜索数据这个操作也成为搜索索引;

​ 倒排索引:

​ Solr添加数据的流程:

在这里插入图片描述
在这里插入图片描述
​ Lucene首先对文档域中的数据进行分词,建立词和文档之间的关系;

在这里插入图片描述
​ 将来我们就可以根据域中的词,快速查找到对应的文档;

​ Lucene中相关概念回顾完毕;

​ 2.添加文档

​ 使用后台管理系统,向Solr中添加文档。文档的数据格式可以是JSON,也可以XML;

​ 以JSON的形式添加文档:
在这里插入图片描述
​ 以XML的形式添加文档

在这里插入图片描述
通常我们可以添加一些测试数据;

3.修改数据

​ Solr要求每一个文档都需要有一个id域;如果添加的文档id在SolrCore中已经存在,即可完成数据修改;

4.删除数据

​ 只能通过XML的格式删除文档;下面我们提供2种删除方式

根据id删除
<delete>
 <id>8</id>
</delete>
<commit/>
根据条件删除,需要指定查询的字符串,根据查询的字符串删除。查询字符串如何编写,后面详细讲解。
<delete>
 <query>*:*</query>
</delete>
<commit/>

3.2.2 Analyse

​ 作用:测试域/域类型(后面讲解)分词效果;

​ 之前我们在讲解倒排索引的时候,当我们向Solr中添加一个文档,底层首先要对文档域中的数据进行分词。

建立词和文档关系。

​ 测试刚才添加文档的域id ,title,name域的分词效果;

思考的问题?

​ id为什么不分词,name域为什么可以分词?

​ name域可以对中文进行分词吗?

​ 添加文档的时候,域名可以随便写吗?

​ 要想搞清楚这些问题,我们需要学习Solr的配置;

3.2.3 Solr的配置-Field

​ 在Solr中我们需要学习其中4个配置文件;

​SolrHome中solr.xml

SolrCore/conf中solrconfig.xml

​SolrCore/confSolrCore中managed-schema

​SolrCore/conf/data-config.xml

​ 其中我们最常用的一个配置文件,managed-schema。

​ managed-schema(掌握)

​ 在Solr中进行索引时,文档中的域需要提前在managed-schema文件中定义,在这个文件中,solr已经提前定义了一些域,比如我们之前使用的id,price,title域。通过管理界面查看已经定义的域;
在这里插入图片描述

下面就是solr中一定定义好的一个域,name

<field name="name" type="text_general" indexed="true" stored="true"/>

​ field标签:定义一个域;

​ name属性:域名

​ indexed:是否索引,是否可以根据该域进行搜索;一般哪些域不需要搜索,图片路径。

​ stored:是否存储,将来查询的文档中是否包含该域的数据; 商品的描述。

​ 举例:将图书的信息存储到Solr中;Description域。indexed设置为true,store设置成false;

​ 可以根据商品描述域进行查询,但是查询出来的文档中是不包含description域的数据;
在这里插入图片描述
​ multiValued:是否多值,该域是否可以存储一个数组; 图片列表;

​ required:是否必须创建文档的时候,该域是否必须;id

​ type:域类型,决定该域使用的分词器。分词器可以决定该域的分词效果(分词,不分词,是否支持中文分词)。域的类型在使用之前必须提前定义;在solr中已经提供了很多的域类型

<fieldType name="text_general" class="solr.TextField" positionIncrementGap="100">
      <analyzer type="index">
        <tokenizer class="solr.StandardTokenizerFactory"/>
        <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
        <!-- in this example, we will only use synonyms at query time
        <filter class="solr.SynonymGraphFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/>
        <filter class="solr.FlattenGraphFilterFactory"/>
        -->
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
      <analyzer type="query">
        <tokenizer class="solr.StandardTokenizerFactory"/>
        <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
        <filter class="solr.SynonymGraphFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
    </fieldType>

​ 以上这些概念和Lucene中讲解的是一样的;

​ 自定义一个商品描述域:

<field name="item_description" type="text_general" indexed="true" stored="false"/>

​ 定义一个图片域

<field name="item_image" type="string" indexed="false" stored="true" multiValued=true/>

重启solr

在这里插入图片描述
​ 测试添加文档

{id:"1101",name:"java编程思想",item_image:["big.jpg","small.jpg"],item_description:"lucene是apache的开源项目,是一个全文检索的工具包。"}

在这里插入图片描述

3.2.4 Solr的配置-FieldType

介绍

上一章节我们讲解了Field的定义。接下来我们要讲解的是FieldType域类型;

刚才我们给大家讲解了如何在schema文件中定义域,接下来我们要讲解域类型如何定义;

每个域都需要指定域类型,而且域类型必须提前定义。域类型决定该域使用的索引和搜索的分词器,影响分词效果。

Solr中已经提供好了一些域类型;

​ text_general:支持英文分词,不支持中文分词;

​ string:不分词;适合于id,订单号等。

​ pfloat:适合小数类型的域,特殊分词,支持大小比较;

​ pdate:适合日期类型的域,特殊分词,支持大小比较;

​ pint:适合整数类型的域,特殊分词,支持大小比较;

​ plong:适合长整数类型的域,特殊分词,支持大小比较;

我们以text_general为例看一下如何定义FiledType.text_general是solr中已经提供好的一个域类型;他的定义如下;

  <fieldType name="text_general" class="solr.TextField" positionIncrementGap="100">
      <analyzer type="index">
        <tokenizer class="solr.StandardTokenizerFactory"/>
        <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
        <!-- in this example, we will only use synonyms at query time
        <filter class="solr.SynonymGraphFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/>
        <filter class="solr.FlattenGraphFilterFactory"/>
        -->
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
      <analyzer type="query">
        <tokenizer class="solr.StandardTokenizerFactory"/>
        <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
        <filter class="solr.SynonymGraphFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
    </fieldType>
3.2.4.1 相关属性

name:域类型名称,定义域类型的必须指定,并且要唯一;将来定义域的时候需要指定通过域名称来指定域类型;(重点)

class:域类型对应的java类,必须指定,如果该类是solr中的内置类,使用solr.类名指定即可。如果该类是第三方的类,需要指定全类名。(重点)

如果class是TextField,我们还需要使用子标签来配置分析器;

positionIncrementGap:用于多值字段,定义多值间的间隔,来阻止假的短语匹配(了解)。

autoGeneratePhraseQueries:用于文本字段,如果设为true,solr会自动对该字段的查询生成短语查询,即使搜索文本没带“”(了解)

enableGraphQueries:是否支持图表查询(了解)
docValuesFormat:docValues字段的存储格式化器:schema-aware codec,配置在solrconfig.xml中的(了解)
postingsFormat:词条格式器:schema-aware codec,配置在solrconfig.xml中的(了解)

3.2.4.2 Solr自带的FieldType类

​ solr除了提供了TextField类,我们也可以查看它提供的其他的FiledType类,我们可以通过官网查看其他的FieldType类的作用:

Field Types Included with Solr | Apache Solr Reference Guide 8.1

在这里插入图片描述

以上的FieldType类的使用,我们不会一一进行讲解,只会讲解常用的一部分;

3.2.4.3 FieldType常用类的使用

首先我们来讲解第一个FieldType类;
在这里插入图片描述
TextField:支持对字符类型的数据进行分词;对于 solr.TextField 域类型,需要为其定义分析器;

我们首先先来搞清楚什么是Solr分析器;

分析器的基本概念

​ 分析器就是将用户输入的一串文本分割成一个个token,一个个token组成了tokenStream,然后遍历tokenStream对其进行过滤操作,比如去除停用词,特殊字符,标点符号和统一转化成小写的形式等。分词的准确的准确性会直接影响搜索的结果,从某种程度上来讲,分词的算法不同,都会影响返回的结果。因此分析器是搜索的基础;

​ 分析器的工作流程:

​ 分词

​ 过滤

在这里插入图片描述

在solr中已经为我们提供了很多的分词器及过滤器;

Solr中提供的分词器tokenizer:Tokenizers | Apache Solr Reference Guide 8.1

标准分词器,经典分词器,关键字分词器,单词分词器等,不同的分词器分词的效果也不尽相同;
在这里插入图片描述

Solr中提供的过滤器tokenfilter:About Filters | Apache Solr Reference Guide 8.1

不同的过滤器过滤效果也不同,有些是去除标点符号的,有些是大写转化小写的;
在这里插入图片描述
这是关于Solr中的分析器我们先介绍到这里;

常用分词器的介绍

​ Standard Tokenizer

​ 作用:这个Tokenizer将文本的空格和标点当做分隔符。注意,你的Email地址(含有@符合)可能会被分解开;用点号(就是小数点)连接的部分不会被分解开。对于有连字符的单词,也会被分解开。

例子:

<analyzer>
  <tokenizer class="solr.StandardTokenizerFactory"/>
</analyzer>
输入:“Please, email john.doe@foo.com by 03-09, re: m37-xq.”
输出: “Please”, “email”, “john.doe”, “foo.com”, “by”, “03”, “09”, “re”, “m37”, “xq

​ Classic Tokenizer

作用:基本与Standard Tokenizer相同。注意,用点号(就是小数点)连接的部分不会被分解开;用@号(Email中常用)连接的部分不会被分解开;互联网域名(比如wo.com.cn)不会被分解开;有连字符的单词,如果是数字连接也会被分解开。

例子:

<analyzer>
  <tokenizer class="solr.ClassicTokenizerFactory"/>
</analyzer>
输入: “Please, email john.doe@foo.com by 03-09, re: m37-xq.”
输出: “Please”, “email”, “john.doe@foo.com”, “by”, “03-09”, “re”, “m37-xq”

​ Keyword Tokenizer

​ 作用:把整个输入文本当做一个整体。

例子:

<analyzer>
  <tokenizer class="solr.KeywordTokenizerFactory"/>
</analyzer>
输入: “Please, email john.doe@foo.com by 03-09, re: m37-xq.”
输出: “Please, email john.doe@foo.com by 03-09, re: m37-xq.”

Letter Tokenizer

​ 作用:只处理字母,其他的符号都被认为是分隔符

例子:

<analyzer>
  <tokenizer class="solr.LetterTokenizerFactory"/>
</analyzer>
输入: “I can’t.”
输出: “I”, “can”, “t”

Lower Case Tokenizer

​ 作用:以非字母元素分隔,将所有的字母转化为小写。

<analyzer>
  <tokenizer class="solr.LowerCaseTokenizerFactory"/>
</analyzer>
输入: “I just LOVE my iPhone!”
输出: “i”, “just”, “love”, “my”, “iphone”

N-Gram Tokenizer

作用:将输入文本转化成指定范围大小的片段的词,注意,空格也会被当成一个字符处理;

参数说明
minGramSize整数,默认1指定最小的片段大小,需大于0
maxGramSize整数,默认2指定最大的片段大小,需大于最小值

例子1:

<analyzer>
  <tokenizer class="solr.NGramTokenizerFactory"/>
</analyzer>
输入: “hey man”
输出: “h”, “e”, “y”, ” “, “m”, “a”, “n”, “he”, “ey”, “y “, ” m”, “ma”, “an”

例子2:

<analyzer>
  <tokenizer class="solr.NGramTokenizerFactory" minGramSize="4" maxGramSize="5"/>
</analyzer>
输入: “bicycle”
输出: “bicy”, “bicyc”, “icyc”, “icycl”, “cycl”, “cycle”, “ycle“

Edge N-Gram Tokenizer

​ 作用:用法和N-Gram Tokenizer类似

参数说明
minGramSize整数,默认1指定最小的片段大小,需大于0
maxGramSize整数,默认1指定最大的片段大小,需大于或等于最小值
side“front” 或 “back”, 默认”front”指定从哪个方向进行解析

例子1:

<analyzer>
  <tokenizer class="solr.EdgeNGramTokenizerFactory" />
</analyzer>
输入: “babaloo”
输出: “b”

例子2:

<analyzer>
  <tokenizer class="solr.EdgeNGramTokenizerFactory" minGramSize="2" maxGramSize="5"/>
</analyzer>
输入: “babaloo”
输出: “ba”, “bab”, “baba”, “babal”

例子3:

<analyzer>
  <tokenizer class="solr.EdgeNGramTokenizerFactory" minGramSize="2" maxGramSize="5" side="back"/>
</analyzer>
输入: “babaloo”
输出: “oo”, “loo”, “aloo”, “baloo”

Regular Expression Pattern Tokenizer

作用:可以指定正则表达式来分析文本。
参数说明
attern必选项正规表达式
roup数字,可选,默认-1负数表示用正则表达式做分界符;非正数表示只分析满足正则表达式的部分;0表示满足整个正则表达式;大于0表示满足正则表达式的第几个括号中的部分

例子1:

<analyzer>
  <tokenizer class="solr.PatternTokenizerFactory" pattern="\s*,\s*"/>
</analyzer>
输入: “fee,fie, foe , fum”
输出: “fee”, “fie”, “foe”, “fum”

​ 例子2:

<analyzer>
  <tokenizer class="solr.PatternTokenizerFactory" pattern="[A-Z][A-Za-z]*" group="0"/>
</analyzer>
输入: “Hello. My name is Inigo Montoya. You killed my father. Prepare to die.”
输出: “Hello”, “My”, “Inigo”, “Montoya”, “You”, “Prepare”
这里的group为0,表示必须满足整个表达式,正则表达式的含义是以大写字母开头,之后是大写字母或小写字母的组合。

例子3:

<analyzer>
  <tokenizer class="solr.PatternTokenizerFactory" pattern="(SKU|Part(\sNumber)?):?\s(\[0-9-\]+)" group="3"/>
</analyzer>
输入: “SKU: 1234, Part Number 5678, Part: 126-987”
输出: “1234”, “5678”, “126-987”
这个group等于3,表示满足第三个括号”[0-9-]+”中的正则表达式

White Space Tokenizer

作用:这个Tokenizer将文本的空格当做分隔符。

参数说明
rule默认java如何定义空格

例子:

<analyzer>
  <tokenizer class="solr.WhitespaceTokenizerFactory" rule="java" />
</analyzer>
输入: “To be, or what?”
输出: “To”, “be,”, “or”, “what?”

在这些分词器中,我们最常用的一个分词器。Standard Tokenizer;但也仅仅只能对英文进行分词;

常用过滤器介绍

​ 上一小结我们学习了Solr中的常用分词器,接下来我们讲解过滤器。过滤器是对分词器的分词结果进行再次处理,比如:将词转化为小写,排除掉停用词等。

​ Lower Case Filter

​ 作用:这个Filter将所有的词中大写字符转化为小写

​ 例子:

<analyzer>
  <tokenizer class="solr.StandardTokenizerFactory"/>
  <filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
原始文本: “Down With CamelCase” 
输入: “Down”, “With”, “CamelCase” 
输出: “down”, “with”, “camelcase”

​ Length Filter

​ 作用:这个Filter处理在给定范围长度的tokens。

​ 参数:

参数说明
min整数,必填指定最小的token长度
max整数,必填,需大于min指定最大的token长度

​ 例子:

<analyzer>
  <tokenizer class="solr.StandardTokenizerFactory"/>
  <filter class="solr.LengthFilterFactory" min="3" max="7"/>
</analyzer>
原始文本: “turn right at Albuquerque” 
输入: “turn”, “right”, “at”, “Albuquerque” 
输出: “turn”, “right”

​ pattern Replace Filter

​ 作用:这个Filter可以使用正则表达式来替换token的一部分内容,与正则表达式想匹配的被替换,不匹配的不变。

​ 参数:

参数说明
pattern必填,正则表达式需要匹配的正则表达式
replacement必填,字符串需要替换的部分
replace“all” 或 “first”, 默认”all”全部替换还是,只替换第一个

例子1:

<analyzer>
  <tokenizer class="solr.StandardTokenizerFactory"/>
  <filter class="solr.PatternReplaceFilterFactory" pattern="cat" replacement="dog"/>
</analyzer>
原始文本: “cat concatenate catycat” 
输入: “cat”, “concatenate”, “catycat” 
输出: “dog”, “condogenate”, “dogydog”

例子2:

<analyzer>
  <tokenizer class="solr.StandardTokenizerFactory"/>
  <filter class="solr.PatternReplaceFilterFactory" pattern="cat" replacement="dog" replace="first"/>
</analyzer>
原始文本: “cat concatenate catycat” 
输入: “cat”, “concatenate”, “catycat” 
输出: “dog”, “condogenate”, “dogycat”

Stop Words Filter

​ 作用:这个Filter会在解析时忽略给定的停词列表(stopwords.txt)中的内容;

​ 参数:

参数说明
words可选,停词列表指定停词列表的路径
format可选,如”snowball”停词列表的格式
ignoreCase布尔值,默认false是否忽略大小写

​ 例子1:

<analyzer>
  <tokenizer class="solr.StandardTokenizerFactory"/>
  <filter class="solr.StopFilterFactory" words="stopwords.txt"/>
</analyzer>
保留词列表stopwords.txt 
	be 
	or 
	to 
原始文本: “To be or what?” 
输入: “To”(1), “be”(2), “or”(3), “what”(4) 
输出: “To”(1), “what”(4)

​ 例子2:

<analyzer>
  <tokenizer class="solr.StandardTokenizerFactory"/>
  <filter class="solr.StopFilterFactory" words="stopwords.txt" ignoreCase="true"/>
</analyzer>
保留词列表stopwords.txt 
be 
or 
to 

原始文本: “To be or what?” 
输入: “To”(1), “be”(2), “or”(3), “what”(4) 
输出: “what”(4)

Keep Word Filter

​ 作用:这个Filter将不属于列表中的单词过滤掉。和Stop Words Filter的效果相反。

参数说明
words必填,以.txt结尾的文件提供保留词列表
ignoreCase布尔值,默认false是否忽略保留词列表大小写

​ 例子1:


<analyzer>
  <tokenizer class="solr.StandardTokenizerFactory"/>
  <filter class="solr.KeepWordFilterFactory" words="keepwords.txt"/>
</analyzer>
保留词列表keepwords.txt 
happy 
funny 
silly 
原始文本: “Happy, sad or funny” 
输入: “Happy”, “sad”, “or”, “funny” 
输出: “funny”

​ 例子2:

<analyzer>
  <tokenizer class="solr.StandardTokenizerFactory"/>
  <filter class="solr.KeepWordFilterFactory" words="keepwords.txt" ignoreCase="true" />
</analyzer>
保留词列表keepwords.txt 
happy 
funny 
silly 
原始文本: “Happy, sad or funny” 
输入: “Happy”, “sad”, “or”, “funny” 
输出: “Happy”, “funny”

Synonym Filter

​ 作用:这个Filter用来处理同义词;

参数说明
synonyms必选,以.txt结尾的文件指定同义词列表
ignoreCase布尔值,默认false是否忽略大小写
format可选,默认solr指定解析同义词的策略
注意,常用的同义词列表格式: 
1. 以#开头的行为注释内容,忽略 
2. 以,分隔的文本,为双向同义词,左右内容等价,互为同义词 
3. 以=>分隔的文本,为单向同义词,匹配到左边内容,将替换为右边内容,反之不成立

例子:

<analyzer>
  <tokenizer class="solr.StandardTokenizerFactory"/>
  <filter class="solr.SynonymFilterFactory" synonyms="mysynonyms.txt"/>
</analyzer>
同义词列表synonyms.txt 
couch,sofa,divan 
teh => the 
huge,ginormous,humungous => large 
small => tiny,teeny,weeny 
原始文本: “teh small couch” 
输入: “teh”(1), “small”(2), “couch”(3) 
输出: “the”(1), “tiny”(2), “teeny”(2), “weeny”(2), “couch”(3), “sofa”(3), “divan”(3)

原始文本: “teh ginormous, humungous sofa” 
输入: “teh”(1), “ginormous”(2), “humungous”(3), “sofa”(4) 
输出: “the”(1), “large”(2), “large”(3), “couch”(4), “sofa”(4), “divan”(4)

到这常用的过滤器我们就讲解完毕了,这些常用的过滤器将来我们在开发一些复杂的需求时候,都可能会用到;

TextField的使用
前面我们已经学习完毕solr中的分词器和过滤器,有了这些知识的储备后,我们就可以使用TextField这种类定义FieldType.

​ 之前我们说过,在我们在使用TextField作为FieldType的class的时候,必须指定Analyzer,用一个<analyzer>标签来声明一个Analyzer;

方式一:直接通过class属性指定分析器类,该类必须继承org.apache.lucene.analysis.Analyzer

<fieldType name="nametext" class="solr.TextField">
  <analyzer class="org.apache.lucene.analysis.core.WhitespaceAnalyzer"/>
</fieldType>

这里的WhitespaceAnalyzer就是一种分析器,这个分析器中封装了我们之前讲过了一个分词器WhitespaceTokenizer。

这种方式写起来比较简单,但是透明度不够,使用者可能不知道这个分析器中封装了哪些分词器和过滤器

测试:

在这里插入图片描述

对于那些复杂的分析需求,我们也可以在分析器中灵活地组合分词器、过滤器;

方式二:可以灵活地组合分词器、过滤器

<fieldType name="nametext" class="solr.TextField">
  <analyzer>
    <tokenizer class="solr.StandardTokenizerFactory"/>
    <filter class="solr.LowerCaseFilterFactory"/>
    <filter class="solr.StopFilterFactory" words="stopwords.txt"/>
  </analyzer>
</fieldType>

测试:
在这里插入图片描述

方式三:如果该类型字段索引、查询时需要使用不同的分析器,则需区分配置analyzer

<fieldType name="nametext" class="solr.TextField">
  <analyzer type="index">
    <tokenizer class="solr.StandardTokenizerFactory"/>
    <filter class="solr.LowerCaseFilterFactory"/>
    <filter class="solr.SynonymFilterFactory" synonyms="syns.txt"/>
  </analyzer>
  <analyzer type="query">
    <tokenizer class="solr.StandardTokenizerFactory"/>
    <filter class="solr.LowerCaseFilterFactory"/>
  </analyzer>
</fieldType>

测试索引分词效果:
在这里插入图片描述

测试搜索分词效果
在这里插入图片描述

通过测试我们发现索引和搜索产生的分词结果是不同;

接下来我们使用myFeildType3定义一个域。使用该域创建一个文档。我们来测试;
在这里插入图片描述
item_content:sofa可以搜索到吗?

索引的时候: sofa被分为couch,sofa,divan;
在这里插入图片描述
搜索的时候,sofa这个内容就被分为sofa这一个词;

item_content:couch可以搜索到吗?

item_content:small可以搜索到吗?

索引的时候,small经过同义词过滤器变成 tiny,teeny,weeny 。small并没有和文档建立倒排索引关系;

搜索的时候small内容只能被分为samll这个词;所以找不到;

结论:

所以一般我们在定义FieldType的时候,索引和搜索往往使用的分析器规则相同;

或者索引的时候采用细粒度的分词器,目的是让更多的词和文档建立倒排索引;

搜索的时候使用粗粒度分词器,词分的少一点,提高查询的精度;

DateRangeField的使用

在这里插入图片描述
​ Solr中提供的时间域类型( DatePointField, DateRangeField)是以时间毫秒数来存储时间的。要求域值以ISO-8601标准格式来表示时间:yyyy-MM-ddTHH:mm:ssZ。Z表示是UTC时间,如1999-05-20T17:33:18Z;

秒上可以带小数来表示毫秒如:1972-05-20T17:33:18.772Z、1972-05-20T17:33:18.77Z、1972-05-20T17:33:18.7Z

域类型定义很简单:

<fieldType name="myDate" class="solr.DateRangeField" />

使用myDate域类型定义一个域

<field name="item_birthday" type="myDate" indexed="true" stored="true" />

基于item_birthday域来索引

{id:666777,name:"xiaoming",item_birthday:"2020-3-6T19:21:22Z"}
{id:777888,name:"misscang",item_birthday:"2020-3-6T19:22:22.333Z"}

如何基于item_birthday搜索

语法:

查询时如果是直接的时间串,需要用转义字符进行转义:
item_birthday:2020-2-14T19\:21\:22Z
#用字符串表示的则不需要转义
item_birthday:"2020-2-14T19:21:22Z"

DateRangeField除了支持精确时间查询,也支持对间段数据的搜索,支持两种时间段表示方式:

方式一:截断日期,它表示整个日期跨度的精确指示。
方式二:时间范围,语法 [t1 TO t2] {t1 TO t2},中括号表示包含边界,大括号表示不包含边界

例子:

2000-11		#表示2000年11月整个月.
2000-11T13		#表示2000年11月每天的13点这一个小时
[2000-11-01 TO 2014-12-01]		#日到日
[2014 TO 2014-12-01]		#2014年开始到2014-12-01止.
[* TO 2014-12-01]		#2014-12-01(含)前.

演示:

item_birthday:2020-11-21   
item_birthday:[2020-02-14T19:21 TO 2020-02-14T19:22]

Solr中还支持用 【NOW ±/ 时间】的数学表达式来灵活表示时间。

NOW+1MONTH  #当前时间加上1个月
NOW+2MONTHS  #当前时间加上两个月,复数要机上S
NOW-1DAY
NOW-2DAYS
NOW/DAY, NOW/HOURS表示,截断。如果当前时间是2017-05-20T23:32:33Z,那么即是2017-05-20T00:00:00Z,2017-05-20T23:00:00Z。取当日的0点,取当前小时0点
NOW+6MONTHS+3DAYS/DAY
1972-05-20T17:33:18.772Z+6MONTHS+3DAYS/DAY

演示:

item_birthday:[NOW-3MONTHS  TO NOW] 生日在三个月内的;
item_birthday:[NOW/DAY TO NOW] 当前时间的0点到当前时间
item_birthday:[NOW/HOURS TO NOW] 当前小时的0点到当前时间

这个关于DateRangeField我们就讲解完毕;

EnumFieldType的使用

在这里插入图片描述
EnumFieldType 用于域值是一个枚举集合,且排序顺序可预定的情况,如新闻分类这样的字段。如果我们想定义一个域类型,他的域值只能取指定的值,我们就可以使用EnumFieldType 定义该域类型;

域类型定义:

<fieldType name="mySex" class="solr.EnumFieldType" 
enumsConfig="enumsConfig.xml" enumName="sexType" docValues="true" />

属性:

参数说明
enumsConfigenumsConfig.xml指定枚举值的配置文件,绝对路径或相对内核conf/的相对路径
enumName任意字符串指定配置文件的枚举名。
docValuestrue/false枚举类型必须设置 true

enumsConfig.xml配置示例(若没有该文件则新建)如下:注意以UTF-8无BOM格式保存;

<?xml version="1.0" encoding="UTF-8"?>
<enumsConfig>
    <enum name="sexType">
        <value>男</value>
        <value>女</value>
    </enum>
    <enum name="new_cat">
        <value>a</value>
        <value>b</value>
        <value>c</value>
        <value>d</value>
    </enum>
</enumsConfig>

演示:

<field name="item_sex" type="mySex" indexed="true" stored="true" />

基于item_sex进行索引

{id:123321,item_birthday:"1992-04-20T20:33:33Z",item_content:"i love sofa",item_sex:"男"}
{id:456564,item_birthday:"1992-04-20T20:33:33Z",item_content:"i love sofa",item_sex:"女"}
{id:789987,item_birthday:"1992-04-20T20:33:33Z",item_content:"i love sofa",item_sex:"妖"}
报错

在这里插入图片描述
基于item_sex进行搜索

item_sex:男

item_sex:女

到此关于FieldType就讲解完毕;

3.2.5 Solr的配置-DynamicField 动态域

​ 在schema文件中我们还可以看到一种标签dynamicField,这个标签也是用来定义域的;他和field标签有什么区别呢

​ 作用:如果某个业务中有近百个域需要定义,其中有很多域类型是相同,重复地定义域就十分的麻烦,因此可以定一个域名的规则,索引的时候,只要域名符合该规则即可;

​ 如:整型域都是一样的定义,则可以定义一个动态域如下

<dynamicField name="*_i"  type="pint"    indexed="true"  stored="true"/>
注意:动态域只能用符号*通配符进行表示,且只有前缀和后缀两种方式

基于动态域索引;

{id:38383,item_birthday:"1992-04-20T20:33:33Z",item_content:"i love sofa",item_sex:"女",
bf_i:5}
bf_i就是符合上面动态域的域名;

3.2.6 Solr的配置-复制域

​ 在schema文件中我们还可以看到一种标签copyField ,这个标签是用来定义复制域的;复制域的作用是什么呢?

​ 作用:复制域允许将一个或多个域的数据填充到另外一个域中。他的最主要的作用,基于某一个域搜索,相当于在多个域中进行搜索;

   <copyField source="cat" dest="text"/>
   <copyField source="name" dest="text"/>
   <copyField source="manu" dest="text"/>
   <copyField source="features" dest="text"/>
   <copyField source="includes" dest="text"/>
   cat name manu features includes text都是solr提前定义好的域。
   将 cat name manu features includes域的内容填充到text域中;
   将来基于text域进行搜索,相当于在cat name manu features includes域中搜索;

​ 演示:

索引:
{id:1,name:"pitter wang"}
{id:2,name:"pitter ma"}
{id:3,manu:"jack ma"}
{id:4,manu:"joice liu"}
搜索:
text:ma

结果:

在这里插入图片描述

3.2.7 Solr的配置-主键域

​ 指定用作唯一标识文档的域,必须。在solr中默认将id域作为主键域;也可以将其他改为主键域,但是一般都会修改;

 <field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" /> 
 <uniqueKey>id</uniqueKey>
 注意:主键域不可作为复制域,且不能分词。

3.2.8 Solr的配置-中文分词器

中文分词器的介绍

​ 之前我们给大家讲解Solr中的分词器的时候,我们说Solr中最常用的分词器是Standard Tokenizer,

Standard Tokenizer可以对英文完成精确的分词,但是对中文分词是有问题的。

​ 之前我们在schema文件中定义的一个FieldType.name为myFeildType2,class为TextField。使用TextField定义的域类型需要指定分析器,该分析器中使用的分词器就是Standard Tokenizer。

    <fieldType name="myFeildType2" class="solr.TextField">
	  <analyzer>
		<tokenizer class="solr.StandardTokenizerFactory"/>
		<filter class="solr.LowerCaseFilterFactory"/>
		<filter class="solr.StopFilterFactory" words="stopwords.txt"/>
	  </analyzer>
	</fieldType>

​ 测试myFeildType2的分词效果:
在这里插入图片描述

对于这种分词效果,显然不符合我们的要求.

中文分词一直以来是分词领域的一个难题,因为中文中的断词需要依赖语境。相同的一句话语境不同可能分出的词就不同。

在Solr中提供了一个中文分词器SmartCN。但是该分词器并没有纳入到Solr的正式包中,属于扩展包。

位置:solr-7.7.2\contrib\analysis-extras\lucene-libs\lucene-analyzers-smartcn-7.7.2.jar

而且SmartCN对中文分词也不太理想,目前市面上比较主流的中文分词器有IK,MMSeg4J,Ansj,Jcseg,TCTCLAS,HanLP.等

接下来我们就介绍一下这些分词器。并且使用这些分词器定义FieldType;

IK Analyzer

​ IK Analyzer是一个基于java语言开发的轻量级中文分词器包。采用词典分词的原理,允许使用者扩展词库。

使用流程:

​ 1.下载地址:GitHub - EugenePig/ik-analyzer-solr5: IKAnalyzer for Solr5

​ 2.编译打包源码:mvn package

​ 3.安装: 把ik-analyzer-solr5-5.x.jar拷贝到Tomcat的Solr/WEB-INF/lib目录;

​ 4.配置: 把IKAnalyzer.cfg.xml和stopword.dic(停用词库),ext.dic(扩展词库)拷贝到Solr/WEB-INF/classes。

​ 5.使用IK的分析器定义FiledType,IKAnalyzer分析器中提供了中分词器和过滤器。

<fieldType name ="text_ik" class ="solr.TextField">
	<analyzer  class="org.wltea.analyzer.lucene.IKAnalyzer"/>
</fieldType>

​ 6.测试text_ik分词效果。
在这里插入图片描述

​ 7.传智播客,被单字分词,此处我们也可以添加扩展词库,让传智播客分成一个词;

​ 编辑Solr/WEB-INF/classes/ext.dic(扩展词库),加入传智播客;

​ 再次测试
在这里插入图片描述

到此关于IKAnalyzer我们就讲解完毕。

Ansj

​ Ansj 是一个开源的 Java 中文分词工具,基于中科院的 ictclas 中文分词算法,比其他常用的开源分词工具(如mmseg4j)的分词准确率更高。Ansj中文分词是一款纯Java的、主要应用于自然语言处理的、高精度的中文分词工具,目标是“准确、高效、自由地进行中文分词”,可用于人名识别、地名识别、组织机构名识别、多级词性标注、关键词提取、指纹提取等领域,支持行业词典、用户自定义词典。

​ 使用流程:

​ 1.下载ansj以及其依赖包nlp-lang的源码

​ GitHub - NLPchina/ansj_seg: ansj分词.ict的真正java实现.分词效果速度都超过开源版的ict. 中文分词,人名识别,词性标注,用户自定义词典

​ GitHub - NLPchina/nlp-lang: 这个项目是一个基本包.封装了大多数nlp项目中常用工具

​ 2.编译打包源码mvn package -DskipTests=true
在这里插入图片描述
在这里插入图片描述

​ 进入到ansj/plugin/ansj_lucene5_plugin目录,打包ansj_lucene5
在这里插入图片描述

​ 4.安装:将以上三个jar包复制到solr/WEB-INF/lib目录

​ 5.配置:将ansj的词库和配置文件复制到solr/WEB-INF/classes目录
在这里插入图片描述

​ 6.使用ansj中提供的分析器,分词器配置FieldType

   <fieldType name="text_ansj" class="solr.TextField" positionIncrementGap="100">
            <analyzer type="index">
                 <tokenizer class="org.ansj.lucene.util.AnsjTokenizerFactory" isQuery="false"/>
            </analyzer>
            <analyzer type="query">
                <tokenizer class="org.ansj.lucene.util.AnsjTokenizerFactory"/>
            </analyzer>
    </fieldType>

​ 7.测试
在这里插入图片描述

关于Ansj中文分词器的使用我们就给大家讲解完毕。

MMSeg4J

​ mmseg4j用Chih-Hao Tsai 的MMSeg算法实现的中文分词工具包,并实现lucene的analyzer和solr的r中使用。 MMSeg 算法有两种分词方法:Simple和Complex,都是基于正向最大匹配。Complex加了四个规则。官方说:词语的正确识别率达到了 98.41%。mmseg4j已经实现了这两种分词算法。

​ 流程:

​ 1.下载mmseg4j-core及mmseg4J-solr的源码;

​ mmseg4j-core包含了一些词库;

​ mmseg4J-solr包含了和solr整合相关的分析器

​ GitHub - chenlb/mmseg4j-core: mmseg4j core MMSEG for java chinese analyzer

​  GitHub - chenlb/mmseg4j-solr: mmseg4j for lucene or solr analyzer

​ 2.编译打包源码:mvn package

​ 3.安装: 把 mmseg4j-core.jar和mmseg4J-solr.jar复制到Solr/WEB-INF/lib

​ 4.使用mmseg4j的分析器定义FiledType,mmseg4j提供了3种不同模式。分别针对不同情况的解析。

​ tokenizer 的参数:

  • dicPath 参数 - 设置词库位置,支持相对路径(相对于 solrCore).

  • mode 参数 - 分词模式。

    complex:复杂模式,针对语义复杂的情况

<fieldtype name="textComplex" class="solr.TextField" positionIncrementGap="100">
	<analyzer>
		<tokenizer class="com.chenlb.mmseg4j.solr.MMSegTokenizerFactory" mode="complex" dicPath="dic"/>
	</analyzer>
</fieldtype>

​ max-word:最大词模式,针对分出的词最多

<fieldtype name="textMaxWord" class="solr.TextField" positionIncrementGap="100">
	<analyzer>
		<tokenizer class="com.chenlb.mmseg4j.solr.MMSegTokenizerFactory" mode="max-word" 
		dicPath="dic"/>
	</analyzer>
</fieldtype>

​ simple:简单模式,针对一般情况

<fieldtype name="textSimple" class="solr.TextField" positionIncrementGap="100">
	<analyzer>
		<tokenizer class="com.chenlb.mmseg4j.solr.MMSegTokenizerFactory" mode="simple" dicPath="dic" />
	</analyzer>
</fieldtype>

5.在collection1下创建词库目录dic

​ 将 mmseg4j-core/resources/data目录中的词库文件复制到solr_home/collection1/dic目录

chars.dic 是单字的词,一般不用改动我们不需要关心它。
units.dic 是单字的单位,一般不用改动我们不需要关心它。
words.dic 是词库文件,一行一词,可以自己进行扩展词库。

6.测试mmseg4j分词效果。

近一百多年来,总有一些公司很幸运地、有意识或者无意识地站在技术革命的浪尖之上。一旦处在了那个位置,即使不做任何事,也可以随着波浪顺顺当当地向前漂个十年甚至更长的时间。在这十几年间,它们代表着科技的浪潮,直到下一波浪潮的来临。从一百年前算起,AT&T公司、IBM公司、苹果(Apple)公司 、英特尔(Intel) 公司、微软(Microsoft) 公司、和谷歌(Google)公司都先后被幸运地推到了浪尖。虽然,它们来自不同的领域,中间有些已经衰落或者正在衰落,但是它们都极度辉煌过。这些公司里的人,无论职位高低,在外人看来,都是时代的幸运儿。因为,虽然对一个公司来说,赶上一次浪潮不能保证其长盛不衰;但是,对一个人来说,一生赶上一次这样的浪潮就足够了。一个弄潮的年轻人,最幸运的,莫过于赶上一波大潮。

在这里插入图片描述

​ 综上所述可以看出,三种分词方法存在着一些同样的错误,比如名词“英特尔“和”谷歌“都没有识别出来。综合比较Complex的分词方法准确率最高。

​ 到这关于mmseg4j分词器就讲解完毕。

jcseg

​ Jcseg是基于mmseg算法的一个轻量级Java中文分词工具包,同时集成了关键字提取,关键短语提取,关键句子提取和文章自动摘要等功能,并且提供了一个基于Jetty的web服务器,方便各大语言直接http调用,同时提供了最新版本的lucene,solr和elasticsearch的搜索分词接口;

使用流程:

​ 1.下载地址:jcseg: Jcseg是基于mmseg算法的一个轻量级Java中文分词器,同时集成了关键字提取,关键短语提取,关键句子提取和文章自动摘要等功能,并且提供了一个基于Jetty的web服务器,方便各大语言直接http调用,同时提供了最新版本的lucene、solr、elasticsearch、opensearch的搜索分词接口

​ 2.编译打包源码jcseg-core,jcseg-analyzer
在这里插入图片描述

​ 3.安装: 将jcseg-analyze.jar和jcseg-core.jar复制到Solr/WEB-INF/lib目录

​ 4.使用jcseg的分析器定义FieldType,Jcseg提供了很多模式;复杂模式最常用。

(1).简易模式:FMM算法,适合速度要求场合。
(2).复杂模式:MMSEG四种过滤算法,具有较高的歧义去除,分词准确率达到了98.41%。
(3).检测模式:只返回词库中已有的词条,很适合某些应用场合。
(4).最多模式:细粒度切分,专为检索而生,除了中文处理外(不具备中文的人名,数字识别等智能功能)其他与复杂模式一致(英文,组合词等)。
(5).分隔符模式:按照给定的字符切分词条,默认是空格,特定场合的应用。
(6).NLP模式:继承自复杂模式,更改了数字,单位等词条的组合方式,增加电子邮件,大陆手机号码,网址,人名,地名,货币等以及无限种自定义实体的识别与返回。
(7).n-gram模式:CJK和拉丁系字符的通用n-gram切分实现。
<!-- 复杂模式分词: -->
<fieldtype name="text_jcseg" class="solr.TextField">
    <analyzer>
        <tokenizer class="org.lionsoul.jcseg.analyzer.JcsegTokenizerFactory" mode="complex"/>
    </analyzer>
</fieldtype>
<!-- 简易模式分词: -->
<fieldtype name="textSimple" class="solr.TextField">
    <analyzer>
        <tokenizer class="org.lionsoul.jcseg.analyzer.JcsegTokenizerFactory" mode="simple"/>
    </analyzer>
</fieldtype>
<!-- 检测模式分词: -->
<fieldtype name="textDetect" class="solr.TextField">
    <analyzer>
        <tokenizer class="org.lionsoul.jcseg.analyzer.JcsegTokenizerFactory" mode="detect"/>
    </analyzer>
</fieldtype>
<!-- 检索模式分词: -->
<fieldtype name="textSearch" class="solr.TextField">
    <analyzer>
        <tokenizer class="org.lionsoul.jcseg.analyzer.JcsegTokenizerFactory" mode="most"/>
    </analyzer>
</fieldtype>
<!-- NLP模式分词: -->
<fieldtype name="textSearch" class="solr.TextField">
    <analyzer>
        <tokenizer class="org.lionsoul.jcseg.analyzer.JcsegTokenizerFactory" mode="nlp"/>
    </analyzer>
</fieldtype>
<!-- 空格分隔符模式分词: -->
<fieldtype name="textSearch" class="solr.TextField">
    <analyzer>
        <tokenizer class="org.lionsoul.jcseg.analyzer.JcsegTokenizerFactory" mode="delimiter"/>
    </analyzer>
</fieldtype>
<!-- n-gram模式分词: -->
<fieldtype name="textSearch" class="solr.TextField">
    <analyzer>
        <tokenizer class="org.lionsoul.jcseg.analyzer.JcsegTokenizerFactory" mode="ngram"/>
    </analyzer>
</fieldtype>

​ 6.扩展词库定义

​ jcseg也支持对词库进行扩展

​ 将jcseg的配置文件从jc-core/jcseg.properties复制到solr/WEB-INF/classess目录

​ 编辑jcseg.properties配置文件,指定lexicon.path即词库位置。

	lexicon.path = D:/jcseg/lexicon

​ 将jcseg\vendors\lexicon目录下的词库文件复制到 D:/jcseg/lexicon

​ jcseg对这些词库文件进行分类

	修改词库文件lex-place.lex加入新词
	传智播客/ns/chuan zhi bo ke/null

​ 7.测试jcseg分词效果。
在这里插入图片描述

ICTCLAS(中科院分词器)

​ ICTCLAS分词器是中国科学院计算技术研究所在多年研究工作积累的基础上,研制出了汉语词法分析系统ICTCLAS(Institute of Computing Technology, Chinese Lexical Analysis System),基于完全C/C++编写,主要功能包括中文分词;词性标注;命名实体识别;新词识别;同时支持用户词典。先后精心打造五年,内核升级6次,目前已经升级到了ICTCLAS3.0。ICTCLAS3.0分词速度单机996KB/s,分词精度98.45%,API不超过200KB,各种词典数据压缩后不到3M,是当前世界上最好的汉语词法分析器,商业收费。

使用流程:

​ 1.下载ICTCLAS的分析器nlpir-analysis-cn-ictclas和ictclas SDK和jna.jar(java调用c的包):

​ nlpir-analysis-cn-ictclas:GitHub - NLPIR-team/nlpir-analysis-cn-ictclas: Lucene/Solr Analyzer Plugin. Support MacOS,Linux x86/64,Windows x86/64. It's a maven project, which allows you change the lucene/solr version. //Maven工程,修改Lucene/Solr版本,以兼容相应版本。

​ ictclas SDK:GitHub - NLPIR-team/NLPIR

​ jna.jar:https://repo1.maven.org/maven2/net/java/dev/jna/jna/5.5.0/jna-5.5.0.jar

​ 2.编译:打包nlpir-analysis-cn-ictclas源码生成lucene-analyzers-nlpir-ictclas-6.6.0.jar

​ 3.安装:将lucene-analyzers-nlpir-ictclas-6.6.0.jar,jna.jar复制到solr/WEB-INF/lib目录

​ 4.配置:在tomcat/bin目录下创建配置文件nlpir.properties,为什么要在tomcat/bin目录中创建配置文件呢?

我猜是他的代码中使用了相对路径。而不是读取classpath下面的文件;

data="D:/javasoft/NLPIR/NLPIR SDK/NLPIR-ICTCLAS" #Data directory‘s parent path,在ictclas SDK中;
encoding=1 #0 GBK;1 UTF-8
sLicenseCode="" # License code,此处也可以看出其收费
userDict="" # user dictionary, a text file
bOverwrite=false # whether overwrite the existed user dictionary or not

​ 5.配置fieldType,指定对应的分析器

 <fieldType name="text_ictclas" class="solr.TextField" positionIncrementGap="100">
    <analyzer type="index">
      <tokenizer class="org.nlpir.lucene.cn.ictclas.NLPIRTokenizerFactory"/>
    </analyzer>
    <analyzer type="query">
      <tokenizer class="org.nlpir.lucene.cn.ictclas.NLPIRTokenizerFactory"/>
    </analyzer>
  </fieldType>

​ 6.测试text_ictclas分词效果。

HanLP

​ HanLP是由一系列模型与算法组成的java开源工具包,目标是普及自然语言处理在生产环境中的应用。HanLP具备功能完善、性能高效、架构清晰、语料时新、可自定义的特点;提供词法分析(中文分词、词性标注、命名实体识别)、句法分析、文本分类和情感分析等功能。

​ 使用流程:

​ 1.下载词典http://nlp.hankcs.com/download.php?file=data

​ 2.下载hanlp的jar包和配置文件http://nlp.hankcs.com/download.php?file=jar

​ 下载handlp整合lucene的jar包https://github.com/hankcs/hanlp-lucene-plugin

​ 3.将词典文件压缩包data-for-1.7.zip解压到指定位置 eg:d:/javasoft下;
在这里插入图片描述

​ 4.安装: 将hanlp.jar和hanlp-lucene-plugin.jar包复制到solr/WEB-INF/lib

​ 5.配置:将配置文件hanlp.properties复制solr/WEB-INF/classes
在这里插入图片描述

​ 6.配置:在hanlp.properties中指定词典数据的目录
在这里插入图片描述

​ 7.配置停用词,扩展词等。
在这里插入图片描述

​ 8.使用HanLP中提供的分析器配置FieldType

<fieldType name="text_hanlp" class="solr.TextField">
		<analyzer type="index">
			<tokenizer class="com.hankcs.lucene.HanLPTokenizerFactory" enableIndexMode="true"/>
		</analyzer>
		<analyzer type="query">
			<!-- 切记不要在query中开启index模式 -->
			<tokenizer class="com.hankcs.lucene.HanLPTokenizerFactory" enableIndexMode="false"/>
		</analyzer>
	</fieldType>

​ 6.测试test_hanlp分词效果。
在这里插入图片描述

中文分词器建议

​ 介绍了这么多的中文分词器,在实际开发中我们如何进行选择呢?

​ 中文分词器核心就是算法和字典,算法决定了分词的效率,字典决定了分词的结果。词库不完善导致词语分不出来,虽然可以通过扩展词典进行补充,但是补充只能是发现问题再去补充,所以分词器自动发现新词的功能就很重要。

​ CRF(条件随机场)算法是目前最好的识别新词的分词算法。Ansj和HanLp都支持CRF,自动补充词库;

​ Ansj缺点是核心词典不能修改,只能借助扩展词典进行补充。

​ HanLp是目前功能最齐全,社区最活跃的分词器。

​ MMseg4J和Jcseg两者都采用MMseg算法。但是Jcseg更活跃。MMseg4J基本不再更新。

​ IK分词器是使用最多的一种分词器,虽然词典不支持自动扩展词汇,但是简单,所以使用率最高;

​ 最后一个就是Ictclas他是功能最强大的一款分词器,但是是基于C/C++编写,而且要进行商业授权。比较适合对分词要求较高且不差钱的公司。

​ 综上:

​ 一般对于追求简单来说建议使用IK分词器;

​ 对分词要求较高,但是希望免费,可以使用HanLp;

​ 关于中文分词器的使用我们就全部讲解完毕。

自定义分词器(了解)

​ 我们都知道分析器由分词器和过滤器构成。

​ 要想使用分析器,首先要定义分词器。如果不需要对分词结果进行过滤,过滤器是可选的。

​ 之前我们在使用分词器的时候,都是使用Solr自带的分词器,比如标准分词器。当然也使用到第三方的一些中文分词器,比如IK分词器。为了更好的理解分析器的java体系结构,下面讲解自定义分词器。

​ 定义分词器的步骤:

​ 继承Tokenizer或者CharTokenizer抽象类;Tokenizer字符串级别的分词器,适合复杂分词器。CharTokenizer字符级别的分词器,适合简单分词器;

​ 定义过滤器的步骤:

​ 继承TokenFiler或者FilteringTokenFilter

​ 需求:

​ 自定义分词器将"This+is+my+family"按照+作为分隔符分成"This" "is" "My" "family";

​ 1.搭建环境

​ 创建模块,引入solr-core依赖,最好和当前solr版本一致

   <dependency>
       <groupId>org.apache.solr</groupId>
       <artifactId>solr-core</artifactId>
       <version>7.7.2</version>
  </dependency>

​ 2.定义一个类继承CharTokenizer(字符级别的分词器)

public class PlusSignTokenizer  extends CharTokenizer {
    public PlusSignTokenizer() {
    }

    public PlusSignTokenizer(AttributeFactory factory) {
        super(factory);
    }
	//isTokenChar哪些字符是词;
    public boolean isTokenChar(int i) {
        //将什么字符作为分隔符进行分词
      return i != '+';
    }
}

​ 3.定义分词器工厂管理分词器,定义类继承TokenizerFactory

public class PlusSignTokenizerFactory extends TokenizerFactory {

    /**
     * 在schem配置文件中配置分词器的时候,指定参数args配置分词器时候的指定的参数
     *    <analyzer>
     *         <tokenizer class="cn.itcast.tokenizer.PlausSignTokenizerFactory" mode="complex"/>
     *     </analyzer>
     * @param args
     */

    public PlusSignTokenizerFactory(Map<String, String> args) {
        //分词器属性配置
        super(args);
    }

    @Override
    public Tokenizer create(AttributeFactory attributeFactory) {
        return new PlusSignTokenizer(attributeFactory);
    }
}

​ 4.package打包代码,将jar包复制到solr/WEB-INF/lib

​ 5.配置FieldType

 <fieldType name="text_custom" class="solr.TextField">
     <analyzer>
    	 <tokenizer class="cn.itcast.tokenizer.PlausSignTokenizerFactory"/>
     </analyzer>
</fieldType>

​ 6.测试

关于如何自定义一个分词器,我们就先说到这,在实际开发中我们基本上不会自己定义,主要体会流程;

自定义过滤器(了解)

上一节课,我们学习了分词器的定义,下面我们讲解定义过滤器。

在Solr中要定义一个过滤器,需要继承TokenFiler或者FilteringTokenFilter

​ 需求:过滤掉"love"词

我们可以参考一个solr内置过滤器LenthFilter;

public final class LengthFilter extends FilteringTokenFilter {
  private final int min;
  private final int max;
  //获取当前词
  private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
  //构造方法接收三个参数,词的集合,词的最小长度和最大长度
  public LengthFilter(TokenStream in, int min, int max) {
    super(in);
    if (min < 0) {
      throw new IllegalArgumentException("minimum length must be greater than or equal to zero");
    }
    if (min > max) {
      throw new IllegalArgumentException("maximum length must not be greater than minimum length");
    }
    this.min = min;
    this.max = max;
  }

  //该方法返回true,保留该词否则不保留;
  @Override
  public boolean accept() {
    final int len = termAtt.length();
    return (len >= min && len <= max);
  }
}

​ 开发步骤:

​ 1.定义一个类继承FilteringTokenFilter

​ 2.定义成员变量,获取当前的词

​ private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);

​ 3.重写FilteringTokenFilter中accept() 方法决定保留哪些词。

public class LoveTokenFilter extends FilteringTokenFilter {
    private String keyword;
    //获取当前词
    private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);

    public LoveTokenFilter(TokenStream in,String keyword) {
        super(in);
        this.keyword = keyword;
    }

    protected boolean accept() throws IOException {
        String token = termAtt.toString();
        if(token.equalsIgnoreCase(keyword)) {
            return false;
        }
        return true;
    }
}

​ 4.定义类继承TokenFilterFactory

public class LoveTokenFilterFactory extends TokenFilterFactory {
    private String keywords;

	//调用父类无参构造
    public LoveTokenFilterFactory(Map<String, String> args) throws IllegalAccessException {
        super(args);
        if(args== null) {
            throw new IllegalAccessException("必须传递keywords参数");
        }
       this.keywords = this.get(args, "keywords");
    }

    public TokenStream create(TokenStream input) {
        return new LoveTokenFilter(input,keywords);
    }
}

5.配置FieldType

	 <fieldType name="text_custom" class="solr.TextField">
		<analyzer>
		  <tokenizer class="cn.itcast.tokenizer.PlusSignTokenizerFactory"/>
		  <filter class="cn.itcast.tokenfilter.LoveTokenFilterFactory" keywords="love"/>
		</analyzer>
	  </fieldType>

6.梳理工作流程:
在这里插入图片描述

7.打包安装重写测试
在这里插入图片描述

自定义分析器(了解)

​ 在Solr中我们通常会使用<tokenizer> + <filter>的形式来组合分析器,这种方式耦合性低,使用起来灵活。

​ 在Solr中也允许我们将一个分词器和一个过滤器直接封装到分析器类中;将来直接使用分析器。

​ 流程:

​ 1.定义类继承Analyzer类。

​ 2.重写createComponents()方法;

​ 3.将分词器和过滤器封装在createComponents()方法的返回值即可;


public class MyAnalyzer extends Analyzer {
   
    @Override
    public TokenStreamComponents createComponents(String fieldName) {
    	//自己的分词器
        Tokenizer plusSignTokenizer = new PlusSignTokenizer();
        //自带的过滤器
        LowerCaseFilter lowerCaseFilter = new LowerCaseFilter(plusSignTokenizer);
        return new TokenStreamComponents(plusSignTokenizer, lowerCaseFilter);
    }
}

​ 4.使用分析器声明FieldType

<fieldType name="text_custom2" class="solr.TextField">
	<analyzer	class="cn.itcast.analyzer.MyAnalyzer">
	</analyzer>
</fieldType>

​ 5.打包测试
在这里插入图片描述

3.2.9 Solr的数据导入(DataImport)

​ 在Solr后台管理系统中提供了一个功能叫DataImport,作用就是将数据库中的数据导入到索引库,简称DHI;

​ DataImport如何将数据库中的数据导入到索引库呢?

​ 1.查询数据库中的记录;

​ 2.将数据库中的一条记录转化为Document,并进行索引;

需求:将以下表中数据导入到MySQL

sql脚本在资料中;

在这里插入图片描述

步骤:

​ 0.准备:

​ 创建MySQL数据库和表(略);

​ 在schema文件中声明图书相关的业务域;

id:使用solr提供的id域;
book_name:使用text_ik类型,因为该域中包含中文,索引,并且存储;
book_price:使用pfloat类型,索引,并且存储;
book_pic:使用string类型,不索引,存储;
book_description:使用text_ik类型,索引,不存储;
book_num:使用pint类型,索引,并且存储;
<field name="book_name" type="text_ik" indexed="true" stored="true"/>
<field name="book_price" type="pfloat" indexed="true" stored="true"/>
<field name="book_pic" type="string" indexed="false" stored="true"/>
<field name="book_description" type="text_ik" indexed="true" stored="false"/>
<field name="book_num" type="pint" indexed="true" stored="true"/>

​ 1.将MySQL的mysql驱动mysql-connector-5.1.16-bin.jar复制到solr\WEB-INF\lib中。

​ 2.查看SolrCore中的配置文件solrconfig.xml,solrconfig.xml文件主要配置了solrcore自身相关的一些参数(后面我们再给大家讲解)。作用:指定DataImport将MySQL数据导入Solr的配置文件为solr-data-config.xml;

  <requestHandler name="/dataimport" class="solr.DataImportHandler">
    <lst name="defaults">
      <str name="config">solr-data-config.xml</str>
    </lst>
  </requestHandler>

​ 3.编辑SolrCore/conf中solr-data-config.xml文件
在这里插入图片描述

<dataConfig>
    <!-- 首先配置数据源指定数据库的连接信息 -->
	<dataSource type="JdbcDataSource"   
          driver="com.mysql.jdbc.Driver"   
          url="jdbc:mysql://localhost:3306/lucene"   
          user="root"   
          password="root"/>
    <document>
        <!-- entity作用:数据库中字段和域名如何映射
        	 name:标识,任意
        	 query:执行的查询语句
        -->
        <entity name="book" query="select * from book">
            <!-- 每一个field映射着数据库中列与文档中的域,column是数据库列,name是solr的域(必须是在managed-schema文件中配置过的域才行) -->
            <field column="id" name="id"/>
			<field column="name" name="book_name"/>
            <field column="price" name="book_price"/>
            <field column="pic" name="book_pic"/>
			<field column="description" name="book_description"/>
			<field column="num" name="book_num"/>
        </entity>
    </document>
</dataConfig>

​ 4.使用DataImport导入。
在这里插入图片描述
到这关于DataImport我们就讲解完毕。

3.2.10 solrconfig.xml

​ 上一节在使用DataImport的时候,使用到了一个配置文件solrconfig.xml,这个配置文件是solr中常用的4个配置文件之一。但是相对schema文件,用的很少。

​ solrconfig.xml作用:主要配置SolrCore相关的一些信息。Lucene的版本,第三方依赖包加载路径,索引和搜索相关的配置;JMX配置,缓存配置等;

1.Lucene的版本配置;一般和Solr版本一致;

 <luceneMatchVersion>7.7.2</luceneMatchVersion>

2.第三方依赖包加载路径

 <lib dir="${solr.install.dir:../../../..}/contrib/extraction/lib" regex=".*\.jar" />

​ 告诉solr,第三方依赖包的位置。一般我们并不会在lib中进行设置,因为lib中的设置,只能该solrCore使用。

其他SolrCore无法使用,一般第三方的依赖包,我们直接会放在Solr/WEB-INF/lib下。所有的solrCore共享。

​ solr.install.dir:SolrCore所在目录,当前配置文件属于哪个SolrCore,solr.install.dir就是那个SolrCore目录‘

  <dataDir>${solr.data.dir:}</dataDir>

​ 索引数据所在目录:默认位置SolrCore/data

3.用来配置创建索引的类

 <directoryFactory name="DirectoryFactory"
                    class="${solr.directoryFactory:solr.NRTCachingDirectoryFactory}"/>
  1. 用来设置Lucene倒排索引的编码工厂类,默认实现是官方提供的SchemaCodecFactory类。;
<codecFactory class="solr.SchemaCodecFactory"/>

​ 5.索引相关配置
在这里插入图片描述

​ 6.搜索相关配置
在这里插入图片描述

3.2.11 Query

接下来我们讲解后台管理系统中Query功能,Query的主要作用是查询;

接下来我们来讲解一下基本使用
在这里插入图片描述
q:表示主查询条件,必须有.

fq:过滤条件。

start,rows:指定分页;

fl:指定查询结果文档中需要的域;

wt:查询结果的格式,JSON/XML;

演示:

​ 查询book_description中包含java;

​ 查询book_description中包含java并且book_name中包含lucene;

​ 对查询的结果按照价格升序,降序。

​ 分页查询满足条件的第一页2条数据,第二页2条数据

​ start=(页码-1) * 每页条数

​ 将查询结果中id域去掉;

​ 查询的结果以JSON形式返回;

到这关于Solr管理后台Query的基本使用我们就暂时讲解到这里;

3.2.13 SolrCore其它菜单

​ overview(概览)

​ 作用:包含基本统计如当前文档数,最大文档数;删除文档数,当前SolrCore配置目录;
在这里插入图片描述
​ files

​ 作用:对SolrCore/conf目录下文件预览
在这里插入图片描述
Ping:拼接Solr的连通性;

Plugins:Solr使用的一些插件;

Replication:集群状态查看,后面搭建完毕集群再来说;

Schema:管理Schema文件中的Field,可以查看和添加域,动态域和复制域;
在这里插入图片描述

SegmentsInfo:展示底层Lucence索引段,包括每个段的大小和数据条数。Solr底层是基于lucene实现的,索引数据最终是存储到SolrCore/data/index目录的索引文件中;这些索引文件有_e开始的, _0开始的....对应的就是不同的索引段。
在这里插入图片描述
在这里插入图片描述

4. Solr查询

4.1 Solr查询概述(理解)

​我们先从整体上对solr查询进行认识。当用户发起一个查询的请求,这个请求会被Solr中的Request Handler所接受并处理,Request Handler是Solr中定义好的组件,专门用来处理用户查询的请求。

Request Handler相关的配置在solrconfig.xml中;

下面就是一个请求处理器的配置

name:uri;

class:请求处理器处理请求的类;

lst:参数设置,eg:rows每页显示的条数。wt:结果的格式

 <requestHandler name="/select" class="solr.SearchHandler">
     <lst name="defaults">
       <str name="echoParams">explicit</str>
       <int name="rows">10</int>
       <str name="df">text</str>
       <!-- Change from JSON to XML format (the default prior to Solr 7.0)
          <str name="wt">xml</str> 
         -->
     </lst>
</requestHandler>

  <requestHandler name="/query" class="solr.SearchHandler">
     <lst name="defaults">
       <str name="echoParams">explicit</str>
       <str name="wt">json</str>
       <str name="indent">true</str>
       <str name="df">text</str>
     </lst>
  </requestHandler>

这是Solr底层处理查询的组件,RequestHandler,简单认识一下;

宏观认识Solr查询的流程;
在这里插入图片描述

当用户输入查询的字符串eg:book_name:java,选择查询处理器/select.点击搜索;

请求首先会到达RequestHandler。RequestHandler会将查询的字符串交由QueryParser进行解析。

Index会从索引库中搜索出相关的结果。

ResponseWriter将最终结果响应给用户;

通过这幅图大家需要明确的是,查询的本质就是基于Http协议和Solr服务进行请求和响应的一个过程。

4.2 相关度排序

​ 上一节我们了解完毕Solr的查询流程,接下来我们来讲解相关度排序。什么叫相关度排序呢?

比如查询book_description中包含java的文档。

查询结果;
在这里插入图片描述

疑问:为什么id为40的文档再最前面?这里面就牵扯到Lucene的相关度排序;

​ 相关度排序是查询结果按照与查询关键字的相关性进行排序,越相关的越靠前。比如搜索“java”关键字,与该关键字最相关的文档应该排在前边。

​ 影响相关度排序2个要素

​ Term Frequency (tf):

​ 指此Term在此文档中出现了多少次。tf越大说明越重要。词(Term)在文档中出现的次数越多,说明此词(Term)对该文档越重要,如“java”这个词,在文档中出现的次数很多,说明该文档主要就是讲java技术的。

​ Document Frequency (df):

​ 指有多少文档包含此Term。df越大说明越不重要。比如,在一篇英语文档中,this出现的次数更多,就说明越重要吗?不是的,有越多的文档包含此词(Term),说明此词(Term)太普通,不足以区分这些文档,因而重要性越低。

​ 相关度评分

​ Solr底层会根据一定的算法,对文档进行一个打分。打分比较高的排名靠前,打分比较低的排名靠后。

​ 设置boost(权重)值影响相关度打分;

​ 举例:查询book_name或者book_description域中包含java的文档;

​ book_name中包含java或者book_description域中包含java的文档都会被查询出来。假如我们认为book_name中包含java应该排名靠前。可以给book_name域增加权重值。book_name域中有java的文档就可以靠前。

4.3 查询解析器(QueryParser)

​ 之前我们在讲解查询流程的时候,我们说用户输入的查询内容,需要被查询解析器解析。所以查询解析器QueryParser作用就是对查询的内容进行解析。

​ solr提供了多种查询解析器,为我们使用者提供了极大的灵活性及控制如何解析器查询内容。

Solr提供的查询解析器包含以下:

​ Standard Query Parser:标准查询解析器;

​ DisMax Query Parser:DisMax 查询解析器;

​ Extends DisMax Query Parser:扩展DisMax 查询解析器

​ Others Query Parser:其他查询解析器

当然Solr也运行用户自定义查询解析器。需要继承QParserPlugin类;

​ 默认解析器:lucene

​ solr默认使用的解析器是lucene,即Standard Query Parser。Standard Query Parser支持lucene原生的查询语法,并且进行增强,使你可以方便地构造结构化查询语句。当然,它还有不好的,就是容错比较差,总是把错误抛出来,而不是像dismax一样消化掉。

​ 查询解析器: disMax

​ 只支持lucene查询语法的一个很小的子集:简单的短语查询、+ - 修饰符、AND OR 布尔操作;
可以灵活设置各个查询字段的相关性权重,可以灵活增加满足某特定查询文档的相关性权重。

​ 查询解析器:edisMax

​ 扩展 DisMax Query Parse 使支标准查询语法(是 Standard Query Parser 和 DisMax Query Parser 的复合)。也增加了不少参数来改进disMax。支持的语法很丰富;很好的容错能力;灵活的加权评分设置。

​ 对于不同的解析器来说,支持的查询语法和查询参数,也是不同的。我们不可能把所有解析器的查询语法和参数讲完。实际开发也用不上。我们重点讲解的是Standard Query Parser支持的语法和参数。

4.4 查询语法

​ 之前我们查询功能都是通过后台管理界面完成查询的。实际上,底层就是向Solr请求处理器发送了一个查询的请求,传递了查询的参数,下面我们要讲解的就是查询语法和参数。
在这里插入图片描述

地址信息说明
http://localhost:8080/solr/collection1/select?q=book_name:java查询请求url
http://localhost:8080/solrsolr服务地址
collection1solrCore
/select请求处理器
?q=xxx查询的参数
4.4.1基本查询参数
参数名描述
q查询条件,必填项
fq过滤查询
start结果集第一条记录的偏移量,用于分页,默认值0
rows返回文档的记录数,用于分页,默认值10
sort排序,格式:sort=`+<asc
fl指定返回的域名,多个域名用逗号或者空格分隔,默认返回所有域
wt指定响应的格式,例如xml、json等;

​ 演示:

​ 查询所有文档:

http://localhost:8080/solr/collection1/select?q=*:*

​ 查询book_name域中包含java的文档

http://localhost:8080/solr/collection1/select?q=book_name:java

​ 查询book_description域中包含java,book_name中包含java。

http://localhost:8080/solr/collection1/select?q=book_description:java&fq=book_name:java

​ 查询第一页的2个文档

​ 查询第一页的2个文档

http://localhost:8080/solr/collection1/select?q=*:*&start=0&rows=2
http://localhost:8080/solr/collection1/select?q=*:*&start=2&rows=2

​ 查询所有文档并且按照book_num升序,降序

http://localhost:8080/solr/collection1/select?q=*:*&sort=book_num+asc
http://localhost:8080/solr/collection1/select?q=*:*&sort=book_num+desc

​ 查询所有文档并且按照book_num降序,如果数量一样按照价格升序。

http://localhost:8080/solr/collection1/select?q=*:*&sort=book_num+desc,book_price+asc

​ 查询所有数据文档,将id域排除

http://localhost:8080/solr/collection1/select?q=*:*&fl=book_name,book_price,book_num,
book_pic

对于基础查询来说还有其他的一些系统级别的参数,但是一般用的较少。简单说2个。

描述参数名称
选择哪个查询解析器对q中的参数进行解析,默认lucene,还可以使用dismax|edismaxdefType
覆盖schema.xml的defaultOperator(有空格时用"AND"还是用"OR"操作逻辑).默认为OR。q.op
默认查询字段df
(query type)指定那个类型来处理查询请求,一般不用指定,默认是standardqt
查询结果是否进行缩进,开启,一般调试json,php,phps,ruby输出才有必要用这个参数indent
查询语法的版本,建议不使用它,由服务器指定默认值version

defType:更改Solr的查询解析器的。一旦查询解析器发生改变,支持其他的查询参数和语法。

q.op:如果我们搜索的关键字可以分出很多的词,到底是基于这些词进行与的关系还是或关系。

q=book_name:java编程 搜索book_name中包含java编程关键字   
java编程---->java 编程。
搜索的条件时候是book_name中不仅行包含java而且包含编程,还是只要有一个即可。
q=book_name&q.op=AND 
q=book_name&q.op=OR

df:指定默认搜索的域,一旦我们指定了默认搜索的域,在搜索的时候,我们可以省略域名,仅仅指定搜索的关键字
就可以在默认域中搜索

q=java&df=book_name
4.4.2 组合查询

​ 上一节我们讲解了基础查询,接下来我们讲解组合查询。在Solr中提供了运算符,通过运算符我们就可以进行组合查询。

运算符说明
?通配符,替代任意单个字符(不能在检索的项开始使用*或者?符号)
*通配符,替代任意多个字符(不能在检索的项开始使用*或者?符号)
~表示相似度查询,如查询类似于"roam"的词,我们可以这样写:roam将找到形如foam和roams的单词;roam0.8,检索返回相似度在0.8以上的文档。 邻近检索,如检索相隔10个单词的"apache"和"jakarta","jakarta apache"~10
AND表示且,等同于 “&&”
OR表示或,等同于 “||”
NOT表示否
()用于构成子查询
[]范围查询,包含头尾
{}范围查询,不包含头尾
+存在运算符,表示文档中必须存在 “+” 号后的项
-不存在运算符,表示文档中不包含 “-” 号后的项

实例:

​ [?]查询book_name中包含c?的词。

http://localhost:8080/solr/collection1/select?q=book_name:c?

​ [*]查询book_name总含spring*的词

http://localhost:8080/solr/collection1/select?q=book_name:spring*

​ [~]模糊查询book_name中包含和java及和java比较像的词,相似度0.75以上

http://localhost:8080/solr/collection1/select?q=book_name:java~0.75
java和jave相似度就是0.75.4个字符中3个相同。

在这里插入图片描述

​ [AND]查询book_name域中既包含servlet又包含jsp的文档;

​ 方式1:使用and

q=book_name:servlet AND book_name:jsp
q=book_name:servlet && book_name:jsp

​ 方式2:使用+

q=+book_name:servlet +book_name:jsp

[OR]查询book_name域中既包含servlet或者包含jsp的文档;

q=book_name:servlet OR book_name:jsp
q=book_name:servlet || book_name:jsp

[NOT]查询book_name域中包含spring,并且id不是4的文档

book_name:spring AND NOT id:4
+book_name:spring  -id:4

[范围查询]

​ 查询商品数量>=4并且<=10

http://localhost:8080/solr/collection1/select?q=book_num:[4 TO 10]

​ 查询商品数量>4并且<10

http://localhost:8080/solr/collection1/select?q=book_num:{4 TO 10}

​ 查询商品数量大于125

http://localhost:8080/solr/collection1/select?q=book_num:{125 TO *]
4.4.3 q和fq的区别

​ 在讲解基础查询的时候我们使用了q和fq,这2个参数从使用上比较的类似,很多使用者可能认为他们的功能相同,下面我们来阐述他们两者的区别;

​ 从使用上:q必须传递参数,fq可选的参数。在执行查询的时候,必须有q,而fq可以有,也可以没有;

​ 从功能上:q有2项功能

​ 第一项:根据用户输入的查询条件,查询符合条件的文档。

​ 第二项:使用相关性算法,匹配到的文档进行相关度排序。

​ fq只有一项功能

​ 对匹配到的文档进行过滤,不会影响相关度排序,效率高;

演示:

​ 需求:查询book_descrption中包含java并且book_name中包含java文档;

​ 单独使用q来完成;
在这里插入图片描述

​ 由于book_name条件的加入造成排序结果的改变;说明q查询符合条件的文档,也可以进行相关度排序;

​ 使用q + fq完成
在这里插入图片描述

说明fq:仅仅只会进行条件过滤,不会影响相关度排序;

4.4.4 Solr返回结果和排序

​ 返回结果的格式

​ 在Solr中默认支持多种返回结果的格式。分别是XML,JSON,Ruby,Python,Binary Java,PHP,CVS等。下面是Solr支持的响应结果格式以及对应的实现类。
在这里插入图片描述

​ 对于使用者来说,我们只需要指定wt参数即可;

​ 查询所有数据文档返回xml

http://localhost:8080/solr/collection1/select?q=*:*&wt=xml

​ 返回结果文档包含的域

​ 通过fl指定返回的文档包含哪些域。

​ 返回伪域。

​ 将返回结果中价格转化为分。product是Solr中的一个函数,表示乘法。后面我们会讲解。

http://localhost:8080/solr/collection1/select?q=*:*&fl=*,product(book_price,100)

在这里插入图片描述

​ 域指定别名

​ 上面的查询我们多了一个伪域。可以为伪域起别名fen

http://localhost:8080/solr/collection1/select?q=*:*&fl=*,fen:product(book_price,100)

在这里插入图片描述

​ 同理也可以给原有域起别名

http://localhost:8080/solr/collection1/select?q=*:*&fl=name:book_name,price:book_price

在这里插入图片描述

​ 这是返回结果,接下来我们再来说一下排序;

​ 在Solr中我们是通过sort参数进行排序。

http://localhost:8080/solr/collection1/select?q=*:*&sort=book_price+asc&wt=json&rows=50

​ 特殊情况:某些文档book_price域值为null,null值排在最前面还是最后面。

​ 定义域类型的时候需要指定2个属性sortMissingLast,sortMissingFirst

​ sortMissingLast=true,无论升序还是降序,null值都排在最后

​ sortMissingFirst=true,无论升序还是降序,null值都排在最前

<fieldtype name="fieldName" class="xx" sortMissingLast="true" sortMissingFirst="false"/>

4.5 高级查询-facet查询

4.5.1简单介绍

​ facet 是 solr 的高级搜索功能之一 , 可以给用户提供更友好的搜索体验 。

​ 作用:可以根据用户搜索条件 ,按照指定域进行分组并统计,类似于关系型数据库中的group by分组查询;

​ eg:查询title中包含手机的商品,按照品牌进行分组,并且统计数量。

select brand,COUNT(*)from tb_item where title like '%手机%' group by brand

​ 适合场景:在电商网站的搜索页面中,我们根据不同的关键字搜索。对应的品牌列表会跟着变化。

这个功能就可以基于Facet来实现;
在这里插入图片描述
在这里插入图片描述

Facet比较适合的域

​ 一般代表了实体的某种公共属性的域 , 如商品的品牌,商品的分类 , 商品的制造厂家 , 书籍的出版商等等;

Facet域的要求

​ Facet 的域必须被索引 . 一般来说该域无需分词,无需存储 ,无需分词是因为该域的值代表了一个整体概念 , 如商品的品牌 ” 联想 ” 代表了一个整体概念 , 如果拆成 ” 联 ”,” 想 ” 两个字都不具有实际意义 . 另外该字段的值无需进行大小写转换等处理 , 保持其原貌即可 .无需存储是因为查询到的文档中不需要该域的数据 , 而是作为对查询结果进行分组的一种手段 , 用户一般会沿着这个分组进一步深入搜索 .

4.5.2 数据准备

​ 为了更好的学习我们后面的知识,我们将商品表数据导入到Solr索引库;

​ 删除图书相关的数据

<delete>
 <query>*:*</query>
</delete>
<commit/>

​ 创建和商品相关的域

	<field name="item_title" type="text_ik" indexed="true" stored="true"/>   标题域
	<field name="item_price" type="pfloat" indexed="true" stored="true"/>    价格域
	<field name="item_images" type="string" indexed="false" stored="true"/>   图片域
	<field name="item_createtime" type="pdate" indexed="true" stored="true"/> 创建时间域
	<field name="item_updatetime" type="pdate" indexed="true" stored="true"/> 更新时间域
	<field name="item_category" type="string" indexed="true" stored="true"/>  商品分类域
	<field name="item_brand" type="string" indexed="true" stored="true"/>     品牌域

​ 修改solrcore/conf目录中solr-data-config.xml配置文件,使用DataImport进行导入;

<entity name="item" query="select id,title,price,image,create_time,update_time,category,brand from tb_item">
       
            <field column="id" name="id"/>
            <field column="title" name="item_title"/>
            <field column="price" name="item_price"/>
            <field column="image" name="item_images"/>
            <field column="create_time" name="item_createtime"/>
            <field column="update_time" name="item_updatetime"/>
            <field column="category" name="item_category"/>
            <field column="brand" name="item_brand"/>
 </entity>

​ 重启服务,使用DateImport导入;
在这里插入图片描述

测试
在这里插入图片描述

4.5.3 Facet查询的分类

​ facet_queries:表示根据条件进行分组统计查询。

​ facet_fields:代表根据域分组统计查询。

​ facet_ranges:可以根据时间区间和数字区间进行分组统计查询;

​ facet_intervals:可以根据时间区间和数字区间进行分组统计查询;

4.5.4 facet_fields

​ 需求:对item_title中包含手机的文档,按照品牌域进行分组,并且统计数量;

​ 1.进行 Facet 查询需要在请求参数中加入 ”facet=on” 或者 ”facet=true” 只有这样 Facet 组件才起作用

​ 2.分组的字段通过在请求中加入 ”facet.field” 参数加以声明

http://localhost:8080/solr/collection1/select?q=item_title:手机&facet=on&facet.field=item_brand

​ 分组结果
在这里插入图片描述

3.如果需要对多个字段进行 Facet查询 , 那么将该参数声明多次;各个分组结果互不影响eg:还想对分类进行分组统计.

http://localhost:8080/solr/collection1/select?q=item_title:手机&facet=on&facet.field=item_brand&facet.field=item_category

​ 分组结果
在这里插入图片描述
4.其他参数的使用。

​ 在facet中,还提供的其他的一些参数,可以对分组统计的结果进行优化处理;

参数说明
facet.prefix表示 Facet 域值的前缀 . 比如 ”facet.field=item_brand&facet.prefix=中国”, 那么对 item_brand字段进行 Facet 查询 , 只会分组统计以中国为前缀的品牌。
facet.sort表示 Facet 字段值以哪种顺序返回 . 可接受的值为 true|false. true表示按照 count 值从大到小排列 . false表示按照域值的自然顺序( 字母 , 数字的顺序 ) 排列 . 默认情况下为 true.
facet.limit限制 Facet 域值返回的结果条数 . 默认值为 100. 如果此值为负数 , 表示不限制 .
facet.offset返回结果集的偏移量 , 默认为 0. 它与 facet.limit 配合使用可以达到分页的效果
facet.mincount限制了 Facet 最小 count, 默认为 0. 合理设置该参数可以将用户的关注点集中在少数比较热门的领域 .
facet.missing默认为 ””, 如果设置为 true 或者 on, 那么将统计那些该 Facet 字段值为 null 的记录.
facet.method取值为 enum 或 fc, 默认为 fc. 该参数表示了两种 Facet 的算法 , 与执行效率相关 .enum 适用于域值种类较少的情况 , 比如域类型为布尔型 .fc适合于域值种类较多的情况。

[facet.prefix] 分组统计以中国前缀的品牌

&facet=on
&facet.field=item_brand
&facet.prefix=中国

在这里插入图片描述

[facet.sort] 按照品牌值进行字典排序

&facet=on
&facet.field=item_brand
&facet.sort=false

在这里插入图片描述

[facet.limit] 限制分组统计的条数为10

&facet=on
&facet.field=item_brand
&facet.limit=10

在这里插入图片描述

[facet.offset]结合facet.limit对分组结果进行分页

&facet=on
&facet.field=item_brand
&facet.offset=5
&facet.limit=5

在这里插入图片描述

[facet.mincount] 搜索标题中有手机的商品,并且对品牌进行分组统计查询,排除统计数量为0的品牌

q=item_title:手机
&facet=on
&facet.field=item_brand
&facet.mincount=1

在这里插入图片描述

4.5.5 facet_ranges

​ 除了字段分组查询外,还有日期区间,数字区间分组查询。作用:将某一个时间区间(数字区间),按照指定大小分割,统计数量;

需求:分组查询2015年,每一个月添加的商品数量;

​ 参数:

参数说明
facet.range该参数表示需要进行分组的字段名 , 与 facet.field 一样 , 该参数可以被设置多次 , 表示对多个字段进行分组。
facet.range.start起始时间/数字 , 时间的一般格式为 ” 1995-12-31T23:59:59Z”, 另外可以使用 ”NOW”,”YEAR”,”MONTH” 等等 , 具体格式可以参考 org.apache.solr.schema. DateField 的 java doc.
facet.range.end结束时间 数字
facet.range.gap时间间隔 . 如果 start 为 2019-1-1,end 为 2020-1-1.gap 设置为 ”+1MONTH” 表示间隔1 个月 , 那么将会把这段时间划分为 12 个间隔段 . 注意 ”+” 因为是特殊字符所以应该用 ”%2B” 代替 .
facet.range.hardend取值可以为 true|false它表示 gap 迭代到 end 处采用何种处理 . 举例说明 start 为 2019-1-1,end 为 2019-12-25,gap 为 ”+1MONTH”,hardend 为 false 的话最后一个时间段为 2009-12-1 至 2010-1-1;hardend 为 true 的话最后一个时间段为 2009-12-1 至 2009-12-25
举例start为0,end为1200,gap为500,hardend为false,最后一个数字区间[1000,1200] ,hardend为true最后一个数字区间[1000,1500]
facet.range.other取值范围为 before|after|between|none|all, 默认为 none.
before 会对 start 之前的值做统计 .
after 会对 end 之后的值做统计 .
between 会对 start 至 end 之间所有值做统计 . 如果 hardend 为 true 的话 , 那么该值就是各个时间段统计值的和 .
none 表示该项禁用 .
all 表示 before,after,between都会统计 .
facet=on&
facet.range=item_createtime&
facet.range.start=2015-01-01T00:00:00Z&
facet.range.end=2016-01-01T00:00:00Z&
facet.range.gap=%2B1MONTH
&facet.range.other=all

结果
在这里插入图片描述

需求:分组统计价格在01000,10002000,2000~3000... 19000~20000及20000以上每个价格区间商品数量;

facet=on&
facet.range=item_price&
facet.range.start=0&
facet.range.end=20000&
facet.range.gap=1000&
facet.range.hardend=true&
facet.range.other=all

结果:
在这里插入图片描述

4.5.6 facet_queries

​ 在facet中还提供了第三种分组查询的方式facet query。提供了类似 filter query (fq)的语法可以更为灵活的对任意字段进行分组统计查询 .

​ 需求:查询分类是平板电视的商品数量 品牌是华为的商品数量 品牌是三星的商品数量;

facet=on&
facet.query=item_category:平板电视&
facet.query=item_brand:华为&
facet.query=item_brand:三星

​ 测试结果
在这里插入图片描述
​ 我们会发现统计的结果中对应名称tem_category:平板电视和item_brand:华为,我们也可以起别名;

facet=on&
facet.query={!key=平板电视}item_category:平板电视&
facet.query={!key=华为品牌}item_brand:华为&
facet.query={!key=三星品牌}item_brand:三星
{---->%7B   }---->%7D
这样可以让字段名统一起来,方便我们拿到请求数据后,封装成自己的对象;

facet=on&
facet.query=%7B!key=平板电视%7Ditem_category:平板电视&
facet.query=%7B!key=华为品牌%7Ditem_brand:华为&
facet.query=%7B!key=三星品牌%7Ditem_brand:三星

在这里插入图片描述

4.5.7 facet_interval

​ 在Facet中还提供了一种分组查询方式facet_interval。功能类似于facet_ranges。facet_interval通过设置一个区间及域名,可以统计可变区间对应的文档数量;

​ 通过facet_ranges和facet_interval都可以实现范围查询功能,但是底层实现不同,性能也不同.

​ 参数:

参数名说明
facet.interval此参数指定统计查询的域。它可以在同一请求中多次使用以指示多个字段。
facet.interval.set指定区间。如果统计的域有多个,可以通过f.<fieldname>.facet.interval.set语法指定不同域的区间。
区间语法
区间必须以'(' 或 '[' 开头,然后是逗号(','),最终值,最后是 ')' 或']'。
例如:
(1,10) - 将包含大于1且小于10的值
[1,10])- 将包含大于或等于1且小于10的值
[1,10] - 将包含大于等于1且小于等于10的值

​ 需求:统计item_price在[0-10]及[1000-2000]的商品数量和item_createtime在2019年~现在添加的商品数量

&facet=on
&facet.interval=item_price
&f.item_price.facet.interval.set=[0,10]
&f.item_price.facet.interval.set=[1000,2000]
&facet.interval=item_createtime
&f.item_createtime.facet.interval.set=[2019-01-01T0:0:0Z,NOW]
由于有特殊符号需要进行URL编码[---->%5B   ]---->%5D
http://localhost:8080/solr/collection1/select?q=*:*&facet=on
&facet.interval=item_price
&f.item_price.facet.interval.set=%5B0,10%5D
&f.item_price.facet.interval.set=%5B1000,2000%5D
&facet.interval=item_createtime
&f.item_createtime.facet.interval.set=%5B2019-01-01T0:0:0Z,NOW%5D

结果
在这里插入图片描述

4.5.8 facet中其他的用法

​ tag操作符和ex操作符

​ 首先我们来看一下使用场景,当查询使用q或者fq指定查询条件的时候,查询条件的域刚好是facet的域;

分组的结果就会被限制,其他的分组数据就没有意义。

q=item_title:手机
&fq=item_brand:三星 
&facet=on
&facet.field=item_brand导致分组结果中只有三星品牌有数量,其他品牌都没有数量

在这里插入图片描述

如果想查看其他品牌手机的数量。给过滤条件定义标记,在分组统计的时候,忽略过滤条件;

查询文档是2个条件,分组统计只有一个条件

&q=item_title:手机
&fq={!tag=brandTag}item_brand:三星   
&facet=on
&facet.field={!ex=brandTag}item_brand
{---->%7B   }---->%7D

&q=item_title:手机
&fq=%7B!tag=brandTag%7Ditem_brand:三星   
&facet=on
&facet.field=%7B!ex=brandTag%7Ditem_brand

​ 测试结果:
在这里插入图片描述

4.5.9 facet.pivot

​ 多维度分组查询。听起来比较抽象。

​ 举例:统计每一个品牌和其不同分类商品对应的数量;

品牌分类数量
华为手机200
华为电脑300
三星手机200
三星电脑20
三星平板电视200
。。。。。。

​ 这种需求就可以使用维度查询完成。

 &facet=on
 &facet.pivot=item_brand,item_category

测试结果
在这里插入图片描述

4.6 高级查询-group查询

4.6.1 group介绍和入门

​ solr group作用:将具有相同字段值的文档分组,并返回每个组的顶部文档。 Group和Facet的概念很像,都是用来分组,但是分组的结果是不同;

​ group查询相关参数

参数类型说明
group布尔值设为true,表示结果需要分组
group.field字符串需要分组的字段,字段类型需要是StrField或TextField
group.func查询语句可以指定查询函数
group.query查询语句可以指定查询语句
rows整数返回多少组结果,默认10
start整数指定结果开始位置/偏移量
group.limit整数每组返回多数条结果,默认1
group.offset整数指定每组结果开始位置/偏移量
sort排序算法控制各个组的返回顺序
group.sort排序算法控制每一分组内部的顺序
group.formatgrouped/simple设置为simple可以使得结果以单一列表形式返回
group.main布尔值设为true时,结果将主要由第一个字段的分组命令决定
group.ngroups布尔值设为true时,Solr将返回分组数量,默认fasle
group.truncate布尔值设为true时,facet数量将基于group分组中匹相关性高的文档,默认fasle
group.cache.percent整数0-100设为大于0时,表示缓存结果,默认为0。该项对于布尔查询,通配符查询,模糊查询有改善,却会减慢普通词查询。

需求:查询Item_title中包含手机的文档,按照品牌对文档进行分组;

q=item_title:手机
&group=true
&group.field=item_brand
group分组结果和Fact分组查询的结果完全不同,他把同组的文档放在一起,显示该组文档数量,仅仅展示了第一个文档。

在这里插入图片描述

4.6.2 group参数-分页参数

​ 上一节我们讲解了group的入门,接下来我们对group中的参数进行详解。

首先我们先来观察一下入门的分组结果。只返回10个组结果。
在这里插入图片描述

​ [rows]通过rows参数设置返回组的个数。

q=item_title:手机&group=true&group.field=item_brand&rows=13
比上面分组结果多了三个组

在这里插入图片描述

通过start和rows参数结合使用可以对分组结果分页;查询第一页3个分组结果

q=item_title:手机&group=true&group.field=item_brand&start=0&rows=3

在这里插入图片描述
​ 除了可以对分组结果进行分页,可以对组内文档进行分页。默认情况下。只展示该组顶部文档。

需求:展示每组前5个文档。

q=item_title:手机&group=true&group.field=item_brand&start=0&rows=3
&group.limit=5&group.offset=0

在这里插入图片描述

4.6.3 group参数-排序参数

​ 排序参数分为分组排序和组内文档排序;

​ [sort]分组进行排序

​ 需求:按照品牌名称进行字典排序

q=item_title:手机&group=true&group.field=item_brand&start=0&rows=5
&group.limit=5&group.offset=0
&sort=item_brand asc

​ [group.sort]组内文档进行排序

​ 需求:组内文档按照价格降序;

q=item_title:手机&group=true&group.field=item_brand&start=0&rows=5
&group.limit=5&group.offset=0
&group.sort=item_price+desc
4.6.4 group查询结果分组

​ 在group中除了支持根据Field进行分组,还支持查询条件分组。

​ 需求:对item_brand是华为,三星,TCL的三个品牌,分组展示对应的文档信息。

q=item_title:手机
&group=true
&group.query=item_brand:华为
&group.query=item_brand:三星
&group.query=item_brand:TCL

在这里插入图片描述

4.6.5 group 函数分组(了解)

​ 在group查询中除了支持Field分组,Query分组。还支持函数分组。

​ 按照价格范围对商品进行分组。01000属于第1组,10002000属于第二组,否则属于第3组。

q=*:*&
group=true&
group.func=map(item_price,0,1000,1,map(item_price,1000,2000,2,3))
map(x,10,100,1,2) 在函数参数中的x如果落在[10,100)之间,则将x的值映射为1,否则将其值映射为2

在这里插入图片描述

4.6.7 group其他参数

​ group.ngroups:是否统计组数量

q=item_title:手机
&group=true
&group.field=item_brand
&group.ngroups=true

​ group.cache.percent

​ Solr Group查询相比Solr的标准查询来说,速度相对要慢很多。

​ 可以通过group.cache.percent参数来开启group查询缓存。该参数默认值为0.可以设置为0-100之间的数字

该值设置的越大,越占用系统内存。对于布尔类型等分组查询。效果比较明显。

​ group.main

​ 获取每一个分组中相关度最高的文档即顶层文档,构成一个文档列表。

q=item_title:手机
&group.field=item_brand
&group=true
&group.main=true

group.truncate(了解)

​ 按照品牌分组展示相关的商品;

q=*:*&group=true&group.field=item_brand

​ 在次基础上使用facet,按照分类分组统计;统计的结果,基于q中的条件进行的facet分组。

q=*:*&group=true&group.field=item_brand
&facet=true
&facet.field=item_category
如果我们想基于每个分组中匹配度高的文档进行Facet分组统计
q=*:*&group=true&group.field=item_brand
&facet=true
&facet.field=item_category
&group.truncate=true

4.7 高级查询-高亮查询

4.7.1 高亮的概述

​ 高亮显示是指根据关键字搜索文档的时候,显示的页面对关键字给定了特殊样式, 让它显示更加突出,如下面商品搜索中,关键字变成了红色,其实就是给定了红色样 ;
在这里插入图片描述

​ 高亮的本质,是对域中包含关键字前后加标签
在这里插入图片描述

4.7.2 Solr高亮分类和高亮基本使用

​ 上一节我们简单阐述了什么是高亮,接下来我们来讲解Solr中高亮的实现方案;

​ 在Solr中提供了常用的3种高亮的组件(Highlighter)也称为高亮器,来支持高亮查询。

​ Unified Highlighter

​ Unified Highlighter是最新的Highlighter(从Solr6.4开始),它是最性能最突出和最精确的选择。它可以通过插件/扩展来处理特定的需求和其他需求。官方建议使用该Highlighter,即使它不是默认值。

​ Original Highlighter

​ Original Highlighter,有时被称为"Standard Highlighter" or "Default Highlighter",是Solr最初的Highlighter,提供了一些定制选项,曾经一度被选择。它的查询精度足以满足大多数需求,尽管它不如统一的Highlighter完美;

​ FastVector Highlighter

​ FastVector Highlighter特别支持多色高亮显示,一个域中不同的词采用不同的html标签作为前后缀。

​ 接下来我们来说一下Highlighter公共的一些参数,先来认识一下其中重要的一些参数;

参数描述
hltrue|false通过此参数值用来开启或者禁用高亮.默认值是false.如果你想使用高亮,必须设置成true
hl.methodunified|original|fastVector使用哪种哪种高亮组件. 接收的值有: unifiedoriginalfastVector. 默认值是 original.
hl.flfiled1,filed2...指定高亮字段列表.多个字段之间以逗号或空格分开.表示那个字段要参与高亮。默认值为空字符串。
hl.qitem_title:三星高亮查询条件,此参数运行高亮的查询条件和q中的查询条件不同。如果你设置了它, 你需要设置 hl.qparser.默认值和q中的查询条件一致
hl.tag.pre<em>高亮词开头添加的标签,可以是任意字符串,一般设置为HTML标签,默认值为.对于Original Highlighter需要指定hl.simple.pre
hl.tag.post</em>高亮词后面添加的标签,可以是任意字符串,一般设置为HTML标签,默认值</em>.对于Original Highlighter需要指定hl.simple.post
hl.qparserlucene|dismax|edismax用于hl.q查询的查询解析器。仅当hl.q设置时适用。默认值是defType参数的值,而defType参数又默认为lucene。
hl.requireFieldMatchtrue|false默认为fasle. 如果置为true,除非该字段的查询结果不为空才会被高亮。它的默认值是false,意味 着它可能匹配某个字段却高亮一个不同的字段。如果hl.fl使用了通配符,那么就要启用该参数
hl.usePhraseHighlightertrue|false默认true.如果一个查询中含有短语(引号框起来的)那么会保证一定要完全匹配短语的才会被高亮
hl.highlightMultiTermtrue|false默认true.如果设置为true,solr将会高亮出现在多terms查询中的短语。
hl.snippets数值默认为1.指定每个字段生成的高亮字段的最大数量.
hl.fragsize数值每个snippet返回的最大字符数。默认是100.如果为0,那么该字段不会被fragmented且整个字段的值会被返回
hl.encoderhtml如果为空(默认值),则存储的文本将返回,而不使用highlighter执行任何转义/编码。如果设置为html,则将对特殊的html/XML字符进行编码;
hl.maxAnalyzedChars数值默认10000. 搜索高亮的最大字符,对一个大字段使用一个复杂的正则表达式是非常昂贵的。

​ 高亮的入门案例:

​ 查询item_title中包含手机的文档,并且对item_title中的手机关键字进行高亮;

q=item_title:手机
&hl=true
&hl.fl=item_title
&hl.simple.pre=<font>
&hl.simple.post=</font>

在这里插入图片描述

​ 关于高亮最基本的用法先介绍到这里。

4.7.3 Solr高亮中其他的参数介绍

​ [hl.fl]指定高亮的域,表示哪些域要参于高亮;

​ 需求:查询Item_title:手机的文档, Item_title,item_category为高亮域

q=item_title:手机
&hl=true
&hl.fl=item_title,item_category
&hl.simple.pre=<font>
&hl.simple.post=</font>

​ 结果中item_title手机产生了高亮,item_category中手机也产生了高亮.
在这里插入图片描述

​ 这种高亮肯对我们来说是不合理的。可以使用hl.requireFieldMatch参数解决。

[hl.requireFieldMatch]只有符合对应查询条件的域中参数才进行高亮;只有item_title手机才会高亮。

q=item_title:手机
&hl=true
&hl.fl=item_title,item_category
&hl.simple.pre=<font>
&hl.simple.post=</font>
&hl.requireFieldMatch=true

在这里插入图片描述
​ [hl.q] 默认情况下高亮是基于q中的条件参数。 使用fl.q让高亮的条件参数和q的条件参数不一致。比较少见。但是solr提供了这种功能。

​ 需求:查询Item_tile中包含三星的文档,对item_category为手机的进行高亮;

q=item_title:三星
&hl=true
&hl.q=item_category:手机
&hl.fl=item_category
&hl.simple.pre=<em>
&hl.simple.post=</em>

在这里插入图片描述

[hl.highlightMultiTerm]默认为true,如果为true,Solr将对通配符查询进行高亮。如果为false,则根本不会高亮显示它们。

q=item_title:micro* OR item_title:panda
&hl=true
&hl.fl=item_title
&hl.simple.pre=<em>
&hl.simple.post=</em>

在这里插入图片描述

对应基于通配符查询的结果不进行高亮;

q=item_title:micro* OR item_title:panda
&hl=true
&hl.fl=item_title
&hl.simple.pre=<em>
&hl.simple.post=</em>
&hl.highlightMultiTerm=false

[hl.usePhraseHighlighter]为true时,将来我们基于短语进行搜索的时候,短语做为一个整体被高亮。为false时,短语中的每个单词都会被单独高亮。在Solr中短语需要用引号引起来;

q=item_title:"老人手机"
&hl=true
&hl.fl=item_title
&hl.simple.pre=<font>
&hl.simple.post=</font>

当不带双引号时q=item_title:老人手机,会先将老人手机先分词,然后在进行索引查询,如果加上双引号q=item_title:"老人手机",会老人手机以整体在solr库中进行索引查询,不会分词以后进行查询
在这里插入图片描述

关闭hl.usePhraseHighlighter=false;

q=item_title:"老人手机"
&hl=true
&hl.fl=item_title
&hl.simple.pre=<font>
&hl.simple.post=</font>
&hl.usePhraseHighlighter=false;

在这里插入图片描述

4.7.4 Highlighter的切换

​ 之前我们已经讲解完毕Highlighter中的通用参数,所有Highlighter都有的。接下来我们要讲解的是Highlighter的切换和特有参数。

​ 如何切换到unified (hl.method=unified),切换完毕后,其实我们是看不出他和original有什么区别。因为unified Highlighter比original Highlighter只是性能提高。精确度提高。

​ 区别不是很大,unified 相比original 的粒度更细;

q=item_title:三星平板电视
&hl=true
&hl.fl=item_title
&hl.simple.pre=<font>
&hl.simple.post=</font>
&hl.method=unified

在这里插入图片描述

​ 如何切换到fastVector

​ 优势:使用fastVector最大的好处,就是可以为域中不同的词使用不同的颜色,第一个词使用黄色,第二个使用红色。而且fastVector和orignal可以混合使用,不同的域使用不同的Highlighter;

​ 使用fastVector的要求:使用FastVector Highlighter需要在高亮域上设置三个属性(termVectors、termPositions和termOffset);

​ 需求:查询item_title中包含三星平板电视,item_category是平板电视的文档。

​ item_title中的高亮词要显示不同的颜色。

<field name="item_title" type="text_ik" indexed="true" stored="true" termVectors="true" termPositions="true" termOffsets="true" />
重启Solr,重写生成索引;
<delete>
 <query>*:*</query>
</delete>
<commit/>

修改solrcofig.xml配置文件指定fastVector高亮器的前后缀

官方给的一个例子
      <fragmentsBuilder name="colored"
                        class="solr.highlight.ScoreOrderFragmentsBuilder">
        <lst name="defaults">
          <str name="hl.tag.pre"><![CDATA[
               <b style="background:yellow">,<b style="background:lawgreen">,
               <b style="background:aquamarine">,<b style="background:magenta">,
               <b style="background:palegreen">,<b style="background:coral">,
               <b style="background:wheat">,<b style="background:khaki">,
               <b style="background:lime">,<b style="background:deepskyblue">]]></str>
          <str name="hl.tag.post"><![CDATA[</b>]]></str>

        </lst>
      </fragmentsBuilder>

需要请求处理器中配置使用colored这一套fastVector的前后缀;

   <str name="hl.fragmentsBuilder">colored</str>
q=item_title:三星平板电视 AND item_category:平板电视
&hl=true
&hl.fl=item_title,item_category
&hl.method=original
&f.item_title.hl.method=fastVector

测试
在这里插入图片描述

到这关于Highlighter的切换我们就讲解完毕了。不同的Highlighter也有自己特有的参数,这些参数大多是性能参数。大家可以参考官方文档。http://lucene.apache.org/solr/guide/8_1/highlighting.html#the-fastvector-highlighter

4.8 Solr Query Suggest

4.8.1 Solr Query Suggest简介

​ Solr从1.4开始便提供了Query Suggest,Query Suggest目前是各大搜索应用的标配,主要作用是避免用户输入错误的搜索词,同时将用户引导到相应的关键词上进行搜索。Solr内置了Query Suggest的功能,它在Solr里叫做Suggest模块. 使用该模块.我们通常可以实现2种功能。拼写检查(Spell-Checking),再一个就是自动建议(AutoSuggest)。

​ 什么是拼写检查(Spell-Checking)

​ 比如说用户搜索的是sorl,搜索应用可能会提示你,你是不是想搜索solr.百度就有这个功能。
在这里插入图片描述

​ 这就是拼写检查(Spell Checking)的功能。

​ 什么又是自动建议(AutoSuggest)

​ AutoSuggest是指用户在搜索的时候,给用户一些提示建议,可以帮助用户快速构建自己的查询。

比如我们搜索java.
在这里插入图片描述

关于Query Suggest的简介,我们先暂时说道这里。

4.8.2 Spell-Checking的使用

​ 要想使用Spell-Checking首先要做的事情,就是在SolrConfig.xml文件中配置SpellCheckComponent;并且要指定拼接检查器,Solr一共提供了4种拼写检查器IndexBasedSpellChecker,DirectSolrSpellChecker,FileBasedSpellChecker和WordBreakSolrSpellChecker。这4种拼写检查的组件,我们通常使用的都是第二种。

​ IndexBasedSpellChecker(了解)

​ IndexBasedSpellChecker 使用 Solr 索引作为拼写检查的基础。它要求定义一个域作为拼接检查term的基础域。

​ 需求:对item_title域,item_brand域,item_category域进行拼接检查。

  1. 定义一个域作为拼接检查term 的基础域item_title_spell,将item_title,item_brand,item_category域复制到item_title_spell;

       <field name="item_title_spell" type="text_ik" indexed="true" stored="false" multiValued="true"/>
        <copyField source="item_title" dest="item_title_spell"/>
        <copyField source="item_brand" dest="item_title_spell"/>
        <copyField source="item_category" dest="item_title_spell"/>
    

    2.在SolrConfig.xml中配置IndexBasedSpellChecker作为拼写检查的组件

<searchComponent name="indexspellcheck" class="solr.SpellCheckComponent">
  <lst name="spellchecker">
    <str name="classname">solr.IndexBasedSpellChecker</str> <!--组件的实现类-->
    <str name="spellcheckIndexDir">./spellchecker</str> <!--拼写检查索引生成目录-->
    <str name="field">item_title_spell</str> <!--拼写检查的域-->
    <str name="buildOnCommit">true</str><!--是否每次提交文档的时候,都生成拼写检查的索引-->
    <!-- optional elements with defaults
    <str name="distanceMeasure">org.apache.lucene.search.spell.LevensteinDistance</str>
    <str name="accuracy">0.5</str>
    -->
 </lst>
</searchComponent>

​ 3.在请求处理器中配置拼写检查的组件;

<arr name="last-components">
	<str>indexspellcheck</str>
</arr>
 q=item_title:meta
 &spellcheck=true

在这里插入图片描述

DirectSolrSpellChecker

​ 需求:基于item_title域,item_brand域,item_category域进行搜索的时候,需要进行拼接检查。

​ 使用流程:

​ 首先要定义一个词典域,拼写检查是需要根据错误词--->正确的词,正确的词构成的域;

​ 1.由于我们在进行拼写检查的时候,可能会有中文,所以需要首先创建一个词典域的域类型。

<!-- 说明 这里spell 单独定义一个类型 是为了屏蔽搜索分词对中文拼写检测的影响,分词后查询结果过多不会给出建议词 -->
	<fieldType name="spell_text_ik" class="solr.TextField">
		<analyzer type="index" class="org.wltea.analyzer.lucene.IKAnalyzer"/>
		<analyzer type="query">
			  <tokenizer class="solr.WhitespaceTokenizerFactory"/>
		</analyzer>
	</fieldType>

​ 2.定义一个词典的域,将需要进行拼写检查的域复制到该域;

	<field name="spell" type="spell_text_ik" multiValued="true" indexed="true" stored="false"/>
   <copyField source="item_title" dest="spell"/>
   <copyField source="item_brand" dest="spell"/>
   <copyField source="item_category" dest="spell"/>

​ 3.修改SolrConfig.xml中的配置文件中的拼写检查器组件。指定拼写词典域的类型,词典域

在这里插入图片描述

​ 4.在Request Handler中配置拼写检查组件

<arr name="last-components">
	<str>spellcheck</str>
</arr>

​ 5.重启,重新创建索引。

测试,spellcheck=true参数表示是否开启拼写检查,搜索内容一定大于等于4个字符。

http://localhost:8080/solr/collection1/select?q=item_title:iphono&spellcheck=true
http://localhost:8080/solr/collection1/select?q=item_title:galax&spellcheck=true
http://localhost:8080/solr/collection1/select?q=item_brand:中国移走&spellcheck=true

在这里插入图片描述

到这关于DirectSolrSpellChecker我们就讲解完毕了。

FileBasedSpellChecker

​ 使用外部文件作为拼写检查字的词典,基于词典中的词进行拼写检查。

​ 1.在solrconfig.xml中配置拼写检查的组件。

<searchComponent name="fileChecker" class="solr.SpellCheckComponent">
  <lst name="spellchecker">
    <str name="classname">solr.FileBasedSpellChecker</str>
    <str name="name">fileChecker</str>
    <str name="sourceLocation">spellings.txt</str>
    <str name="characterEncoding">UTF-8</str>
    <!-- optional elements with defaults-->
    <str name="distanceMeasure">org.apache.lucene.search.spell.LevensteinDistance</str>
    
      <!--精确度,介于0和1之间的值。值越大,匹配的结果数越少。假设对 helle进行拼写检查,
         如果此值设置够小的话," hellcat","hello"都会被认为是"helle"的正确的拼写。如果此值设置够大,则"hello"会被认为是正确的拼写 -->
    <str name="accuracy">0.5</str>
    
  <!--表示最多有几个字母变动。比如:对"manag"进行拼写检查,则会找到"manager"做为正确的拼写检查;如果对"mana"进行拼写检查,因为"mana"到"manager",需有3个字母的变动,所以"manager"会被遗弃  -->
    <int name="maxEdits">2</int>
  
	<!-- 最小的前辍数。如设置为1,意思是指第一个字母不能错。比如:输入"cinner",虽然和"dinner"只有一个字母的编辑距离,但是变动的是第一个字母,所以"dinner"不是"cinner"的正确拼写 -->
    <int name="minPrefix">1</int>
   <!-- 进行拼写检查所需要的最小的字符数。此处设置为4,表示如果只输入了3个以下字符,则不会进行拼写检查(3个以下的字符用户基本) -->
    <int name="minQueryLength">4</int>
  
  
  <!-- 被推荐的词在文档中出现的最小频率。整数表示在文档中出现的次数,百分比数表示有百分之多少的文档出现了该推荐词-->
    <float name="maxQueryFrequency">0.01</float>
 </lst>
</searchComponent>

​ 2.在SolrCore/conf目录下,创建一个拼写检查词典文件,该文件已经存在。

在这里插入图片描述

3.加入拼写检查的词;

pizza
history
传智播客

4.在请求处理器中配置拼写检查组件

<arr name="last-components">
      <str>fileChecker</str>
 </arr>
http://localhost:8080/solr/collection1/select?q=item_title:pizzaaa&spellcheck=true&spellcheck.dictionary=fileChecker&spellcheck.build=true

补充:spellcheck.dictionary参数值对应下面参数的name的值,如果name的值为default,spellcheck.dictionary参数可以不写
在这里插入图片描述

在这里插入图片描述

WordBreakSolrSpellChecker

​ WordBreakSolrSpellChecker可以通过组合相邻的查询词/或将查询词分解成多个词来提供建议。是对SpellCheckComponent增强,通常WordBreakSolrSpellChecker拼写检查器可以配合一个传统的检查器(即:DirectSolrSpellChecker)。

    <lst name="spellchecker">
      <str name="name">wordbreak</str>
      <str name="classname">solr.WordBreakSolrSpellChecker</str>
      <str name="field">sepll</str>
      <str name="combineWords">true</str>
      <str name="breakWords">true</str>
      <int name="maxChanges">10</int>
    </lst>

在这里插入图片描述

http://localhost:8080/solr/collection1/select?
q=item_title:ip hone&spellcheck=true
&spellcheck.dictionary=default&spellcheck.dictionary=wordbreak
4.8.3 拼写检查相关的参数
参数描述
spellchecktrue|false此参数为请求打开拼写检查建议。如果true,则会生成拼写建议。如果需要拼写检查,则这是必需的。
spellcheck.buildtrue|false如果设置为true,则此参数将创建用于拼写检查的字典。在典型的搜索应用程序中,您需要在使用拼写检查之前构建字典。但是,并不总是需要先建立一个字典。例如,您可以将拼写检查器配置为使用已存在的字典。
spellcheck.count数值此参数指定拼写检查器应为某个术语返回的最大建议数。如果未设置此参数,则该值默认为1。如果参数已设置但未分配一个数字,则该值默认为5。如果参数设置为正整数,则该数字将成为拼写检查程序返回的建议的最大数量。
spellcheck.dictionary指定要使用的拼写检查器默认值defualt

其他参数参考http://lucene.apache.org/solr/guide/8_1/spell-checking.html

4.8.4 AutoSuggest 入门

​ AutoSuggest 是搜索应用一个方便的功能,对用户输入的关键字进行预测和建议,减少了用户的输入。首先我们先来讲解一下suggest的入门流程;

​ 1.在solrconfig.xml中配置suggsetComponent,并且要在组件中配置自动建议器,配置文件已经配置过。

<searchComponent name="suggest" class="solr.SuggestComponent">
  <lst name="suggester">
    <str name="name">mySuggester</str> 自动建议器名称,需要在查询的时候指定
    <str name="lookupImpl">FuzzyLookupFactory</str>  查找器工厂类,确定如何从字典索引中查找词
    <str name="dictionaryImpl">DocumentDictionaryFactory</str>决定如何将词存入到索引。
    <str name="field">item_title</str>  词典域。一般指定查询的域/或者复制域。
    <str name="weightField">item_price</str> 根据哪个域进行查询结果文档的排序  
    <str name="suggestAnalyzerFieldType">text_ik</str> 自动建议分析器的域类型,自动建议的参数中可能包括中文,所以选用ik
  </lst>
</searchComponent>

​ 2.修改组件参数

​ 3.在请求处理器中配置suggest组件,sugges名称要和SearchComponent的name一致。

   <arr name="components">
      <str>suggest</str>
   </arr>
   补充:在solrConfig.xml中已经定义了一个请求处理器,并且已经配置了查询建议组件
<requestHandler name="/suggest" class="solr.SearchHandler" startup="lazy">
    <lst name="defaults">
      <str name="suggest">true</str>
      <str name="suggest.count">10</str>
    </lst>
    <arr name="components">
      <str>suggest</str>
    </arr>
  </requestHandler>
   

​ 4.执行查询,指定suggest参数

http://localhost:8080/solr/collection1/suggest?q=三星
http://localhost:8080/solr/collection1/select?q=三星&suggest=true&suggest.dictionary=mySuggester

​ 5.结果

在这里插入图片描述

4.8.5 AutoSuggest中相关的参数。
<searchComponent name="suggest" class="solr.SuggestComponent">
  <lst name="suggester">
   <str name="name">mySuggester</str> 自动建议器名称,需要在查询的时候指定
   <str name="field">item_title</str>  表示自动建议的内容是基于哪个域的
   <str name="dictionaryImpl">DocumentDictionaryFactory</str>词典实现类,表示分词(Term)是如何存储Suggestion字典的。 DocumentDictionaryFactory一个基于词,权重,创建词典;
   <str name="lookupImpl">FuzzyLookupFactory</str>  查询实现类,表示如何在Suggestion词典中找到分词(Term)
    <str name="weightField">item_price</str>   权重域。
    <str name="suggestAnalyzerFieldType">text_ik</str> 域类型
    <str name="buildOnCommit">true</str>
  </lst>
</searchComponent>
4.8.6 AutoSuggest查询相关参数
http://localhost:8080/solr/collection1/select?q=三星&suggest=true&suggest.dictionary=mySuggester
参数描述
suggesttrue|false是否开启suggest
suggest.dictionary字符串指定使用的suggest器名称
suggest.count数值默认值是1
suggest.buildtrue|false如果设置为true,这个请求会导致重建suggest索引。这个字段一般用于初始化的操作中,在线上环境,一般不会每个请求都重建索引,如果线上你希望保持字典最新,最好使用buildOnCommit或者buildOnOptimize来操作。
4.8.7 其他lookupImpl,dictionaryImpl.

​ 其他的实现大家可以参考。

​ http://lucene.apache.org/solr/guide/8_1/suggester.html

 

5.SolrJ

5.1 SolrJ的引入

​ 使用java如何操作Solr;

​ 第一种方式Rest API

在这里插入图片描述

​ 使用Rest API的好处,我们只需要通过Http协议连接到Solr,即可完成全文检索的功能。

​ 在java中可以发送Http请求的工具包比较多。

​ Apache:HttpClient

​ Square: OKHttpClient

​ JDK : URLConnection

​ Spring: 以上的API提供的发送Http请求的方式都是不同,所以Spring提供了一个模板RestTemplate,可以对以上Http请求工具类进行封装从而统一API。

​ 演示:

​ 通过RestTemplate发送http请求,请求Solr服务完成查询item_title:手机的文档。并且需要完成结果封装。

​ 开发步骤:

​ 1.创建服务,引入spring-boot-stater-web的依赖(包含了RestTemplate)

	<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.10.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>

​ 2.编写启动类和yml配置文件

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

​ 3.将RestTemplate交由spring管理

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate(); //底层封装URLConnection
    }

​ 4. 使用RestTemplate,向Solr发送请求,获取结果并进行解析。

@Test
    public void test01() {
        //定义请求的url
        String url = "http://localhost:8080/solr/collection1/select?q=item_title:手机";
        //发送请求,指定响应结果类型,由于响应的结果是JSON,指定Pojo/Map
        ResponseEntity<Map> resp = restTemplate.getForEntity(url, Map.class);
        //获取响应体结果
        Map<String,Object> body = resp.getBody();
        //获取Map中--->response
        Map<String,Object> response = (Map<String, Object>) body.get("response");
        //获取总记录数
        System.out.println(response.get("numFound"));
        //获取起始下标
        System.out.println(response.get("start"));
        //获取文档
        List<Map<String,Object>> docs = (List<Map<String, Object>>) response.get("docs");
        for (Map<String, Object> doc : docs) {
            System.out.println(doc.get("id"));
            System.out.println(doc.get("item_title"));
            System.out.println(doc.get("item_price"));
            System.out.println(doc.get("item_image"));
            System.out.println("=====================");
        }
    }

​ 到这使用Solr RestAPI的方式我们就讲解完毕了。

5.2 SolrJ 介绍

​ Solr官方就推出了一套专门操作Solr的java API,叫SolrJ。

​ 使用SolrJ操作Solr会比利用RestTemplate来操作Solr要简单。SolrJ底层还是通过使用httpClient中的方法来完成Solr的操作.

​ SolrJ核心的API

​ SolrClient

​ HttpSolrClient:适合于单节点的情况下和Solr进行交互。

​ CloudSolrClient:适合于集群的情况下和Solr进行交互。

​ 由于当前我们Solr未搭建集群,所以我们使用HttpSolrClient即可。要想使用SolrJ,需要在项目中引入SorlJ的依赖,建议Solr的版本一致.

<dependency>
    <groupId>org.apache.solr</groupId>
    <artifactId>solr-solrj</artifactId>
    <version>7.7.2</version>
</dependency>

​ 将HttpSolrClient交由spring管理。

​ 1.在yml配置文件中配置Solr服务· `

url: http://localhost:8080/solr/collection1

​ 2.在启动类中配置HttpSolrClient

    @Value("${url}")
    private String url;
    @Bean
    public HttpSolrClient httpSolrClient() {
        HttpSolrClient.Builder builder = new HttpSolrClient.Builder(url);
        return builder.build();
    }

准备工作到此就准备完毕。

5.3 HttpSolrClient

5.3.1 索引
5.3.1.1添加

​ 需求:添加一个图书文档。

​ 添加有很多重载方法,SolrJ中支持添加一个文档,也支持一次添加文档集合。

@Test
    public void testAddDocument() throws IOException, SolrServerException {
        //创建文档
        SolrInputDocument document = new SolrInputDocument();
        //指定文档中的域
        document.setField("id", "889922");
        document.setField("item_title","华为 Meta30 高清手机");
        document.setField("item_price", 20);
        document.setField("item_images", "21312312.jpg");
        document.setField("item_createtime", new Date());
        document.setField("item_updatetime", new Date());
        document.setField("item_category", "手机");
        document.setField("item_brand","华为");
        //添加文档
        httpSolrClient.add(document);
        httpSolrClient.commit();
    }
5.3.1.2 修改

​ 如果文档id相同就是修改;

    @Test
    public void testUpdateDocument() throws IOException, SolrServerException {

        //创建文档
        SolrInputDocument document = new SolrInputDocument();
        //指定文档中的域
        document.setField("id", "889922");
        document.setField("item_title","SolrJ是Solr提供的操作Solr的javaAPI,挺好用");
        document.setField("item_price", 20);
        document.setField("item_images", "21312312.jpg");
        document.setField("item_createtime", new Date());
        document.setField("item_updatetime", new Date());
        document.setField("item_category", "手机");
        document.setField("item_brand","华为");
        //添加文档
        httpSolrClient.add(document);
        httpSolrClient.commit();
    }
5.3.1.3 删除

​ 支持基于id删除,支持基于条件删除。

​ 基于id删除

   @Test
    public void testDeleteDocument() throws IOException, SolrServerException {
        httpSolrClient.deleteById("889922");
        httpSolrClient.commit();
    }

​ 支持基于条件删除,删除所有数据要慎重

    @Test
    public void testDeleteQuery() throws IOException, SolrServerException {
        httpSolrClient.deleteByQuery("book_name:java"); //*:*删除所有
        httpSolrClient.commit();
    }

到这关于使用SolrJ完成索引相关的操讲解完毕。下面讲解查询。

5.3.2 基本查询
5.3.2.1 主查询+过滤查询

​ 查询的操作分为很多种下面我们讲解基本查询。

​ 核心的API方法:

​ solrClient.query(SolrParams);

​ SolrParams是一个抽象类,通常使用其子类SolrQuery封装查询条件;

​ 查询item_title中包含手机的商品

    @Test
    public void testBaseQuery() throws IOException, SolrServerException {
        SolrQuery solrQuery=new SolrQuery();
        //封装查询条件
        //solrQuery.set("q","item_title:手机");
        //solrQuery.setQuery与solrQuery.set("q","item_title:手机")等价
        solrQuery.setQuery("item_title:手机");
        QueryResponse response = httpSolrClient.query(solrQuery);
        //获取满足条件文档
        SolrDocumentList solrDocumentList = response.getResults();
        solrDocumentList.stream().forEach(System.out::println);

        //获取满足条件文档数量
        System.out.println(solrDocumentList.getNumFound());
    }

​ 这是关于我们这一块核心API,接下来我们在这个基础上。我们做一些其他操作。添加过滤条件:品牌是华为。

价格在[1000-2000].

​ 注意:过滤条件可以添加多个,所以使用的是SolrQuery的add方法。如果使用set后面过滤条件会将前面的覆盖.

     @Test
    public void testBaseQuery() throws IOException, SolrServerException {
        SolrQuery solrQuery=new SolrQuery();
        //封装查询条件
        //solrQuery.set("q","item_title:手机");
        //solrQuery.setQuery与olrQuery.set("q","item_title:手机")等价
        solrQuery.setQuery("item_title:手机");
        solrQuery.setFilterQueries("item_brand:华为");
        solrQuery.setFilterQueries("item_price:[1000 TO 2000]");
        QueryResponse response = httpSolrClient.query(solrQuery);
        //获取满足条件文档
        SolrDocumentList solrDocumentList = response.getResults();
        solrDocumentList.stream().forEach(System.out::println);

        //获取满足条件文档数量
        System.out.println(solrDocumentList.getNumFound());
    }
5.3.2.1 分页

​ 接下来我们要完成的是分页,需求:在以上查询的条件查询,查询第1页的20条数据。

solrQuery.setStart((1-1)*20);
solrQuery.setRows(20);
5.3.2.1 排序

​ 除了分页外,还有排序。需求:按照价格升序。如果价格相同,按照id降序。

solrQuery.addSort("item_price", SolrQuery.ORDER.desc);
solrQuery.addSort("id",SolrQuery.ORDER.asc);
5.3.2.2 域名起别名

​ 到这基本查询就基本讲解完毕。有时候我们需要对查询结果文档的字段起别名。

​ 需求:将域名中的item_去掉。

 //指定查询结果的字段列表,并且指定别名
 solrQuery.setFields("id,price:item_price,title:item_title,brand:item_brand,category:item_category,image:item_image");

​ 别名指定完毕后,便于我们后期进行封装;到这关于基本查询讲解完毕,下面讲解组合查询。

5.3.3 组合查询

​ 需求:查询Item_title中包含手机或者平板电视的文档。

solrQuery.setQuery("q","item_title:手机 OR item_title:平板电视");

​ 需求:查询Item_title中包含手机 并且包含三星的文档

solrQuery.setQuery("item_title:手机 AND item_title:三星");
solrQuery.setQuery("+item_title:手机  +item_title:三星");

​ 需求: 查询item_title中包含手机但是不包含三星的文档

solrQuery.setQuery("item_title:手机  NOT item_title:三星");
solrQuery.setQuery("+item_title:手机  -item_title:三星");

​ 需求:查询item_title中包含iphone开头的词的文档,使用通配符

solrQuery.setQuery("item_title:iphone*");

到这关于SolrJ中组合查询我们就讲解完毕了。

1.1 facet查询

​ 之前我们讲解Facet查询,我们说他是分为4类。

​ Field,Query,Range(时间范围,数字范围),Interval(和Range类似)

1.1.2 基于Field的Facet查询

​ 需求:对item_title中包含手机的文档,按照品牌域进行分组,并且统计数量;

http://localhost:8080/solr/collection1/select?q=item_title:手机&facet=on&facet.field=item_brand&facet.mincount=1
    @Test
    public void testFacetFieldQuery() throws IOException, SolrServerException {
        //http://localhost:8080/solr/collection1/select?q=item_title:手机&facet=on&facet.field=item_brand&facet.mincount=1
        SolrQuery query=new SolrQuery();
        //查询条件
        query.setQuery("item_title:手机");
        //Facet相关参数
        query.setFacet(true);
        query.addFacetField("item_brand");
        query.setFacetMinCount(1);
        QueryResponse response = httpSolrClient.query(query);
        FacetField facetField = response.getFacetField("item_brand");
        List<FacetField.Count> valueList = facetField.getValues();
        valueList.stream().forEach(System.out::println);
    }
1.1.3 基于Query的Facet查询

​ 需求:查询分类是平板电视的商品数量 ,品牌是华为的商品数量 ,品牌是三星的商品数量,价格在1000-2000的商品数量;

    http://localhost:8080/solr/collection1/select?
    q=*:*&
    facet=on&
    facet.query=item_category:平板电视&
    facet.query=item_brand:华为&
    facet.query=item_brand:三星&
    facet.query=item_price:%5B1000 TO 2000%5D
    @Test
    public void testFacetFieldQuery()throws IOException, SolrServerException {
        //http://localhost:8080/solr/collection1/select?
        //q=*:*&
        //facet=on&
        //facet.query=item_category:平板电视&
        //facet.query=item_brand:华为&
        //facet.query=item_brand:三星&
        //facet.query=item_price:%5B1000 TO 2000%5D
        SolrQuery query=new SolrQuery();
        //查询条件
        query.setQuery("*:*");
        //Facet相关参数
        query.setFacet(true);
        //query.addFacetQuery("item_category:平板电视");
        //query.addFacetQuery("item_brand:华为");
        //query.addFacetQuery("item_brand:三星");
        //query.addFacetQuery("item_price:[1000 TO 2000]");
        //起别名
        query.addFacetQuery("{!key=平板电视}item_category:平板电视");
        query.addFacetQuery("{!key=华为品牌}item_brand:华为");
        query.addFacetQuery("{!key=三星品牌}item_brand:三星");
        query.addFacetQuery("{!key=1000到2000}item_price:[1000 TO 2000]");
        QueryResponse response = httpSolrClient.query(query);
        Map<String, Integer> facetQuery = response.getFacetQuery();
        for (String key : facetQuery.keySet()) {
            System.out.println(key+"--"+facetQuery.get(key));
        }
    }
1.1.4 基于Range的Facet查询

​ 需求:分组查询价格0-2000 ,2000-4000,4000-6000....18000-20000每个区间商品数量

q=*:*&
facet=on&
facet.range=item_price&
facet.range.start=0&
facet.range.end=20000&
facet.range.gap=2000
    @Test
    public void testFacetFieldQuery()throws IOException, SolrServerException {
        //http://localhost:8080/solr/collection1/select?
        //q=*:*&
        //facet=on&
        //facet.range=item_price&
        //facet.range.start=0&
        //facet.range.end=20000&
        //facet.range.gap=2000
        SolrQuery query=new SolrQuery();
        //查询条件
        query.setQuery("*:*");
        //Facet相关参数
        query.setFacet(true);
        query.addNumericRangeFacet("item_price",0,20000,2000);
        QueryResponse response = httpSolrClient.query(query);
        List<RangeFacet> facetRanges = response.getFacetRanges();
        for (RangeFacet facetRange : facetRanges) {
            System.out.println(facetRange.getName());
            List<RangeFacet.Count> counts = facetRange.getCounts();
            for (RangeFacet.Count count : counts) {
                System.out.println(count.getValue()+"---"+count.getCount());
            }
        }
    }

需求:统计2015年每个季度添加的商品数量

http://localhost:8080/solr/collection1/select?
q=*:*&
facet=on&
facet.range=item_createtime&
facet.range.start=2015-01-01T00:00:00Z&
facet.range.end=2016-01-01T00:00:00Z&
facet.range.gap=%2B3MONTH

    @Test
    public void testFacetFieldQuery() throws IOException, SolrServerException, ParseException {
        //http://localhost:8080/solr/collection1/select?
        //q=*:*&
        //facet=on&
        //facet.range=item_createtime&
        //facet.range.start=2015-01-01T00:00:00Z&
        //facet.range.end=2016-01-01T00:00:00Z&
        //facet.range.gap=%2B3MONTH
        SolrQuery query=new SolrQuery();
        //查询条件
        query.setQuery("*:*");
        //Facet相关参数
        query.setFacet(true);
        Date start =  new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2015-01-01 00:00:00");
        Date end = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2016-01-01 00:00:00");
        query.addDateRangeFacet("item_createtime",start,end,"+3MONTH");
        QueryResponse response = httpSolrClient.query(query);
        List<RangeFacet> facetRanges = response.getFacetRanges();
        for (RangeFacet facetRange : facetRanges) {
            System.out.println(facetRange.getName());
            List<RangeFacet.Count> counts = facetRange.getCounts();
            for (RangeFacet.Count count : counts) {
                System.out.println(count.getValue()+"---"+count.getCount());
            }
        }
    }
1.1.5 基于Interval的Facet查询

​ 需求:统计item_price在0-1000和0-100商品数量和item_createtime是2019年~现在添加的商品数量

&facet=on
&facet.interval=item_price
&f.item_price.facet.interval.set=[0,1000]
&f.item_price.facet.interval.set=[0,100]
&facet.interval=item_createtime
&f.item_createtime.facet.interval.set=[2019-01-01T0:0:0Z,NOW]
由于有特殊符号需要进行URL编码[---->%5B   ]---->%5D
http://localhost:8080/solr/collection1/select?q=*:*&facet=on&facet.interval=item_price&f.item_price.facet.interval.set=%5B0,1000%5D&f.item_price.facet.interval.set=%5B0,100%5D&facet.interval=item_createtime&f.item_createtime.facet.interval.set=%5B2019-01-01T0:0:0Z,NOW%5D
    @Test
    public void testFacetFieldQuery() throws IOException, SolrServerException, ParseException {
        //&facet=on
        //&facet.interval=item_price
        //&f.item_price.facet.interval.set=[0,1000]
        //&f.item_price.facet.interval.set=[0,100]
        //&facet.interval=item_createtime
        //&f.item_createtime.facet.interval.set=[2019-01-01T0:0:0Z,NOW]
        SolrQuery query=new SolrQuery();
        //查询条件
        query.setQuery("*:*");
        //Facet相关参数
        query.setFacet(true);
        query.addIntervalFacets("item_price", new String[]{"[0,10]"});
        query.addIntervalFacets("item_createtime", new String[]{"[2019-01-01T0:0:0Z,NOW]"});
        QueryResponse response = httpSolrClient.query(query);

        /*
            item_price: {
            [0,10]: 11
            },
            item_createtime: {
            [2019-01-01T0:0:0Z,NOW]: 22
            }
         */
        List<IntervalFacet> intervalFacets = response.getIntervalFacets();
        for (IntervalFacet intervalFacet : intervalFacets) {
            String field = intervalFacet.getField();
            System.out.println(field);
            List<IntervalFacet.Count> intervals = intervalFacet.getIntervals();
            for (IntervalFacet.Count interval : intervals) {
                System.out.println(interval.getKey());
                System.out.println(interval.getCount());
            }

        }
    }
1.1.6 Facet维度查询

​ 需求:统计每一个品牌和其不同分类商品对应的数量;

​ 联想 手机 10

​ 联想 电脑 2

​ 华为 手机 10

​ ...

 http://localhost:8080/solr/collection1/select?q=*:*&
 &facet=on
 &facet.pivot=item_brand,item_category
    @Test
    public void testFacetFieldQuery() throws IOException, SolrServerException, ParseException {
        // http://localhost:8080/solr/collection1/select?q=*:*&
        // &facet=on
        // &facet.pivot=item_brand,item_category
        SolrQuery query=new SolrQuery();
        //查询条件
        query.setQuery("*:*");
        //Facet相关参数
        query.setFacet(true);
        query.addFacetPivotField("item_brand,item_category");

        QueryResponse response = httpSolrClient.query(query);

        NamedList<List<PivotField>> facetPivot = response.getFacetPivot();
        List<PivotField> pivotFields = facetPivot.get("item_brand,item_category");
        for (PivotField pivotField : pivotFields) {
            String field = pivotField.getField();
            Object value = pivotField.getValue();
            int count = pivotField.getCount();
            System.out.println(field+"-"+value+"-"+count);

            List<PivotField> fieldList = pivotField.getPivot();
            for (PivotField pivotField1 : fieldList) {
                String field1 = pivotField1.getField();
                Object value1 = pivotField1.getValue();
                int count1 = pivotField1.getCount();
                System.out.println(field1+"-"+value1+"-"+count1);
            }
        }
    }

​ 到这关于SolrJ和Facet查询相关的操作就讲解完毕。

1.2 group查询
1.2.1 基础的分组

​ 需求:查询Item_title中包含手机的文档,按照品牌对文档进行分组;同组中的文档放在一起。

http://localhost:8080/solr/collection1/select?
q=item_title:手机
&group=true
&group.field=item_brand
@Test
    public void testGroupQuery() throws IOException, SolrServerException, ParseException {
        SolrQuery params = new SolrQuery();
        params.setQuery("item_title:手机");

        /**
         * q=item_title:手机
         * &group=true
         * &group.field=item_brand
         */
        //注意solrJ中每没有提供分组特有API。需要使用set方法完成
        params.setGetFieldStatistics(true);
        params.set(GroupParams.GROUP, true);
        params.set(GroupParams.GROUP_FIELD,"item_brand");

        QueryResponse response = httpSolrClient.query(params);
        GroupResponse groupResponse = response.getGroupResponse();
        //由于分组的字段可以是多个。所以返回数组
        List<GroupCommand> values = groupResponse.getValues();
        //获取品牌分组结果
        GroupCommand groupCommand = values.get(0);
        //匹配到的文档数量
        int matches = groupCommand.getMatches();
        System.out.println(matches);
        //每个组合每个组中的文档信息
        List<Group> groups = groupCommand.getValues();
        for (Group group : groups) {
            //分组名称
            System.out.println(group.getGroupValue());
            //组内文档
            SolrDocumentList result = group.getResult();
            System.out.println(group.getGroupValue() +":文档个数" + result.getNumFound());
            for (SolrDocument entries : result) {
                System.out.println(entries);
            }
        }
    }
1.2.2 group分页

​ 默认情况下分组结果中只会展示前10个组,并且每组展示相关对最高的1个文档。我们可以使用start和rows可以设置组的分页,使用group.limit和group.offset设置组内文档分页。

q=item_title:手机&group=true&group.field=item_brand&start=0&rows=3
&group.limit=5&group.offset=0

​ 展示前3个组及每组前5个文档。

 //设置组的分页参数
 params.setStart(0);
 params.setRows(3);
 //设置组内文档的分页参数
 params.set(GroupParams.GROUP_OFFSET, 0);
 params.set(GroupParams.GROUP_LIMIT, 5);
1.2.3 group排序

​ 之前讲解排序的时候,group排序分为组排序,组内文档排序;对应的参数为sort和group.sort

​ 需求:按照组内价格排序降序;

 params.set(GroupParams.GROUP_SORT, "item_price desc");

​ 当然分组还有其他的用法,都是大同小异,大家可以参考我们之前讲解分组的知识;

1.3 高亮
1.3.1 高亮查询

​ 查询item_title中包含手机的文档,并且对item_title中的手机关键字进行高亮;

   http://localhost:8080/solr/collection1/select?
    q=item_title:手机
    &hl=true
    &hl.fl=item_title
    &hl.simple.pre=<font>
    &h1.simple.post=</font>
@Test
    public void testHighlightingQuery() throws IOException, SolrServerException {
        SolrQuery params = new SolrQuery();
        params.setQuery("item_title:三星手机");
        //开启高亮
        params.setHighlight(true);
        //设置高亮域
        //高亮的前后缀
        params.addHighlightField("item_title");
        params.setHighlightSimplePre("<font>");
        params.setHighlightSimplePost("</font>");

        QueryResponse response = httpSolrClient.query(params);

        SolrDocumentList results = response.getResults();
        for (SolrDocument result : results) {
            System.out.println(result);
        }
        
        //解析高亮
        Map<String, Map<String, List<String>>> highlighting = response.getHighlighting();
        //map的key是文档id,map的value包含高亮的数据
        for (String id : highlighting.keySet()) {
            System.out.println(id);
            /**
             * item_title: [
             * "飞利浦 老人<em>手机</em> (X2560) 深情蓝 移动联通2G<em>手机</em> 双卡双待"
             * ]
             */
            Map<String, List<String>> highLightData = highlighting.get(id);
            //highLightData key包含高亮域名
            //获取包含高亮的数据
            if(highLightData != null && highLightData.size() > 0) {
                //[
                //             * "飞利浦 老人<em>手机</em> (X2560) 深情蓝 移动联通2G<em>手机</em> 双卡双待"
                //             * ]
                List<String> stringList = highLightData.get("item_title");
                if(stringList != null && stringList.size() >0) {
                    String title = stringList.get(0);
                    System.out.println(title);
                }
            }
        }

        //将高亮的数据替换到原有文档中。

    }
1.3.2 高亮器的切换

​ 当然我们也可以使用SolrJ完成高亮器的切换。之前我们讲解过一个高亮器fastVector,可以实现域中不同的词使用不同颜色。

​ 查询item_title中包含三星手机的文档.item_title中三星手机中不同的词,显示不同的颜色;

http://localhost:8080/solr/collection1/select?
q=item_title:三星手机
&hl=true
&hl.fl=item_title
&hl.method=fastVector
SolrQuery params = new SolrQuery();
params.setQuery("item_title:三星手机");
//开启高亮
params.setHighlight(true);
params.addHighlightField("item_title");
params.set("hl.method","fastVector");

QueryResponse response = httpSolrClient.query(params);

​ 到这使用SolrJ进行高亮查询就讲解完毕。

1.4 suggest查询
1.4.1 spell-checking 拼写检查。

​ 需求:查询item_title中包含iphone的内容。要求进行拼写检查。

http://localhost:8080/solr/collection1/select?
q=item_title:iphonx&spellcheck=true

@Test
    public void test01() throws IOException, SolrServerException {
        SolrQuery params = new SolrQuery();
        params.setQuery("item_title:iphonxx");
        params.set("spellcheck",true);
        QueryResponse response = httpSolrClient.query(params);
        /**
         * suggestions: [
         * "iphonxx",
         * {
         * numFound: 1,
         * startOffset: 11,
         * endOffset: 18,
         * suggestion: [
         * "iphone6"
         * ]
         * }
         * ]
         */
        SpellCheckResponse spellCheckResponse = response.getSpellCheckResponse();
        Map<String, SpellCheckResponse.Suggestion> suggestionMap = spellCheckResponse.getSuggestionMap();
        for (String s : suggestionMap.keySet()) {
            //错误的词
            System.out.println(s);
            //建议的词
            SpellCheckResponse.Suggestion suggestion = suggestionMap.get(s);

            List<String> alternatives = suggestion.getAlternatives();
            System.out.println(alternatives);
        }

    }

1.4.2Auto Suggest自动建议。

上面我们讲解完毕拼写检查,下面我们讲解自动建议,自动建议也是需要在SolrConfig.xml中进行相关的配置。

需求:查询三星,要求基于item_title域进行自动建议

http://localhost:8080/solr/collection1/select?
q=三星&suggest=true&suggest.dictionary=mySuggester&suggest.count=5

@Test
    public void test02() throws IOException, SolrServerException {
        SolrQuery params = new SolrQuery();
        //设置参数
        params.setQuery("java");
        //开启自动建议
        params.set("suggest",true);
        //指定自动建议的组件
        params.set("suggest.dictionary","mySuggester");
      
        QueryResponse response = httpSolrClient.query(params);
        SuggesterResponse suggesterResponse = response.getSuggesterResponse();
        Map<String, List<Suggestion>> suggestions = suggesterResponse.getSuggestions();
        for (String key : suggestions.keySet()) {
            //词
            System.out.println(key);
            List<Suggestion> suggestionList = suggestions.get(key);
            for (Suggestion suggestion : suggestionList) {
                String term = suggestion.getTerm();
                System.out.println(term);
            }
        }
    }
1.5 使用SolrJ完成Core的管理
1.5.1 Core添加

​ 1.要想完成SolrCore的添加,在solr_home必须提前创建好SolrCore的目录结构,而且必须包相关的配置文件。
在这里插入图片描述

修改配置文件中url:http://localhost:8080/solr
CoreAdminRequest.createCore("collection4", "D:\\solr_home\\collection4", solrClient );
1.5.2 重新加载Core

​ 从Solr中移除掉,然后在添加。

CoreAdminRequest.reloadCore("collection4",  solrClient );
1.5.3 重命名Core
CoreAdminRequest.renameCore("collection4","newCore" , solrClient)
1.5.4 卸载solrCore

​ 卸载仅仅是从Solr中将该Core移除,但是SolrCore的物理文件依然存在

CoreAdminRequest.unloadCore("collection4",  solrClient );
1.5.5 solrCore swap
 CoreAdminRequest.swapCore("collection1", "collection4", solrClient);

2. Solr集群搭建

2.1 SolrCloud简介

2.1.1 什么是SolrCloud

​ Solr集群也成为SolrCloud,是Solr提供的分布式搜索的解决方案,当你需要大规模存储数据或者需要分布式索引和检索能力时使用 SolrCloud。

​ 所有数据库集群,都是为了解决4个核心个问题,单点故障,数据扩容,高并发,效率

​ 搭建SolrCloud是需要依赖一个中间件Zookeeper,它的主要思想是使用Zookeeper作为集群的配置中心。

2.1.2 SolrCloud架构

SolrCloud逻辑概念:

​ 一个Solr集群是由多个collection组成,collection不是SolrCore只是一个逻辑概念。一个collection是由多个文档组成,这些文档归属于指定的分片。下面表示的就是一个逻辑结构图。

​ 该集群只有一个collection组成。connection中的文档数据分散在4个分片。
在这里插入图片描述

​ 分片中的数据,到底存储在那呢?这就属于物理概念。

SolrCloud物理概念:

​ Solr 集群由一个或多个 Solr服务(tomcat中运行solr服务)组成,这些Solr服务可以部署一台服务器上,也可以在多台服务器。每个Solr服务可以包含一个或者多个Solr Core 。SolrCore中存储的是Shard的数据;

​ 下面的图是一个物理结构和逻辑结构图。
在这里插入图片描述

概念:

​ Collection是一个逻辑概念,可以认为是多个文档构成的一个集合。

​ Shard是一个逻辑概念,是对Collection进行逻辑上的划分。

​ Replica:Shard的一个副本,一个Shard有多个副本,同一个Shard副本中的数据一样;

​ Leader:每个Shard会以多个Replica的形式存在,其中一个Replica会被选为Leader,负责集群环境中的索引和搜索。

​ SolrCore:存储分片数据的基本单元。一个SolrCore只存储一个分片数据,一个分片数据可以存储到多个SolrCore中;一个SolrCore对应一个Replica

​ Node:集群中的一个Solr服务

2.2Linux集群搭建

2.2.1 基于tomcat的集群搭建

2.2.1.1集群架构介绍

​ 物理上:

​ 搭建三个Zookeeper组成集群,管理SolrCloud。

​ 搭建四个Solr服务,每个Solr服务一个SorCore.

​ 逻辑上:

​ 整个SolrCloud一个Connection;

​ Connection中的数据分为2个Shard;

​ Shard1的数据在物理上存储到solr1和solr2的SolrCore

​ Shard2的数据在物理上存储到solr3和solr4的SolrCore

在这里插入图片描述

2.2.1.2 环境说明

​ 环境和我们之前搭建单机版的环境相同。

系统版本
LinuxCentOS 7
JDKJDK8
Tomcattomcat 8.5
zookeeperzookeeper-3.4.14
solrsolr 7.7.2
2.2.1.3 Zookeeper集群搭建

​ 首先我们先要完成Zookeeper集群搭建。在搭建集群之前,首先我们先来说一下Zookeeper集群中要求节点的个数。

​ 在Zookeeper集群中节点的类型主要有2类,Leader,Follower,一个集群只有一个Leader。

​ 到底谁是Leader呢?

​ 投票选举制度:集群中所有的节点来进行投票,半数以上获票的节点就是Leader.Zookeeper要求集群节点个数奇数。

​ 容错:zookeeper集群一大特性是只要集群中半数以上的节点存活,集群就可以正常提供服务,而2n+1节点和2n+2个节点的容错能力相同,都是允许n台节点宕机。本着节约的宗旨,一般选择部署2n+1台机器

​ 本次我们采用最少的集群节点3个。

在这里插入图片描述

​ 1.下载Zookeeper的安装包到linux。

​ 下载地址:

http://archive.apache.org/dist/zookeeper/zookeeper-3.4.14/		
上面的地址可能受每日访问量现在							
wget
https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz

​ 2. 解压zookeeper, 复制三份到/usr/local/solrcloud下,复制三份分别并将目录名改为zk01、在zk02、zk03

mkdir  /usr/local/solrcloud
tar -xzvf zookeeper-3.4.14.tar.gz
cp -r zookeeper-3.4.14 /usr/local/solrcloud/zk01
cp -r zookeeper-3.4.14 /usr/local/solrcloud/zk02
cp -r zookeeper-3.4.14 /usr/local/solrcloud/zk03

​ 结果
在这里插入图片描述

  1. 为每一个Zookeeper创建数据目录

    mkdir zk01/data
    mkdir zk02/data
    mkdir zk03/data
    

在这里插入图片描述

  1. 在data目录中为每一个Zookeeper创建myid文件,并且在文件中指定该Zookeeper的id;

    使用重定向指令>>将打印在控制台上的1 2 3分别写入到data目录的myid文件中。
    echo 1 >> zk01/data/myid
    echo 2 >> zk02/data/myid
    echo 3 >> zk03/data/myid
    

在这里插入图片描述

  1. 修改Zookeeper的配置文件名称。

    ​ 5.1 需要将每个zookeeper/conf目录中的zoo_sample.cfg 文件名改名为zoo.cfg,否则该配置文件不起作用。

    mv zk01/conf/zoo_sample.cfg zk01/conf/zoo.cfg
    mv zk02/conf/zoo_sample.cfg zk02/conf/zoo.cfg
    mv zk03/conf/zoo_sample.cfg zk03/conf/zoo.cfg
    

在这里插入图片描述

​ 6. 编辑zoo.cfg配置文件

​ 5.1 修改Zookeeper客户端连接的端口,Zookeeper对外提供服务的端口。修改目的,我们需要在一台计算机上启动三个Zookeeper。

clientPort=2181
clientPort=2182
clientPort=2183

​ 5.2 修改数据目录

dataDir=/usr/local/solrcloud/zk01/data
dataDir=/usr/local/solrcloud/zk02/data
dataDir=/usr/local/solrcloud/zk03/data

​ 5.3 在每一个配置文件的尾部加入,指定集群中每一个Zookeeper的节点信息。

server.1=192.168.200.128:2881:3881
server.2=192.168.200.128:2882:3882
server.3=192.168.200.128:2883:3883

server.1 server.2 server.3 指定Zookeeper节点的id.之前我们myid已经指定了每个节点的id;
内部通信端口:
	Zookeeper集群节点相互通信端口;
为什么会有投票端口呢?
1.因为三台Zookeeper要构成集群,决定谁是leader,Zookeeper中是通过投票机制决定谁是leader。
2.如果有节点产生宕机,也需要通过投票机制将宕机节点从集群中剔除。
所以会有一个投票端口

​ 7.启动Zookeeper集群。

​ 在bin目录中提供了一个脚本zkServer.sh,使用zkServer.sh start|restart|stop|status 就可以完成启动,重启,停止,查看状态。

可以写一个脚本性启动所有Zookeeper。也可以一个个的启动;
./zk01/bin/zkServer.sh start
./zk02/bin/zkServer.sh start
./zk03/bin/zkServer.sh start

在这里插入图片描述

8.查看Zookeeper集群状态。

./zk01/bin/zkServer.sh status
./zk02/bin/zkServer.sh status
./zk03/bin/zkServer.sh status

在这里插入图片描述

follower就是slave;

leader就是master;

如果是单机版的Zookeeper,standalone

到这关于Zookeeper集群的搭建,我们就讲解完毕。

2.2.1.4 SolrCloud集群部署

​ 上一节我们已经完成了Zookeeper集群的搭建,下面我们来搭建solr集群。

在这里插入图片描述

​ 复制4个tomcat,并且要在4个tomcat中部署solr,单机版solr的搭建,我们之前已经讲解过了。

​ 下面是我们已经搭建好的单机版的solr,他的tomcat和solrhome
在这里插入图片描述

1.1 将tomcat复制4份到solrcloud中,便于集中管理。这4个tomcat中都部署了solr;

cp -r apache-tomcat-8.5.50 solrcloud/tomcat1
cp -r apache-tomcat-8.5.50 solrcloud/tomcat2
cp -r apache-tomcat-8.5.50 solrcloud/tomcat3
cp -r apache-tomcat-8.5.50 solrcloud/tomcat4

在这里插入图片描述

1.2 复制4个solrhome到solrcloud中。分别对应每一个solr的solrhome。

cp -r solr_home solrcloud/solrhome1
cp -r solr_home solrcloud/solrhome2
cp -r solr_home solrcloud/solrhome3
cp -r solr_home solrcloud/solrhome4

在这里插入图片描述

1.3 修改tomcat的配置文件,修改tomcat的端口,保证在一台计算机上,可以启动4个tomcat;

​ 编辑server.xml

修改停止端口
对外提供服务端口
AJP端口
<Server port="8005"shutdown="SHUTDOWN">
<Connector port="8080"protocol="HTTP/1.1" connectionTimeout="20000" redirect
<Connector port="8009" protocol="AJP/1.3"redirectPort="8443" />
tomcat1  8105  8180  8109
tomcat1  8205  8280  8209
tomcat1  8305  8380  8309
tomcat1  8405  8480  8409

1.4 为tomcat中每一个solr指定正确的solrhome,目前是单机版solrhome的位置。

​ 编辑solr/web.xml指定对应的solrhome

	 <env-entry>
		<env-entry-name>solr/home</env-entry-name>
		<env-entry-value>/usr/local/solrcloud/solrhomeX</env-entry-value>
		<env-entry-type>java.lang.String</env-entry-type>
  	</env-entry>

1.5 修改每个solrhome中solr.xml的集群配置,对应指定tomcat的ip和端口

​ 编辑solrhome中solr.xml

 <solrcloud>
    <str name="host">192.168.200.1288</str>
    <int name="hostPort">8X80</int>
 </solrcloud>
2.2.1.5 Zookeeper管理的Solr集群

​ 上一节课我们已经搭建好了Zookeeper集群,和Solr的集群。但是现在Zookeeper集群和Solr集群没有关系。

需要让Zookeeper管理Solr集群。
1.启动所有的tomcat

 ./tomcat1/bin/startup.sh
 ./tomcat2/bin/startup.sh
 ./tomcat3/bin/startup.sh
 ./tomcat4/bin/startup.sh

​ 2.让每一个solr节点和zookeeper集群关联

​ 编辑每一个tomcat中bin/catalina.sh文件,指定Zookeeper集群地址。

  JAVA_OPTS="-DzkHost=192.168.200.128:2181,192.168.200.128:2182,192.168.200.128:2183"
  需要指定客户端端口即Zookeeper对外提供服务的端口

​ 3.让zookeeper统一管理solr集群的配置文件。

​ 因为现在我们已经是一个集群,集群中的每个solr节点配置文件需要一致。所以我们就需要让zookeeper管理solr集群的配置文件。主要管理的就是solrconfig.xml / schema.xml文件

下面的命令就是将conf目录中的配置文件,上传到Zookeeper,以后集群中的配置文件就以Zookeeper中的为准;
搭建好集群后,solrCore中是没有配置文件的。
./zkcli.sh -zkhost 192.168.200.128:2181,192.168.200.128:2182,192.168.200.128:2183 -cmd upconfig -confdir /usr/local/solrcloud/solrhome1/collection1/conf -confname myconf

-zkhost:指定zookeeper的地址列表;
upconfig :上传配置文件;
-confdir :指定配置文件所在目录;
-confname:指定上传到zookeeper后的目录名;

进入到solr安装包中
/root/solr-7.7.2/server/scripts/cloud-scripts
./zkcli.sh -zkhost 192.168.200.128:2181,192.168.200.128:2182,192.168.200.128:2183 -cmd upconfig -confdir /usr/local/solrcloud/solrhome1/collection1/conf -confname myconf


./zkcli.sh -zkhost 192.168.200.128:2181,192.168.200.128:2182,192.168.200.128:2183 -cmd upconfig -confdir /root/solr7.7.2/server/solr/configsets/sample_techproducts_configs/conf
 -confname myconf

在这里插入图片描述

​ 4.查看Zookeeper中的配置文件。

​ 登录到任意一个Zookeeper上,看文件是否上传成功。

进入到任意一个Zookeeper,查看文件的上传状态。
/zKCli.sh -server 192.168.200.128:2182

​ 通过Zookeeper客户端工具查看。idea的Zookeeper插件,ZooInspector等。

  1. 由于solr的配置文件,已经交由Zookeeper管理。所有SolrHome中的SolrCore就可以删除。

​ 到这Zookeeper集群和Solr集群整合就讲解完毕。

2.2.1.6 创建Solr集群的逻辑结构

​ 之前我们已经搭建好了Zookeeper集群及Solr集群。接下来我们要完成的是逻辑结构的创建。

在这里插入图片描述

​ 我们本次搭建的逻辑结构整个SolrCloud是由一个Collection构成。Collection分为2个分片。每个分片有2个副本。分别存储在不同的SolrCore中。

​ 启动所有的tomcat

 ./tomcat1/bin/startup.sh
 ./tomcat2/bin/startup.sh
 ./tomcat3/bin/startup.sh
 ./tomcat4/bin/startup.sh

​ 访问任4个solr管理后台;

http://192.168.200.131:8180/solr/index.html#/~collections
http://192.168.200.131:8280/solr/index.html#/~collections
http://192.168.200.131:8380/solr/index.html#/~collections
http://192.168.200.131:8480/solr/index.html#/~collections

​ 使用任何一个后台管理系统,创建逻辑结构

在这里插入图片描述

name:指定逻辑结构collection名称;

config set:指定使用Zookeeper中配置文件;

numShards:分片格式

​replicationFactory:每个分片副本格式;

​ 当然我们也可以通过Solr RestAPI完成上面操作

http://其中一个solr节点的IP:8983/solr/admin/collections?action=CREATE&name=testcore&numShards=2&replicationFactor=2&collection.configName=myconf

​ 查询逻辑结构

在这里插入图片描述

​ 查看物理结构

在这里插入图片描述

说明一下: 原来SolrCore的名字已经发生改变。

​ 一旦我们搭建好集群后,每个SolrCore中是没有conf目录即没有配置文件。

​ 整个集群的配置文件只有一份,Zookeeper集群中的配置文件。

2.2.1.7测试集群

​ 向collection中添加数据

{id:"100",name:"zhangsan"}

​ 由于整合集群逻辑上就一个collection,所以在任何一个solr节点都可以获取数据。

2.2.1.8 使用SolrJ操作集群

​ 之前我们操作单节点的Solr,使用的是HttpSolrClient,操作集群我们使用的是CloudSolrClient。

  1. 将CloudSolrClient交由spring管理。
    @Bean
    public CloudSolrClient cloudSolrClient() {
         //指定Zookeeper的地址。
        List<String> zkHosts = new ArrayList<>();
        zkHosts.add("192.168.200.131:2181");
        zkHosts.add("192.168.200.131:2182");
        zkHosts.add("192.168.200.131:2183");
        //参数2:指定
        CloudSolrClient.Builder builder = new CloudSolrClient.Builder(zkHosts,Optional.empty());
        CloudSolrClient solrClient = builder.build();
        //设置连接超时时间
        solrClient.setZkClientTimeout(30000);
        solrClient.setZkConnectTimeout(30000);
        //设置collection
        solrClient.setDefaultCollection("collection");
        return solrClient;
    }

​ 2.使用CloudSolrClient中提供的API操作Solr集群,和HttpSolrClient相同

索引


	@Autowired
    private CloudSolrClient cloudSolrClient;
        @Test
        public void testSolrCloudAddDocument() throws Exception {
            SolrInputDocument doc = new SolrInputDocument();
            doc.setField("id", 1);
            doc.setField("name", "java");
            cloudSolrClient.add(doc);
			cloudSolrClient.commit();
        }

搜索

 @Test
    public void testSolrQuery() throws Exception {
        SolrQuery params = new SolrQuery();
        params.setQuery("*:*");
        QueryResponse response = cloudSolrClient.query(params);
        SolrDocumentList results = response.getResults();
        for (SolrDocument result : results) {
            System.out.println(result);
        }
    }

2.2.2 SolrCloud的其他操作

2.2.2.1 SolrCloud使用中文分词器(IK)

​ 1.在每一个solr服务中,引入IK分词器的依赖包

在这里插入图片描述

​ 2.在classes中引入停用词和扩展词库及配置文件

在这里插入图片描述

​ 3.重启所有的tomcat

​ 4.修改单机版solrcore中schema,加入FiledType

<fieldType name ="text_ik" class ="solr.TextField">
    <analyzer class="org.wltea.analyzer.lucene.IKAnalyzer"/>
</fieldType>

​ 5.将schema重新提交到Zookeeper
​ Solr集群中的配置文件统一由Zookeeper进行管理,所以如果需要修改配置文件,我们需要将修改的文件上传的Zookeeper。

./zkcli.sh -zkhost 192.168.200.128:2181,192.168.200.128:2182,192.168.200.128:2183 -cmd putfile /configs/myconf/managed-schema /usr/local/solr_home/collection1/conf/managed-schema

​ 6. 测试
在这里插入图片描述

​ 7.利用text_ik 创建相关的业务域

​ 创建业务域,我们依然需要修改shema文件,这个时候,我们建议使用后台管理系统。

在这里插入图片描述

8.查看schema文件是否被修改。

​ 通过files查看集群中的配置文件,内容

在这里插入图片描述

​ 到这关于如何修改集群中的配置文件,已经如何管理Filed我们就讲解完毕。

2.2.2.2 查询指定分片数据

​ 在SolrCloud中,如果我们想查询指定分片的数据也是允许的。

​ 需求:查询shard1分片的数据

http://192.168.200.128:8180/solr/myCollection/select?q=*:*&shards=shard1

在这里插入图片描述

​ 需求:查询shard1和shard2分片的数据

http://192.168.200.128:8180/solr/myCollection/select?q=*:*&shards=shard1,shard2

​ 上面的操作都是从根据shard的id随机从shard的副本中获取数据。shard1的数据可能来自8380,8280

shard2的数据可能来自8480,8180
在这里插入图片描述

​ 也可以指定具体的副本;

​ 需求:获取shard1分片8280副本中的数据

http://192.168.200.128:8180/solr/myCollection/select?q=*:*&shards=192.168.200.128:8280/solr/myCollection

​ 需求:获取8280和8380副本中数据

http://192.168.200.128:8180/solr/myCollection/select?q=*:*&shards=192.168.200.128:8280/solr/myCollection,192.168.200.128:8380/solr/myCollection

​ 混合使用,通过Shard的ID随机获取+通过副本获取。

http://192.168.200.128:8180/solr/myCollection/select?q=*:*&shards=shard1,192.168.200.128:8380/solr/myCollection
2.2.2.3 SolrCloud并发和线程池相关的一些配置

​ 在SolrCloud中,我们也可以配置Shard并发和线程相关的配置,来优化集群性能。

​ 主要的配置在solr.xml文件。

<solr>
  <!--集群相关的配置,solr服务的地址,端口,上下文solr.是否自动生成solrCore名称,zk超时时间-->
  <solrcloud>  
    <str name="host">192.168.200.131</str>
    <int name="hostPort">8180</int>
    <str name="hostContext">${hostContext:solr}</str>
    <bool name="genericCoreNodeNames">${genericCoreNodeNames:true}</bool>
    <int name="zkClientTimeout">${zkClientTimeout:30000}</int>
    <int name="distribUpdateSoTimeout">${distribUpdateSoTimeout:600000}</int>
    <int name="distribUpdateConnTimeout">${distribUpdateConnTimeout:60000}</int>
    <str name="zkCredentialsProvider">${zkCredentialsProvider:org.apache.solr.common.cloud.DefaultZkCredentialsProvider}</str>
    <str name="zkACLProvider">${zkACLProvider:org.apache.solr.common.cloud.DefaultZkACLProvider}</str>
  </solrcloud>
	

<!-- 分片并发和线程相关的信息-->
  <shardHandlerFactory name="shardHandlerFactory"
    class="HttpShardHandlerFactory">
    <int name="socketTimeout">${socketTimeout:600000}</int>
    <int name="connTimeout">${connTimeout:60000}</int>
    <str name="shardsWhitelist">${solr.shardsWhitelist:}</str>
  </shardHandlerFactory>

</solr>

相关参数

参数名描述默认值
socketTimeout指客户端和服务器建立连接后,客户端从服务器读取数据的timeoutdistribUpdateSoTimeout
connTimeout指客户端和服务器建立连接的timeoutdistribUpdateConnTimeout
maxConnectionsPerHost最大并发数量20
maxConnections最大连接数量10000
corePoolSize线程池初始化线程数量0
maximumPoolSize线程池中线程的最大数量Integer.MAX_VALUE
maxThreadldleTime设置线程在被回收之前空闲的最大时间5秒
sizeOfQueue指定此参数 ,那么线程池会使用队列来代替缓冲区,对于追求高吞吐量的系统而 ,可能希望配置为-1 。对于于追求低延迟的系统可能希望配置合理的队列大小来处理请求 。-1
fairnessPolicy用于选择JVM特定的队列的公平策略:如果启用公平策略,分布式查询会以先进先出的方式来处理请求,但是是是以有损吞吐量为代价;如果禁用公平策略,会提高集群查询吞吐量,但是以响应延迟为代价。false
useRetries是否启 HTTP 连接自动重试机制,避免由 HTTP 连接池 的限制或者竞争导致的 IOException ,默认未开启false

2.3.2 基于docker的集群搭建

2.3.2.1 环境准备

​ 1.搭建docker

​ 要想在docker上搭建solr集群,首先安装docker的环境。这个就不再演示,如果没有学过docker的同学可以参考下面的视频地址进行学习。

​ Docker容器化技术入门与应用-JavaEE免费视频课程-博学谷

​ 如果学习过但是忘了如何搭建,参考一下地址。

​ CentOS Docker 安装 | 菜鸟教程

  1. 拉取zookeeper镜像和solr的镜像,采用的版本是3.4.14和7.7.2

    docker pull zookeeper:3.4.14
    docker pull solr:7.7.2
    

    查看拉取镜像

    docker images
    

在这里插入图片描述

2.3.2.2 搭建zookeeper集群

​ 搭建zookeeper集群,我们需要利用我们刚才拉取的zookeeper的镜像创建3个容器。并且让他们产生集群关系。

​ 单独创建一个桥接网卡

docker network create itcast-zookeeper
docker network ls
1.容器1创建
docker run 
	 -id 
	 --restart=always 
     -v /opt/docker/zookeeper/zoo1/data:/data 
     -v /opt/docker/zookeeper/zoo1/datalog:/datalog 
     -e ZOO_MY_ID=1 
     -e ZOO_SERVERS="server.1=zookeeper1:2888:3888 server.2=zookeeper2:2888:3888 server.3=zookeeper3:2888:3888" 
     -p 2181:2181
     --name=zookeeper1 
     --net=itcast-zookeeper
     --privileged 
     zookeeper:3.4.14
 
 docker run -id --restart=always -v /opt/docker/zookeeper/zoo1/data:/data -v /opt/docker/zookeeper/zoo1/datalog:/datalog -e ZOO_MY_ID=1 -e ZOO_SERVERS="server.1=zookeeper1:2888:3888 server.2=zookeeper2:2888:3888 server.3=zookeeper3:2888:3888" -p 2181:2181  --name=zookeeper1 --privileged --net=itcast-zookeeper zookeeper:3.4.14
 
 说明:
 	 --restart:docker重启,容器重启。
 	-d:守护式方式运行方式,除此之外还有-it
 	-v:目录挂载,用宿主机/opt/docker/zookeeper/zoo1/data,映射容器的/data目录
 	-e:指定环境参数。分别指定了自己的id,集群中其他节点的地址,包含通信端口和投票端口
 	--name:容器的名称
 	-p:端口映射,用宿主机2181映射容器 2181,将来我们就需要通过本机2181访问容器。
 	--privileged:开启特权,运行修改容器参数
 	 --net=itcast-zookeeper 指定桥接网卡
 	zookeeper:3.4.14:创建容器的镜像

​ 查看创建好的容器

docker ps

在这里插入图片描述

​ 查看宿主机的目录,该目录映射的就是zookeeper1容器中的data目录。

cd /opt/docker/zookeeper/zoo1/data

​ 查看Zookeeper节点的id

cat myid
2.容器2创建
docker run -d --restart=always 
     -v /opt/docker/zookeeper/zoo2/data:/data 
     -v /opt/docker/zookeeper/zoo2/datalog:/datalog 
     -e ZOO_MY_ID=2 
     -e ZOO_SERVERS="server.1=zookeeper1:2888:3888 server.2=zookeeper2:2888:3888 server.3=zookeeper3:2888:3888" 
     -p 2182:2181
     --name=zookeeper2
     --net=itcast-zookeeper
     --privileged 
     zookeeper:3.4.14
 
 docker run -d --restart=always -v /opt/docker/zookeeper/zoo2/data:/data -v /opt/docker/zookeeper/zoo2/datalog:/datalog -e ZOO_MY_ID=2 -e ZOO_SERVERS="server.1=zookeeper1:2888:3888 server.2=zookeeper2:2888:3888 server.3=zookeeper3:2888:3888" -p 2182:2181 --name=zookeeper2 --net=itcast-zookeeper --privileged zookeeper:3.4.14
 说明:
 	需要修改目录挂载。
 	   修改Zookeeper的id
 	   端口映射:用宿主机2182 映射容器 2181
 	   容器名称:zookeeper2
 
3.容器3创建
docker run -d --restart=always 
     -v /opt/docker/zookeeper/zoo3/data:/data 
     -v /opt/docker/zookeeper/zoo3/datalog:/datalog 
     -e ZOO_MY_ID=3
     -e ZOO_SERVERS="server.1=zookeeper1:2888:3888 server.2=zookeeper2:2888:3888 server.3=zookeeper3:2888:3888" 
     -p 2183:2181
     --name=zookeeper3
      --net=itcast-zookeeper
     --privileged 
     zookeeper:3.4.14
 
 docker run -d --restart=always -v /opt/docker/zookeeper/zoo3/data:/data -v /opt/docker/zookeeper/zoo3/datalog:/datalog -e ZOO_MY_ID=3 -e ZOO_SERVERS="server.1=zookeeper1:2888:3888 server.2=zookeeper2:2888:3888 server.3=zookeeper3:2888:3888" -p 2183:2181  --name=zookeeper3 --net=itcast-zookeeper --privileged zookeeper:3.4.14
 说明:
 	   需要修改目录挂载。
 	   修改Zookeeper的id
 	   端口映射:用宿主机2183 映射容器 2181 
 	   容器名称:zookeeper3
 	

​ 查看容器创建情况

在这里插入图片描述

2.3.2.3 测试Zookeeper集群的搭建情况

​ 使用yum安装nc指令

yum install -y nc

​ 方式1:

​ 通过容器的ip查看Zookeeper容器状态

​ 查看三个Zookeeper容器的ip

docker   inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'   zookeeper1
docker   inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'   zookeeper2
docker   inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'   zookeeper3

​ 查看Zookeeper集群的状态

echo stat|nc ip 2181
echo stat|nc ip 2181
echo stat|nc ip 2181

在这里插入图片描述

方式2:通过宿主机的ip查询Zookeeper容器状态

在这里插入图片描述

2.3.2.4 zookeeper集群的架构

​ Zookeeper集群架构
在这里插入图片描述

​ Zookeeper客户端连接Zookeeper容器
在这里插入图片描述

2.3.2.3 搭建solr集群

​ 搭建Solr集群,我们需要利用我们刚才拉取的solr的镜像创建4个容器。并且需要将集群交由Zookeeper管理,Solr容器内部使用jetty作为solr的服务器。

​ 1.容器1创建

docker run --name solr1 --net=itcast-zookeeper -d -p 8983:8983 solr:7.7.2  bash -c '/opt/solr/bin/solr start -f -z zookeeper1:2181,zookeeper2:2181,zookeeper3:2181'
--name:指定容器名称
--net:指定网卡,之前创建的桥接网卡
-d:后台运行
-p:端口映射,宿主机8983映射容器中8983
-c:
/opt/solr/bin/solr:通过容器中脚本,指定Zookeeper的集群地址

​ 2.容器2创建

docker run --name solr2 --net=itcast-zookeeper -d -p 8984:8983 solr:7.7.2  bash -c '/opt/solr/bin/solr start -f -z zookeeper1:2181,zookeeper2:2181,zookeeper3:2181'
--name:指定容器名称
--net:指定网卡,之前创建的桥接网卡
-d:后台运行
-p:端口映射,宿主机8983映射容器中8984
-c:/opt/solr/bin/solr:通过容器中脚本,指定Zookeeper的集群地址

​ 3.容器3创建

docker run --name solr3 --net=itcast-zookeeper -d -p 8985:8983 solr:7.7.2  bash -c '/opt/solr/bin/solr start -f -z zookeeper1:2181,zookeeper2:2181,zookeeper3:2181'
--name:指定容器名称
--net:指定网卡,之前创建的桥接网卡
-d:后台运行
-p:端口映射,宿主机8983映射容器中8984
-c:/opt/solr/bin/solr:通过容器中脚本,指定Zookeeper的集群地址

​ 4.容器4创建

docker run --name solr4 --net=itcast-zookeeper -d -p 8986:8983 solr:7.7.2  bash -c '/opt/solr/bin/solr start -f -z zookeeper1:2181,zookeeper2:2181,zookeeper3:2181'
--name:指定容器名称
--net:指定网卡,之前创建的桥接网卡
-d:后台运行
-p:端口映射,宿主机8983映射容器中8985
-c:/opt/solr/bin/solr:通过容器中脚本,指定Zookeeper的集群地址

​ 5.查看结果

在这里插入图片描述

2.3.2.5 整体架构

在这里插入图片描述

2.3.2.6 创建集群的逻辑结构

​ 1.通过端口映射访问solr的后台系统(注意关闭防火墙)

	http://192.168.200.128:8983/solr
	http://192.168.200.128:8984/solr
	http://192.168.200.128:8985/solr
	http://192.168.200.128:8986/solr

​ 2.上传solr配置文件到Zookeeper集群。

​ 进入到某个solr容器中,使用/opt/solr/server/scripts/cloud-scripts/的zkcli.sh 。上传solr的配置文件。

位置/opt/solr/example/example-DIH/solr/solr/conf到Zookeeper集群zookeeper1:2181,zookeeper2:2181,zookeeper3:2181。

docker exec -it solr1 /opt/solr/server/scripts/cloud-scripts/zkcli.sh -zkhost zookeeper1:2181,zookeeper2:2181,zookeeper3:2181 -cmd upconfig -confdir /opt/solr/server/solr/configsets/sample_techproducts_configs/conf -confname myconfig

​ 3.使用zooInterceptor查看配置文件上传情况
在这里插入图片描述

​ 4.使用后台管理系统创建connection
在这里插入图片描述

​ 5. 查看集群的逻辑结构
在这里插入图片描述

​ 到这基于docker的solr集群我们就讲解完毕。

​ 6.测试集群

{id:1,name:"zx"}
2.3.2.7 solr配置文件修改

1.在linux服务器上需要有一份solr的配置文件,修改宿主机中的配置文件。

​ 之前单机版solr的/usr/local/solr_home下,就有solr的配置文件。

​ /usr/local/solr_home下面的配置文件,schema已经配置了基于IK分词器的FieldType

2.将宿主机中的配置文件,替换到某个solr容器中。

docker cp /usr/local/solr_home/collection1/conf/managed-schema solr1:/opt/solr/server/solr/configsets/sample_techproducts_configs/conf/managed-schema

3.将容器中修改后managed-schema配置文件,重新上传到Zookeeper集群。

​ 进入到solr1容器中,使用zkcli.sh命令,将最新的配置文件上传到Zookeeper集群

​ 容器文件位置:/opt/solr/server/solr/configsets/sample_techproducts_configs/conf/managed-schema

​ 集群文件位置: /configs/myconfig/managed-schema

docker exec -it solr1 /opt/solr/server/scripts/cloud-scripts/zkcli.sh -zkhost zookeeper1:2181,zookeeper2:2181,zookeeper3:2181 -cmd putfile /configs/myconfig/managed-schema /opt/solr/server/solr/configsets/sample_techproducts_configs/conf/managed-schema

4.在Files中查看,修改后的配置文件

5.将IK分词器的jar包,及配置文件复制到4个solr容器中。

​ 5.1 将IK分词器上传到linux服务器

​ 5.2 使用docker cp命令进行jar包的拷贝

docker cp /root/ik-analyzer-solr5-5.x.jar solr1:/opt/solr/server/solr-webapp/webapp/WEB-INF/lib/ik-analyzer-solr5-5.x.jar
docker cp /root/ik-analyzer-solr5-5.x.jar solr2:/opt/solr/server/solr-webapp/webapp/WEB-INF/lib/ik-analyzer-solr5-5.x.jar
docker cp /root/ik-analyzer-solr5-5.x.jar solr3:/opt/solr/server/solr-webapp/webapp/WEB-INF/lib/ik-analyzer-solr5-5.x.jar
docker cp /root/ik-analyzer-solr5-5.x.jar solr4:/opt/solr/server/solr-webapp/webapp/WEB-INF/lib/ik-analyzer-solr5-5.x.jar

5.3 使用docker cp命令进行配置文件的拷贝

​ 将IK分析器的相关配置文件复制到/root/classes目录中

docker cp /root/classes solr1:/opt/solr/server/solr-webapp/webapp/WEB-INF/classes
docker cp  /root/classes solr2:/opt/solr/server/solr-webapp/webapp/WEB-INF/classes
docker cp /root/classes solr3:/opt/solr/server/solr-webapp/webapp/WEB-INF/classes
docker cp  /root/classes solr4:/opt/solr/server/solr-webapp/webapp/WEB-INF/classes

6、重启所有solr容器

docker restart solr1
docker restart solr2
docker restart solr3
docker restart solr4

7、使用text_ik创建业务域

不再使用修改配置文件的方式,直接利用后台管理系统进行。
在这里插入图片描述

{id:2,book_name:"java编程思想"}
2.3.2.7 使用SolrJ操作solr集群

​ 1.修改Zookeeper地址和connection名称

 @Bean
    public CloudSolrClient cloudSolrClient() {
        //指定Zookeeper的地址。
        List<String> zkHosts = new ArrayList<>();
        zkHosts.add("192.168.200.128:2181");
        zkHosts.add("192.168.200.128:2182");
        zkHosts.add("192.168.200.128:2183");
        //参数2:指定
        CloudSolrClient.Builder builder = new CloudSolrClient.Builder(zkHosts,Optional.empty());
        CloudSolrClient solrClient = builder.build();
        //设置连接超时时间
        solrClient.setZkClientTimeout(30000);
        solrClient.setZkConnectTimeout(30000);
        //设置collection
        solrClient.setDefaultCollection("myCollection");
        return solrClient;
    }
  1. 将之前的索引和搜索的代码执行一下

    @Test
            public void testSolrCloudAddDocument() throws Exception {
                 SolrInputDocument doc = new SolrInputDocument();
                 doc.setField("id", "3");
                 doc.setField("book_name", "葵花宝典");
    
                  cloudSolrClient.add(doc);
                cloudSolrClient.commit();
            }
    
        @Test
        public void testSolrQuery() throws Exception {
            SolrQuery params = new SolrQuery();
            params.setQuery("*:*");
            QueryResponse response = cloudSolrClient.query(params);
            SolrDocumentList results = response.getResults();
            for (SolrDocument result : results) {
                System.out.println(result);
                
            }
        }
    
  2. 执行之前的代码会报错
    在这里插入图片描述

    错误的原因:连接不上172.19.0.6.8983。

    这个容器是谁?
    在这里插入图片描述

    为什么连接不上呢?

    1. 错误的原因
      在这里插入图片描述

    2. 正确的测试方案

      ​ 将客户端应用部署到docker容器中,容器之间是可以相互通信的。

      ​ 如何将我们的应用部署到docker容器中。

      ​ 5.1 修改代码,将添加文档的操作写在controller方法中,便于我们进行测试;

      @RestController
      @RequestMapping("/test")
      public class TestController {
          @Autowired
          private CloudSolrClient cloudSolrClient;
          @RequestMapping("/addDocument")
          public Map<String,Object> addDocument(String id,String bookName) throws IOException, SolrServerException {
              SolrInputDocument doc = new SolrInputDocument();
              doc.setField("id", id);
              doc.setField("book_name", bookName);
              cloudSolrClient.add(doc);
              cloudSolrClient.commit();
              Map<String,Object> result = new HashMap<>();
              result.put("flag", true);
              result.put("msg","添加文档成功" );
              return result;
          }
      }
      

      5.2 将spring boot项目打成可以运行jar包。

      <build>
              <plugins>
                  <plugin>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-maven-plugin</artifactId>
                  </plugin>
              </plugins>
      </build>
      

      5.3 使用package命令打成jar

      clean package -DskipTests=true
      

      5.4 将jar包上传到linux服务器制作镜像

      ​ 5.4.1上传

      ​ 5.4.2 编写DockerFile文件

      #基础镜像
      FROM java:8  
      #作者
      MAINTAINER itcast 
      #将jar包添加到镜像app.jar
      ADD solr_solrJ_other-1.0-SNAPSHOT.jar app.jar 
      #运行app的命令 java -jar app.jar
      ENTRYPOINT ["java","-jar","/app.jar"]
      

      ​ 5.4.3 创建镜像

      docker build -t myapp:1.0 ./   使用当前目录的DockerFile文件创建镜像myapp,版本1.0
      

      ​ 查看镜像

      docker images
      

      查看
      在这里插入图片描述

      ​ 5.4.4 利用myapp:1.0镜像创建容器

       docker run -d --net=itcast-zookeeper -p 8888:8080 myapp:1.0  #使用本地8888映射容器中8080
      

      ​ 5.4.5 查看日志

      docker logs -f 容器id
      

      ​ 5.5.6 访问controller

      http://192.168.200.128:8888/test/addDocument?id=999&bookName=葵花宝典
      

      ​ 注意:如果发现连接不上Zookeeper,注意关闭防火墙;

      systemctl stop firewalld
      

3.Solr查询高级

3.1 函数查询

​ 在Solr中提供了大量的函数,允许我们在查询的时候使用函数进行动态运算。

3.2.1 函数调用语法

​ 函数调用语法和java基本相同,格式为:函数名(实参),实参可以是常量(100,1.45,"hello"),也可以是域名,下面是函数调用例子。

functionName()  
functionName(...)  
functionName1(functionName2(...),..)

​ 数据准备:为了便于我们后面的讲解,采用的案例数据,员工信息。

业务域:
	员工姓名,工作,领导的员工号,入职日期,月薪,奖金
	 <field name="emp_ename" type="text_ik" indexed="true" stored="true"/>
    <field name="emp_ejob" type="text_ik" indexed="true" stored="true"/>
    <field name="emp_mgr" type="string" indexed="true" stored="true"/>
    <field name="emp_hiredate" type="pdate" indexed="true" stored="true"/>
    <field name="emp_sal" type="pfloat" indexed="true" stored="true"/>
    <field name="emp_comm" type="pfloat" indexed="true" stored="true"/>
    <field name="emp_cv" type="text_ik" indexed="true" stored="false"/>
    <field name="emp_deptno" type="string" indexed="true" stored="true"/>
    
data-import.xml
		<entity name="emp" query="select * from emp">
            <!-- 每一个field映射着数据库中列与文档中的域,column是数据库列,name是solr的域(必须是在managed-schema文件中配置过的域才行) -->
            <field column="empno" name="id"/>
            <field column="ename" name="emp_ename"/>
            <field column="ejob" name="emp_ejob"/>
            <field column="mgr" name="emp_mgr"/>
            <field column="hiredate" name="emp_hiredate"/>
            <field column="sal" name="emp_sal"/>
            <field column="comm" name="emp_comm"/>
            <field column="cv" name="emp_cv"/>
			<field column="deptno" name="emp_deptno"/>
        </entity>

3.2.2 函数的使用

3.2.2.1 将函数返回值作为关键字

​ 在solr中通常的搜索方式都是根据关键字进行搜索。底层首先要对关键字分词,在倒排索引表中根据词查找对应的文档返回。

item_title:三星手机

​ 需求:查询年薪在30000~50000的员工。

​ 主查询条件:查询所有:

​ 过滤条件: 利用product()函数计算员工年薪,product函数是Solr自带函数,可以计算2个数之积。{!frange}指定范围。l表示最小值,u表示最大值.{!frange}语法是Function Query Parser解析器中提供的一种语法。

	q=*:*
	&fq={!frange l=30000 u=50000}product(emp_sal,12)	

​ 需求:查询年薪+奖金大于50000的员工

​ 通过sum函数计算年薪+奖金。

	q=*:*
	&fq={!frange l=50000}sum(product(emp_sal,12),emp_comm)	
	上面查询不出来数据,原因是有些员工emp_comm为空值。
	emp_comm进行空值处理。判断emp_comm是否存在,不存在指定位0,存在,本身的值
	exists判断域是否存在
	if指定存在和不存在值
	{!frange l=40000}sum(product(emp_sal,12),if(exists(emp_comm),emp_comm,0))	
3.2.2.1.2 将函数返回值作为伪域

​ 伪域:文档中本身不包含的域,利用函数生成。

​ 需求:查询所有的文档,每个文档中包含年薪域。

q=*:*&
fl=*,product(emp_sal,12)
指定别名:yearsql:product(emp_sal,12) 便于将来封装
3.2.2.1.3 根据函数进行排序

​ 需求:查询所有的文档,并且按照(年薪+奖金)之和降序排序

q=*:*&
sort=sum(product(emp_sal,12),if(exists(emp_comm),emp_comm,0)) desc
3.2.2.1.4 使用函数影响相关度排序(了解)

​ 之前在讲解查询的时候,我们说的相关度排序,Lucene底层会根据查询的词和文档关系进行相关度打分。

打分高的文档排在前面,打分低的排在后面。打分的2个指标tf,df;

​ 使用函数可以影响相关度排序。

​ 需求:查询emp_cv中包含董事长的员工,年薪高的员工相关度打分高一点。

q=emp_cv:董事长 
	AND _val_:"product(emp_sal,12)"  #用来影响相关度排序,不会影响查询结果									  
可以简单这么理解 
lucene本身对查询文档有一个打分叫score,每个文档分数可能不同。
通过函数干预后,打分score += 函数计算结果

_val_:规定

另外一种写法:
	AND _query_:"{!func}product(emp_sal,12)"
	

3.2.2.1.5 Solr内置函数和自定义函数
常用内置函数
函数说明举例
abs(x)返回绝对值abs(-5)
def(field,value)获取指定域中值,如果文档没有指定域,使用value作为默认值;def(emp_comm,0)
div(x,y)除法,x除以ydiv(1,5)
dist计算两点之间的距离dis(2,x,y,0,0)
docfreq(field,val)返回某值在某域出现的次数docfreq(title,’solr’)
field(field)返回该field的索引数量field(‘title’)
hsin曲面圆弧上两点之间的距离hsin(2,true,x,y,0,0)
idfInverse document frequency 倒排文档频率idf(field,’solr’)
ifif(test,value1,value2)if(termfreq(title,’solr’),popularity,42)
linear(x,m,c)就是m*x+c,等同于sum(product(m,x),c)linear(1,2,4)=1x2+4=6
log(x)以10为底,x的对数log(sum(x,100))
map(x,min,max,target)如果x在min和max之间,x=target,否则x=xmap(x,0,0,1)
max(x,y,…)返回最大值max(2,3,0)
maxdoc返回索引的个数,查看有多少文档,包括被标记为删除状态的文档maxdoc()
min(x,y,…)返回最小值min(2,4,0)
ms返回两个参数间毫秒级的差别ms(datefield1,2000-01-01T00:00:00Z)
norm(field)返回该字段索引值的范数norm(title)
numdocs返回索引的个数,查看有多少文档,不包括被标记为删除状态的文档numdocs()
ord根据顺序索引发货结果ord(title)
pow(x,y)返回x的y次方pow(x,log(y))
product(x,y)返回多个值得乘积product(x,2)
query返回给定的子查询的得分,或者文档不匹配的默认值值query(subquery,default)
recip(x,m,a,b)相当于a/(m*x+b),a,m,b是常量,x是变量recip(myfield,m,a,b)
rord按ord的结果反序返回
scale返回一个在最大值和最小值之间的值scale(x,1,3)
sqedist平方欧氏距离计算sqedist(x_td,y_td,0,0)
sqrt返回指定值得平方根sqrt(x)sqrt(100)
strdist计算两个字符串之间的距离strdist(“SOLR”,id,edit)
sub返回x-ysub(field1,field2)
sum(x,y)返回指定值的和sum(x,y,…)
sumtotaltermfreq返回所有totaltermfreq的和
termfreq词出现的次数termfreq(title,’sorl’)
tf词频tf(text,’solr’)
top功能类似于ord
totaltermfreq返回这个词在该字段出现的次数ttf(title,’memory’)
and返回true值当且仅当它的所有操作为trueand(not(exists(popularity)),exists(price))
or返回true值当有一个操作为trueor(value1,value2)
xor返回false值如果所有操作都为真xor(field1,field2)
not排除操作not(exists(title))
exists如果字段存在返回真exists(title)
gt,gte,lt,lte,eq比较函数2 gt 1
自定义函数

​ 自定义函数流程:

​ 准备:

<dependency>
	<groupId>org.apache.solr</groupId>
	<artifactId>solr-core</artifactId>
	<version>7.7.2</version>
</dependency>

​ 1.定义一个类继承 ValueSource 类,用于封装函数执行结果。

​ 2.定义一个类继承ValueSourceParser,用来解析函数调用语法,并且可以解析参数类型,以及返回指定结果。

​ 3. 在solrconfig.xml中配置文件中配置ValueSourceParser实现类的全类名

​ 需求:定义一个solr字符串拼接的函数,将来函数的调用语法concat(域1,域2),完成字符串拼接。
​ 第一步:

/**
 * 用来解析函数调用语法,并且将解析的参数返回给第一个类处理。
 * concat(field1,field2)
 */
public class ConcatValueSourceParser extends ValueSourceParser {
    //对函数调用语法进行解析,获取参数,将参数封装到ValueSource,返回函数执行结果
    public ValueSource parse(FunctionQParser functionQParser) throws SyntaxError {
        //1.获取函数调用参数,由于参数类型可能是字符串,int类型,域名,所以封装成ValueSource
        //1.1获取第一个参数封装对象
        ValueSource arg1 = functionQParser.parseValueSource();
        //1.2获取第二个参数封装对象
        ValueSource arg2 = functionQParser.parseValueSource();

        //返回函数执行结果
        return new ConcatValueSource(arg1, arg2);
    }
}

​ 第二步:

public class ConcatValueSource extends ValueSource {

    //使用构造方法接收ConcatValueSourceParser解析获取的参数
    private ValueSource arg1;
    private ValueSource arg2;

    public ConcatValueSource(ValueSource arg1, ValueSource arg2) {
        this.arg1 = arg1;
        this.arg2 = arg2;
    }

    //返回arg1和arg2计算结果
    public FunctionValues getValues(Map map, LeafReaderContext leafReaderContext) throws IOException {
        FunctionValues values1 = arg1.getValues(map, leafReaderContext); //第一个参数
        FunctionValues values2 = arg1.getValues(map, leafReaderContext); //第二个参数

        //返回一个字符串的结果
        return new StrDocValues(this) {
            @Override
            public String strVal(int doc) throws IOException {
                //根据参数从文档中获取值
                return values1.strVal(doc) + values2.strVal(doc);
            }
        };
    }

​ 第三步:在solrconfig.xml配置

<valueSourceParser name="concat" class="cn.itcast.funcation.ConcatValueSourceParser" />

​ 第四步:打包,并且在solr/WEB-INF/lib 目录中配置

​ 第五步:测试

fl=*,concat(id,emp_ename)

3.2 地理位置查询

​ 在Solr中也支持地理位置查询,前提是我们需要将地理位置的信息存储的Solr中,其中地理位置中必须包含一个比较重要的信息,经度和纬度,经度范围[-180,180],纬度范围[-90,90]。对于经度来说东经为正,西经为负,对于纬度来说,北纬正,南纬为负。

3.2.1 数据准备

​ 首先我们先来完成数据准备,需要将地理位置信息存储到Solr中;

​ 1.在MySQL导入地理位置信息

​ name是地址名称

​ position:经纬度坐标,中间用,分割(纬度,经度)

​ 2.schema.xml中定义业务域

	name域的定义
	<field name="address_name" type="text_ik" indexed="true" stored="true"/>
	注意:
		坐标域定义
        域类型location,这域类型就是用来定义地理位置域。
        使用location域类型定义的域,底层会被拆分成2个域进行存储,分别是经度和维度。
        这个域将来会被拆分成2个域进行存储。
        所以需要定义动态域对底层操作进行支持。
	<field name="address_position" type="location" indexed="true" stored="true"/>

	以下在schema中已经定义好了。
	<fieldType name="location" class="solr.LatLonType" subFieldSuffix="_coordinate"/>
	<dynamicField name="*_coordinate"  type="tdouble" indexed="true"  stored="false"/>

​ 3.使用DataImport导入数据

	<entity name="address" query="select * from address">
            <!-- 每一个field映射着数据库中列与文档中的域,column是数据库列,name是solr的域(必须是在managed-schema文件中配置过的域才行) -->
            <field column="id" name="id"/>
            <field column="name" name="address_name"/>
            <field column="position" name="address_position"/>
        </entity>

​ 4.测试
在这里插入图片描述

​ id:主键

address_name:位置名称

address_position:位置坐标

​ 还多出2个域:address_position_0_coordinate ,address_position_1_coordinate 基于动态域生成的维度和经度坐标。

3.2.2 简单地理位置查询

​ Solr中 提供了一种特殊的查询解析器(geofilt),它能解析以指定坐标(包含经纬度)为中心。 以指定距离(千米)为半径画圆,坐标落在圆面积中的文档,

在这里插入图片描述

​ 需求:查询离西安半径 30 千米范围内的城市。

1.查询西安的坐标

	address_name:西安

在这里插入图片描述

  1. 查询距离西安30千米范围内的城市

    q=*:*&
    fq={!geofilt sfield=address_position pt=34.27,108.95 d=30}
    

    sfiled:指定坐标域

    pt:中心

    d:半径

在这里插入图片描述

除了可以查询落在圆面积中的地址外,对于精确度不高的查询,也可以查询落在外切正方形中的地理位置。

这种方式比圆的方式效率要高。

在这里插入图片描述

q=*:*&
ft={!bbox sfield=address_position pt=34.27,108.95 d=30}
  1. 在solr中还提供了一些计算地理位置的函数,比较常用的函数geodist,该函数可以计算2个地理位置中间的距离。调用方式:geodist(sfield,latitude,longitude);

    需求:计算其他城市到北京的距离,作为伪域展示

    q=*:*&
    fl=*,distance:geodist(address_position,39.92,116.46)
    sort=geodist(address_position,39.92,116.46) asc
    

3.2.3 地理位置高级查询

​ 之前我们在讲解简单地理位置查询的时候,都是基于正方形或者基于圆进行。有时候我们在进行地理位置查询的时候需要基于其他形状。在Solr中提供了一个类SpatialRecursivePrefixTreeFieldType(RPT)可以完成基于任意多边形查询。

在这里插入图片描述

3.2.3.1对地理数据进行重写索引

​ 1.修改address_position域类型

​ 之前的域定义,域类型location

<field name="address_position" type="location" indexed="true" stored="true"/>

​ 修改后的域定义,域类型location_rpt


<field name="address_position" type="location_rpt" indexed="true" stored="true" multiValued="true"/>
	
	location_rpt类型是schema文件已经提供好的一个类型,class就是RPT
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
		spatialContextFactory="JTS"
        geo="true" distErrPct="0.025" maxDistErr="0.001" distanceUnits="kilometers" />
	
	
  1. location_rpt类型,允许存储的数据格式。

    ​ 点:

    纬度 经度
    纬度,经度
    

    ​ 任意形状

    POLYGON((坐标1,坐标2....))
    

    将来我们可以查询一个点,或者任意形状是否在另外一个几何图形中。

​ 3. 复制jst的jar包到solr/WEB-INF/classes目录

<dependency>
    <groupId>org.locationtech.jts</groupId>
    <artifactId>jts-core</artifactId>
    <version>1.16.1</version>
</dependency>
jst:用于构建图形的jar包
  1. 插入测试数据,address_position域中存储的是一个几何图形

    insert into address values(8989,'三角形任意形状','POLYGON((108.94224 35.167441,108.776664 34.788863,109.107815 34.80404,108.94224 35.167441))'); 
    

在这里插入图片描述

  1. 使用DataImport重新进行索引
3.2.3.2 基于任意图形进行查询

​ 查询基于A,B,C,D这4个点围城的图形有哪些城市。

在这里插入图片描述

​ 可以使用百度地图的坐标拾取得知A,B,C,D四个点的坐标。

​ 拾取坐标系统

A 109.034226,35.574317

B 111.315491,33.719499

C108.243142,33.117791

D 107.98558,34.37802

​ 利用ABCD四个点构建几个图形,查询地理坐标在该几何图形中的文档。

address_position:"Intersects(POLYGON((109.034226 35.574317,
									111.315491 33.719499,
									108.243142 33.117791,
									107.98558 34.37802,
									109.034226 35.574317)))"

说明:

POLYGON是JST中提供的构建任意多边形的语法。

​ 每个坐标之间使用,进行分割。

​ 经度和纬度之间使用空格隔开

​ 如果构建的是一个封闭图形,最后一个坐标要和第一个坐标相同。

Intersects是一个操作符

​ 作用:匹配2个图形相交部分的文档。

IsWithin

​ 作用:匹配包含在指定图形面积之内的文档,不含边界

Contains:

​ 作用:匹配包含在指定图形面积之内的文档,含边界

3.3 JSON Facet

​ 在Solr中对于Facet查询提供了另外一种查询格式。称为JSON Facet。使用JSON Fact有很多好处,书写灵活,便于自定义响应结果,便于使用分组统计函数等。

3.3.1 JSON Facet 入门

​ 根据商品分类,分组统计每个分类对应的商品数量,要求显示前3个。

传统的Facet查询

q=*:*&
facet=true&
facet.field=item_category&
facet.limit=3

JSON Facet

q=*:*&
json.facet={
	category:{
		type:terms,
		field:item_category,
		limit:3
	}
}
测试
http://localhost:8080/solr/collection1/select?q=*:*&json.facet=%7Bcategory:%7Btype:terms,field:item_category,limit:3%7D%7D

其中category是自定义的一个键。
type:指的是facet的类型
field:分组统计的域

在这里插入图片描述

3.3.2 JSON Facet的语法

json.facet = {
	<facet_name>:{
		type:<fact_type>,
		<other_facet_parameter>
	}
}
facet_name:可以任意取值,是为了将来解析结果使用。
type:取值terms,query,range.
		terms根据域进行Facet查询,query根据查询解决进行facet查询。range根据范围进行facet查询。
other_facet_parameter:其他facet的一些参数。

​ 根据上面的语法,再来完成一个需求

​ 需求:分组统计2015年每月添加的商品数量。

q=*:*,
json.facet={
	month_count:{
		type:range,
		field:item_createtime,
		start:'2015-01-01T00:00:00Z',
		end:'2016-01-01T00:00:00Z',
		gap:'+1MONTH'
	}
}

http://localhost:8080/solr/collection1/select?q=*:*&json.facet=%7B
	month_count:%7B
		type:range,
		field:item_createtime,
		start:'2015-01-01T00:00:00Z',
		end:'2016-01-01T00:00:00Z',
		gap:'%2B1MONTH'
	%7D
%7D

在这里插入图片描述

​ 需求:统计品牌是华为的商品数量,分类是平板电视的商品数量。

q=*:*,
json.facet={
	brand_count:{
		type:query,
		q:"item_brand:华为"
	},
	category_count:{
		type:query,
		q:"item_category:平板电视"
	}
}

http://localhost:8080/solr/collection1/select?q=*:*&json.facet=%7B
	brand_count:%7B
		type:query,
		q:"item_brand:华为"
	%7D,
	category_count:%7B
		type:query,
		q:"item_category:平板电视"
	%7D
%7D

3.3.3 SubFacet

​ 在JSON Facet中,还支持另外一种Facet查询,叫SubFacet.允许用户在之前分组统计的基础上,再次进行分组统计,可以实现pivot Facet(多维度分组查询功能)查询的功能。

​ 需求:对商品的数据,按照品牌进行分组统计,获取前5个分组数据。

q=*:*&
json.facet={
	top_brand:{
		type:terms,
		field:item_brand,
		limit:5
	}
}
http://localhost:8080/solr/collection1/select?q=*:*&
json.facet=%7B
	top_brand:%7B
		type:terms,
		field:item_brand,
		limit:5
	%7D
%7D

在这里插入图片描述

在以上分组的基础上,统计每个品牌,不同商品分类的数据。eg:统计三星手机多少个,三星平板电视多少?

q=*:*&
json.facet={
	top_brand:{
		type:terms,
		field:item_brand,
		limit:5,
		facet:{
			top_category:{
				type:terms,
				field:item_category,
				limit:3
			}
		}
	}
}
http://localhost:8080/solr/collection1/select?q=*:*&
json.facet=%7B
	top_brand:%7B
		type:terms,
		field:item_brand,
		limit:5,
		facet:%7B
			top_category:%7B
				type:terms,
				field:item_category,
				limit:3
			%7D
		%7D
	%7D
%7D

在这里插入图片描述

3.3.4 聚合函数查询

​ 在JSON Facet中,还支持另外一种强大的查询功能,聚合函数查询。在Facet中也提供了很多聚合函数,也成为Facet函数。很多和SQL中的聚合函数类似。

​ 函数如下:

聚合函数例子描述
sumsum(sales)聚合求和
avgavg(popularity)聚合求平均值
minmin(salary)最小值
maxmax(mul(price,popularity))最大值
uniqueunique(author)统计域中唯一值的个数
sumsqsumsq(rent)平方和
percentilepercentile(salary,30,60,10)可以根据百分比进行统计

需求:查询华为手机价格最大值,最小值,平均值.

q=item_title:手机
&fq=item_brand:华为
&json.facet={
	avg_price:"avg(item_price)",
	min_price:"min(item_price)",
	max_price:"max(item_price)"
}

http://localhost:8080/solr/collection1/select?q=item_title:手机
&fq=item_brand:华为
&json.facet=%7B
	avg_price:"avg(item_price)",
	min_price:"min(item_price)",
	max_price:"max(item_price)"
%7D

查询结果:
在这里插入图片描述

需求:查询每个品牌下,最高的手机的价格.

q=item_title:手机
&json.facet={
	categories:{
     type:terms,
     field:item_brand,
     facet:{
       x : "max(item_price)"
     }
   }
}
http://localhost:8080/solr/collection1/select?q=item_title:手机&json.facet=%7Bcategories:%7Btype:field,field:item_brand,facet:%7Bx:"max(item_price)"%7D%7D%7D

结果:
在这里插入图片描述

1.1 facet查询

​ 之前我们讲解Facet查询,我们说他是分为4类。

​ Field,Query,Range(时间范围,数字范围),Interval(和Range类似)

1.1.2 基于Field的Facet查询

​ 需求:对item_title中包含手机的文档,按照品牌域进行分组,并且统计数量;

http://localhost:8080/solr/collection1/select?q=item_title:手机&facet=on&facet.field=item_brand&facet.mincount=1
    @Test
    public void testFacetFieldQuery() throws IOException, SolrServerException {
        //http://localhost:8080/solr/collection1/select?q=item_title:手机&facet=on&facet.field=item_brand&facet.mincount=1
        SolrQuery query=new SolrQuery();
        //查询条件
        query.setQuery("item_title:手机");
        //Facet相关参数
        query.setFacet(true);
        query.addFacetField("item_brand");
        query.setFacetMinCount(1);
        QueryResponse response = httpSolrClient.query(query);
        FacetField facetField = response.getFacetField("item_brand");
        List<FacetField.Count> valueList = facetField.getValues();
        valueList.stream().forEach(System.out::println);
    }
1.1.3 基于Query的Facet查询

​ 需求:查询分类是平板电视的商品数量 ,品牌是华为的商品数量 ,品牌是三星的商品数量,价格在1000-2000的商品数量;

    http://localhost:8080/solr/collection1/select?
    q=*:*&
    facet=on&
    facet.query=item_category:平板电视&
    facet.query=item_brand:华为&
    facet.query=item_brand:三星&
    facet.query=item_price:%5B1000 TO 2000%5D
    @Test
    public void testFacetFieldQuery()throws IOException, SolrServerException {
        //http://localhost:8080/solr/collection1/select?
        //q=*:*&
        //facet=on&
        //facet.query=item_category:平板电视&
        //facet.query=item_brand:华为&
        //facet.query=item_brand:三星&
        //facet.query=item_price:%5B1000 TO 2000%5D
        SolrQuery query=new SolrQuery();
        //查询条件
        query.setQuery("*:*");
        //Facet相关参数
        query.setFacet(true);
        //query.addFacetQuery("item_category:平板电视");
        //query.addFacetQuery("item_brand:华为");
        //query.addFacetQuery("item_brand:三星");
        //query.addFacetQuery("item_price:[1000 TO 2000]");
        //起别名
        query.addFacetQuery("{!key=平板电视}item_category:平板电视");
        query.addFacetQuery("{!key=华为品牌}item_brand:华为");
        query.addFacetQuery("{!key=三星品牌}item_brand:三星");
        query.addFacetQuery("{!key=1000到2000}item_price:[1000 TO 2000]");
        QueryResponse response = httpSolrClient.query(query);
        Map<String, Integer> facetQuery = response.getFacetQuery();
        for (String key : facetQuery.keySet()) {
            System.out.println(key+"--"+facetQuery.get(key));
        }
    }
1.1.4 基于Range的Facet查询

​ 需求:分组查询价格0-2000 ,2000-4000,4000-6000....18000-20000每个区间商品数量

q=*:*&
facet=on&
facet.range=item_price&
facet.range.start=0&
facet.range.end=20000&
facet.range.gap=2000
    @Test
    public void testFacetFieldQuery()throws IOException, SolrServerException {
        //http://localhost:8080/solr/collection1/select?
        //q=*:*&
        //facet=on&
        //facet.range=item_price&
        //facet.range.start=0&
        //facet.range.end=20000&
        //facet.range.gap=2000
        SolrQuery query=new SolrQuery();
        //查询条件
        query.setQuery("*:*");
        //Facet相关参数
        query.setFacet(true);
        query.addNumericRangeFacet("item_price",0,20000,2000);
        QueryResponse response = httpSolrClient.query(query);
        List<RangeFacet> facetRanges = response.getFacetRanges();
        for (RangeFacet facetRange : facetRanges) {
            System.out.println(facetRange.getName());
            List<RangeFacet.Count> counts = facetRange.getCounts();
            for (RangeFacet.Count count : counts) {
                System.out.println(count.getValue()+"---"+count.getCount());
            }
        }
    }

需求:统计2015年每个季度添加的商品数量

http://localhost:8080/solr/collection1/select?
q=*:*&
facet=on&
facet.range=item_createtime&
facet.range.start=2015-01-01T00:00:00Z&
facet.range.end=2016-01-01T00:00:00Z&
facet.range.gap=%2B3MONTH

    @Test
    public void testFacetFieldQuery() throws IOException, SolrServerException, ParseException {
        //http://localhost:8080/solr/collection1/select?
        //q=*:*&
        //facet=on&
        //facet.range=item_createtime&
        //facet.range.start=2015-01-01T00:00:00Z&
        //facet.range.end=2016-01-01T00:00:00Z&
        //facet.range.gap=%2B3MONTH
        SolrQuery query=new SolrQuery();
        //查询条件
        query.setQuery("*:*");
        //Facet相关参数
        query.setFacet(true);
        Date start =  new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2015-01-01 00:00:00");
        Date end = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2016-01-01 00:00:00");
        query.addDateRangeFacet("item_createtime",start,end,"+3MONTH");
        QueryResponse response = httpSolrClient.query(query);
        List<RangeFacet> facetRanges = response.getFacetRanges();
        for (RangeFacet facetRange : facetRanges) {
            System.out.println(facetRange.getName());
            List<RangeFacet.Count> counts = facetRange.getCounts();
            for (RangeFacet.Count count : counts) {
                System.out.println(count.getValue()+"---"+count.getCount());
            }
        }
    }
1.1.5 基于Interval的Facet查询

​ 需求:统计item_price在0-1000和0-100商品数量和item_createtime是2019年~现在添加的商品数量

&facet=on
&facet.interval=item_price
&f.item_price.facet.interval.set=[0,1000]
&f.item_price.facet.interval.set=[0,100]
&facet.interval=item_createtime
&f.item_createtime.facet.interval.set=[2019-01-01T0:0:0Z,NOW]
由于有特殊符号需要进行URL编码[---->%5B   ]---->%5D
http://localhost:8080/solr/collection1/select?q=*:*&facet=on&facet.interval=item_price&f.item_price.facet.interval.set=%5B0,1000%5D&f.item_price.facet.interval.set=%5B0,100%5D&facet.interval=item_createtime&f.item_createtime.facet.interval.set=%5B2019-01-01T0:0:0Z,NOW%5D
    @Test
    public void testFacetFieldQuery() throws IOException, SolrServerException, ParseException {
        //&facet=on
        //&facet.interval=item_price
        //&f.item_price.facet.interval.set=[0,1000]
        //&f.item_price.facet.interval.set=[0,100]
        //&facet.interval=item_createtime
        //&f.item_createtime.facet.interval.set=[2019-01-01T0:0:0Z,NOW]
        SolrQuery query=new SolrQuery();
        //查询条件
        query.setQuery("*:*");
        //Facet相关参数
        query.setFacet(true);
        query.addIntervalFacets("item_price", new String[]{"[0,10]"});
        query.addIntervalFacets("item_createtime", new String[]{"[2019-01-01T0:0:0Z,NOW]"});
        QueryResponse response = httpSolrClient.query(query);

        /*
            item_price: {
            [0,10]: 11
            },
            item_createtime: {
            [2019-01-01T0:0:0Z,NOW]: 22
            }
         */
        List<IntervalFacet> intervalFacets = response.getIntervalFacets();
        for (IntervalFacet intervalFacet : intervalFacets) {
            String field = intervalFacet.getField();
            System.out.println(field);
            List<IntervalFacet.Count> intervals = intervalFacet.getIntervals();
            for (IntervalFacet.Count interval : intervals) {
                System.out.println(interval.getKey());
                System.out.println(interval.getCount());
            }

        }
    }
1.1.6 Facet维度查询

​ 需求:统计每一个品牌和其不同分类商品对应的数量;

​ 联想 手机 10

​ 联想 电脑 2

​ 华为 手机 10

​ ...

 http://localhost:8080/solr/collection1/select?q=*:*&
 &facet=on
 &facet.pivot=item_brand,item_category
    @Test
    public void testFacetFieldQuery() throws IOException, SolrServerException, ParseException {
        // http://localhost:8080/solr/collection1/select?q=*:*&
        // &facet=on
        // &facet.pivot=item_brand,item_category
        SolrQuery query=new SolrQuery();
        //查询条件
        query.setQuery("*:*");
        //Facet相关参数
        query.setFacet(true);
        query.addFacetPivotField("item_brand,item_category");

        QueryResponse response = httpSolrClient.query(query);

        NamedList<List<PivotField>> facetPivot = response.getFacetPivot();
        List<PivotField> pivotFields = facetPivot.get("item_brand,item_category");
        for (PivotField pivotField : pivotFields) {
            String field = pivotField.getField();
            Object value = pivotField.getValue();
            int count = pivotField.getCount();
            System.out.println(field+"-"+value+"-"+count);

            List<PivotField> fieldList = pivotField.getPivot();
            for (PivotField pivotField1 : fieldList) {
                String field1 = pivotField1.getField();
                Object value1 = pivotField1.getValue();
                int count1 = pivotField1.getCount();
                System.out.println(field1+"-"+value1+"-"+count1);
            }
        }
    }

​ 到这关于SolrJ和Facet查询相关的操作就讲解完毕。

1.2 group查询
1.2.1 基础的分组

​ 需求:查询Item_title中包含手机的文档,按照品牌对文档进行分组;同组中的文档放在一起。

http://localhost:8080/solr/collection1/select?
q=item_title:手机
&group=true
&group.field=item_brand
@Test
    public void testGroupQuery() throws IOException, SolrServerException, ParseException {
        SolrQuery params = new SolrQuery();
        params.setQuery("item_title:手机");

        /**
         * q=item_title:手机
         * &group=true
         * &group.field=item_brand
         */
        //注意solrJ中每没有提供分组特有API。需要使用set方法完成
        params.setGetFieldStatistics(true);
        params.set(GroupParams.GROUP, true);
        params.set(GroupParams.GROUP_FIELD,"item_brand");

        QueryResponse response = httpSolrClient.query(params);
        GroupResponse groupResponse = response.getGroupResponse();
        //由于分组的字段可以是多个。所以返回数组
        List<GroupCommand> values = groupResponse.getValues();
        //获取品牌分组结果
        GroupCommand groupCommand = values.get(0);
        //匹配到的文档数量
        int matches = groupCommand.getMatches();
        System.out.println(matches);
        //每个组合每个组中的文档信息
        List<Group> groups = groupCommand.getValues();
        for (Group group : groups) {
            //分组名称
            System.out.println(group.getGroupValue());
            //组内文档
            SolrDocumentList result = group.getResult();
            System.out.println(group.getGroupValue() +":文档个数" + result.getNumFound());
            for (SolrDocument entries : result) {
                System.out.println(entries);
            }
        }
    }
1.2.2 group分页

​ 默认情况下分组结果中只会展示前10个组,并且每组展示相关对最高的1个文档。我们可以使用start和rows可以设置组的分页,使用group.limit和group.offset设置组内文档分页。

q=item_title:手机&group=true&group.field=item_brand&start=0&rows=3
&group.limit=5&group.offset=0

​ 展示前3个组及每组前5个文档。

 //设置组的分页参数
 params.setStart(0);
 params.setRows(3);
 //设置组内文档的分页参数
 params.set(GroupParams.GROUP_OFFSET, 0);
 params.set(GroupParams.GROUP_LIMIT, 5);
1.2.3 group排序

​ 之前讲解排序的时候,group排序分为组排序,组内文档排序;对应的参数为sort和group.sort

​ 需求:按照组内价格排序降序;

 params.set(GroupParams.GROUP_SORT, "item_price desc");

​ 当然分组还有其他的用法,都是大同小异,大家可以参考我们之前讲解分组的知识;

1.3 高亮
1.3.1 高亮查询

​ 查询item_title中包含手机的文档,并且对item_title中的手机关键字进行高亮;

   http://localhost:8080/solr/collection1/select?
    q=item_title:手机
    &hl=true
    &hl.fl=item_title
    &hl.simple.pre=<font>
    &h1.simple.post=</font>
@Test
    public void testHighlightingQuery() throws IOException, SolrServerException {
        SolrQuery params = new SolrQuery();
        params.setQuery("item_title:三星手机");
        //开启高亮
        params.setHighlight(true);
        //设置高亮域
        //高亮的前后缀
        params.addHighlightField("item_title");
        params.setHighlightSimplePre("<font>");
        params.setHighlightSimplePost("</font>");

        QueryResponse response = httpSolrClient.query(params);

        SolrDocumentList results = response.getResults();
        for (SolrDocument result : results) {
            System.out.println(result);
        }
        
        //解析高亮
        Map<String, Map<String, List<String>>> highlighting = response.getHighlighting();
        //map的key是文档id,map的value包含高亮的数据
        for (String id : highlighting.keySet()) {
            System.out.println(id);
            /**
             * item_title: [
             * "飞利浦 老人<em>手机</em> (X2560) 深情蓝 移动联通2G<em>手机</em> 双卡双待"
             * ]
             */
            Map<String, List<String>> highLightData = highlighting.get(id);
            //highLightData key包含高亮域名
            //获取包含高亮的数据
            if(highLightData != null && highLightData.size() > 0) {
                //[
                //             * "飞利浦 老人<em>手机</em> (X2560) 深情蓝 移动联通2G<em>手机</em> 双卡双待"
                //             * ]
                List<String> stringList = highLightData.get("item_title");
                if(stringList != null && stringList.size() >0) {
                    String title = stringList.get(0);
                    System.out.println(title);
                }
            }
        }

        //将高亮的数据替换到原有文档中。

    }
1.3.2 高亮器的切换

​ 当然我们也可以使用SolrJ完成高亮器的切换。之前我们讲解过一个高亮器fastVector,可以实现域中不同的词使用不同颜色。

​ 查询item_title中包含三星手机的文档.item_title中三星手机中不同的词,显示不同的颜色;

http://localhost:8080/solr/collection1/select?
q=item_title:三星手机
&hl=true
&hl.fl=item_title
&hl.method=fastVector
SolrQuery params = new SolrQuery();
params.setQuery("item_title:三星手机");
//开启高亮
params.setHighlight(true);
params.addHighlightField("item_title");
params.set("hl.method","fastVector");

QueryResponse response = httpSolrClient.query(params);

​ 到这使用SolrJ进行高亮查询就讲解完毕。

1.4 suggest查询
1.4.1 spell-checking 拼写检查。

​ 需求:查询item_title中包含iphone的内容。要求进行拼写检查。

http://localhost:8080/solr/collection1/select?
q=item_title:iphonx&spellcheck=true

@Test
    public void test01() throws IOException, SolrServerException {
        SolrQuery params = new SolrQuery();
        params.setQuery("item_title:iphonxx");
        params.set("spellcheck",true);
        QueryResponse response = httpSolrClient.query(params);
        /**
         * suggestions: [
         * "iphonxx",
         * {
         * numFound: 1,
         * startOffset: 11,
         * endOffset: 18,
         * suggestion: [
         * "iphone6"
         * ]
         * }
         * ]
         */
        SpellCheckResponse spellCheckResponse = response.getSpellCheckResponse();
        Map<String, SpellCheckResponse.Suggestion> suggestionMap = spellCheckResponse.getSuggestionMap();
        for (String s : suggestionMap.keySet()) {
            //错误的词
            System.out.println(s);
            //建议的词
            SpellCheckResponse.Suggestion suggestion = suggestionMap.get(s);

            List<String> alternatives = suggestion.getAlternatives();
            System.out.println(alternatives);
        }

    }

1.4.2Auto Suggest自动建议。

上面我们讲解完毕拼写检查,下面我们讲解自动建议,自动建议也是需要在SolrConfig.xml中进行相关的配置。

需求:查询三星,要求基于item_title域进行自动建议

http://localhost:8080/solr/collection1/select?
q=三星&suggest=true&suggest.dictionary=mySuggester&suggest.count=5

@Test
    public void test02() throws IOException, SolrServerException {
        SolrQuery params = new SolrQuery();
        //设置参数
        params.setQuery("java");
        //开启自动建议
        params.set("suggest",true);
        //指定自动建议的组件
        params.set("suggest.dictionary","mySuggester");
      
        QueryResponse response = httpSolrClient.query(params);
        SuggesterResponse suggesterResponse = response.getSuggesterResponse();
        Map<String, List<Suggestion>> suggestions = suggesterResponse.getSuggestions();
        for (String key : suggestions.keySet()) {
            //词
            System.out.println(key);
            List<Suggestion> suggestionList = suggestions.get(key);
            for (Suggestion suggestion : suggestionList) {
                String term = suggestion.getTerm();
                System.out.println(term);
            }
        }
    }
1.5 使用SolrJ完成Core的管理
1.5.1 Core添加

​ 1.要想完成SolrCore的添加,在solr_home必须提前创建好SolrCore的目录结构,而且必须包相关的配置文件。
在这里插入图片描述

修改配置文件中url:http://localhost:8080/solr
CoreAdminRequest.createCore("collection4", "D:\\solr_home\\collection4", solrClient );
1.5.2 重新加载Core

​ 从Solr中移除掉,然后在添加。

CoreAdminRequest.reloadCore("collection4",  solrClient );
1.5.3 重命名Core
CoreAdminRequest.renameCore("collection4","newCore" , solrClient)
1.5.4 卸载solrCore

​ 卸载仅仅是从Solr中将该Core移除,但是SolrCore的物理文件依然存在

CoreAdminRequest.unloadCore("collection4",  solrClient );
1.5.5 solrCore swap
 CoreAdminRequest.swapCore("collection1", "collection4", solrClient);

2. Solr集群搭建

2.1 SolrCloud简介

2.1.1 什么是SolrCloud

​ Solr集群也成为SolrCloud,是Solr提供的分布式搜索的解决方案,当你需要大规模存储数据或者需要分布式索引和检索能力时使用 SolrCloud。

​ 所有数据库集群,都是为了解决4个核心个问题,单点故障,数据扩容,高并发,效率

​ 搭建SolrCloud是需要依赖一个中间件Zookeeper,它的主要思想是使用Zookeeper作为集群的配置中心。

2.1.2 SolrCloud架构

SolrCloud逻辑概念:

​ 一个Solr集群是由多个collection组成,collection不是SolrCore只是一个逻辑概念。一个collection是由多个文档组成,这些文档归属于指定的分片。下面表示的就是一个逻辑结构图。

​ 该集群只有一个collection组成。connection中的文档数据分散在4个分片。
在这里插入图片描述

​ 分片中的数据,到底存储在那呢?这就属于物理概念。

SolrCloud物理概念:

​ Solr 集群由一个或多个 Solr服务(tomcat中运行solr服务)组成,这些Solr服务可以部署一台服务器上,也可以在多台服务器。每个Solr服务可以包含一个或者多个Solr Core 。SolrCore中存储的是Shard的数据;

​ 下面的图是一个物理结构和逻辑结构图。
在这里插入图片描述

概念:

​ Collection是一个逻辑概念,可以认为是多个文档构成的一个集合。

​ Shard是一个逻辑概念,是对Collection进行逻辑上的划分。

​ Replica:Shard的一个副本,一个Shard有多个副本,同一个Shard副本中的数据一样;

​ Leader:每个Shard会以多个Replica的形式存在,其中一个Replica会被选为Leader,负责集群环境中的索引和搜索。

​ SolrCore:存储分片数据的基本单元。一个SolrCore只存储一个分片数据,一个分片数据可以存储到多个SolrCore中;一个SolrCore对应一个Replica

​ Node:集群中的一个Solr服务

2.2 Linux集群搭建

2.2.1 基于tomcat的集群搭建

2.2.1.1集群架构介绍

​ 物理上:

​ 搭建三个Zookeeper组成集群,管理SolrCloud。

​ 搭建四个Solr服务,每个Solr服务一个SorCore.

​ 逻辑上:

​ 整个SolrCloud一个Connection;

​ Connection中的数据分为2个Shard;

​ Shard1的数据在物理上存储到solr1和solr2的SolrCore

​ Shard2的数据在物理上存储到solr3和solr4的SolrCore

在这里插入图片描述

2.2.1.2 环境说明

​ 环境和我们之前搭建单机版的环境相同。

系统版本
LinuxCentOS 7
JDKJDK8
Tomcattomcat 8.5
zookeeperzookeeper-3.4.14
solrsolr 7.7.2
2.2.1.3 Zookeeper集群搭建

​ 首先我们先要完成Zookeeper集群搭建。在搭建集群之前,首先我们先来说一下Zookeeper集群中要求节点的个数。

​ 在Zookeeper集群中节点的类型主要有2类,Leader,Follower,一个集群只有一个Leader。

​ 到底谁是Leader呢?

​ 投票选举制度:集群中所有的节点来进行投票,半数以上获票的节点就是Leader.Zookeeper要求集群节点个数奇数。

​ 容错:zookeeper集群一大特性是只要集群中半数以上的节点存活,集群就可以正常提供服务,而2n+1节点和2n+2个节点的容错能力相同,都是允许n台节点宕机。本着节约的宗旨,一般选择部署2n+1台机器

​ 本次我们采用最少的集群节点3个。

在这里插入图片描述

​ 1.下载Zookeeper的安装包到linux。

​ 下载地址:

http://archive.apache.org/dist/zookeeper/zookeeper-3.4.14/		
上面的地址可能受每日访问量现在							
wget
https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz

​ 2. 解压zookeeper, 复制三份到/usr/local/solrcloud下,复制三份分别并将目录名改为zk01、在zk02、zk03

mkdir  /usr/local/solrcloud
tar -xzvf zookeeper-3.4.14.tar.gz
cp -r zookeeper-3.4.14 /usr/local/solrcloud/zk01
cp -r zookeeper-3.4.14 /usr/local/solrcloud/zk02
cp -r zookeeper-3.4.14 /usr/local/solrcloud/zk03

​ 结果
在这里插入图片描述

  1. 为每一个Zookeeper创建数据目录

    mkdir zk01/data
    mkdir zk02/data
    mkdir zk03/data
    

在这里插入图片描述

  1. 在data目录中为每一个Zookeeper创建myid文件,并且在文件中指定该Zookeeper的id;

    使用重定向指令>>将打印在控制台上的1 2 3分别写入到data目录的myid文件中。
    echo 1 >> zk01/data/myid
    echo 2 >> zk02/data/myid
    echo 3 >> zk03/data/myid
    

在这里插入图片描述

  1. 修改Zookeeper的配置文件名称。

    ​ 5.1 需要将每个zookeeper/conf目录中的zoo_sample.cfg 文件名改名为zoo.cfg,否则该配置文件不起作用。

    mv zk01/conf/zoo_sample.cfg zk01/conf/zoo.cfg
    mv zk02/conf/zoo_sample.cfg zk02/conf/zoo.cfg
    mv zk03/conf/zoo_sample.cfg zk03/conf/zoo.cfg
    

在这里插入图片描述

​ 6. 编辑zoo.cfg配置文件

​ 5.1 修改Zookeeper客户端连接的端口,Zookeeper对外提供服务的端口。修改目的,我们需要在一台计算机上启动三个Zookeeper。

clientPort=2181
clientPort=2182
clientPort=2183

​ 5.2 修改数据目录

dataDir=/usr/local/solrcloud/zk01/data
dataDir=/usr/local/solrcloud/zk02/data
dataDir=/usr/local/solrcloud/zk03/data

​ 5.3 在每一个配置文件的尾部加入,指定集群中每一个Zookeeper的节点信息。

server.1=192.168.200.128:2881:3881
server.2=192.168.200.128:2882:3882
server.3=192.168.200.128:2883:3883

server.1 server.2 server.3 指定Zookeeper节点的id.之前我们myid已经指定了每个节点的id;
内部通信端口:
	Zookeeper集群节点相互通信端口;
为什么会有投票端口呢?
1.因为三台Zookeeper要构成集群,决定谁是leader,Zookeeper中是通过投票机制决定谁是leader。
2.如果有节点产生宕机,也需要通过投票机制将宕机节点从集群中剔除。
所以会有一个投票端口

​ 7.启动Zookeeper集群。

​ 在bin目录中提供了一个脚本zkServer.sh,使用zkServer.sh start|restart|stop|status 就可以完成启动,重启,停止,查看状态。

可以写一个脚本性启动所有Zookeeper。也可以一个个的启动;
./zk01/bin/zkServer.sh start
./zk02/bin/zkServer.sh start
./zk03/bin/zkServer.sh start

在这里插入图片描述

8.查看Zookeeper集群状态。

./zk01/bin/zkServer.sh status
./zk02/bin/zkServer.sh status
./zk03/bin/zkServer.sh status

在这里插入图片描述

follower就是slave;

leader就是master;

如果是单机版的Zookeeper,standalone

到这关于Zookeeper集群的搭建,我们就讲解完毕。

2.2.1.4 SolrCloud集群部署

​ 上一节我们已经完成了Zookeeper集群的搭建,下面我们来搭建solr集群。

在这里插入图片描述

​ 复制4个tomcat,并且要在4个tomcat中部署solr,单机版solr的搭建,我们之前已经讲解过了。

​ 下面是我们已经搭建好的单机版的solr,他的tomcat和solrhome
在这里插入图片描述

1.1 将tomcat复制4份到solrcloud中,便于集中管理。这4个tomcat中都部署了solr;

cp -r apache-tomcat-8.5.50 solrcloud/tomcat1
cp -r apache-tomcat-8.5.50 solrcloud/tomcat2
cp -r apache-tomcat-8.5.50 solrcloud/tomcat3
cp -r apache-tomcat-8.5.50 solrcloud/tomcat4

在这里插入图片描述

1.2 复制4个solrhome到solrcloud中。分别对应每一个solr的solrhome。

cp -r solr_home solrcloud/solrhome1
cp -r solr_home solrcloud/solrhome2
cp -r solr_home solrcloud/solrhome3
cp -r solr_home solrcloud/solrhome4

在这里插入图片描述

1.3 修改tomcat的配置文件,修改tomcat的端口,保证在一台计算机上,可以启动4个tomcat;

​ 编辑server.xml

修改停止端口
对外提供服务端口
AJP端口
<Server port="8005"shutdown="SHUTDOWN">
<Connector port="8080"protocol="HTTP/1.1" connectionTimeout="20000" redirect
<Connector port="8009" protocol="AJP/1.3"redirectPort="8443" />
tomcat1  8105  8180  8109
tomcat1  8205  8280  8209
tomcat1  8305  8380  8309
tomcat1  8405  8480  8409

1.4 为tomcat中每一个solr指定正确的solrhome,目前是单机版solrhome的位置。

​ 编辑solr/web.xml指定对应的solrhome

	 <env-entry>
		<env-entry-name>solr/home</env-entry-name>
		<env-entry-value>/usr/local/solrcloud/solrhomeX</env-entry-value>
		<env-entry-type>java.lang.String</env-entry-type>
  	</env-entry>

1.5 修改每个solrhome中solr.xml的集群配置,对应指定tomcat的ip和端口

​ 编辑solrhome中solr.xml

 <solrcloud>
    <str name="host">192.168.200.1288</str>
    <int name="hostPort">8X80</int>
 </solrcloud>
2.2.1.5 Zookeeper管理的Solr集群

​ 上一节课我们已经搭建好了Zookeeper集群,和Solr的集群。但是现在Zookeeper集群和Solr集群没有关系。

需要让Zookeeper管理Solr集群。
1.启动所有的tomcat

 ./tomcat1/bin/startup.sh
 ./tomcat2/bin/startup.sh
 ./tomcat3/bin/startup.sh
 ./tomcat4/bin/startup.sh

​ 2.让每一个solr节点和zookeeper集群关联

​ 编辑每一个tomcat中bin/catalina.sh文件,指定Zookeeper集群地址。

  JAVA_OPTS="-DzkHost=192.168.200.128:2181,192.168.200.128:2182,192.168.200.128:2183"
  需要指定客户端端口即Zookeeper对外提供服务的端口

​ 3.让zookeeper统一管理solr集群的配置文件。

​ 因为现在我们已经是一个集群,集群中的每个solr节点配置文件需要一致。所以我们就需要让zookeeper管理solr集群的配置文件。主要管理的就是solrconfig.xml / schema.xml文件

下面的命令就是将conf目录中的配置文件,上传到Zookeeper,以后集群中的配置文件就以Zookeeper中的为准;
搭建好集群后,solrCore中是没有配置文件的。
./zkcli.sh -zkhost 192.168.200.128:2181,192.168.200.128:2182,192.168.200.128:2183 -cmd upconfig -confdir /usr/local/solrcloud/solrhome1/collection1/conf -confname myconf

-zkhost:指定zookeeper的地址列表;
upconfig :上传配置文件;
-confdir :指定配置文件所在目录;
-confname:指定上传到zookeeper后的目录名;

进入到solr安装包中
/root/solr-7.7.2/server/scripts/cloud-scripts
./zkcli.sh -zkhost 192.168.200.128:2181,192.168.200.128:2182,192.168.200.128:2183 -cmd upconfig -confdir /usr/local/solrcloud/solrhome1/collection1/conf -confname myconf


./zkcli.sh -zkhost 192.168.200.128:2181,192.168.200.128:2182,192.168.200.128:2183 -cmd upconfig -confdir /root/solr7.7.2/server/solr/configsets/sample_techproducts_configs/conf
 -confname myconf

在这里插入图片描述

​ 4.查看Zookeeper中的配置文件。

​ 登录到任意一个Zookeeper上,看文件是否上传成功。

进入到任意一个Zookeeper,查看文件的上传状态。
/zKCli.sh -server 192.168.200.128:2182

​ 通过Zookeeper客户端工具查看。idea的Zookeeper插件,ZooInspector等。

  1. 由于solr的配置文件,已经交由Zookeeper管理。所有SolrHome中的SolrCore就可以删除。

​ 到这Zookeeper集群和Solr集群整合就讲解完毕。

2.2.1.6 创建Solr集群的逻辑结构

​ 之前我们已经搭建好了Zookeeper集群及Solr集群。接下来我们要完成的是逻辑结构的创建。

在这里插入图片描述

​ 我们本次搭建的逻辑结构整个SolrCloud是由一个Collection构成。Collection分为2个分片。每个分片有2个副本。分别存储在不同的SolrCore中。

​ 启动所有的tomcat

 ./tomcat1/bin/startup.sh
 ./tomcat2/bin/startup.sh
 ./tomcat3/bin/startup.sh
 ./tomcat4/bin/startup.sh

​ 访问任4个solr管理后台;

http://192.168.200.131:8180/solr/index.html#/~collections
http://192.168.200.131:8280/solr/index.html#/~collections
http://192.168.200.131:8380/solr/index.html#/~collections
http://192.168.200.131:8480/solr/index.html#/~collections

​ 使用任何一个后台管理系统,创建逻辑结构

在这里插入图片描述

name:指定逻辑结构collection名称;

config set:指定使用Zookeeper中配置文件;

numShards:分片格式

​replicationFactory:每个分片副本格式;

​ 当然我们也可以通过Solr RestAPI完成上面操作

http://其中一个solr节点的IP:8983/solr/admin/collections?action=CREATE&name=testcore&numShards=2&replicationFactor=2&collection.configName=myconf

​ 查询逻辑结构

在这里插入图片描述

​ 查看物理结构

在这里插入图片描述

说明一下: 原来SolrCore的名字已经发生改变。

​ 一旦我们搭建好集群后,每个SolrCore中是没有conf目录即没有配置文件。

​ 整个集群的配置文件只有一份,Zookeeper集群中的配置文件。

2.2.1.7测试集群

​ 向collection中添加数据

{id:"100",name:"zhangsan"}

​ 由于整合集群逻辑上就一个collection,所以在任何一个solr节点都可以获取数据。

2.2.1.8 使用SolrJ操作集群

​ 之前我们操作单节点的Solr,使用的是HttpSolrClient,操作集群我们使用的是CloudSolrClient。

  1. 将CloudSolrClient交由spring管理。
    @Bean
    public CloudSolrClient cloudSolrClient() {
         //指定Zookeeper的地址。
        List<String> zkHosts = new ArrayList<>();
        zkHosts.add("192.168.200.131:2181");
        zkHosts.add("192.168.200.131:2182");
        zkHosts.add("192.168.200.131:2183");
        //参数2:指定
        CloudSolrClient.Builder builder = new CloudSolrClient.Builder(zkHosts,Optional.empty());
        CloudSolrClient solrClient = builder.build();
        //设置连接超时时间
        solrClient.setZkClientTimeout(30000);
        solrClient.setZkConnectTimeout(30000);
        //设置collection
        solrClient.setDefaultCollection("collection");
        return solrClient;
    }

​ 2.使用CloudSolrClient中提供的API操作Solr集群,和HttpSolrClient相同

索引


	@Autowired
    private CloudSolrClient cloudSolrClient;
        @Test
        public void testSolrCloudAddDocument() throws Exception {
            SolrInputDocument doc = new SolrInputDocument();
            doc.setField("id", 1);
            doc.setField("name", "java");
            cloudSolrClient.add(doc);
			cloudSolrClient.commit();
        }

搜索

 @Test
    public void testSolrQuery() throws Exception {
        SolrQuery params = new SolrQuery();
        params.setQuery("*:*");
        QueryResponse response = cloudSolrClient.query(params);
        SolrDocumentList results = response.getResults();
        for (SolrDocument result : results) {
            System.out.println(result);
        }
    }

2.2.2 SolrCloud的其他操作

2.2.2.1 SolrCloud使用中文分词器(IK)

​ 1.在每一个solr服务中,引入IK分词器的依赖包

在这里插入图片描述

​ 2.在classes中引入停用词和扩展词库及配置文件

在这里插入图片描述

​ 3.重启所有的tomcat

​ 4.修改单机版solrcore中schema,加入FiledType

<fieldType name ="text_ik" class ="solr.TextField">
    <analyzer class="org.wltea.analyzer.lucene.IKAnalyzer"/>
</fieldType>

​ 5.将schema重新提交到Zookeeper
​ Solr集群中的配置文件统一由Zookeeper进行管理,所以如果需要修改配置文件,我们需要将修改的文件上传的Zookeeper。

./zkcli.sh -zkhost 192.168.200.128:2181,192.168.200.128:2182,192.168.200.128:2183 -cmd putfile /configs/myconf/managed-schema /usr/local/solr_home/collection1/conf/managed-schema

​ 6. 测试
在这里插入图片描述

​ 7.利用text_ik 创建相关的业务域

​ 创建业务域,我们依然需要修改shema文件,这个时候,我们建议使用后台管理系统。

在这里插入图片描述

8.查看schema文件是否被修改。

​ 通过files查看集群中的配置文件,内容

在这里插入图片描述

​ 到这关于如何修改集群中的配置文件,已经如何管理Filed我们就讲解完毕。

2.2.2.2 查询指定分片数据

​ 在SolrCloud中,如果我们想查询指定分片的数据也是允许的。

​ 需求:查询shard1分片的数据

http://192.168.200.128:8180/solr/myCollection/select?q=*:*&shards=shard1

在这里插入图片描述

​ 需求:查询shard1和shard2分片的数据

http://192.168.200.128:8180/solr/myCollection/select?q=*:*&shards=shard1,shard2

​ 上面的操作都是从根据shard的id随机从shard的副本中获取数据。shard1的数据可能来自8380,8280

shard2的数据可能来自8480,8180
在这里插入图片描述

​ 也可以指定具体的副本;

​ 需求:获取shard1分片8280副本中的数据

http://192.168.200.128:8180/solr/myCollection/select?q=*:*&shards=192.168.200.128:8280/solr/myCollection

​ 需求:获取8280和8380副本中数据

http://192.168.200.128:8180/solr/myCollection/select?q=*:*&shards=192.168.200.128:8280/solr/myCollection,192.168.200.128:8380/solr/myCollection

​ 混合使用,通过Shard的ID随机获取+通过副本获取。

http://192.168.200.128:8180/solr/myCollection/select?q=*:*&shards=shard1,192.168.200.128:8380/solr/myCollection
2.2.2.3 SolrCloud并发和线程池相关的一些配置

​ 在SolrCloud中,我们也可以配置Shard并发和线程相关的配置,来优化集群性能。

​ 主要的配置在solr.xml文件。

<solr>
  <!--集群相关的配置,solr服务的地址,端口,上下文solr.是否自动生成solrCore名称,zk超时时间-->
  <solrcloud>  
    <str name="host">192.168.200.131</str>
    <int name="hostPort">8180</int>
    <str name="hostContext">${hostContext:solr}</str>
    <bool name="genericCoreNodeNames">${genericCoreNodeNames:true}</bool>
    <int name="zkClientTimeout">${zkClientTimeout:30000}</int>
    <int name="distribUpdateSoTimeout">${distribUpdateSoTimeout:600000}</int>
    <int name="distribUpdateConnTimeout">${distribUpdateConnTimeout:60000}</int>
    <str name="zkCredentialsProvider">${zkCredentialsProvider:org.apache.solr.common.cloud.DefaultZkCredentialsProvider}</str>
    <str name="zkACLProvider">${zkACLProvider:org.apache.solr.common.cloud.DefaultZkACLProvider}</str>
  </solrcloud>
	

<!-- 分片并发和线程相关的信息-->
  <shardHandlerFactory name="shardHandlerFactory"
    class="HttpShardHandlerFactory">
    <int name="socketTimeout">${socketTimeout:600000}</int>
    <int name="connTimeout">${connTimeout:60000}</int>
    <str name="shardsWhitelist">${solr.shardsWhitelist:}</str>
  </shardHandlerFactory>

</solr>

相关参数

参数名描述默认值
socketTimeout指客户端和服务器建立连接后,客户端从服务器读取数据的timeoutdistribUpdateSoTimeout
connTimeout指客户端和服务器建立连接的timeoutdistribUpdateConnTimeout
maxConnectionsPerHost最大并发数量20
maxConnections最大连接数量10000
corePoolSize线程池初始化线程数量0
maximumPoolSize线程池中线程的最大数量Integer.MAX_VALUE
maxThreadldleTime设置线程在被回收之前空闲的最大时间5秒
sizeOfQueue指定此参数 ,那么线程池会使用队列来代替缓冲区,对于追求高吞吐量的系统而 ,可能希望配置为-1 。对于于追求低延迟的系统可能希望配置合理的队列大小来处理请求 。-1
fairnessPolicy用于选择JVM特定的队列的公平策略:如果启用公平策略,分布式查询会以先进先出的方式来处理请求,但是是是以有损吞吐量为代价;如果禁用公平策略,会提高集群查询吞吐量,但是以响应延迟为代价。false
useRetries是否启 HTTP 连接自动重试机制,避免由 HTTP 连接池 的限制或者竞争导致的 IOException ,默认未开启false

2.3.2 基于docker的集群搭建

2.3.2.1 环境准备

​ 1.搭建docker

​ 要想在docker上搭建solr集群,首先安装docker的环境。这个就不再演示,如果没有学过docker的同学可以参考下面的视频地址进行学习。

​ Docker容器化技术入门与应用-JavaEE免费视频课程-博学谷

​ 如果学习过但是忘了如何搭建,参考一下地址。

​ CentOS Docker 安装 | 菜鸟教程

  1. 拉取zookeeper镜像和solr的镜像,采用的版本是3.4.14和7.7.2

    docker pull zookeeper:3.4.14
    docker pull solr:7.7.2
    

    查看拉取镜像

    docker images
    

在这里插入图片描述

2.3.2.2 搭建zookeeper集群

​ 搭建zookeeper集群,我们需要利用我们刚才拉取的zookeeper的镜像创建3个容器。并且让他们产生集群关系。

​ 单独创建一个桥接网卡

docker network create itcast-zookeeper
docker network ls
1.容器1创建
docker run 
	 -id 
	 --restart=always 
     -v /opt/docker/zookeeper/zoo1/data:/data 
     -v /opt/docker/zookeeper/zoo1/datalog:/datalog 
     -e ZOO_MY_ID=1 
     -e ZOO_SERVERS="server.1=zookeeper1:2888:3888 server.2=zookeeper2:2888:3888 server.3=zookeeper3:2888:3888" 
     -p 2181:2181
     --name=zookeeper1 
     --net=itcast-zookeeper
     --privileged 
     zookeeper:3.4.14
 
 docker run -id --restart=always -v /opt/docker/zookeeper/zoo1/data:/data -v /opt/docker/zookeeper/zoo1/datalog:/datalog -e ZOO_MY_ID=1 -e ZOO_SERVERS="server.1=zookeeper1:2888:3888 server.2=zookeeper2:2888:3888 server.3=zookeeper3:2888:3888" -p 2181:2181  --name=zookeeper1 --privileged --net=itcast-zookeeper zookeeper:3.4.14
 
 说明:
 	 --restart:docker重启,容器重启。
 	-d:守护式方式运行方式,除此之外还有-it
 	-v:目录挂载,用宿主机/opt/docker/zookeeper/zoo1/data,映射容器的/data目录
 	-e:指定环境参数。分别指定了自己的id,集群中其他节点的地址,包含通信端口和投票端口
 	--name:容器的名称
 	-p:端口映射,用宿主机2181映射容器 2181,将来我们就需要通过本机2181访问容器。
 	--privileged:开启特权,运行修改容器参数
 	 --net=itcast-zookeeper 指定桥接网卡
 	zookeeper:3.4.14:创建容器的镜像

​ 查看创建好的容器

docker ps

在这里插入图片描述

​ 查看宿主机的目录,该目录映射的就是zookeeper1容器中的data目录。

cd /opt/docker/zookeeper/zoo1/data

​ 查看Zookeeper节点的id

cat myid
2.容器2创建
docker run -d --restart=always 
     -v /opt/docker/zookeeper/zoo2/data:/data 
     -v /opt/docker/zookeeper/zoo2/datalog:/datalog 
     -e ZOO_MY_ID=2 
     -e ZOO_SERVERS="server.1=zookeeper1:2888:3888 server.2=zookeeper2:2888:3888 server.3=zookeeper3:2888:3888" 
     -p 2182:2181
     --name=zookeeper2
     --net=itcast-zookeeper
     --privileged 
     zookeeper:3.4.14
 
 docker run -d --restart=always -v /opt/docker/zookeeper/zoo2/data:/data -v /opt/docker/zookeeper/zoo2/datalog:/datalog -e ZOO_MY_ID=2 -e ZOO_SERVERS="server.1=zookeeper1:2888:3888 server.2=zookeeper2:2888:3888 server.3=zookeeper3:2888:3888" -p 2182:2181 --name=zookeeper2 --net=itcast-zookeeper --privileged zookeeper:3.4.14
 说明:
 	需要修改目录挂载。
 	   修改Zookeeper的id
 	   端口映射:用宿主机2182 映射容器 2181
 	   容器名称:zookeeper2
 
3.容器3创建
docker run -d --restart=always 
     -v /opt/docker/zookeeper/zoo3/data:/data 
     -v /opt/docker/zookeeper/zoo3/datalog:/datalog 
     -e ZOO_MY_ID=3
     -e ZOO_SERVERS="server.1=zookeeper1:2888:3888 server.2=zookeeper2:2888:3888 server.3=zookeeper3:2888:3888" 
     -p 2183:2181
     --name=zookeeper3
      --net=itcast-zookeeper
     --privileged 
     zookeeper:3.4.14
 
 docker run -d --restart=always -v /opt/docker/zookeeper/zoo3/data:/data -v /opt/docker/zookeeper/zoo3/datalog:/datalog -e ZOO_MY_ID=3 -e ZOO_SERVERS="server.1=zookeeper1:2888:3888 server.2=zookeeper2:2888:3888 server.3=zookeeper3:2888:3888" -p 2183:2181  --name=zookeeper3 --net=itcast-zookeeper --privileged zookeeper:3.4.14
 说明:
 	   需要修改目录挂载。
 	   修改Zookeeper的id
 	   端口映射:用宿主机2183 映射容器 2181 
 	   容器名称:zookeeper3
 	

​ 查看容器创建情况

在这里插入图片描述

2.3.2.3 测试Zookeeper集群的搭建情况

​ 使用yum安装nc指令

yum install -y nc

​ 方式1:

​ 通过容器的ip查看Zookeeper容器状态

​ 查看三个Zookeeper容器的ip

docker   inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'   zookeeper1
docker   inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'   zookeeper2
docker   inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'   zookeeper3

​ 查看Zookeeper集群的状态

echo stat|nc ip 2181
echo stat|nc ip 2181
echo stat|nc ip 2181

在这里插入图片描述

方式2:通过宿主机的ip查询Zookeeper容器状态

在这里插入图片描述

2.3.2.4 zookeeper集群的架构

​ Zookeeper集群架构
在这里插入图片描述

​ Zookeeper客户端连接Zookeeper容器
在这里插入图片描述

2.3.2.3 搭建solr集群

​ 搭建Solr集群,我们需要利用我们刚才拉取的solr的镜像创建4个容器。并且需要将集群交由Zookeeper管理,Solr容器内部使用jetty作为solr的服务器。

​ 1.容器1创建

docker run --name solr1 --net=itcast-zookeeper -d -p 8983:8983 solr:7.7.2  bash -c '/opt/solr/bin/solr start -f -z zookeeper1:2181,zookeeper2:2181,zookeeper3:2181'
--name:指定容器名称
--net:指定网卡,之前创建的桥接网卡
-d:后台运行
-p:端口映射,宿主机8983映射容器中8983
-c:
/opt/solr/bin/solr:通过容器中脚本,指定Zookeeper的集群地址

​ 2.容器2创建

docker run --name solr2 --net=itcast-zookeeper -d -p 8984:8983 solr:7.7.2  bash -c '/opt/solr/bin/solr start -f -z zookeeper1:2181,zookeeper2:2181,zookeeper3:2181'
--name:指定容器名称
--net:指定网卡,之前创建的桥接网卡
-d:后台运行
-p:端口映射,宿主机8983映射容器中8984
-c:/opt/solr/bin/solr:通过容器中脚本,指定Zookeeper的集群地址

​ 3.容器3创建

docker run --name solr3 --net=itcast-zookeeper -d -p 8985:8983 solr:7.7.2  bash -c '/opt/solr/bin/solr start -f -z zookeeper1:2181,zookeeper2:2181,zookeeper3:2181'
--name:指定容器名称
--net:指定网卡,之前创建的桥接网卡
-d:后台运行
-p:端口映射,宿主机8983映射容器中8984
-c:/opt/solr/bin/solr:通过容器中脚本,指定Zookeeper的集群地址

​ 4.容器4创建

docker run --name solr4 --net=itcast-zookeeper -d -p 8986:8983 solr:7.7.2  bash -c '/opt/solr/bin/solr start -f -z zookeeper1:2181,zookeeper2:2181,zookeeper3:2181'
--name:指定容器名称
--net:指定网卡,之前创建的桥接网卡
-d:后台运行
-p:端口映射,宿主机8983映射容器中8985
-c:/opt/solr/bin/solr:通过容器中脚本,指定Zookeeper的集群地址

​ 5.查看结果

在这里插入图片描述

2.3.2.5 整体架构

在这里插入图片描述

2.3.2.6 创建集群的逻辑结构

​ 1.通过端口映射访问solr的后台系统(注意关闭防火墙)

	http://192.168.200.128:8983/solr
	http://192.168.200.128:8984/solr
	http://192.168.200.128:8985/solr
	http://192.168.200.128:8986/solr

​ 2.上传solr配置文件到Zookeeper集群。

​ 进入到某个solr容器中,使用/opt/solr/server/scripts/cloud-scripts/的zkcli.sh 。上传solr的配置文件。

位置/opt/solr/example/example-DIH/solr/solr/conf到Zookeeper集群zookeeper1:2181,zookeeper2:2181,zookeeper3:2181。

docker exec -it solr1 /opt/solr/server/scripts/cloud-scripts/zkcli.sh -zkhost zookeeper1:2181,zookeeper2:2181,zookeeper3:2181 -cmd upconfig -confdir /opt/solr/server/solr/configsets/sample_techproducts_configs/conf -confname myconfig

​ 3.使用zooInterceptor查看配置文件上传情况
在这里插入图片描述

​ 4.使用后台管理系统创建connection
在这里插入图片描述

​ 5. 查看集群的逻辑结构
在这里插入图片描述

​ 到这基于docker的solr集群我们就讲解完毕。

​ 6.测试集群

{id:1,name:"zx"}
2.3.2.7 solr配置文件修改

1.在linux服务器上需要有一份solr的配置文件,修改宿主机中的配置文件。

​ 之前单机版solr的/usr/local/solr_home下,就有solr的配置文件。

​ /usr/local/solr_home下面的配置文件,schema已经配置了基于IK分词器的FieldType

2.将宿主机中的配置文件,替换到某个solr容器中。

docker cp /usr/local/solr_home/collection1/conf/managed-schema solr1:/opt/solr/server/solr/configsets/sample_techproducts_configs/conf/managed-schema

3.将容器中修改后managed-schema配置文件,重新上传到Zookeeper集群。

​ 进入到solr1容器中,使用zkcli.sh命令,将最新的配置文件上传到Zookeeper集群

​ 容器文件位置:/opt/solr/server/solr/configsets/sample_techproducts_configs/conf/managed-schema

​ 集群文件位置: /configs/myconfig/managed-schema

docker exec -it solr1 /opt/solr/server/scripts/cloud-scripts/zkcli.sh -zkhost zookeeper1:2181,zookeeper2:2181,zookeeper3:2181 -cmd putfile /configs/myconfig/managed-schema /opt/solr/server/solr/configsets/sample_techproducts_configs/conf/managed-schema

4.在Files中查看,修改后的配置文件

5.将IK分词器的jar包,及配置文件复制到4个solr容器中。

​ 5.1 将IK分词器上传到linux服务器

​ 5.2 使用docker cp命令进行jar包的拷贝

docker cp /root/ik-analyzer-solr5-5.x.jar solr1:/opt/solr/server/solr-webapp/webapp/WEB-INF/lib/ik-analyzer-solr5-5.x.jar
docker cp /root/ik-analyzer-solr5-5.x.jar solr2:/opt/solr/server/solr-webapp/webapp/WEB-INF/lib/ik-analyzer-solr5-5.x.jar
docker cp /root/ik-analyzer-solr5-5.x.jar solr3:/opt/solr/server/solr-webapp/webapp/WEB-INF/lib/ik-analyzer-solr5-5.x.jar
docker cp /root/ik-analyzer-solr5-5.x.jar solr4:/opt/solr/server/solr-webapp/webapp/WEB-INF/lib/ik-analyzer-solr5-5.x.jar

5.3 使用docker cp命令进行配置文件的拷贝

​ 将IK分析器的相关配置文件复制到/root/classes目录中

docker cp /root/classes solr1:/opt/solr/server/solr-webapp/webapp/WEB-INF/classes
docker cp  /root/classes solr2:/opt/solr/server/solr-webapp/webapp/WEB-INF/classes
docker cp /root/classes solr3:/opt/solr/server/solr-webapp/webapp/WEB-INF/classes
docker cp  /root/classes solr4:/opt/solr/server/solr-webapp/webapp/WEB-INF/classes

6、重启所有solr容器

docker restart solr1
docker restart solr2
docker restart solr3
docker restart solr4

7、使用text_ik创建业务域

不再使用修改配置文件的方式,直接利用后台管理系统进行。
在这里插入图片描述

{id:2,book_name:"java编程思想"}
2.3.2.7 使用SolrJ操作solr集群

​ 1.修改Zookeeper地址和connection名称

 @Bean
    public CloudSolrClient cloudSolrClient() {
        //指定Zookeeper的地址。
        List<String> zkHosts = new ArrayList<>();
        zkHosts.add("192.168.200.128:2181");
        zkHosts.add("192.168.200.128:2182");
        zkHosts.add("192.168.200.128:2183");
        //参数2:指定
        CloudSolrClient.Builder builder = new CloudSolrClient.Builder(zkHosts,Optional.empty());
        CloudSolrClient solrClient = builder.build();
        //设置连接超时时间
        solrClient.setZkClientTimeout(30000);
        solrClient.setZkConnectTimeout(30000);
        //设置collection
        solrClient.setDefaultCollection("myCollection");
        return solrClient;
    }
  1. 将之前的索引和搜索的代码执行一下

    @Test
            public void testSolrCloudAddDocument() throws Exception {
                 SolrInputDocument doc = new SolrInputDocument();
                 doc.setField("id", "3");
                 doc.setField("book_name", "葵花宝典");
    
                  cloudSolrClient.add(doc);
                cloudSolrClient.commit();
            }
    
        @Test
        public void testSolrQuery() throws Exception {
            SolrQuery params = new SolrQuery();
            params.setQuery("*:*");
            QueryResponse response = cloudSolrClient.query(params);
            SolrDocumentList results = response.getResults();
            for (SolrDocument result : results) {
                System.out.println(result);
                
            }
        }
    
  2. 执行之前的代码会报错
    在这里插入图片描述

    错误的原因:连接不上172.19.0.6.8983。

    这个容器是谁?
    在这里插入图片描述

    为什么连接不上呢?

    1. 错误的原因
      在这里插入图片描述

    2. 正确的测试方案

      ​ 将客户端应用部署到docker容器中,容器之间是可以相互通信的。

      ​ 如何将我们的应用部署到docker容器中。

      ​ 5.1 修改代码,将添加文档的操作写在controller方法中,便于我们进行测试;

      @RestController
      @RequestMapping("/test")
      public class TestController {
          @Autowired
          private CloudSolrClient cloudSolrClient;
          @RequestMapping("/addDocument")
          public Map<String,Object> addDocument(String id,String bookName) throws IOException, SolrServerException {
              SolrInputDocument doc = new SolrInputDocument();
              doc.setField("id", id);
              doc.setField("book_name", bookName);
              cloudSolrClient.add(doc);
              cloudSolrClient.commit();
              Map<String,Object> result = new HashMap<>();
              result.put("flag", true);
              result.put("msg","添加文档成功" );
              return result;
          }
      }
      

      5.2 将spring boot项目打成可以运行jar包。

      <build>
              <plugins>
                  <plugin>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-maven-plugin</artifactId>
                  </plugin>
              </plugins>
      </build>
      

      5.3 使用package命令打成jar

      clean package -DskipTests=true
      

      5.4 将jar包上传到linux服务器制作镜像

      ​ 5.4.1上传

      ​ 5.4.2 编写DockerFile文件

      #基础镜像
      FROM java:8  
      #作者
      MAINTAINER itcast 
      #将jar包添加到镜像app.jar
      ADD solr_solrJ_other-1.0-SNAPSHOT.jar app.jar 
      #运行app的命令 java -jar app.jar
      ENTRYPOINT ["java","-jar","/app.jar"]
      

      ​ 5.4.3 创建镜像

      docker build -t myapp:1.0 ./   使用当前目录的DockerFile文件创建镜像myapp,版本1.0
      

      ​ 查看镜像

      docker images
      

      查看
      在这里插入图片描述

      ​ 5.4.4 利用myapp:1.0镜像创建容器

       docker run -d --net=itcast-zookeeper -p 8888:8080 myapp:1.0  #使用本地8888映射容器中8080
      

      ​ 5.4.5 查看日志

      docker logs -f 容器id
      

      ​ 5.5.6 访问controller

      http://192.168.200.128:8888/test/addDocument?id=999&bookName=葵花宝典
      

      ​ 注意:如果发现连接不上Zookeeper,注意关闭防火墙;

      systemctl stop firewalld
      

3.Solr查询高级

3.1 函数查询

​ 在Solr中提供了大量的函数,允许我们在查询的时候使用函数进行动态运算。

3.2.1 函数调用语法

​ 函数调用语法和java基本相同,格式为:函数名(实参),实参可以是常量(100,1.45,"hello"),也可以是域名,下面是函数调用例子。

functionName()  
functionName(...)  
functionName1(functionName2(...),..)

​ 数据准备:为了便于我们后面的讲解,采用的案例数据,员工信息。

业务域:
	员工姓名,工作,领导的员工号,入职日期,月薪,奖金
	 <field name="emp_ename" type="text_ik" indexed="true" stored="true"/>
    <field name="emp_ejob" type="text_ik" indexed="true" stored="true"/>
    <field name="emp_mgr" type="string" indexed="true" stored="true"/>
    <field name="emp_hiredate" type="pdate" indexed="true" stored="true"/>
    <field name="emp_sal" type="pfloat" indexed="true" stored="true"/>
    <field name="emp_comm" type="pfloat" indexed="true" stored="true"/>
    <field name="emp_cv" type="text_ik" indexed="true" stored="false"/>
    <field name="emp_deptno" type="string" indexed="true" stored="true"/>
    
data-import.xml
		<entity name="emp" query="select * from emp">
            <!-- 每一个field映射着数据库中列与文档中的域,column是数据库列,name是solr的域(必须是在managed-schema文件中配置过的域才行) -->
            <field column="empno" name="id"/>
            <field column="ename" name="emp_ename"/>
            <field column="ejob" name="emp_ejob"/>
            <field column="mgr" name="emp_mgr"/>
            <field column="hiredate" name="emp_hiredate"/>
            <field column="sal" name="emp_sal"/>
            <field column="comm" name="emp_comm"/>
            <field column="cv" name="emp_cv"/>
			<field column="deptno" name="emp_deptno"/>
        </entity>

3.2.2 函数的使用

3.2.2.1 将函数返回值作为关键字

​ 在solr中通常的搜索方式都是根据关键字进行搜索。底层首先要对关键字分词,在倒排索引表中根据词查找对应的文档返回。

item_title:三星手机

​ 需求:查询年薪在30000~50000的员工。

​ 主查询条件:查询所有:

​ 过滤条件: 利用product()函数计算员工年薪,product函数是Solr自带函数,可以计算2个数之积。{!frange}指定范围。l表示最小值,u表示最大值.{!frange}语法是Function Query Parser解析器中提供的一种语法。

	q=*:*
	&fq={!frange l=30000 u=50000}product(emp_sal,12)	

​ 需求:查询年薪+奖金大于50000的员工

​ 通过sum函数计算年薪+奖金。

	q=*:*
	&fq={!frange l=50000}sum(product(emp_sal,12),emp_comm)	
	上面查询不出来数据,原因是有些员工emp_comm为空值。
	emp_comm进行空值处理。判断emp_comm是否存在,不存在指定位0,存在,本身的值
	exists判断域是否存在
	if指定存在和不存在值
	{!frange l=40000}sum(product(emp_sal,12),if(exists(emp_comm),emp_comm,0))	
3.2.2.1.2 将函数返回值作为伪域

​ 伪域:文档中本身不包含的域,利用函数生成。

​ 需求:查询所有的文档,每个文档中包含年薪域。

q=*:*&
fl=*,product(emp_sal,12)
指定别名:yearsql:product(emp_sal,12) 便于将来封装
3.2.2.1.3 根据函数进行排序

​ 需求:查询所有的文档,并且按照(年薪+奖金)之和降序排序

q=*:*&
sort=sum(product(emp_sal,12),if(exists(emp_comm),emp_comm,0)) desc
3.2.2.1.4 使用函数影响相关度排序(了解)

​ 之前在讲解查询的时候,我们说的相关度排序,Lucene底层会根据查询的词和文档关系进行相关度打分。

打分高的文档排在前面,打分低的排在后面。打分的2个指标tf,df;

​ 使用函数可以影响相关度排序。

​ 需求:查询emp_cv中包含董事长的员工,年薪高的员工相关度打分高一点。

q=emp_cv:董事长 
	AND _val_:"product(emp_sal,12)"  #用来影响相关度排序,不会影响查询结果									  
可以简单这么理解 
lucene本身对查询文档有一个打分叫score,每个文档分数可能不同。
通过函数干预后,打分score += 函数计算结果

_val_:规定

另外一种写法:
	AND _query_:"{!func}product(emp_sal,12)"
	

3.2.2.1.5 Solr内置函数和自定义函数
常用内置函数
函数说明举例
abs(x)返回绝对值abs(-5)
def(field,value)获取指定域中值,如果文档没有指定域,使用value作为默认值;def(emp_comm,0)
div(x,y)除法,x除以ydiv(1,5)
dist计算两点之间的距离dis(2,x,y,0,0)
docfreq(field,val)返回某值在某域出现的次数docfreq(title,’solr’)
field(field)返回该field的索引数量field(‘title’)
hsin曲面圆弧上两点之间的距离hsin(2,true,x,y,0,0)
idfInverse document frequency 倒排文档频率idf(field,’solr’)
ifif(test,value1,value2)if(termfreq(title,’solr’),popularity,42)
linear(x,m,c)就是m*x+c,等同于sum(product(m,x),c)linear(1,2,4)=1x2+4=6
log(x)以10为底,x的对数log(sum(x,100))
map(x,min,max,target)如果x在min和max之间,x=target,否则x=xmap(x,0,0,1)
max(x,y,…)返回最大值max(2,3,0)
maxdoc返回索引的个数,查看有多少文档,包括被标记为删除状态的文档maxdoc()
min(x,y,…)返回最小值min(2,4,0)
ms返回两个参数间毫秒级的差别ms(datefield1,2000-01-01T00:00:00Z)
norm(field)返回该字段索引值的范数norm(title)
numdocs返回索引的个数,查看有多少文档,不包括被标记为删除状态的文档numdocs()
ord根据顺序索引发货结果ord(title)
pow(x,y)返回x的y次方pow(x,log(y))
product(x,y)返回多个值得乘积product(x,2)
query返回给定的子查询的得分,或者文档不匹配的默认值值query(subquery,default)
recip(x,m,a,b)相当于a/(m*x+b),a,m,b是常量,x是变量recip(myfield,m,a,b)
rord按ord的结果反序返回
scale返回一个在最大值和最小值之间的值scale(x,1,3)
sqedist平方欧氏距离计算sqedist(x_td,y_td,0,0)
sqrt返回指定值得平方根sqrt(x)sqrt(100)
strdist计算两个字符串之间的距离strdist(“SOLR”,id,edit)
sub返回x-ysub(field1,field2)
sum(x,y)返回指定值的和sum(x,y,…)
sumtotaltermfreq返回所有totaltermfreq的和
termfreq词出现的次数termfreq(title,’sorl’)
tf词频tf(text,’solr’)
top功能类似于ord
totaltermfreq返回这个词在该字段出现的次数ttf(title,’memory’)
and返回true值当且仅当它的所有操作为trueand(not(exists(popularity)),exists(price))
or返回true值当有一个操作为trueor(value1,value2)
xor返回false值如果所有操作都为真xor(field1,field2)
not排除操作not(exists(title))
exists如果字段存在返回真exists(title)
gt,gte,lt,lte,eq比较函数2 gt 1
自定义函数

​ 自定义函数流程:

​ 准备:

<dependency>
	<groupId>org.apache.solr</groupId>
	<artifactId>solr-core</artifactId>
	<version>7.7.2</version>
</dependency>

​ 1.定义一个类继承 ValueSource 类,用于封装函数执行结果。

​ 2.定义一个类继承ValueSourceParser,用来解析函数调用语法,并且可以解析参数类型,以及返回指定结果。

​ 3. 在solrconfig.xml中配置文件中配置ValueSourceParser实现类的全类名

​ 需求:定义一个solr字符串拼接的函数,将来函数的调用语法concat(域1,域2),完成字符串拼接。
​ 第一步:

/**
 * 用来解析函数调用语法,并且将解析的参数返回给第一个类处理。
 * concat(field1,field2)
 */
public class ConcatValueSourceParser extends ValueSourceParser {
    //对函数调用语法进行解析,获取参数,将参数封装到ValueSource,返回函数执行结果
    public ValueSource parse(FunctionQParser functionQParser) throws SyntaxError {
        //1.获取函数调用参数,由于参数类型可能是字符串,int类型,域名,所以封装成ValueSource
        //1.1获取第一个参数封装对象
        ValueSource arg1 = functionQParser.parseValueSource();
        //1.2获取第二个参数封装对象
        ValueSource arg2 = functionQParser.parseValueSource();

        //返回函数执行结果
        return new ConcatValueSource(arg1, arg2);
    }
}

​ 第二步:

public class ConcatValueSource extends ValueSource {

    //使用构造方法接收ConcatValueSourceParser解析获取的参数
    private ValueSource arg1;
    private ValueSource arg2;

    public ConcatValueSource(ValueSource arg1, ValueSource arg2) {
        this.arg1 = arg1;
        this.arg2 = arg2;
    }

    //返回arg1和arg2计算结果
    public FunctionValues getValues(Map map, LeafReaderContext leafReaderContext) throws IOException {
        FunctionValues values1 = arg1.getValues(map, leafReaderContext); //第一个参数
        FunctionValues values2 = arg1.getValues(map, leafReaderContext); //第二个参数

        //返回一个字符串的结果
        return new StrDocValues(this) {
            @Override
            public String strVal(int doc) throws IOException {
                //根据参数从文档中获取值
                return values1.strVal(doc) + values2.strVal(doc);
            }
        };
    }

​ 第三步:在solrconfig.xml配置

<valueSourceParser name="concat" class="cn.itcast.funcation.ConcatValueSourceParser" />

​ 第四步:打包,并且在solr/WEB-INF/lib 目录中配置

​ 第五步:测试

fl=*,concat(id,emp_ename)

3.2 地理位置查询

​ 在Solr中也支持地理位置查询,前提是我们需要将地理位置的信息存储的Solr中,其中地理位置中必须包含一个比较重要的信息,经度和纬度,经度范围[-180,180],纬度范围[-90,90]。对于经度来说东经为正,西经为负,对于纬度来说,北纬正,南纬为负。

3.2.1 数据准备

​ 首先我们先来完成数据准备,需要将地理位置信息存储到Solr中;

​ 1.在MySQL导入地理位置信息

​ name是地址名称

​ position:经纬度坐标,中间用,分割(纬度,经度)

​ 2.schema.xml中定义业务域

	name域的定义
	<field name="address_name" type="text_ik" indexed="true" stored="true"/>
	注意:
		坐标域定义
        域类型location,这域类型就是用来定义地理位置域。
        使用location域类型定义的域,底层会被拆分成2个域进行存储,分别是经度和维度。
        这个域将来会被拆分成2个域进行存储。
        所以需要定义动态域对底层操作进行支持。
	<field name="address_position" type="location" indexed="true" stored="true"/>

	以下在schema中已经定义好了。
	<fieldType name="location" class="solr.LatLonType" subFieldSuffix="_coordinate"/>
	<dynamicField name="*_coordinate"  type="tdouble" indexed="true"  stored="false"/>

​ 3.使用DataImport导入数据

	<entity name="address" query="select * from address">
            <!-- 每一个field映射着数据库中列与文档中的域,column是数据库列,name是solr的域(必须是在managed-schema文件中配置过的域才行) -->
            <field column="id" name="id"/>
            <field column="name" name="address_name"/>
            <field column="position" name="address_position"/>
        </entity>

​ 4.测试
在这里插入图片描述

​ id:主键

address_name:位置名称

address_position:位置坐标

​ 还多出2个域:address_position_0_coordinate ,address_position_1_coordinate 基于动态域生成的维度和经度坐标。

3.2.2 简单地理位置查询

​ Solr中 提供了一种特殊的查询解析器(geofilt),它能解析以指定坐标(包含经纬度)为中心。 以指定距离(千米)为半径画圆,坐标落在圆面积中的文档,

在这里插入图片描述

​ 需求:查询离西安半径 30 千米范围内的城市。

1.查询西安的坐标

	address_name:西安

在这里插入图片描述

  1. 查询距离西安30千米范围内的城市

    q=*:*&
    fq={!geofilt sfield=address_position pt=34.27,108.95 d=30}
    

    sfiled:指定坐标域

    pt:中心

    d:半径

在这里插入图片描述

除了可以查询落在圆面积中的地址外,对于精确度不高的查询,也可以查询落在外切正方形中的地理位置。

这种方式比圆的方式效率要高。

在这里插入图片描述

q=*:*&
ft={!bbox sfield=address_position pt=34.27,108.95 d=30}
  1. 在solr中还提供了一些计算地理位置的函数,比较常用的函数geodist,该函数可以计算2个地理位置中间的距离。调用方式:geodist(sfield,latitude,longitude);

    需求:计算其他城市到北京的距离,作为伪域展示

    q=*:*&
    fl=*,distance:geodist(address_position,39.92,116.46)
    sort=geodist(address_position,39.92,116.46) asc
    

3.2.3 地理位置高级查询

​ 之前我们在讲解简单地理位置查询的时候,都是基于正方形或者基于圆进行。有时候我们在进行地理位置查询的时候需要基于其他形状。在Solr中提供了一个类SpatialRecursivePrefixTreeFieldType(RPT)可以完成基于任意多边形查询。

在这里插入图片描述

3.2.3.1对地理数据进行重写索引

​ 1.修改address_position域类型

​ 之前的域定义,域类型location

<field name="address_position" type="location" indexed="true" stored="true"/>

​ 修改后的域定义,域类型location_rpt


<field name="address_position" type="location_rpt" indexed="true" stored="true" multiValued="true"/>
	
	location_rpt类型是schema文件已经提供好的一个类型,class就是RPT
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
		spatialContextFactory="JTS"
        geo="true" distErrPct="0.025" maxDistErr="0.001" distanceUnits="kilometers" />
	
	
  1. location_rpt类型,允许存储的数据格式。

    ​ 点:

    纬度 经度
    纬度,经度
    

    ​ 任意形状

    POLYGON((坐标1,坐标2....))
    

    将来我们可以查询一个点,或者任意形状是否在另外一个几何图形中。

​ 3. 复制jst的jar包到solr/WEB-INF/classes目录

<dependency>
    <groupId>org.locationtech.jts</groupId>
    <artifactId>jts-core</artifactId>
    <version>1.16.1</version>
</dependency>
jst:用于构建图形的jar包
  1. 插入测试数据,address_position域中存储的是一个几何图形

    insert into address values(8989,'三角形任意形状','POLYGON((108.94224 35.167441,108.776664 34.788863,109.107815 34.80404,108.94224 35.167441))'); 
    

在这里插入图片描述

  1. 使用DataImport重新进行索引
3.2.3.2 基于任意图形进行查询

​ 查询基于A,B,C,D这4个点围城的图形有哪些城市。

在这里插入图片描述

​ 可以使用百度地图的坐标拾取得知A,B,C,D四个点的坐标。

​ 拾取坐标系统

A 109.034226,35.574317

B 111.315491,33.719499

C108.243142,33.117791

D 107.98558,34.37802

​ 利用ABCD四个点构建几个图形,查询地理坐标在该几何图形中的文档。

address_position:"Intersects(POLYGON((109.034226 35.574317,
									111.315491 33.719499,
									108.243142 33.117791,
									107.98558 34.37802,
									109.034226 35.574317)))"

说明:

POLYGON是JST中提供的构建任意多边形的语法。

​ 每个坐标之间使用,进行分割。

​ 经度和纬度之间使用空格隔开

​ 如果构建的是一个封闭图形,最后一个坐标要和第一个坐标相同。

Intersects是一个操作符

​ 作用:匹配2个图形相交部分的文档。

IsWithin

​ 作用:匹配包含在指定图形面积之内的文档,不含边界

Contains:

​ 作用:匹配包含在指定图形面积之内的文档,含边界

3.3 JSON Facet

​ 在Solr中对于Facet查询提供了另外一种查询格式。称为JSON Facet。使用JSON Fact有很多好处,书写灵活,便于自定义响应结果,便于使用分组统计函数等。

3.3.1 JSON Facet 入门

​ 根据商品分类,分组统计每个分类对应的商品数量,要求显示前3个。

传统的Facet查询

q=*:*&
facet=true&
facet.field=item_category&
facet.limit=3

JSON Facet

q=*:*&
json.facet={
	category:{
		type:terms,
		field:item_category,
		limit:3
	}
}
测试
http://localhost:8080/solr/collection1/select?q=*:*&json.facet=%7Bcategory:%7Btype:terms,field:item_category,limit:3%7D%7D

其中category是自定义的一个键。
type:指的是facet的类型
field:分组统计的域

在这里插入图片描述

3.3.2 JSON Facet的语法

json.facet = {
	<facet_name>:{
		type:<fact_type>,
		<other_facet_parameter>
	}
}
facet_name:可以任意取值,是为了将来解析结果使用。
type:取值terms,query,range.
		terms根据域进行Facet查询,query根据查询解决进行facet查询。range根据范围进行facet查询。
other_facet_parameter:其他facet的一些参数。

​ 根据上面的语法,再来完成一个需求

​ 需求:分组统计2015年每月添加的商品数量。

q=*:*,
json.facet={
	month_count:{
		type:range,
		field:item_createtime,
		start:'2015-01-01T00:00:00Z',
		end:'2016-01-01T00:00:00Z',
		gap:'+1MONTH'
	}
}

http://localhost:8080/solr/collection1/select?q=*:*&json.facet=%7B
	month_count:%7B
		type:range,
		field:item_createtime,
		start:'2015-01-01T00:00:00Z',
		end:'2016-01-01T00:00:00Z',
		gap:'%2B1MONTH'
	%7D
%7D

在这里插入图片描述

​ 需求:统计品牌是华为的商品数量,分类是平板电视的商品数量。

q=*:*,
json.facet={
	brand_count:{
		type:query,
		q:"item_brand:华为"
	},
	category_count:{
		type:query,
		q:"item_category:平板电视"
	}
}

http://localhost:8080/solr/collection1/select?q=*:*&json.facet=%7B
	brand_count:%7B
		type:query,
		q:"item_brand:华为"
	%7D,
	category_count:%7B
		type:query,
		q:"item_category:平板电视"
	%7D
%7D

3.3.3 SubFacet

​ 在JSON Facet中,还支持另外一种Facet查询,叫SubFacet.允许用户在之前分组统计的基础上,再次进行分组统计,可以实现pivot Facet(多维度分组查询功能)查询的功能。

​ 需求:对商品的数据,按照品牌进行分组统计,获取前5个分组数据。

q=*:*&
json.facet={
	top_brand:{
		type:terms,
		field:item_brand,
		limit:5
	}
}
http://localhost:8080/solr/collection1/select?q=*:*&
json.facet=%7B
	top_brand:%7B
		type:terms,
		field:item_brand,
		limit:5
	%7D
%7D

在这里插入图片描述

在以上分组的基础上,统计每个品牌,不同商品分类的数据。eg:统计三星手机多少个,三星平板电视多少?

q=*:*&
json.facet={
	top_brand:{
		type:terms,
		field:item_brand,
		limit:5,
		facet:{
			top_category:{
				type:terms,
				field:item_category,
				limit:3
			}
		}
	}
}
http://localhost:8080/solr/collection1/select?q=*:*&
json.facet=%7B
	top_brand:%7B
		type:terms,
		field:item_brand,
		limit:5,
		facet:%7B
			top_category:%7B
				type:terms,
				field:item_category,
				limit:3
			%7D
		%7D
	%7D
%7D

在这里插入图片描述

3.3.4 聚合函数查询

​ 在JSON Facet中,还支持另外一种强大的查询功能,聚合函数查询。在Facet中也提供了很多聚合函数,也成为Facet函数。很多和SQL中的聚合函数类似。

​ 函数如下:

聚合函数例子描述
sumsum(sales)聚合求和
avgavg(popularity)聚合求平均值
minmin(salary)最小值
maxmax(mul(price,popularity))最大值
uniqueunique(author)统计域中唯一值的个数
sumsqsumsq(rent)平方和
percentilepercentile(salary,30,60,10)可以根据百分比进行统计

需求:查询华为手机价格最大值,最小值,平均值.

q=item_title:手机
&fq=item_brand:华为
&json.facet={
	avg_price:"avg(item_price)",
	min_price:"min(item_price)",
	max_price:"max(item_price)"
}

http://localhost:8080/solr/collection1/select?q=item_title:手机
&fq=item_brand:华为
&json.facet=%7B
	avg_price:"avg(item_price)",
	min_price:"min(item_price)",
	max_price:"max(item_price)"
%7D

查询结果:
在这里插入图片描述

需求:查询每个品牌下,最高的手机的价格.

q=item_title:手机
&json.facet={
	categories:{
     type:terms,
     field:item_brand,
     facet:{
       x : "max(item_price)"
     }
   }
}
http://localhost:8080/solr/collection1/select?q=item_title:手机&json.facet=%7Bcategories:%7Btype:field,field:item_brand,facet:%7Bx:"max(item_price)"%7D%7D%7D

结果:
在这里插入图片描述

4.Solr查询进阶

4.1 深度分页

​ 在Solr中 默认分页中,我们需要使用 start和 rows 参数。一般情况下使用start和rows进行分页不会有什么问题。但是在极端情况下,假如希望查询第 10000 页且每页显示10条,意味着 Solr 需要提取前 10000 × 10 = 100000 条数据,并将这100000 条数据缓存在内存中,然后返回最后10条即用户想要的第10000页数据。

​ 引发问题:将这100000条数据缓存到内存中,会占用很多内存。页码越靠后,分页查询性能越差。多数情况下,用户也不会查询第10000页的数据,但是不排除用户直接修改url的参数,查询第10000页的数据。

​ 解决方案:

​ 为此Solr 提供了一种全新的分页方式, 被称为游标。游标可以返回下一页数据的起始标识。该标识记录着下一页数据在索引库中绝对的索引位置。一旦我们使用了游标,Solr就不会采用之前的分页方式。而是根据每一页数据在索引库中绝对索引位置来获取分页数据。

​ 使用流程:

​ 1.查询数据的时候需要指定一个参数cursorMark=*,可以理解为start=0,通过该参数就可以获取第一页数据。在返回的结果中包含另外一个参数nextCursorMark

​ 2.nextCursorMark是下一页数据开始位置。

​ 3.将下一页数据的开始位置作为cursorMark的值来查询下一页数据。

​ 4.如果返回nextCursorMark和cursorMark相同,表示没有下一页数据

​ 注意点:

​ 1.cursorMark在进行分页的时候,是不能再指定start这个参数。

​ 2.使用cursorMark在进行分页的时候,必须使用排序,而且排序字段中必须包含主键。

​ eg: id asc

​ eg: id asc name desc

​ 演示:

​ 需求:查询item_title中包含LED的第一页的50条数据

http://localhost:8080/solr/collection1/select?
q=item_title:LED&
cursorMark=*&
rows=50&
sort=id asc

在这里插入图片描述

​ 利用下一页数据的起始标识查询下一页数据,将cursorMark改为下一页数据的起始标识

q=item_title:LED&
cursorMark=AoEnMTEzNjY5NQ==&
rows=50&
sort=id asc

​ 如果发现cursorMark和返回的nextCursorMark相同,表示没有下一页数据
在这里插入图片描述

4.2 Solr Join查询

4.2.1 Join查询简介

​ 在Solr 中的索引文档一般建议扁平化、非关系化,所谓扁平化即每个文档包含的域个数和类型可以不同,而非关系型化则表示每个文档之间没有任何关联,是相互独立 。不像关系型数据库中两张表之间可以通过外键建立联系。

​ 案例场景:

​ 需求:需要将员工表和部门表的数据导入到Solr中进行搜索。

​ 通常我们的设计原则。将员工表和部门表的字段冗余到一个文档中。将来查询出来的文档中包含员工和部门信息。

​ 但是在Solr中他也提供了Join查询,类似于SQL中子查询,有时候我们利用Join查询也可以完成类似子查询的功能。

4.2.2 join查询的数据准备

​ 1.将部门表的数据导入到Solr中。

​ 业务域

  	<field name="dept_dname" type="text_ik" indexed="true" stored="true"/>
	<field name="dept_loc" type="text_ik" indexed="true" stored="true"/>
	
	<fieldType name ="text_ik" class ="solr.TextField">
		<analyzer class="org.wltea.analyzer.lucene.IKAnalyzer"/>
	</fieldType>

​ DataImport配置文件

<dataConfig>
    <!-- 首先配置数据源指定数据库的连接信息 -->
    <dataSource type="JdbcDataSource"
                driver="com.mysql.jdbc.Driver"
                url="jdbc:mysql://localhost:3306/lucene"
                user="root"
                password="123"/>
    <document>
		<entity name="dept" query="select * from dept">
            <!-- 每一个field映射着数据库中列与文档中的域,column是数据库列,name是solr的域(必须是在managed-schema文件中配置过的域才行) -->
            <field column="deptno" name="id"/>
            <field column="dname" name="dept_dname"/>
            <field column="loc" name="dept_loc"/>
        </entity>
    </document>
</dataConfig>

​ 在collection2中导入部门数据

在这里插入图片描述

4.2.3 Join查询的案例使用场景

需求:查询部门名称为SALES下的员工信息。

sql子查询:
	1.查询SALES部门名称对应的部门id
		select depno from dept where dname = 'SALES'
	2.查询部门id下员工信息
		select * from emp where deptno in (select depno from dept where dname = 'SALES')

在这里插入图片描述

​ 类比sql中的子查询,来完成Solr中Join查询。

q=*:*&  //主查询条件------->查询所有员工
//过滤条件collection2中dept_dname:SALES
fq=dept_dname:SALES

建立连接关系
{!join fromIndex=collection2 toIndex=collection1 from=id to=emp_deptno}

fromIndex:被连接的collection
toIndex:主collection
from:部门号
to:外键

最终:
	q=*:*&
	fq={!join fromIndex=collection2 toIndex=collection1 from=id 
	to=emp_deptno}dept_dname:SALES

在这里插入图片描述

需求:查询7369员工的部门信息。

sql子查询
	查询7369员工的部门号。
	select deptno from emp where empno = 7369
	根据上面部门号查询部门信息
	select * from dept where deptno = (
			select deptno from emp where empno = 7369
	)

在这里插入图片描述

类比sql中的子查询,来完成Solr中Join查询。

主查询条件------->查询所有部门
q=*:*&
fq=id:7369
--------->子查询条件员工id为7369
使用join进行连接
{!join fromIndex=collection1  toIndex=collection2  from=emp_deptno to=id}

在这里插入图片描述

分析:
在这里插入图片描述

需求:查询7369员工的领导的信息。

sql子查询
1.查询7369员工的领导的编号。
	select mgr from emp where empno = 7369
2.根据领导的编号,查询领导信息
	select * from emp where empno = (
	  select mgr from emp where empno = 7369
	)

​ 类比sql中的子查询,来完成Solr中Join查询。

主查询条件
q=*:*------->主查询条件
fq={!join fromIndex=collection1 toIndex=collection1 from=emp_mgr to=id}id:7369

结果:
在这里插入图片描述

分析:
在这里插入图片描述

​ 需求:统计SALES部门下,薪水最高的员工

​ 步骤1:查询SALES部门下的所有员工。

q=*:*&  //主查询条件------->查询所有员工
//过滤条件collection2中dept_dname:SALES
fq={!join fromIndex=collection2 toIndex=collection1 from=id to=emp_deptno}dept_dname:SALES

​ 步骤2:使用Facet的函数查询最高的薪水;

q=*:*&  //主查询条件------->查询所有员工
//过滤条件collection2中dept_dname:SALES
fq={!join fromIndex=collection2 toIndex=collection1 from=id to=emp_deptno}dept_dname:SALES&
json.facet={
	x:"max(emp_sal)"
}
http://localhost:8080/solr/collection1/select?q=*:*&
fq=%7B!join fromIndex=collection2 toIndex=collection1 from=id to=emp_deptno%7Ddept_dname:SALES&
json.facet=%7B
	x:"max(emp_sal)"
%7D

在这里插入图片描述

4.2.4 Block Join

​ 在Solr中还支持一种Join查询,我们称为Block Join。但前提是我们的 Document 必须是 Nested Document (内嵌的Document);

4.2.4.1什么是内嵌的Document

内嵌的Document 就是我们可以在 Document 内部再添加一个Document ,形成父子关系,就好比HTML中的 div标签内部能够嵌套其他div标签,形成多层级的父子关系,以员工和部门为例,如果我们采用内嵌 Document 来进行索引,我们使用一个collection来存储部门和员工的信息。此时我们的索引数据结构应该是类似这样的:

<add>  
	 	<doc>         
			<field name="id">1</field>  
			<field name="dept_dname">ACCOUNTING</field> 
			<field name="dept_loc">NEW YORK</field> 
			<doc>
                <field name="id">7369</field>  
                <field name="emp_ename">SMITH COCO</field> 
                <field name="emp_job">CLERK</field> 
                <field name="emp_hiredate">1980-12-17</field
                ...
                <field name="emp_deptno">1</field> 
			</doc>
			<doc>
                <field name="id">7566</field>  
                <field name="emp_ename">JONES</field> 
                <field name="emp_job">MANAGER</field> 
                <field name="emp_hiredate">1981-04-02</field
                ...
                <field name="emp_deptno">1</field> 
			</doc>
		</doc>
 
</add>
4.2.4.1内嵌Document相关域创建
	<!--员工相关的域-->
    <field name="emp_ename" type="text_ik" indexed="true" stored="true"/>
    <field name="emp_job" type="text_ik" indexed="true" stored="true"/>
    <field name="emp_mgr" type="string" indexed="true" stored="true"/>
    <field name="emp_hiredate" type="pdate" indexed="true" stored="true"/>
    <field name="emp_sal" type="pfloat" indexed="true" stored="true"/>
    <field name="emp_comm" type="pfloat" indexed="true" stored="true"/>
	<field name="emp_cv" type="text_ik" indexed="true" stored="false"/>



	<!--部门表的业务域-->
	<field name="dept_dname" type="text_ik" indexed="true" stored="true"/>
	<field name="dept_loc" type="text_ik" indexed="true" stored="true"/>
	<!--标识那个是父节点-->
	<field name="docParent" type="string" indexed="true" stored="true"/>
	<!--创建内嵌的document必须有_root_域,该域在schema文件中已经有了,不需要额外添加-->
	<field name="_root_" type="string" indexed="true" stored="true"/>
4.2.4.2 使用solrJ/spring data solr将部门和员工信息以内嵌Document的形式进行索引

​ SolrJ核心的API

SolrInputDocument doc = new SolrInputDocument()
SolrInputDocument subDoc = new SolrInputDocument()
doc.addChildDocument(subDoc); //建立父子关系的API方法。

​ Spring Data Solr

如果学习了Spring Data Solr可以使用@Field注解和@ChildDocument注解

​ 思路

使用mybatis查询部门表的所有信息。
迭代每一个部门-------->部门Doucment
根据部门号查询该部门下所有的员工------->员工Document集合
建立部门Doucment和其员工Document的父子关系

​ 引入依赖

 	 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
    </parent>
 	   <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.solr</groupId>
            <artifactId>solr-solrj</artifactId>
            <version>7.7.2</version>
        </dependency>


        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>


        <!--通用mapper起步依赖-->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>2.0.4</version>
        </dependency>
        <!--MySQL数据库驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        

​ 编写yml文件

#数据库的连接信息
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/lucene?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: 123

#solr collection1地址
url: http://localhost:8080/solr/collection1

​ 编写启动类

@SpringBootApplication
@MapperScan("cn.itcast.dao")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Value("${url}")
    private String url;
    @Bean
    public HttpSolrClient httpSolrClient() {
        HttpSolrClient.Builder builder = new HttpSolrClient.Builder(url);
        return builder.build();
    }

}

​ 编写实体类

@Data
@Table(name = "emp")
public class Emp {
    @Id
    private String empno;
    private String ename;
    private String job;
    private String mgr;
    private Date hiredate;
    private Double sal;
    private Double comm;
    private String cv;
    private String deptno;
}

@Data
@Table(name = "dept")
public class Dept {
    @Id
    private String deptno;
    private String dname;
    private String loc;
}

​ Dao接口的编写

public interface DeptDao extends Mapper<Dept> {
}
public interface EmpDao extends Mapper<Emp> {
}

​ 编写测试方法,导入数据

@RunWith(SpringRunner.class)
@SpringBootTest(classes=Application.class)
public class AppTest {
    @Autowired
    private DeptDao deptDao;

    @Autowired
    private EmpDao empDao;

    @Autowired
    private SolrClient solrClient;

    @Test
    public void test01() throws IOException, SolrServerException {
        List<Dept> depts = deptDao.selectAll();
        //迭代每一个部门
        for (Dept dept : depts) {
            //每个部门转化为一个Document
            System.out.println(dept);
            SolrInputDocument deptDocument = new SolrInputDocument();
            deptDocument.setField("id", dept.getDeptno());
            deptDocument.setField("dept_dname", dept.getDname());
            deptDocument.setField("dept_loc", dept.getLoc());
            deptDocument.setField("docParent", "isParent");

            //获取每个部门的员工。
            List<SolrInputDocument> emps = findEmpsByDeptno(dept.getDeptno());
            deptDocument.addChildDocuments(emps);

            solrClient.add(deptDocument);
            solrClient.commit();
        }
    }

    private List<SolrInputDocument> findEmpsByDeptno(String deptno) {
        Example example = new Example(Emp.class) ;
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo("deptno", deptno);
        List<Emp> emps = empDao.selectByExample(example);
        System.out.println(emps);
        List<SolrInputDocument> solrInputDocuments = new ArrayList<>();
        for (Emp emp : emps) {
            SolrInputDocument document = emp2SolrInputDocument(emp);
            solrInputDocuments.add(document);
        }
        return solrInputDocuments;
    }

    private SolrInputDocument emp2SolrInputDocument(Emp emp) {
        SolrInputDocument document = new SolrInputDocument();
        document.setField("id", emp.getEmpno());
        document.setField("emp_ename", emp.getEname());
        document.setField("emp_job", emp.getJob());
        document.setField("emp_mgr", emp.getMgr());
        document.setField("emp_hiredate", emp.getHiredate());
        document.setField("emp_sal", emp.getSal());
        document.setField("emp_comm", emp.getComm());
        document.setField("emp_cv", emp.getCv());
        return document;
    }

}

​ 测试:

在这里插入图片描述

4.2.4.3 基于内嵌的document查询

​ 语法:用来查询子文档

​ allParents:查询所有父文档的条件,someParents过滤条件

q={!child of=<allParents>}<someParents>

​ 语法:用来查询父文档

​ allParents:查询所有父文档的条件,someChildren子文档的过滤条件

q={!parent which=<allParents>}<someChildren>

​ 需求:查询ACCOUNTING部门下面所有的员工。

q={!child of=docParent:isParent}dept_dname:ACCOUNTING

​ 需求:查询CLARK员工所属的部门

q={!parent which=docParent:isParent}emp_ename:CLARK

4.3 相关度排序

4.3.1 Field权重

​ 比如我们有图书相关的文档,需要根据book_name或者book_description搜索图书文档。我们可能认为book_name域的权重可能要比book_description域的权重要高。我们就可以在搜索的时候给指定的域设置权重。

演示:默认的相关度排序

q=book_name:java OR book_description:java
http://localhost:8080/solr/collection1/select?q=book_name:java OR book_description:java

在这里插入图片描述

​ 演示:设置域的权重

​ 方式1:在schema文件中给指定field指定boost属性值。

​ boost默认值1.0

​ 此方式不建议:原因是需要重新对数据进行索引。

	<field name="book_name" type="text_ik" indexed="true" stored="true" boost="10.0"/>

​ 方式2:

​ 使用edismax查询解析器。

defType=edismax
&q=java
&qf=book_name book_description^10
http://localhost:8080/solr/collection1/select?
defType=edismax&q=java
&qf=book_name book_description%5E10

q:查询条件是java
qf:在哪个域中进行查询
4.3.2 Term权重

​ 有时候我们查询的时候希望为某个域中的词设置权重。

​ 查询book_name中包含 java或者spring的,我们希望包含java这个词的权重高一些。

book_name:java OR book_name:spring
book_name:java^10 OR book_name:spring
4.3.3 Function权重

​ function权重,我们在之前讲解函数查询的时候,讲解过。

4.3.4 临近词权重

​ 有时候我们有这种需求,比如搜索iphone plus将来包含iphone 或者 plus的文档都会被返回,但是我们希望包含iphone plus完整短语的文档权重高一些。

​ 第一种方式:使用edismax查询解析器

http://localhost:8080/solr/collection1/select?defType=edismax& 
q=iphone plus&qf=book_name

在这里插入图片描述

可以添加一个参数
pf=域名~slop^boost

http://localhost:8080/solr/collection1/select?
defType=edismax& 
q=iphone plus&
qf=book_name&
pf=book_name~0^10 
				0^10	book_name中包含iphone 没有任何词 plus文档权重是10
				1^10	book_name中包含iphone 一个词/没有词 plus文档权重是10
				2^10	book_name中包含”iphone 2个及2个以下词 plus"文档权重是10
%5E

q:查询条件是java
qf:在哪个域中进行查询

​ 第二种方式:使用标准解析器中也可以完成临近词的加权

/select?q=iphone plus OR "iphone plus"~0^10 &df=book_name

q:查询条件是java
df:在哪个域中进行查询
4.3.5 Document权重

​ 除了上面的修改相关度评分的操作外,我们也可以在索引的时候,设置文档的权重。使用SolrJ或者Spring DataSolr完成索引操作的时候调用相关方法来设置文档的权重。在Solr7以后已经废弃。

  @Deprecated
  public void setDocumentBoost(float documentBoost) {
    _documentBoost = documentBoost;
  }

5.Solr性能优化

5.1 Schema文件设计的注意事项(理解)

硬件方面主要影响solr的性能主要是内存,solr的内存主要应用在两个方面:(1)Java的堆内存;(2)数据缓存;建议使用64位jdk,并且配套64位操作系统(32位的jdk最大支持的堆内存使用2G)

​ indexed属性(重要):设置了indexed=true的域要比indxed=false的域,索引时占用更多的内存空间。而且设置了index=true的域所占用磁盘空间也比较大,所以对于一些不需要搜索的域,我们需要将其indexed属性设置为false。比如:图片地址。

​ omitNorms属性选择:如果我们不关心词在文档中出现总的次数,影响最终的相关度评分。可以设置omitNorms=true。它可以减少磁盘和内存的占用,也可以加快索引时间。

​ omitPosition属性选择:如果我们不需要根据该域进行高亮,可以设置成true,也可以减少索引体积。

​ omitTermFreqAndPositions属性:如果我们只需要利用倒排索引结构根据指定Term找到相应Document,不需要考虑词的频率,词的位置信息,可以将该属性设置为true,也可以减少索引体积。

​ stored属性(重要):如果某个域的值很长,比如想要存储一本书的信息,首先我们要考虑该域要不要存储。如果我们确实想要在Solr 查询中能够返回该域值,此时可以考虑使用 ExtemalFileField 域类型。

​ 如果我们存储的域值长度不是很大,但是希望降低磁盘的IO。可以设置 compressed=true 即启用域值数据 压缩,域值数据压缩可以降低磁盘 IO ,同时会增加 CPU 执行开销。

​ 如果你的域值很大很大,比如是一个几十MB PDF 文件的内容 ,此时如果你将域的 stored 属性设置为 true 存储在 Solr中,它不仅会影响你的索引创建性能,还会影响我们的查询性能,如果查询时,fl参数里返回该域,那对系统性能打击会很大,如果了使用 ExtemalFileField 域类型,该域是不支持查询的,只支持显示。通常我们的做法是将该域的信息存储到Redis,Memcached缓存数据库。当我们需要该域的数据时,我们可以根据solr中文档的主键在缓存数据库中查询。

​ multiValued属性:如果我们某个域的数据有多个时,通常可以使用multiValued属性。eg:鞋子的尺码,鞋子的尺码可能会有多个36,38,39,40,41...。就可以设置multiValued属性为true。

​ 但是有些域类型是不支持multiValued属性,eg:地理位置查询LatLonType;

​ Group查询也不支持multiValued属性为true的域。

​ 我们可以考虑使用内嵌的Document来解决次问题。可以为该文档添加多个子文档。

​ 对于日期类型的数据,在Solr中我们强烈建议使用solr中提供的日期类型域类来存储。不建议使用string域类型来存储。否则是无法根据范围进行查询。

​ Solr官方提供的schema示例文件中定义了很多<dynamicField><copyField>以及

<fieldType>,上线后这里建议你将它们都清理掉, <fieldType>只保留一些基本的域类型string、 boolean

pint plong pfloat pdate 即可,一定保留内置的_version_,_root_ 域,否则一些功能就不能使用。

比如删除_root_就无法创建内嵌的document;

5.2 索引更新和提交的建议

​ 在Solr中我们在进行索引更新的时候,一般不建议显式的调用 commit()进行硬提交,建议我们 solrconfig. xml 中配置自动提交和软提交;

​ 硬提交: 所谓硬提交是将没有提交的数据flush到硬盘中,并且能够查询到这条数据。如果我们每次都进行硬提交的话,频繁的和磁盘IO进行交互,会影响索引的性能。

​ 软提交:所谓软提交是将提交数据到写到内存里面,并且开启一个searcher。它可以保证数据可以被搜索到,等到一定的时机后再进行自动硬提交,再将数据flush到硬盘上。

​ 在SolrConfig.xml中如何配置软提交呢

<updateHandler class="solr.DirectUpdateHandler2">
<autoCommit>
    <!--表示软提交达到1万条的时候会自动进行一次硬提交-->
    <maxDocs>10000</maxDocs>

    <!--每10秒执行一次硬提交-->
    <maxTime>10000</maxTime>
</autoCommit>

    <autoSoftCommit> 
      <!--1秒执行一次软提交-->
      <maxTime>1000</maxTime> 
    </autoSoftCommit>
</updateHandler>

5.3 Solr缓存

​ 在Solr中缓存扮演者很重要的角色,它很大程度上决定了我们的Solr查询性能。在Solr中一共开启了3种内置缓存filterCache documentCache queryResultCache ,这些缓存都是被IndexSearcher所管理。

在这里插入图片描述

​ 1.用户发起一个查询请求后,请求首先被请求处理器接收

​ 2.请求处理器会将搜索的查询字符串,交由QueryParser解析

​ 3.解析完成后会生成Query对象,由IndexSearcher来执行查询。

​ 在Solr中对IndexSearcher的实现类,叫SolrIndexSearcher。在SolrIndexSearcher中,管理了相关的缓存。

一个SolrIndexSearcher对应一套缓存体系,一般来说一个SolrCore只需要一个SolrIndexSearcher实例

在Solr中允许我们在solrConfig.xml的<query>标签下修改缓存的属性。

5.3.1 缓存公共属性

​ 无论是那种缓存,都有4个相同属性。

​ class:设置缓存的实现类,solr内置了三个实现类。FastLRUCache,LRUCache,LFUCache

​ LRU和LFU是2种不同的缓存淘汰算法。

​ LRU算法的思想是:如果一个数据在最近一段时间没有被访问到,那么可以认为在将来它被访问的可能性也很小。因此,当缓存满时,最久没有访问的数据最先被置换(淘汰)。

​ LFU算法的思想是:如果一个数据在最近一段时间很少被访问到,那么可以认为在将来它被访问的可能性也很小。因此,当空间满时,最小频率访问的数据最先被淘汰。
在这里插入图片描述

​ size:cache中可保存的最大的项数。

​ initialSize:cache初始化时的大小。

​ autowarmCount:SolrCore重启(reload)的时候,Indexsearcher会重新创建,不过旧的SolrCore和旧的Indexsearcher会继续存在一段时间,autowarmCount表示从旧的缓存中获取多少项来在新的SolrIndexSearcher缓存中被重新生成。如果设置成0,表示SolrCore重启(reload)的时候,旧的缓存数据就不要了。我们把这个过程叫缓存预热。

5.3.2 缓存特有属性

​ 不同的缓存类型有一些特有的属性。

​ FastLRUCache:

​ minSize:当cache达到size以后,使用淘汰策略使其降到minSize大小,默认是0.9*size;

​ acceptableSize:当淘汰数据时,首先会尝试删除到minSize,但可能会做不到,至少可以删除到acceptableSize,默认是0.95*size。

​ cleanupThread:它表示是否需要单独起一个线程来删除缓存项,当我们的缓存 size非常大时你可能会需要将此参数设置为 true,默认值false。因为当cache大小很大时,每一次的淘汰数据就可能会花费较长时间,这对于提供查询请求的线程来说就不太合适,由独立的后台线程来做就很有必要。

5.3.3 Solr查询缓存的分类和作用

​ filterCache

​ filterCache中存储的是满足过滤条件的document id集合。

​ 什么时候filterCache会进行缓存。

​ (1)当我们执行一个查询,如果包含fq(可能会有多个),Solr会先执行每一个fq,对fq执行的结果取并集,之后将fq和q结果取并集。在这个过程中filerCache会将单个过滤条件(类型为Query)作为key,符合条件的document id存储到Set集合中作为value缓存起来。

​ (2)filterCache也会缓存Facet查询结果。

​ 演示FilterCache

q:item_title:手机&
fq:item_brand:华为
   item_price:[2000 TO 3000]

在这里插入图片描述

将来我们就可以根据后台的统计来设置filterCache的参数。

queryResultCache

​ 查询结果集缓存,缓存的是查询结果集有序文档ID的集合,key是q和fq及sort组合的一个唯一标识。value是满足条件文档的id集合。

​ eg:查询q=item_title:手机 fq=item_brand:华为,item_price:[1000 TO 2000],并且按照价格排序;

​ 会将q,fq及sort作为key将满足条件的文档id集合作为value缓存到queryResultCache。

​ 要想命中缓存,需要保证q,fq,sort一致。

在这里插入图片描述

​ 接下来我们再来说一下queryResult缓存的2个配置。

  比如查询匹配的documentID是[0, 10)之间,queryResultWindowSize= 50,那么DocumentID [0, 50] 会被并缓存。
  为什么这么做呢?原因是我们查询的时候可能会带有start和rows,如果所以某个QueryResultKey可能命中了cache,但是start和rows可能不在缓存的文档id集合范围。 可以使用参数来加大缓存的文档id集合范围。
   <queryResultWindowSize>20</queryResultWindowSize>
   	value中最大存储的文档的id个数
  <queryResultMaxDocsCached>200</queryResultMaxDocsCached>
  这两个值一般设置为每个page大小的2-3倍。

​ documentCache

​ documentCache (即索引文档缓存)用于存储已经从磁盘上查询出来的Document 对象。键是文档id值是文档对象。

​ 但是Document中并不会保存所有域的信息,只会保存fl中指定的域,如果没有指定则保存所有的域。

没有保存的域会标识为延迟加载,当我们需要延迟加载的域信息的时候,再从磁盘上进行查询。

5.4 其他优化建议

​ 增大JVM的堆内存

​ 需要在tomcat/catalina.sh文件中加入

​ JAVA_OPTS="$JAVA_OPTS -server -Xms2048m -Xmx2048m"

​ -Xms:初始Heap大小,使用的最小内存,cpu性能高时此值应设的大一些
​ -Xmx:java heap最大值,使用的最大内存
​ 上面两个值是分配JVM的最小和最大内存,取决于硬件物理内存的大小,建议均设为物理内存的一半。

​ 取消指定过滤查询的Filter缓存。

​ 在Solr中默认会为每个FilterQuery 启用 Filter 缓存,大部分情况下这能提升查询性能,但是比如我们想要查询某个价格区间或者时间范围内的商品信息。

q=*:*,
fq=item_price:[0 TO 3000],
fq=item_brand:三星

​ 但是对于每个用户而言,设置的价格区间参数可能是不一致的,有的用户提供的价格区间可能是 [20, 50],也有可能 [20 ,60] ,[30, 60],这种价格区间太多太多,如果对每个价格区间的 Filter Query都启用 Filter缓存就不太合适。

q=*:*
&fq={!cache=false}item_price:[0 TO 3000],
&fq=item_brand:三星

​ 对于什么时候要禁用Filter缓存,我们需要判断,取决于这个Filter Query的查询条件有没有共性。item_brand:三星过滤条件就比较适合做缓存。因为品牌的值本身就比较少。价格区间、时间区间就不适合,因为区间范围有不确定性。就算你做了缓存,缓存的命中率也很低,对于我们内存也是一种浪费。

​ Filter Query的执行顺序。

​ 假如我们的查询中有多个FilterQuery ,此时我们需要考虑每个 Filter Query 的执行顺序,因为Filter Query 的执行顺序可能会影响最终的查询的性能。

​ 一般我们需要让能过滤掉大量文档的FilterQuery优先执行。我们当前有三个过滤条件,其中item_category:手机过滤能力最强,其次是item_brand:三星。设置cost参数,参数值越小,优先级越高。

q=*:*
&fq={!cache=false cost=100}item_price:[0 TO 3000],
&fq={!cost=2}item_brand:三星,
&fq={!cost=1}item_category:手机

6.Spring Data Solr(开发)

6.1 Spring Data Solr的简介

​ Spring Data Solr和SolrJ的关系

​ Spring Data Solr是Spring推出的对SolrJ封装的一套API,使用Spring Data Solr可以极大的简化编码。

Spring Data 家族还有其他的API,Spring Data Redis|JPA|MongoDB等。

​ Spring Data Solr的环境准备。

​ 1.创建服务,继承父工程引入spring data solr的起步依赖

	<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.10.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-solr</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

​ 2.编写启动类和yml配置文件。

​ 在yml配置文件中,我们需要配置Solr的地址。

​ 如果使用单机版的Solr配置host;指定solr服务的地址。

spring:
  data:
    solr:
      host: http://localhost:8080/solr

​ 如果使用集群版的solr配置zk-host,如果Zookeeper搭建了集群,地址中间使用,分割。

spring:
  data:
    solr:
      zk-host: 192.168.200.131:2181,192.168.200.131:2182,192.168.200.131:2183

6.2 SolrTemplate

​ 如果我们要使用Spring Data Solr来操作Solr,核心的API是SolrTemplate,所以首先我们需要在配置类中,将SolrTemplate交由spring管理;

   @Bean
    public SolrTemplate solrTemplate(SolrClient solrClient) {
        return new SolrTemplate(solrClient);
    }
6.2.1 索引
6.2.1.1添加

​ 需求:添加一个商品。

​ 1.创建一个商品实体类,并且建立实体类属性和域的映射关系。将来操作实体类对象,就是操作文档。

@Data
public class Item {
    @Field("id")
    @Id
    private String id;
    @Field("item_title")
    private String title;
    @Field("item_price")
    private Double price;
    @Field("item_images")
    private String image;
    @Field("item_createtime")
    private Date createTime;
    @Field("item_updatetime")
    private Date updateTime;
    @Field("item_category")
    private String category;
    @Field("item_brand")
    private String brand;
}

​ 2.注入SolrTemplate,使用saveBean方法完成添加操作

    @Test
    public void testSave() {
        Item item = new Item();
        item.setId("9999");
        item.setBrand("锤子");
        item.setTitle("锤子(SHARP) 智能锤子");
        item.setCategory("手机");
        item.setCreateTime(new Date());
        item.setUpdateTime(new Date());
        item.setPrice(9999.0);
        item.setImage("https://www.baidu.com/img/bd_logo1.png");

        solrTemplate.saveBean("collection1",item);
        solrTemplate.commit("collection1");
    }
  1. 在后台管理系统中进行查看

在这里插入图片描述

6.2.1.2 修改

​ 如果文档id在索引库中已经存在就是修改;

    @Test
    public void testUpdate() {
        Item item = new Item();
        item.setId("9999");
        item.setBrand("香蕉");
        item.setTitle("香蕉(SHARP)LCD-46DS40A 46英寸 日本原装液晶面板 智能全高清平板电脑");
        item.setCategory("平板电脑");
        item.setCreateTime(new Date());
        item.setUpdateTime(new Date());
        item.setPrice(9999.0);
        item.setImage("https://www.baidu.com/img/bd_logo1.png");
        solrTemplate.saveBean("collection1",item);
        solrTemplate.commit("collection1");
    }
6.2.1.3 删除

​ 支持基于id删除,支持基于条件删除。

​ 基于id删除

    @Test
    public void testDeleteDocument() {
        solrTemplate.deleteByIds("collection1", "9999");
        solrTemplate.commit("collection1");
    }

​ 支持基于条件删除,删除collection1中所有的数据(删除所有数据要慎重)

    @Test
    public void testDeleteQuery(){
        SolrDataQuery query = new SimpleQuery("*:*");
        solrTemplate.delete("collection1",query);
        solrTemplate.commit("collection1");
    }
6.2.2 基本查询
6.2.2.1 主查询+过滤查询

​ 核心的API方法:

/**
* 参数1:要操作的collection
* 参数2:查询条件	
* 参数3: 查询的文档数据要封装成什么类型。
* 返回值ScoredPage是一个分页对象,封装了总记录数,总页数,当前页的数据。
*/
public <T> ScoredPage<T> queryForPage(String collection, Query query, Class<T> clazz); 

​ 需求:查询item_title中包含手机的文档

    @Test
    public void testBaseQuery() {
        //条件封装
        Query query = new SimpleQuery("item_title:手机");
        //执行查询
        ScoredPage<Item> scoredPage = solrTemplate.queryForPage("collection1", query, Item.class);
        //解析结果
        long elements = scoredPage.getTotalElements();
        System.out.println("总记录数" + elements);
        int totalPages = scoredPage.getTotalPages();
        System.out.println("总页数" + totalPages);

        //第一页的数据
        List<Item> content = scoredPage.getContent();
        for (Item item : content) {
            System.out.println(item);
        }
    }    

​ 结果:总记录数,总页数是1页,说明没有分页。虽然没有分页。但是他并没有把716条数据都查询出来,只查询了满足条件并且相关度高的前10个。

在这里插入图片描述

接下来我们在这个基础上。我们进行过滤查询。添加过滤条件:品牌是华为,价格在[1000-2000].

    @Test
    public void testBaseQuery() {
        //条件封装
        Query query = new SimpleQuery("item_title:手机");
        FilterQuery filterQuery=new SimpleFilterQuery();
        //品牌:华为过滤条件
        filterQuery.addCriteria(new Criteria("item_brand").is("华为"));
        //价格:区间
        filterQuery.addCriteria(new Criteria("item_price").greaterThanEqual(1000));
        filterQuery.addCriteria(new Criteria("item_price").lessThanEqual(2000));
        query.addFilterQuery(filterQuery);
        //执行查询
        ScoredPage<Item> scoredPage = solrTemplate.queryForPage("collection1", query, Item.class);
        //解析结果
        long elements = scoredPage.getTotalElements();
        System.out.println("总记录数" + elements);
        int totalPages = scoredPage.getTotalPages();
        System.out.println("总页数" + totalPages);

        //第一页的数据
        List<Item> content = scoredPage.getContent();
        for (Item item : content) {
            System.out.println(item);
        }
    }
6.2.2.2分页

​ 查询满足条件的第2页的10条数据

query.setOffset(20L); //start
query.setRows(10);//rows

​ 查询结果

在这里插入图片描述

6.2.2.3 排序

​ 需求:按照价格升序。如果价格相同,按照id降序。

query.addSort(new Sort(Sort.Direction.ASC,"item_price"));
query.addSort(new Sort(Sort.Direction.DESC,"id"));
6.2.3 组合查询

​ 需求:查询Item_title中包含手机或者电视的文档。

Query query = new SimpleQuery("item_title:手机 OR item_title:电视");
Query query = new SimpleQuery("item_title:手机 || item_title:电视");

​ 需求:查询Item_title中包含手机 并且包含三星的文档

Query query = new SimpleQuery("+item_title:手机  +item_title:三星");
Query query = new SimpleQuery("item_title:手机 AND item_title:三星");
Query query = new SimpleQuery("item_title:手机 && item_title:三星");

​ 需求: 查询item_title中包含手机但是不包含三星的文档

Query query = new SimpleQuery("+item_title:手机  -item_title:三星");
Query query = new SimpleQuery("item_title:手机  NOT item_title:三星");

​ 需求:查询item_title中包含iphone开头的词的文档,使用通配符。;

Query query = new SimpleQuery("item_title:iphone*");

6.3 其他查询

6.3.1 facet查询

​ 之前我们讲解Facet查询,我们说他是分为4类。

​ Field,Query,Range(时间范围,数字范围),Interval(和Range类似)

6.3.1.1 基于Field的Facet查询

​ 需求:查询item_title中包含手机的文档,并且按照品牌域进行分组统计数量;

http://localhost:8080/solr/collection1/select?q=item_title:手机&facet=on&facet.field=item_brand&facet.mincount=1
        //查询条件
        FacetQuery query = new SimpleFacetQuery(new Criteria("item_title").is("手机"));
        //设置Facet相关的参数
        FacetOptions facetOptions = new FacetOptions("item_brand");
        facetOptions.addFacetOnField("item_brand");
        facetOptions.setFacetMinCount(1);
        //facet分页:页码从零开始
        //facetOptions.setPageable(PageRequest.of(1,10));
        query.setFacetOptions(facetOptions);
        //执行Facet查询
        FacetPage<Item> facetPage = solrTemplate.queryForFacetPage("collection1", query, Item.class);

        //解析Facet的结果
        //由于Facet的域可能是多个,需要指定名称
        Page<FacetFieldEntry> facetResultPage = facetPage.getFacetResultPage("item_brand");
        List<FacetFieldEntry> content = facetResultPage.getContent();
        for (FacetFieldEntry facetFieldEntry : content) {
            System.out.println(facetFieldEntry.getValue());
            System.out.println(facetFieldEntry.getValueCount());
        }
6.3.1.2 基于Query的Facet查询

​ 需求:查询分类是平板电视的商品数量 ,品牌是华为的商品数量 ,品牌是三星的商品数量,价格在1000-2000的商品数量;

http://localhost:8080/solr/collection1/select?
q=*:*&
facet=on&
facet.query=item_category:平板电视&
facet.query=item_brand:华为&
facet.query=item_brand:三星&
facet.query=item_price:%5B1000 TO 2000%5D
 @Test
    public void testQueryFacet() {
        //主查询条件,查询所有
        FacetQuery query = new SimpleFacetQuery(new SimpleStringCriteria("*:*"));

        //设置Facet相关的参数
        //定义4个query
        SolrDataQuery solrDataQuery1 = new SimpleQuery("{!key=pb}item_category:平板电视");
        SolrDataQuery solrDataQuery2 = new SimpleQuery("{!key=hw}item_brand:华为");
        SolrDataQuery solrDataQuery3 = new SimpleQuery("{!key=sx}item_brand:三星");
        SolrDataQuery solrDataQuery4 = new SimpleQuery(new Criteria("{!key=price}item_price").between(1000,2000));

        FacetOptions facetOptions = new FacetOptions();
        facetOptions.addFacetQuery(solrDataQuery1);
        facetOptions.addFacetQuery(solrDataQuery2);
        facetOptions.addFacetQuery(solrDataQuery3);
        facetOptions.addFacetQuery(solrDataQuery4);
        query.setFacetOptions(facetOptions);
        //执行Facet查询
        FacetPage<Item> facetPage = solrTemplate.queryForFacetPage("collection1", query, Item.class);
        //获取分页结果
        Page<FacetQueryEntry> facetQueryResult = facetPage.getFacetQueryResult();
        //获取当前页数据
        List<FacetQueryEntry> content = facetQueryResult.getContent();
        for (FacetQueryEntry facetQueryEntry : content) {
            System.out.println(facetQueryEntry.getValue());
            System.out.println(facetQueryEntry.getValueCount());
        }
    }
6.3.1.3 基于Range的Facet查询

​ 需求:分组查询价格0-2000 ,2000-4000,4000-6000....18000-20000每个区间商品数量

http://localhost:8080/solr/collection1/select?
q=*:*&
facet=on&
facet.range=item_price&
facet.range.start=0&
facet.range.end=20000&
facet.range.gap=2000&
&facet.range.other=all
@Test
    public void testRangeFacet() {
        //设置主查询条件
        FacetQuery query = new SimpleFacetQuery(new SimpleStringCriteria("*:*"));

        //设置Facet参数
        FacetOptions facetOptions = new FacetOptions();
        facetOptions.addFacetByRange(new FacetOptions.FieldWithNumericRangeParameters("item_price", 0, 20000, 2000));
        query.setFacetOptions(facetOptions);

        //执行Facet查询
        FacetPage<Item> facetPage = solrTemplate.queryForFacetPage("collection1", query, Item.class);

        //解析Facet的结果
        Page<FacetFieldEntry> page = facetPage.getRangeFacetResultPage("item_price");
        List<FacetFieldEntry> content = page.getContent();
        for (FacetFieldEntry facetFieldEntry : content) {
            System.out.println(facetFieldEntry.getValue());
            System.out.println(facetFieldEntry.getValueCount());
        }

    }

需求:统计2015年每个季度添加的商品数量

http://localhost:8080/solr/collection1/select?
q=*:*&
facet=on&
facet.range=item_createtime&
facet.range.start=2015-01-01T00:00:00Z&
facet.range.end=2016-01-01T00:00:00Z&
facet.range.gap=%2B3MONTH
  @Test
    public void testRangeDateFacet() throws ParseException {
        //设置主查询条件
        FacetQuery query = new SimpleFacetQuery(new SimpleStringCriteria("*:*"));

        //设置Facet参数
        FacetOptions facetOptions = new FacetOptions();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT+8:00"));
        //facet为0的也可以查询出来
        facetOptions.setFacetMinCount(0); 
        Date start = simpleDateFormat.parse("2015-01-01 00:00:00");
        Date end = simpleDateFormat.parse("2016-01-01 00:00:00");
        facetOptions.addFacetByRange(new FacetOptions.FieldWithDateRangeParameters("item_createtime", start, end, "+3MONTH"));
        query.setFacetOptions(facetOptions);

        //执行Facet查询
        FacetPage<Item> facetPage = solrTemplate.queryForFacetPage("collection1", query, Item.class);

        //解析Facet的结果
        Page<FacetFieldEntry> page = facetPage.getRangeFacetResultPage("item_createtime");
        List<FacetFieldEntry> content = page.getContent();
        for (FacetFieldEntry facetFieldEntry : content) {
            System.out.println(facetFieldEntry.getValue());
            System.out.println(facetFieldEntry.getValueCount());
        }

    }
6.3.1.4 Facet维度查询

​ 需求:统计每一个品牌和其不同分类商品对应的数量;

​ 联想 手机 10

​ 联想 电脑 2

​ 华为 手机 10

 http://localhost:8080/solr/collection1/select?
 q=*:*&
 &facet=on
 &facet.pivot=item_brand,item_category
@Test
    public void intervalFacet() {
        //设置主查询条件
        FacetQuery query = new SimpleFacetQuery(new SimpleStringCriteria("*:*"));
        //设置Facet查询参数
        FacetOptions facetOptions = new FacetOptions();
        facetOptions.addFacetOnPivot("item_brand","item_category");

        query.setFacetOptions(facetOptions);
        //执行Facet查询
        FacetPage<Item> facetPage = solrTemplate.queryForFacetPage("collection1", query, Item.class);
        //解析Facet结果

        List<FacetPivotFieldEntry> pivot = facetPage.getPivot("item_brand,item_category");
        for (FacetPivotFieldEntry facetPivotFieldEntry : pivot) {
            //品牌分组
            System.out.println(facetPivotFieldEntry.getValue());
            System.out.println(facetPivotFieldEntry.getValueCount());
            //品牌下对应的分类的分组数据
            List<FacetPivotFieldEntry> fieldEntryList = facetPivotFieldEntry.getPivot();
            for (FacetPivotFieldEntry pivotFieldEntry : fieldEntryList) {
                System.out.println(" "+pivotFieldEntry.getValue());
                System.out.println(" " +pivotFieldEntry.getValueCount());
            }
        }

    }
6.3.2 group查询

​ 使用Group查询可以将同组文档进行归并。在Solr中是不支持多维度group。group的域可以是多个,但是都是独立的。

6.3.2.1 基础的分组

​ 需求:查询Item_title中包含手机的文档,按照品牌对文档进行分组,同组中的文档放在一起.

http://localhost:8080/solr/collection1/select?
q=item_title:手机
&group=true
&group.field=item_brand
        //设置主查询条件
        Query query = new SimpleQuery("item_title:手机");

        //设置分组参数
        GroupOptions groupOptions = new GroupOptions();
        groupOptions.addGroupByField("item_brand");
        groupOptions.setOffset(0);
        query.setGroupOptions(groupOptions);

        //执行分组查询
        GroupPage<Item> groupPage = solrTemplate.queryForGroupPage("collection1", query, Item.class);
        //解析分组结果,由于分组的域可能是多个,需要根据域名获取分组
        GroupResult<Item> groupResult = groupPage.getGroupResult("item_brand");

        System.out.println("匹配到的文档数量" + groupResult.getMatches());

        //获取分组数据
        Page<GroupEntry<Item>> groupEntries = groupResult.getGroupEntries();

        List<GroupEntry<Item>> content = groupEntries.getContent();
        //迭代每一个分组数据,包含组名称,组内文档数据
        for (GroupEntry<Item> itemGroupEntry : content) {
            String groupValue = itemGroupEntry.getGroupValue();
            System.out.println("组名称" + groupValue);
            //组内文档数据
            Page<Item> result = itemGroupEntry.getResult();
            List<Item> itemList = result.getContent();
            for (Item item : itemList) {
                System.out.println(item);
            }
        }


    }
6.3.2.2 group分页

​ 默认情况下group查询只会展示前10个组,并且每组展示相关对最高的1个文档。我们可以使用start和rows可以设置组的分页,使用group.limit和group.offset设置组内文档分页。

​ 展示前3个组及每组前5个文档。

http://localhost:8080/solr/collection1/select?
q=item_title:手机&group=true&group.field=item_brand&start=0&rows=3&group.limit=5&group.offset=0
//设置组的分页参数
query.setOffset(0L);
query.setRows(3);

//设置组内文档的分页参数
groupOptions.setOffset(0);
groupOptions.setLimit(5);
6.3.2.3 group排序

​ 之前讲解group排序的时候,group排序分为组排序,组内文档排序;对应的参数为sort和group.sort

​ 需求:按照组内价格排序降序;

    groupOptions.addSort(new Sort(Sort.Direction.DESC,"item_price"));

6.3.3 高亮
6.3.3.1 高亮查询

​ 查询item_title中包含手机的文档,并且对item_title中的手机关键字进行高亮;

 http://localhost:8080/solr/collection1/select?   
    q=item_title:手机
    &hl=true
    &hl.fl=item_title
    &hl.simple.pre=<font>
    &simpletag.post=</font>
@Test
    public void testHighlightingQuery() throws IOException, SolrServerException {
        //指定查询条件
        HighlightQuery query = new SimpleHighlightQuery(new Criteria("item_title").is("手机"));
        //设置高亮参数
        HighlightOptions highlightOptions = new HighlightOptions();
        highlightOptions.addField("item_title");
        highlightOptions.setSimplePrefix("<font>");
        highlightOptions.setSimplePostfix("</font>");
        query.setHighlightOptions(highlightOptions);
        //执行高亮查询
        HighlightPage<Item> highlightPage = solrTemplate.queryForHighlightPage("collection1", query, Item.class);
        //获取满足条件的文档数据
        List<Item> content = highlightPage.getContent();
        for (Item item : content) {
            System.out.println(content);
        }
        
        //解析高亮数据
        List<HighlightEntry<Item>> highlighted = highlightPage.getHighlighted();
        for (HighlightEntry<Item> highlightEntry : highlighted) {
            Item item = highlightEntry.getEntity();
            List<HighlightEntry.Highlight> highlights = highlightEntry.getHighlights();
            if(highlights != null && highlights.size() > 0) {
                List<String> snipplets = highlights.get(0).getSnipplets();
                if(snipplets != null && highlights.size() >0) {
                    String s = snipplets.get(0);
                    item.setTitle(s);
                }
            }
        }
    }

 }
6.3.3.2 高亮器的切换

​ 需求:查询item_title中包含三星手机的文档,要求item_title中三星手机中不同的词,显示不同的颜色;

http://localhost:8080/solr/collection1/select?
q=item_title:三星手机
&hl=true
&hl.fl=item_title
&hl.method=fastVector
highlightOptions.addHighlightParameter("hl.method", "fastVector");
6.3.4 suggest查询
6.3.4.1 spell-checking 拼写检查。

​ 要完成拼写检查,首先我们需要在SolrConfig.xml中进行相关的配置。在之前课程中已经讲解完毕,并且已经配置过了。

​ 需求:查询item_title中包含:iphoneX galaxz 的内容。要求进行拼写检查。

http://localhost:8080/solr/collection1/select?q=item_title:iphoneX galaxz&spellcheck=true

​ 对于我们来说,我们就需要通过Spring Data Solr将正确的词提取处理。

@Test
    public void test01() throws IOException, SolrServerException {
    //查询item_title中包含iphonexX Galaxz的
         SimpleQuery q = new SimpleQuery("item_title:iphonxx Galaxz");
        q.setSpellcheckOptions(SpellcheckOptions.spellcheck().extendedResults());
        SpellcheckedPage<Item> page = solrTemplate.query("collection1",q, Item.class);
        long totalElements = page.getTotalElements();
        if(totalElements == 0) {
            Collection<SpellcheckQueryResult.Alternative> alternatives = page.getAlternatives();
            for (SpellcheckQueryResult.Alternative alternative : alternatives) {
                System.out.println(alternative.getTerm());
                System.out.println(alternative.getSuggestion());
            }
        }
    }

6.3.4.2Auto Suggest自动建议。


自动建议的API,在Spring Data Solr中没有好像没有封装。官方API没有找到。

Spring Data Solr 4.2.0.M3 API

我们需要使用SolrJ中的API完成。在Spring Data Solr中如何使用SolrJ的API呢?

1.注入SolrClient-------->HttpSolrClient  CloudSolrClient,取决于我们在YML中配置的是单机还是集群。
2.通过SolrTemplate获取SolrClient。本身SolrTemplate封装SolrClient
3.参考之前课程中SolrJ完成查询建议的代码。
6.3.5 Join查询

​ 准备:需要将员工的信息导入collection1,将部门信息导入到collection2

​ 需求:查询部门名称为SALES下的员工信息。

主查询条件q=*:*
过滤条件fq={!join fromIndex=collection2 toIndex=collection1 from=id to=emp_deptno}dept_dname:SALES

​ Spring Data Solr 完成 Join查询

​ 创建Emp的实体类,并且建立实体类属性和域字段映射关系

@Data
public class Emp {
    @Field("id")
    @Id
    private String empno;
    @Field("emp_ename")
    private String ename;
    @Field("emp_job")
    private String job;
    @Field("emp_mgr")
    private String mgr;
    @Field("emp_hiredate")
    private String hiredate;
    @Field("emp_sal")
    private String sal;
    @Field("emp_comm")
    private String comm;
    private String cv;
    @Field("emp_deptno")
    private String deptno;
}

​ 代码

@Test
    public void test03() {
        //主查询条件
        Query query = new SimpleQuery("*:*");
        //过滤条件collection2中dept_name是SALES
        SimpleFilterQuery simpleFilterQuery = new SimpleFilterQuery(new Criteria("dept_dname").is("SALES"));
        //使用Join建立子查询关系
        Join join = new Join.Builder("id").fromIndex("collection2").to("emp_deptno");
        simpleFilterQuery.setJoin(join);

        query.addFilterQuery(simpleFilterQuery);

        ScoredPage<Emp> scoredPage = solrTemplate.queryForPage("collection1", query, Emp.class);
        List<Emp> content = scoredPage.getContent();
        for (Emp emp : content) {
            System.out.println(emp);
        }
    }

J的API呢?

1.注入SolrClient-------->HttpSolrClient  CloudSolrClient,取决于我们在YML中配置的是单机还是集群。
2.通过SolrTemplate获取SolrClient。本身SolrTemplate封装SolrClient
3.参考之前课程中SolrJ完成查询建议的代码。
6.3.5 Join查询

​ 准备:需要将员工的信息导入collection1,将部门信息导入到collection2

​ 需求:查询部门名称为SALES下的员工信息。

主查询条件q=*:*
过滤条件fq={!join fromIndex=collection2 toIndex=collection1 from=id to=emp_deptno}dept_dname:SALES

​ Spring Data Solr 完成 Join查询

​ 创建Emp的实体类,并且建立实体类属性和域字段映射关系

@Data
public class Emp {
    @Field("id")
    @Id
    private String empno;
    @Field("emp_ename")
    private String ename;
    @Field("emp_job")
    private String job;
    @Field("emp_mgr")
    private String mgr;
    @Field("emp_hiredate")
    private String hiredate;
    @Field("emp_sal")
    private String sal;
    @Field("emp_comm")
    private String comm;
    private String cv;
    @Field("emp_deptno")
    private String deptno;
}

​ 代码

@Test
    public void test03() {
        //主查询条件
        Query query = new SimpleQuery("*:*");
        //过滤条件collection2中dept_name是SALES
        SimpleFilterQuery simpleFilterQuery = new SimpleFilterQuery(new Criteria("dept_dname").is("SALES"));
        //使用Join建立子查询关系
        Join join = new Join.Builder("id").fromIndex("collection2").to("emp_deptno");
        simpleFilterQuery.setJoin(join);

        query.addFilterQuery(simpleFilterQuery);

        ScoredPage<Emp> scoredPage = solrTemplate.queryForPage("collection1", query, Emp.class);
        List<Emp> content = scoredPage.getContent();
        for (Emp emp : content) {
            System.out.println(emp);
        }
    }

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值