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);
        }
    }

 

 

1 概述 4 1.1 企业搜索引擎方案选型 4 1.2 Solr的特性 4 1.2.1 Solr使用Lucene并且进行了扩展 4 1.2.2 Schema(模式) 5 1.2.3 查询 5 1.2.4 核心 5 1.2.5 缓存 5 1.2.6 复制 6 1.2.7 管理接口 6 1.3 Solr服务原理 6 1.3.1 索引 6 1.3.2 搜索 7 1.4 源码结构 8 1.4.1 目录结构说明 8 1.4.2 Solr home说明 9 1.4.3 solr的各包的说明 10 1.5 版本说明 11 1.5.1 1.3版本 11 1.5.2 1.4版本 12 1.6 分布式和复制 Solr 架构 13 2 Solr的安装与配置 13 2.1 在Tomcat下Solr安装 13 2.1.1 安装准备 13 2.1.2 安装过程 14 2.1.3 验证安装 15 2.2 中文分词配置 15 2.2.1 mmseg4j 15 2.2.2 paoding 19 2.3 多核(MultiCore)配置 22 2.3.1 MultiCore的配置方法 22 2.3.2 为何使用多core ? 23 2.4 配置文件说明 23 2.4.1 schema.xml 24 2.4.2 solrconfig.xml 25 3 Solr应用 29 3.1 SOLR应用概述 29 3.1.1 Solr应用模式 29 3.1.2 SOLR的使用过程说明 30 3.2 一个简单的例子 30 3.2.1 Solr Schema 设计 30 3.2.2 构建索引 30 3.2.3 搜索测试 31 3.3 搜索引擎的规划设计 32 3.3.1 定义业务模型 32 3.3.2 定制索引服务 34 3.3.3 定制搜索服务 34 3.4 搜索引擎配置 34 3.4.1 Solr Schema 设计(如何定制索引的结构?) 34 3.5 如何进行索引操作? 36 3.5.1 基本索引操作 36 3.5.2 批量索引操作 37 3.6 如何进行搜索 39 3.6.1 搜索语法 39 3.6.2 排序 42 3.6.3 字段增加权重 42 3.6.4 Solr分词器、过滤器、分析器 42 3.6.5 Solr高亮使用 46 4 SolrJ的用法 46 4.1 搜索接口的调用实例 46 4.2 Solrj的使用说明 47 4.2.1 Adding Data to Solr 47 4.2.2 Directly adding POJOs to Solr 49 4.2.3 Reading Data from Solr 51 4.3 创建查询 51 4.4 使用 SolrJ 创建索引 52 4.5 Solrj包的结构说明 53 4.5.1 CommonsHttpSolrServer 53 4.5.2 Setting XMLResponseParser 53 4.5.3 Changing other Connection Settings 53 4.5.4 EmbeddedSolrServer 54 5 Solr的实际应用测试报告 54 5.1 线下压力测试报告 54 5.2 线上环境运行报告 54 6 solr性能调优 55 6.1 Schema Design Considerations 55 6.1.1 indexed fields 55 6.1.2 stored fields 55 6.2 Configuration Considerations 55 6.2.1 mergeFactor 55 6.2.2 mergeFactor Tradeoffs 56 6.3 Cache autoWarm Count Considerations 56 6.4 Cache hit rate(缓存命中率) 56 6.5 Explicit Warming of Sort Fields 56 6.6 Optimization Considerations 56 6.7 Updates and Commit Frequency Tradeoffs 56 6.8 Query Response Compression 57 6.9 Embedded vs HTTP Post 57 6.10 RAM Usage Considerations(内存方面的考虑) 57 6.10.1 OutOfMemoryErrors 57 6.10.2 Memory allocated to the Java VM 57 7 FAQ 58 7.1 出现乱码或者查不到结果的排查方法: 58
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值