Solr学习笔记,
什么是Solr?
Solr是Apache Lucene项目的开源企业搜索平台。其主要功能包括全文检索、命中标示[1]、分面搜索、动态聚类、数据库集成,以及富文本(如Word、PDF)的处理。Solr是高度可扩展的,并提供了分布式搜索和索引复制。Solr是最流行的企业级搜索引擎,[2]Solr 4还增加了NoSQL支持。[3]
Solr是用Java编写、运行在Servlet容器(如Apache Tomcat或Jetty)的一个独立的全文搜索服务器。 Solr采用了Lucene Java搜索库为核心的全文索引和搜索,并具有类似REST的HTTP/XML和JSON的API。 Solr强大的外部配置功能使得无需进行Java编码,便可对其进行调整以适应多种类型的应用程序。Solr有一个插件架构,以支持更多的高级定制。
Solr安装及整合Tomcat
Solr安装可以在官网下载相应版本解压即可,本文使用的Solr版本为7.1,其目录结构如下:
solr-7.1.0
├── CHANGES.txt
├── LICENSE.txt
├── LUCENE_CHANGES.txt
├── NOTICE.txt
├── README.txt
├── bin //solr的运行脚本
├── contrib //solr的插件用来增强其功能
├── dist
├── docs //文档API
├── example
├── licenses
└── server
├── README.txt
├── contexts
├── etc
├── lib //solr相关jar包
├── modules
├── resources //包含日志文件
├── scripts
├── solr
//solr home,当Solr运行时,它需要访问主目录。首次安装Solr时,您的主目录是server/solr,但我们会更改此位置。
//主目录包含重要的配置,我们会在这里建立核心`core`,在Solr中,术语核心用于指代单个索引以及关联的事务日志和配置文件(包括`solrconfig.xml`和`Schema`文件等)。
//如果需要,您的Solr安装可以有多个核心,Solr的索引就会保存在核心中,每一个核心相当于一个索引库。
├── solr-webapp //包含一个web工程,后面会将其放入tomcat中
└── start.jar
因为Solr使用的是内置的Jetty服务器,没有Tomcat好用,所以一般都会和Tomcat服务器整合,步骤如下:
- ①将
solr-7.1.0/server/solr-webapp下的webapp扔到Tomcat的webapps目录下并改名为solr; - ②将
solr-7.1.0/server/lib/下的包含metrics名称的所有jar包和solr-7.1.0/server/lib/ext下的所有jar包复制到第一步的solr工程的lib目录下; - ③在
solr工程的WEB-INF目录下下创建classes目录,然后将solr-7.1.0/server/resources下的log4j.properties日志文件复制到classes目录下; - ④在
tomcat(可以将原有的复制一个,改名为tomcat-solr)同级目录创建solr-home目录,并将solr-7.1.0/server/solr目录下的所有文件及solr-7.1.0下的contrib和dist文件夹复制进solr-home目录,然后再修改solr工程的WEB-INF目录下web.xml文件,将40行左右的注释去掉,配置自己创建的solr-home路径,示例如下:
<env-entry>
<env-entry-name>solr/home</env-entry-name>
<env-entry-value>/usr/local/Cellar/solr-home</env-entry-value>
<env-entry-type>java.lang.String</env-entry-type>
</env-entry>
- ⑤而且要将最后的一部分内容注释掉,以解除Solr限制资源访问的问题:
<!-- Get rid of error message
<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>
-->
至此就整合完成了,下载的solr-7.1.0就可以删除了,因为我们已经将Solr整合到Tomcat里了。
Solr后台界面介绍
当我们将整合后的Tomcat服务器启动后,就会自动加载配置好的Solr了,通过地址http://localhost:8080/solr/index.html访问的界面如下:
Solr的菜单栏介绍如下:
-
Dashboadr:仪表盘,显示了该Solr实例开始启动的运行时间、版本、系统资源、JVM等信息 -
Logging:日志记录,显示Solr运行出现的异常或错误,正常状态为一个不停转圈的小图标。可以更改日志记录级别。 -
Core Admin:Solr的核心管理界面,在这里可以添加SolrCore实例。主要有Add Core(添加核心)、Unload(卸载核心)、Rename(重命名核心)、Reload(重新加载核心)等功能按钮。Add Core会在solr-home对应的文件夹生成一个core.properties配置文件和data文件夹。 -
Java Properties:Solr在JVM运行环境中的属性信息,包括类路径、文件编码、JVM内存设置等信息。 -
Thread Dump:显示Solr Server中当前活跃的线程信息,同时也可以跟踪线程运行栈信息。 -
Core selector(重点):需要在Core Admin添加了core才有此选项
上面的大部分了解即可,需要重点了解下Core selector,其中包括了大部分信息,先分别简单介绍一下:
-
Overview:core相关信息; -
Analysis: 通过此按钮的界面可以测试索引分析器和搜索分析器的执行情况(注:分析器是绑定在域的类型中的,我们将在managed-schema中配置IK分词器); -
Dataimport:可以自定义数据导入处理器,能从关系型数据库将数据导入到Solr索引库中,需要手动配置; -
Documents:提供一个简单的表单,允许您直接从浏览器执行各种Solr索引命令(创建、更新、删除、查询索引等操作) -
Files:显示当前的核心配置文件,例如solrconfig.xml -
Query:允许您提交有关核心的各种元素的结构化查询
Solr的域
Solr在核心实例中的managed-schema文件中定义了许多域,详情见:solr官网域介绍,下面简单介绍:
- 内置的常规域:如域名,域类型,是否索引,是否存储等信息,如:
<field name="price" type="float" default="0.0" indexed="true" stored="true"/>
- 自定义域:可以按照常规域的格式,自定义域。
- 唯一键:其中的id是在
Field标签中已经定义好的域名,而且该域设置required为true。一个managed-schema文件中必须有且仅有一个唯一键。
<uniqueKey>id</uniqueKey>
-
copyField(复制域):使用source指定要复制的普通域,dest指定目标域。比如,我们在搜索时输入java,一篇文章分为标题、简介、内容等很多字段,输入的关键字需要指定Solr中的域进行检索,不可能从一个表中将所有字段进行索引,因为有些字段不需要索引,所以出现了复制域,多个域时,可以放到同一个域中,就不用定义那么多域了。例子如下:
//2个普通域
<field name="name" type="text_general" indexed="true" stored="true"/>
<field name="cat" type="string" indexed="true" stored="true" multiValued="true"/>
//使用复制域:放入到text域,其可以同时对2个普通域进行索引检索
<copyField source="cat" dest="text"/>
<copyField source="name" dest="text"/>
//该域名field name ="text"即是复制域
<field name="text" type="text_general" indexed="true" stored="false" multiValued="true"/>
- 动态域:使用了通配符,所以在索引文档时,与任何显式定义的域不匹配的域可以与动态域匹配。例如,假设您的架构包含名称为的动态域
*_i。如果您尝试使用cost_i域索引文档,但cost_i架构中未定义显式域,则该cost_i域类型将被分析定义*_i的int类型。
与常规域一样,动态域也具有名称,域类型和选项,如下:
<dynamicField name="*_i" type="int" indexed="true" stored="true"/>
- 分析器:
name指定域类型的名称,class指定该域类型对应的solr的类型,Analyzer指定分析器,type的index和query分别指定搜索和索引时的分析器,Filter指定过滤器,代码如下:
<fieldType name="managed_en" class="solr.TextField" positionIncrementGap="100">
<analyzer type="index">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.ManagedStopFilterFactory" managed="english" />
<filter class="solr.ManagedSynonymGraphFilterFactory" managed="english" />
<filter class="solr.FlattenGraphFilterFactory"/>
</analyzer>
<analyzer type="query">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.ManagedStopFilterFactory" managed="english" />
<filter class="solr.ManagedSynonymGraphFilterFactory" managed="english" />
</analyzer>
</fieldType>
下面为域类型和Java的对应图:
配置IK中文分词器
因为Solr自带的中文分词器不是很好,所以需要配置更好的IK中文分词器,可以去github下载IK分词器(配置见其说明)
MySQL数据导入到Solr索引库中
导入数据前需要先创建一个core(核心,索引库会在此创建,可以理解为一个数据库),在solr-home目录下创建一个核心名(可以随便取)为core-demo的文件夹,将该solr-home/configsets/sample_techproducts_configs/下的conf文件夹复制到core-demo下,并修改复制后的conf的solrconfig.xml文件,主要是将其路径更改一下,以便其能正确的加载到相关jar包:
<lib dir="${solr.install.dir:../}/contrib/extraction/lib" regex=".*\.jar" />
<lib dir="${solr.install.dir:../}/dist/" regex="solr-cell-\d.*\.jar" />
<lib dir="${solr.install.dir:../}/contrib/clustering/lib/" regex=".*\.jar" />
<lib dir="${solr.install.dir:../}/dist/" regex="solr-clustering-\d.*\.jar" />
<lib dir="${solr.install.dir:../}/contrib/langid/lib/" regex=".*\.jar" />
<lib dir="${solr.install.dir:../}/dist/" regex="solr-langid-\d.*\.jar" />
<lib dir="${solr.install.dir:../}/dist/" regex="solr-ltr-\d.*\.jar" />
<lib dir="${solr.install.dir:../}/contrib/velocity/lib" regex=".*\.jar" />
<lib dir="${solr.install.dir:../}/dist/" regex="solr-velocity-\d.*\.jar" />
①将solr-home/dist目录下的两个jar包及mysql数据库连接的jar包加入到solr/WEB-INF/lib目录下:
solr-dataimporthandler-7.1.0.jar
solr-dataimporthandler-extras-7.1.0.jar
mysql-connector-java-8.0.13.jar // 与你mysql版本相关的jar包
②在core-demo/conf目录的solrconfig.xml文件中的720行添加如下内容:
<requestHandler name="/dataimport" class="org.apache.solr.handler.dataimport.DataImportHandler">
<lst name="defaults">
<str name="config">data-config.xml</str>
</lst>
</requestHandler>
③在core-demo/conf目录下创建data-config.xml文件,配置MySQL数据库连接相关信息及你所想要导入的数据表:
<?xml version="1.0" encoding="UTF-8" ?>
<dataConfig>
<dataSource driver="com.mysql.cj.jdbc.Driver" url="jdbc:mysql://localhost:3306/solr?serverTimezone=GMT%2B8&useSSL=false"
user="root"
password="123" />
<document>
<entity name="product" pk="pid" query="SELECT pid,name,catalog_name,price,description,picture FROM products">
<field column="pid" name="id" />
<field column="name" name="prod_name" />
<field column="price" name="prod_price" />
<field column="description" name="prod_description" />
<field column="picture" name="prod_picture" />
<field column="catalog_name" name="prod_catalog_name" />
</entity>
</document>
</dataConfig>
④在managed-schema文件最后添加和上面相对应的数据表信息。
<!--配置从数据库导入到sorl中的数据的字段内容,所以每次要从数据库导入什么就需要配置什么-->
<field name="prod_name" type="text_ik" indexed="true" stored="true"/>
<field name="prod_price" type="pdouble" indexed="true" stored="true"/>
<field name="prod_description" type="text_ik" indexed="true" stored="false"/>
<field name="prod_picture" type="string" indexed="false" stored="true"/>
<field name="prod_catalog_name" type="string" indexed="true" stored="true"/>
⑤在Solr界面的Dataimport下点击Execute按钮导入MySQL的数据。若导入数据不成功,则说明上述配置有问题,请检查核对。
客户端查询界面介绍
当我们在Solr主界面点击了Query按钮后,出现如下界面:
各输入框的作用如下:
-
q:(query)查询的关键字,此参数最为重要,例如输入*:*代表返回所有数据,多个参数可以用AND和OR连接; -
fq:(filter query)过滤查询,可以将q查询出结果用fq输入的参数筛选一遍。比如q查询出所有商品,而fq可以指定查询给定价格区间里的商品:prod_price:[100 TO 200] -
sort:排序方式,例如prod_price desc代表按照prod_price降序排列,asc升序。 -
start:指定返回结果的从第几条记录开始,一般分页时使用,默认从0开始; -
rows:指定返回结果最多有多少条记录,默认为10,与start配合实现分页; -
fl:仅回显给定的域,用逗号或空格分隔且区分大小写,例如输入prod_name,prod_picture,prod_price则只返回这3个域; -
df:指定默认域,当指定默认域后,q中只写具体域值即可。例如本来p中需要输入prod_name:手机,当df输入prod_name后,则p中只需给定手机值即可; -
hl:高亮显示。
Java与Solr交互的API:SolrJ
什么是SolrJ?
SolrJ是一个API,让我们使用Java(或任何基于JVM的语言)编写的应用程序可以轻松地域Solr交互。要想使用SolrJ,需要添加下面的依赖包:
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-solrj</artifactId>
<version>7.1.0</version>
</dependency>
在Java中,发送到Solr的所有请求都是通过SolrClient完成的,SolrClient是SolrJ核心的主要工具,用来与Solr进行连接和交互,通常都使用它的实现类来完成我们的工作。对所有的请求,都是通过SolrRequests的子类对象发送,而SolrResponses的子类对象则负责返回所有结果。
比如,SolrClient的实现类HttpSolrClient负责直接与单个Solr节点通信,UpdateResponse和QueryResponse分别对应SolrRequests和SolrResponses的子类,前者用来操作索引的增、删、改,后者用来查询相关索引。这些API可以很方便的处理特定域的对象,使你的应用程序更容易被理解。
使用SolrJ完成索引维护
要想使用SolrJ完成索引维护,一般需要:
- 绑定Java对象
- 基本URL:客户端使用这些URL向Solr发送HTTP请求,可以指定特定的核心,如
http://localhost:8983/solr/core-demo,代表只操作core-demo核心;当然也可以不指定核心,如http://localhost:8983/solr/,此时代表对任何核心发出请求,但必须在所有请求上指定受影响的核心。 -
SolrClient的实现类:SolrClient不同的实现类的功能也不同,这里我们只需了解一个实现类HttpSolrClient,它负责直接与单个Solr节点通信。 - 执行具体功能:
UpdateResponse对象负责操作索引的增、删、改,而QueryResponse对象负责查询。 - 提交事务;(如果是查询则不需要提交事务)
- 关闭资源。
Java对象绑定
SolrJ支持通过@Field注解隐式转换文档域任何类。因此,通过使用@Field注解,可以将Java对象的每个实例变量映射到Solr的相对应的域中,即将Java对象与Solr的域绑定。
添加/修改索引
在Solr中,索引库中都会存在一个唯一键,因此如果一个Document的唯一键不存在,则执行添加操作,否则执行修改操作。
增加索引的具体代码如下,首先将Bean实体于Solr的对应域绑定,之后添加进索引库即可:
public class Products {
@Field("id")
private String pid;
@Field("prod_name")
private String pname;
@Field("prod_price")
private Double price;
@Field("prod_description")
private String description;
@Field("prod_picture")
private String picture;
private Integer catalog;
@Field("prod_catalog_name")
private String catalogName;
private Integer number;
private Date releaseTime;
getter/setter方法省略。。。
}
//1.请求连接
String solrUrl = "http://localhost:8081/solr/core-demo";
//2.创建对象
HttpSolrClient client = new HttpSolrClient.Builder(solrUrl).build();
//3.执行添加功能
Products products = new Products();
products.setPid("88888");
products.setPname("后台测试");
products.setCatalogName("测试类别");
products.setPrice(3000.0);
products.setDescription("这是一个测试描述");
products.setPicture("aa.jpg");
UpdateResponse response = client.addBean(products);
//4.事务提交
client.commit();
//5.关闭资源
client.close();
添加操作结果如下:
{
"responseHeader":{
"status":0,
"QTime":0,
"params":{
"q":"product_price:3000.0",
"_":"1556885445729"}},
"response":{"numFound":1,"start":0,"docs":[
{
"id":"88888",
"product_name":"后台测试",
"product_catalog_name":"测试类别",
"product_price":3000.0,
"product_picture":"aa.jpg",
"_version_":1632513232508813312}]
}}
修改索引的代码和增加索引的代码基本一样,只是看操作的Document的唯一键是否存在而已:
//1.请求连接
String solrUrl = "http://localhost:8081/solr/core-demo";
//2.创建对象
HttpSolrClient client = new HttpSolrClient.Builder(solrUrl).build();
//3.执行修改功能
Products products = new Products();
products.setPid("88888");
products.setPname("后台测试3");
products.setCatalogName("测试类别3");
products.setPrice(3000.0);
products.setDescription("这是一个测试描述2");
products.setPicture("bb.jpg");
UpdateResponse response = client.addBean(products);
//4.事务提交
client.commit();
//5.关闭资源
client.close();
修改操作结果如下:
"responseHeader":{
"status":0,
"QTime":0,
"params":{
"q":"product_price:3000.0",
"_":"1556885445729"}},
"response":{"numFound":1,"start":0,"docs":[
{
"id":"88888",
"product_name":"后台测试3",
"product_catalog_name":"测试类别3",
"product_price":3000.0,
"product_picture":"bb.jpg",
"_version_":1632513297470193664}]
}}
删除索引
删除索引可以分为:
- 根据
id删除; - 删除所有索引(
*:*) - 根据查询条件删除(如
product_price:[10 TO 200]代表删除价格为10到200的商品)
除了业务代码不同,其他代码和添加/修改一样:
//1.请求连接
String solrUrl = "http://localhost:8081/solr/core-demo";
//2.创建对象
HttpSolrClient client = new HttpSolrClient.Builder(solrUrl).build();
//3.执行删除功能
client.deleteById("88888");//删除特定id
// client.deleteByQuery("*:*");//删除所有
// client.deleteByQuery("product_price:[10 TO 200]");//根据查询条件删除
//4.事务提交
client.commit();
//5.关闭资源
client.close();
}
查询索引
查询索引可以分为简单查询和复杂查询:
-
简单查询:查询条件简单,如
*:*; -
复杂查询:查询条件较为复杂,需要进行拼接,如查询指定价格区间商品,指定种类商品等等。
查询索引时输入查询条件,原来在Solr界面的q输入框条件,现在由Java代码的String定义,再将其作为参数传递SolrQuery对象。当client客户端对象根据查询条件获取结果后,会返回QueryResponse对象,该对象能将返回的结果和相关Bean相绑定。
简单查询代码如下:
//1.请求连接
String solrUrl = "http://localhost:8081/solr/core-demo";
//2.创建对象
HttpSolrClient client = new HttpSolrClient.Builder(solrUrl).build();
//3.执行查询功能
String q = "*:*";
SolrQuery query = new SolrQuery(q);
QueryResponse queryResponse = client.query(query);
List<Products> productsList = queryResponse.getBeans(Products.class);
System.out.println(productsList.size());
for (Products p : productsList) {
System.out.println(p);
}
//4.关闭资源
client.close();
返回的结果:
10
Products(pid=1, pname=花儿朵朵彩色金属门后挂 8钩免钉门背挂钩2066, price=18.9, description=null, picture=2014032613103438.png, catalog=null, catalogName=幽默杂货, number=null, releaseTime=null)
Products(pid=2, pname=幸福一家人彩色金属门后挂 8钩免钉门背挂钩2088, price=18.9, description=null, picture=2014032612461139.png, catalog=null, catalogName=幽默杂货, number=null, releaseTime=null)
Products(pid=3, pname=神偷奶爸电影同款 惨叫发泄公仔 发声小黄人, price=10.0, description=null, picture=2014032417271233.png, catalog=null, catalogName=幽默杂货, number=null, releaseTime=null)
Products(pid=4, pname=神偷奶爸电影同款 发泄公仔 暴眼小黄人, price=13.0, description=null, picture=2014032416533215.png, catalog=null, catalogName=幽默杂货, number=null, releaseTime=null)
Products(pid=5, pname=有趣浪漫魔力无痕挂钩 环保厨房卫生间衣钩无痕挂钩, price=3.0, description=null, picture=2014031913145400_S.jpg, catalog=null, catalogName=幽默杂货, number=null, releaseTime=null)
Products(pid=6, pname=zakka杂货 情侣小鹿树脂摆件家居装饰品一对, price=15.0, description=null, picture=2014031517190225.jpg, catalog=null, catalogName=幽默杂货, number=null, releaseTime=null)
Products(pid=7, pname=魔幻星座音乐水晶球内雕音乐盒七彩渐变音乐球, price=70.0, description=null, picture=2014030610151185.jpg, catalog=null, catalogName=幽默杂货, number=null, releaseTime=null)
Products(pid=8, pname=家天下情侣款仿真缝纫机音乐盒八音盒 创意礼品, price=25.0, description=null, picture=2013120216363484_S.jpg, catalog=null, catalogName=幽默杂货, number=null, releaseTime=null)
Products(pid=9, pname=家天下嘻哈动物魔术贴挂钩绕带无痕挂钩2个装RB205, price=5.5, description=null, picture=2013112909444459_S.jpg, catalog=null, catalogName=幽默杂货, number=null, releaseTime=null)
Products(pid=10, pname=创意卡通动物铁艺门后挂钩挂物ZT3103, price=5.0, description=null, picture=2013112010220971_S.jpg, catalog=null, catalogName=幽默杂货, number=null, releaseTime=null)
复杂查询代码如下:
//1.请求连接
String solrUrl = "http://localhost:8081/solr/core-demo";
//2.创建对象
HttpSolrClient client = new HttpSolrClient.Builder(solrUrl).build();
//3.执行查询功能
SolrQuery query = new SolrQuery();
String keywords = "手机";
//(1)设置查询条件q,根据用户输入情况返回不同结果
if (StringUtils.isEmpty(keywords)) {
query.set("q", "*:*");//用户没有输入数据则返回所有结果
} else {
query.set("q", "prod_name:" + keywords);//用户输入手机则返回相关结果
}
//(2)设置过滤查询fq,过滤出相关查询
//(2.1)类别筛选
String prod_catalog_name = "手机饰品";
if (!StringUtils.isEmpty(prod_catalog_name)) {
// query.set("fq","prod_catalog_name:" + prod_catalog_name);//两种写法均可种写法
query.addFilterQuery("prod_catalog_name:" + prod_catalog_name);
}
//(2.2)价格筛选
String prod_price = "1-";
if (!StringUtils.isEmpty(prod_price)) {
String[] strings = prod_price.split("-");
if (strings.length == 1) {
query.addFilterQuery("prod_price:[" + strings[0] + " TO *]");
} else {
if (StringUtils.isEmpty(strings[0])) {
query.addFilterQuery("prod_price:[* TO " + strings[1] + "]");
} else
query.addFilterQuery("prod_price:[" + strings[0] + " TO " + strings[1] + "]");
}
}
//(3)设置排序条件sort,排序相关内容
//psort = 1升序,2降序
int psort = 0;
if (psort == 1) {
query.addSort("prod_price", SolrQuery.ORDER.asc);
} else if (psort == 2) {
query.addSort("prod_price", SolrQuery.ORDER.desc);
}
//(4)设置分页功能,start默认为0,rows默认为10
/**
* 类似于mysql的分页
* start offset 偏移量
* rows rows 返回的最大记录数
*
* start = rows * (page - 1)
*/
query.setStart(0);
query.setRows(60);
//(5)设置回显fl,作用:保护隐私数据。因此对其他数据都不显示
// query.setFields("prod_name", "prod_catalog_name");//只显示商品名称和种类,对价格和其他数据则获取为null
//(6)设置默认域df
// query.set("df","prod_name");
//(7)设置高亮hl
query.setHighlight(true);//启动高亮设置
query.addHighlightField("prod_name");//指定域高亮
query.setHighlightSimplePre("<font color='red'>");//设置前缀
query.setHighlightSimplePost("</font>");//设置后缀
QueryResponse queryResponse = client.query(query);
Map<String, Map<String, List<String>>> map = queryResponse.getHighlighting();
List<Products> productsList = queryResponse.getBeans(Products.class);
System.out.println(productsList.size());
for (Products p : productsList) {
String id = p.getPid();
Map<String, List<String>> map1 = map.get(id);
List<String> map2 = map1.get("prod_name");
if (map1 != null) {
System.out.println(map2.get(0) + p);
} else
System.out.println(p);
}
//4.关闭资源
client.close();
参考资料
Solr7.1官方文档
转载于:https://www.jianshu.com/p/9762089f0e83