solr
1. Solr入门
1.1. Solr下载安装
官方下载地址:https://archive.apache.org/dist/lucene/solr/
Windows系统下载zip包,Linux、MaxOS系统下载tgz包
1.1.1. Solr目录结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WoPP5VHl-1575691051854)(solr-folder.png)]
- bin: 存放solr的可执行文件
- contrib: 存放solr提供的扩展包
- dist: Solr运行需要的jar包
- docs: 文档目录
- example: 官方提供的示例目录,配合官方示例教程,体验Solr功能
- licences: 协议目录
- server: solr工作的主目录,里面有默认的配置,将来创建的核心也会默认存储到此目录下,Solr Admin程序也在此目录下
1.2. Solr入门
1.2.1. 运行官方示例项目
官方示例教程文档: https://lucene.apache.org/solr/guide/8_2/solr-tutorial.html
官方提供了三个示例教程,从Solr怎么简单使用,到怎么创建自己的搜索库,一步一步有引导,推荐跟着练习一遍。
1.2.2. Solr Admin UI的使用
Solr Admin是Solr给我们提供的一个方便查询和管理Solr的Web控制台应用,通过此应用,我们不需要编写任何程序就可以对Solr的很多功能进行操作。
默认访问地址: http://localhost:8983/solr
1.2.2.1. 简单查询
1.2.2.2. 按照某个字段搜索
1.2.2.3. 搜索短语
1.2.2.4. 搜索结果中只返回某些字段
1.2.2.5. 搜索结果分页
1.2.2.6. 搜索结果高亮显示
搜索参数设置:
搜索结果展示:
1.2.2.7. 删除Solr索引库中的数据
2. 深入Solr
2.1. Solr配置文件
2.1.1. managed-schema文件
managed-schema文件是Solr中core或collection的搜索库定义配置文件,里面配置了搜索库有哪些Field和FieldType等。
Solr的工作流程大体如下:
- 创建用于存储被搜索数据的core或collection(集群模式);
- 定义创建的core有哪些字段,以及哪些字段需要索引、哪些字段只存储,不需要索引。具体来说,就是通过Solr提供的SchemaAPI去管理core中的字段;
- 将数据导入到Solr中,在此过程中,Solr会对导入的数据建索引(所以一定要先定义Schema字段,再导入数据);
- 调用Solr的HttpAPI搜索数据
managed-schema文件就是用来管理某个core中有哪些字段或者字段类型,可以把Solr当成是一个数据库,里面有字段、有字段类型、能存储、能查询(搜索)
2.1.1.1. Solr中常用的数据类型
Solr中主要的数据类型由实现类和类型定义两部分组成, 数据类型是由Solr中定义好的Java类,类型定义是在managed-schema文件中定义的,数据类型不能直接使用,必须在managed-schema中定义后才能直接在字段中引用。
在managed-schema中定义字段类型时,会将数据类型和其他属性(如:是否多值、是否存储)组合到一起
默认的managed-schema配置文件中已经定义了一些常用的数据类型,如
<fieldType name="string" class="solr.StrField" sortMissingLast="true" docValues="true" />
<fieldType name="strings" class="solr.StrField" sortMissingLast="true" multiValued="true" docValues="true" />
<fieldType name="boolean" class="solr.BoolField" sortMissingLast="true"/>
<fieldType name="booleans" class="solr.BoolField" sortMissingLast="true" multiValued="true"/>
<fieldType name="pint" class="solr.IntPointField" docValues="true"/>
<fieldType name="pfloat" class="solr.FloatPointField" docValues="true"/>
<fieldType name="plong" class="solr.LongPointField" docValues="true"/>
<fieldType name="pdouble" class="solr.DoublePointField" docValues="true"/>
<fieldType name="pints" class="solr.IntPointField" docValues="true" multiValued="true"/>
<fieldType name="pfloats" class="solr.FloatPointField" docValues="true" multiValued="true"/>
<fieldType name="plongs" class="solr.LongPointField" docValues="true" multiValued="true"/>
<fieldType name="pdoubles" class="solr.DoublePointField" docValues="true" multiValued="true"/>
<fieldType name="random" class="solr.RandomSortField" indexed="true"/>
<fieldType name="pdate" class="solr.DatePointField" docValues="true"/>
<fieldType name="pdates" class="solr.DatePointField" docValues="true" multiValued="true"/>
<fieldType name="binary" class="solr.BinaryField"/>
可以看到每个字段类型定义都由类型的实现类和若干个其他属性组合而成
除了上面定义的简单类型之外默认的managed-schema文件中还定义了一些特殊的字段类型
<!-- ignored类型本身是一个StrField类型,但是将stored和indexed都定义成了false,代表不存储、不建索引,实际上就是忽略数据中的此字段不处理 -->
<fieldType name="ignored" stored="false" indexed="false" multiValued="true" class="solr.StrField" />
带分词功能的字段类型
<!-- text_general类型和上面的字段类型定义语法一样,本身数据solr.TextField类型,但是声明时通过内部的子标签指定了数据存储索引和数据搜索索引的分词器 -->
<fieldType name="text_general" class="solr.TextField" positionIncrementGap="100" multiValued="true">
<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>
2.1.1.2. 添加字段类型定义(FieldType)
我们添加自定义字段类型的一个重要应用场景就是当我们的字段需要支持中文分词时,指定字段的实现类型是
solr.TextField,然后指定数据存储索引和搜索的索引分词器为支持中文的分词器。
语法参见上一章节 带分词功能的字段类型,中文分词器的配置参见配置中文分词器章节
2.1.1.3. Solr中的"无模式"模式和字段类型自动推测
我们在将数据导入到Solr中时,如果事先没有添加字段,Solr会根据数据的内容,自动推测应该用什么字段类型,并且自动生成field配置标签到当前core对应的managed-schema配置文件中
2.1.1.4. 添加字段(Field)
前面说过managed-schema中主要就是定义了fieldType和field, 在以前老版本的solr中managed-schema的名字叫做schema.xml,可以直接手动编辑文件维护其中的字段定义。
而在新版本的Solr中,官方不建议手动修改此文件,改用Schema API的方式来维护字段
配置示例
<field name="price" type="float" default="0.0" indexed="true" stored="true"/>
定义字段需要指定字段的名称、类型(引用上面声明的fieldType的名称)、默认值、是否存储、是否索引等。
2.1.1.5. 添加拷贝字段(Copy Field)
Solr支持通过配置Copy Field,将多个字段拷贝到某个字段上,这样在搜索时,可以只用一个字段实现同时搜索多个字段内容的效果。
配置示例
<copyField source="cat" dest="text" maxChars="30000" />
2.1.1.6. 动态字段(Dynamic Fields)
Solr可以通过配置动态字段来实现对某些名称相似的字段统一管理,因为动态字段定义时名称中允许包含通配符。
动态字段和普通字段作用一致,唯一不同的就是名称中允许包含通配符,Solr在索引数据时会优先查找配置中定义的准确字段(通过field配置的字段),如果没有找到匹配的,就从动态字段中找是否有匹配的,如果找到,就用动态字段的字段定义来索引数据。
配置示例
<dynamicField name="*_i" type="int" indexed="true" stored="true"/>
2.1.2. Schema API
为了减少手动编辑managed-schema文件引入的错误,新版本的Solr提供了一套基于HTTP协议的Schema API来维护managed-schema文件
Schema API可以完成以下操作
2.1.2.1. add-field
使用示例
curl -X POST -H 'Content-type:application/json' --data-binary '{
"add-field":{
"name":"sell_by",
"type":"pdate",
"stored":true }
}' http://localhost:8983/api/cores/gettingstarted/schema
2.1.2.2. delete-field
使用示例
curl -X POST -H 'Content-type:application/json' --data-binary '{
"delete-field" : { "name":"sell_by" }
}' http://localhost:8983/api/cores/gettingstarted/schema
2.1.2.3. replace-field
使用示例
curl -X POST -H 'Content-type:application/json' --data-binary '{
"replace-field":{
"name":"sell_by",
"type":"date",
"stored":false }
}' http://localhost:8983/api/cores/gettingstarted/schema
2.1.2.4. add-dynamic-field
使用示例
curl -X POST -H 'Content-type:application/json' --data-binary '{
"add-dynamic-field":{
"name":"*_s",
"type":"string",
"stored":true }
}' http://localhost:8983/api/cores/gettingstarted/schema
2.1.2.5. delete-dynamic-field
使用示例
curl -X POST -H 'Content-type:application/json' --data-binary '{
"delete-dynamic-field":{ "name":"*_s" }
}' http://localhost:8983/api/cores/gettingstarted/schema
2.1.2.6. replace-dynamic-field
使用示例
curl -X POST -H 'Content-type:application/json' --data-binary '{
"replace-dynamic-field":{
"name":"*_s",
"type":"text_general",
"stored":false }
}' http://localhost:8983/solr/gettingstarted/schema
2.1.2.7. add-field-type
使用示例
curl -X POST -H 'Content-type:application/json' --data-binary '{
"add-field-type":{
"name":"myNewTextField",
"class":"solr.TextField",
"indexAnalyzer":{
"tokenizer":{
"class":"solr.PathHierarchyTokenizerFactory",
"delimiter":"/" }},
"queryAnalyzer":{
"tokenizer":{
"class":"solr.KeywordTokenizerFactory" }}}
}' http://localhost:8983/api/cores/gettingstarted/schema
2.1.2.8. delete-field-type
使用示例
curl -X POST -H 'Content-type:application/json' --data-binary '{
"delete-field-type":{ "name":"myNewTxtField" }
}' http://localhost:8983/api/cores/gettingstarted/schema
2.1.2.9. replace-field-type
使用示例
curl -X POST -H 'Content-type:application/json' --data-binary '{
"replace-field-type":{
"name":"myNewTxtField",
"class":"solr.TextField",
"positionIncrementGap":"100",
"analyzer":{
"tokenizer":{
"class":"solr.StandardTokenizerFactory" }}}
}' http://localhost:8983/api/cores/gettingstarted/schema
2.1.2.10. add-copy-field
使用示例
curl -X POST -H 'Content-type:application/json' --data-binary '{
"add-copy-field":{
"source":"shelf",
"dest":[ "location", "catchall" ]}
}' http://localhost:8983/api/cores/gettingstarted/schema
2.1.2.11. delete-copy-field
使用示例
curl -X POST -H 'Content-type:application/json' --data-binary '{
"delete-copy-field":{ "source":"shelf", "dest":"location" }
}' http://localhost:8983/api/cores/gettingstarted/schema
2.1.3. solrconfig.xml文件
solrconfig.xml文件中定义了Solr core的数据目录、需要的第三方jar包、索引配置等。一般我们修改这个配置文件主要是配置中文分词器,因为Solr官方默认没有配置支持中文分词器的字段类型。
2.2. Core VS Collection
在单机模式启动的Solr中一个搜索数据集叫做core,而在集群模式启动的Solr中搜索数据集的名称叫做collection
2.2.1. 单机模式使用Solr
2.2.1.1. 以单机模式启动Solr
bin/solr start # linux、macos
bin\solr.cmd start # windows
启动成功后命令行会有如下输出
Started Solr server on port 8983. Happy searching!
2.2.1.2. 创建core核心
bin/solr create -c 核心名 # linux、macos
bin\solr.cmd create -c 核心名 # windows
2.2.1.3. 导入数据到Solr
参见使用SpringBoot操作Solr章节
2.2.1.4. 停止单机版Solr
bin/solr stop -all # linux、macos
bin\solr.cmd stop -all # windows
2.2.2. 以集群模式启动Solr
2.2.2.1. 以集群模式启动Solr
以集群模式启动Solr
bin/solr start -c # linux、macos
bin\solr.cmd start -c # windows
以集群模式启动后,如果没指定端口号,Solr默认会启动一个8983的节点;同时还会将内置的Zookeeper服务也启动起来,运行到9983端口上
向Solr集群中添加节点
bin/solr start -c -p 节点端口号 -z localhost:9983 # linux、macos
bin\solr.cmd start -c -p 节点端口号 -z localhost:9983 # windows
-p参数指定节点运行的端口号,-z参数将新创建的节点添加到集群中
Zookeeper是一个分布式管理框架,专门用来管理集群中各节点的状态。
2.2.2.2. 创建collection
bin/solr create -c collection名称 -s 2 -rf 2 # linux、macos
bin\solr.cmd create -c collection名称 -s 2 -rf 2 # windows
-c 参数指定创建的collection名称
-s 参数表示该collection要分布到几个分片上
-rf 参数表示每个分片有几个副本(用于容灾)
Solr的集群模式和Reids集群类似,每个分片都有一个master节点和若干个slave节点组成,当master节点发生故障无法对外提供服务时,Solr集群会自动选举一个slave节点作为master对外服务。
2.2.2.3. 导入数据到Solr集群
参见使用SpringBoot操作Solr章节
2.2.2.4. 停止Solr集群
-
停止集群中某个节点
bin/solr stop -p 要停止的节点的端口号 # linux、macos bin\solr.cmd stop -p 要停止的节点的端口号 # windows
-
停止整个Solr集群
bin/solr stop -all # linux、macos bin\solr.cmd stop -all # windows
2.3. 配置中文分词器
分词器的作用在于将搜索的文本按照词组进行分割,可以大大的提高搜索的准确度。Solr对大部分的语言分词都进行了支持,其中也包括简体中文,但是遗憾的是截止最新的8.3.0版本,官方只是提供了中文分词的jar包,但是并没有对其进行配置。这就需要我们在用的时候配置中文分词器。
Solr官方提供了两种中文分词器:smartcn和icu
以smartcn分词器为例,配置方法如下:
2.3.1. 修改solrconfig.xml配置文件,添加中文分词器依赖
......
<lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-ltr-\d.*\.jar" />
<!-- 添加这行 -->
<lib dir="${solr.install.dir:../../../..}/contrib/analysis-extras/lucene-libs" regex=".*\.jar" />
......
2.3.2. 修改managed-schema配置文件,新建fieldType
<!-- 添加自定义的支持中文分词的字段类型 -->
<fieldType name="text_zh" class="solr.TextField" positionIncrementGap="100">
<analyzer type="index">
<tokenizer class="org.apache.lucene.analysis.cn.smart.HMMChineseTokenizerFactory"/>
<filter class="solr.CJKWidthFilterFactory"/>
<filter class="solr.StopFilterFactory"
words="org/apache/lucene/analysis/cn/smart/stopwords.txt"/>
<filter class="solr.PorterStemFilterFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
<analyzer type="query">
<tokenizer class="org.apache.lucene.analysis.cn.smart.HMMChineseTokenizerFactory"/>
<filter class="solr.CJKWidthFilterFactory"/>
<filter class="solr.StopFilterFactory"
words="org/apache/lucene/analysis/cn/smart/stopwords.txt"/>
<filter class="solr.PorterStemFilterFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
</fieldType>
在%solr安装目录%/server/configsets/_default/conf目录下有一份默认的配置文件managed-schema、solrconfig.xml。建议配置中文分词器时直接修改这个默认配置文件。因为在新建核心时,Solr会将这里的默认配置文件拷贝一份作为新核心的配置文件。
2.4. 使用SpringBoot操作Solr
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-solr</artifactId>
</dependency>
application.yml中配置
spring:
data:
solr:
host: http://localhost:8983/solr/
通过Java代码导入数据到Solr和搜索
@Slf4j
@Service
public class SolrService {
@Autowired
private SolrClient solrClient;
@Autowired
private MessageDao messageDao;
public static final int BATCH_SIZE = 10;
public static final String CORE_NAME = "lanou3g";
/**
* 将数据批量导入Solr中
* @throws IOException
* @throws SolrServerException
*/
public void importData2Solr() throws IOException, SolrServerException {
List<Message> messages = messageDao.loadAllMessage();
List<SolrInputDocument> batchList = new ArrayList<>();
messages.forEach((message -> {
// 将message对象转换成solr的inputDocument
SolrInputDocument inDoc = new SolrInputDocument();
inDoc.addField("id", message.getId());
inDoc.addField("from_id", message.getFromId());
inDoc.addField("to_id", message.getToId());
inDoc.addField("subject", message.getSubject());
inDoc.addField("content", message.getContent());
inDoc.addField("createtime", message.getCreatetime());
inDoc.addField("status", message.getStatus());
inDoc.addField("attachment", message.getAttachment());
batchList.add(inDoc);
if(batchList.size() % BATCH_SIZE == 0) {
try {
solrClient.add(CORE_NAME ,batchList);
batchList.clear();
log.info("批量导入"+BATCH_SIZE+"条到solr.");
} catch (SolrServerException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}));
if(batchList.size() > 0) {
solrClient.add(CORE_NAME, batchList);
log.info("批量导入"+batchList.size()+"条到solr.");
batchList.clear();
}
log.info("数据导入完成!");
// 提交数据到solr
// solrClient.commit(); // 无参的需要在配置文件中将核心名称添加到solr url中,参见application.yml
solrClient.commit(CORE_NAME);
}
/**
* 从Solr中分页搜索数据
* @param q 搜索关键字
* @param fields 限制返回的结果集中只允许哪个字段
* @param start 分页参数
* @param pageSize 分页参数
*/
public void searchFromSolr(String q, String[] fields, int start, int pageSize) {
SolrQuery params = new SolrQuery(q);
if(fields != null && fields.length > 0) {
params.setFields(fields);
}
// 分页参数
params.setStart(start);
params.setRows(pageSize);
// 不设置按照哪个字段搜索的时候,默认搜索哪个字段
// (一般会将系统中所有支持检索的字段通过CopyField的方式拷贝到一个统一的字段上,用于搜索,比如下面的keywords)
params.setParam("df", "keywords");
// 设置搜索结果高亮显示
params.setHighlight(true);
// 设置往搜索结果中所有匹配关键字的地方添加指定的前缀和后缀(内容随意)
params.setHighlightSimplePre("<i class=\"keywords\">");
params.setHighlightSimplePost("</i>");
try {
// QueryResponse queryResp = solrClient.query(params);
QueryResponse queryResp = solrClient.query(CORE_NAME, params);
SolrDocumentList results = queryResp.getResults();
long numFound = results.getNumFound();
System.out.println("总共搜索到"+numFound+"条结果");
results.forEach((solrDoc) -> {
StringBuilder sb = new StringBuilder();
sb.append("{");
Collection<String> fieldNames = solrDoc.getFieldNames();
fieldNames.forEach((fieldName) -> {
Object fieldValue = solrDoc.getFieldValue(fieldName);
sb.append("\""+fieldName+"\":\""+fieldValue+"\",");
});
if(sb.length() > 1) {
sb.deleteCharAt(sb.length() - 1);
}
sb.append("}");
System.out.println("row: " + sb.toString());
});
} catch (SolrServerException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}