欢迎来到我的博客: http://chenb.in
1 Solr基础介绍
1.1 什么是Solr
Solr是Apache开源的、基于Lucene的Java Api的封装,是一款成熟稳定的搜索引擎。
1.2 Solr的优缺点
优点:
-
大量的用户群体与成熟的社区
-
支持多种格式索引,例如HTML、CSV、JSON、XML等
-
诞生于与2004年,多年的沉淀使得Solr成熟、稳定
-
如果不考虑建索引的同时进行查询,查询速度更快,像我目前接手的法律查询软件,法律的录入不是十分频繁,那么用Solr就会比较合适
缺点:
- 在建立索引时,搜索效率不高,例如淘宝热卖那种需要实时更新索引的项目,Solr就显得有点力不从心了
1.3 使用版本
- JDK-1.8
- Solr-7.7.3(未使用最新版是因为公司用的不是最新版,然后核心知识都大同小异,后续会再看8.x有什么更新)
- ik分词器-7.x
1.4 安装
-
下载安装包 https://mirrors.bfsu.edu.cn/apache/lucene/solr/7.7.3/solr-7.7.3.tgz
-
到安装路径下解压 tar -zxvf solr-7.7.3.tgz(tgz相当于是tar.gz)
1.5 目录布局
目录 | 介绍 |
---|---|
bin | 包含启动等一些重要脚本 |
contrib | Solr的附加插件 |
dist | Solr的jar包 |
docs | Solr的在线文档 |
example | 演示案例 |
licenses | Solr使用的第三方库的许可证 |
server | Solr的核心应用,包含管理UI界面等 |
1.6 常用命令
bin/solr start 启动(macOS与Linux均以下述方式启动,Windows需要在加个.cmd如:bin/solr.cmd start)
bin/solr stop 关闭
bin/solr restart 重启
bin/solr status 查看状态
Solr有单机运行与集群运行两种方式,本文主要以单机运行为主了,默认启动端口为8983,若想指定端口可以在后面加上 -p 参数,如
- bin/solr start -p 端口号
- bin/solr restart -p 端口号
此时可以在浏览器输入 http://localhost:8983/ 来打开admin UI界面:
2 core配置
2.1 什么是core
是一个Lucene实例,其中包含Solr所有的配置文件,我们需要创建一个core来完成索引与分析等操作
一个Solr可以有多个core,如果需要core之间也是可以互相通信的
2.2 core的创建
-
create命令创建
./solr create -c new_core1
-
admin UI上直接创建
a. 此步执行之前,需要先将solr的默认配置文件夹复制到solr/server/solr/下,并重命名(下例中取名为new_core2),参考命令: cp -r ~/Library/solr-7.7.3/server/solr/configsets/_default/conf/ ~/Library/solr-7.7.3/server/solr/new_core2/
b. 重启
c.
注意项:
- 用第一种方法创建,会自动在server/solr下创建一个名为new_core1的core文件夹,里面有着所需的配置文件(server/solr也称solr_home)
- 第二种方式创建相当于我们手动创建了core文件夹
- core还有其他创建方式,但这两种比较常见
2.3 core的删除
./solr delete -c my_core
3 schema配置
3.1 什么是schema
在讲schema之前需要先了解一个概念 index(索引)
这里的索引和数据库中的索引不同,可以简单理解为Solr中的一张表,与Elasticsearch中的索引概念类似,举个例子:
我有学生表和课程表,我有一条很频繁的查询是某某学生某某课程考了多少分,那我就需要从数据库把这两张表的内容查询出来处理完后再传回客户端,而solr会把这两张表中需要的字段提取出来,存放到solr中去,而存放的数据结构就是索引,这相当于是数据库里的一张表
而schema就是Solr如何建立索引的说明书,例如字段的数据类型以及分词方式等
3.2 schema配置
在Solr5.5之前的配置文件叫schema.xml,这些需要手动配置,密密麻麻的XML看着还挺头疼的,之后出现了的配置文件叫做managed-schema,他的配置方式是使用schemaAPI来实时配置,并且配置之后无须重启即可生效,更加适合生产环境,也更好操作
常见的配置内容:
-
field: 相当于Java类的属性字段,可以给他配上其他属性,比如是否需要显示,是否可以用做索引字段等,有明确的定义的普通字段
a. name: 查询时字段
b. type: 字段类型
c. indexed: 是否为索引字段
d. stored: 是否存储
e. multiValued: 是否允许多值 -
dynamicField: 与field类似,是未明确定义名字的字段,常用属性与field了类似
-
copyField:
- 可以将field字段拷贝到其他字段
- 可以将dynamicField字段拷贝到其他字段
- 可以将多个field字段拷贝到多值字段
- 可以将多个dynamicField字段拷贝到多值字段
-
fieldType: 一般用来定义分词器,然后让field/dynamicField通过type引用
-
analyzer: fieldType的子属性,常用于设置分词器
3.3 ik分词器配置
步骤:
-
下载jar包:https://search.maven.org/search?q=com.github.magese
-
把jar包放置到(以我的为例)solr-7.7.3/server/solr-webapp/webapp/WEB-INF/lib下
-
到solr-7.7.3/server/solr/new_core/managed-schema下最底下粘贴以下内容:
<!-- ik分词器 --> <fieldType name="text_ik" class="solr.TextField"> <analyzer type="index"> <tokenizer class="org.wltea.analyzer.lucene.IKTokenizerFactory" useSmart="false" conf="ik.conf"/> <filter class="solr.LowerCaseFilterFactory"/> </analyzer> <analyzer type="query"> <tokenizer class="org.wltea.analyzer.lucene.IKTokenizerFactory" useSmart="true" conf="ik.conf"/> <filter class="solr.LowerCaseFilterFactory"/> </analyzer> </fieldType>
-
重启Solr
-
测试分词器
Field Value(Index)与Field Value(Query)是Solr在索引时与在查询时会怎么处理数据,可以根据这两个来帮助设计分析器、分词器和过滤器(本文采用了ik分词器,上述三者ik分词器已经自己设计好了,关于这三者具体内容见下文)
4 分析器、分词器、过滤器
Solr主要通过分析器(Analyzer)、标记器(Tokenizers)、过滤器(Filter)来分解和处理文本。
在介绍之前,先讲述一个概念文档(Document),之前说index相当于数据库里的一张表,而文档就可以理解为表里的一条数据。
4.1 概述
当对一个文档进行索引时,每个field的数据都会经过分析,分词过滤等操作,例如,将一句话分词若干个单词和词组,然后去掉空格与语气词等多余的词以及进行同义词替换等,例如:
what a beautiful girl,在这里what和a这样的词以及空格会被去掉,最终的处理结果为beautiful和girl或者是beautiful girl
除了索引会做这些工作,查询时也会做这些工作,并且为了保证索引与查询可以正确匹配,二者的处理规则往往也是相同的,例如:
ABCD索引分词为AB CD,查询时分词为A BCD,这样匹配的结果就为0
analyzer包含着两个部分,Tokenizers与Filter。Tokenizers就是将句子拆分成单个词,而Filter就是对分词的结果中比如中文里的"的"、“呀”、“啊”,英文里的"am"、“is”、"are"这类对句子主体意思关系不大的词去掉
solr有自带了一些分词器,如果你需要使用中文分词器就需要自己配置,参考3.3
4.2 使用方式
analyzer为fieldType下的一个子元素,主要作用是检查字段的文本并生成令牌流
最简单的使用方式如下:
<fieldType name="nametext" class="solr.TextField">
<analyzer class="org.apache.lucene.analysis.core.WhitespaceAnalyzer"/>
</fieldType>
其中analyzer通过一个全限定Java类名引入,且命名的类必须继承自org.apache.lucene.analysis.Analyzer
对于简单的文章,这样一个分析器类就足够完成任务,但是通常情况下是需要更为复杂的分析
Solr提供了大量的Tokenizers与Filter来协助分析,例如:
<fieldType name="nametext" class="solr.TextField">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.StandardFilterFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory"/>
<filter class="solr.EnglishPorterFilterFactory"/>
</analyzer>
</fieldType>
org.apache.solr.analysis 包中的类可以在这里用简写的 solr. 前缀来代替
上述中,analyzer未使用指定的分析器类,而用许多类组合到一起,成为该字段的分析器,任何使用"nametext"fieldType字段进行索引或查询时,都会经过从StandardTokenizerFactory到EnglishPorterFilterFactory的过滤
分析发生在两种情况下:分别是索引时与查询时
在索引时:当一个字段被创建时,分析得到的令牌流将被添加到一个索引中,并为该字段定义一组术语(包括位置、大小等)。
在查询时:分析正在搜索的值,并将结果的条件与存储在字段索引中的条件进行匹配。
如果想要在不同阶段使用不同的分析器,可以指定type属性:如下:
<fieldType name="nametext" class="solr.TextField">
<analyzer type="index">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.KeepWordFilterFactory" words="keepwords.txt"/>
<filter class="solr.SynonymFilterFactory" synonyms="syns.txt"/>
</analyzer>
<analyzer type="query">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
</fieldType>
在索引时,文本被标记化,并设置为小写,未列在keepwords.txt中的会被丢弃,保留下来的会根据syns.txt进行同义词替换
在查询时,就只是将文本标记后设置为小写
5 Solr的使用
5.1 根据文件导入数据
Solr可以导入多种格式的文件来作为index,在solr-7.7.3/example/exampledocs中有各个格式的测试案例,本文随机采用一种演示
可以通过bin目录下的post命令导入index
输入 bin/post -h 可查看具体使用方式
示例: bin/post -c 导入的核心名 导入的文件(可多个)
下面演示一下往new_core里导入book.csv与book.json文件:
- bin/post -c new_core example/exampledocs/books.csv example/exampledocs/books.json
- 查看演示数据
5.2 添加/更新文档
添加/更新内容(根据是否有id判断为添加还是更新操作):
{
"id":"1234567890",
"cat":["book"],
"name":["BeanChan is a handsome man"],
"price":[799],
"inStock":[true],
"author":["BeanChan"],
"series_t":"A man has a pretty face",
"sequence_i":1,
"genre_s":"fantasy"
}
5.3 删除文档
需基于XML方式
<!--删除所有索引-->
<delete>
<query>*:*</query>
</delete>
<commit />
<!-- 根据 id 进行删除-->
<delete>
<id>1</id>
</delete>
<commit />
5.4 查询数据
参数 | 描述 |
---|---|
q | 主要查询参数 |
fq | 过滤器查询参数 |
start | 起始页 |
rows | 查询行数 |
sort | 排序方式 |
fl | 结果集字段列表 |
wt | 展示格式(下图以csv格式展示) |
下图为例:
6 整合springboot
-
引入pom依赖
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-solr</artifactId> </dependency>
-
配置yml
spring: data: solr: host: http://127.0.0.1:8983/solr
-
配置entity
package beanchan.cn.springbootdemo.entity; import lombok.Data; import org.apache.solr.client.solrj.beans.Field; import org.springframework.data.solr.core.mapping.SolrDocument; /** * @Classname SolrEntity * @Author ChenBin * @Description * @CreateDate 2021/2/20 2:00 PM */ @SolrDocument(collection = "new_core") @Data public class SolrEntity { @Field private String id; @Field("name") private String name; }
-
配置dao
package beanchan.cn.springbootdemo.dao; import beanchan.cn.springbootdemo.entity.SolrEntity; import org.springframework.data.solr.repository.SolrCrudRepository; import org.springframework.stereotype.Repository; /** * @Classname solaDao * @Author ChenBin * @Description * @CreateDate 2021/2/20 3:25 PM */ @Repository public interface SolrDao extends SolrCrudRepository<SolrEntity,String> { }
-
配置service及其实现类
package beanchan.cn.springbootdemo.service; import beanchan.cn.springbootdemo.entity.SolrEntity; import org.apache.solr.client.solrj.SolrServerException; import java.io.IOException; import java.util.List; /** * @InterfaceName ISolrService * @Author ChenBin * @Description * @CreateDate 2021/2/20 3:33 PM */ public interface ISolrService { void add(); void update(); void delete(); List<SolrEntity> quertList(String keyword) throws IOException, SolrServerException; }
package beanchan.cn.springbootdemo.service.impl; import beanchan.cn.springbootdemo.dao.SolrDao; import beanchan.cn.springbootdemo.entity.SolrEntity; import beanchan.cn.springbootdemo.service.ISolrService; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * @Classname ISolrServiceImpl * @Author ChenBin * @Description * @CreateDate 2021/2/20 3:34 PM */ @Service public class ISolrServiceImpl implements ISolrService { @Resource SolrDao solrDao; @Resource private SolrClient solrClient; @Override public void add() { SolrEntity solrEntity = new SolrEntity(); solrEntity.setId("001"); solrEntity.setName("阿姆斯特朗回旋加速喷气式阿姆斯特朗炮制作图纸"); solrDao.save(solrEntity); } @Override public void update() { SolrEntity solrEntity = new SolrEntity(); solrEntity.setId("001"); solrEntity.setName("阿姆斯特朗回旋加速喷气式阿姆斯特朗炮使用手册"); solrDao.save(solrEntity); } @Override public void delete() { solrDao.deleteById("001"); } @Override public List<SolrEntity> quertList(String keyword) throws IOException, SolrServerException { SolrQuery query = new SolrQuery(); //设置查询条件 query.setQuery("name:阿姆斯特朗"); //按照时间排序 // query.addSort("create_time", SolrQuery.ORDER.desc); //开始页 query.setStart(0); //一页显示多少条 query.setRows(10); //开启高亮 //query.setHighlight(true); //设置高亮字段 //query.addHighlightField("demoName"); //前缀 //query.setHighlightSimplePre("<font color='red'>"); //后缀 //query.setHighlightSimplePost("</font>"); //执行查找 QueryResponse response = solrClient.query("new_core",query); SolrDocumentList results = response.getResults(); //获取查询到的数据总量 long numFound = results.getNumFound(); if (numFound <= 0) { //如果小于0,表示未查询到任何数据,返回null return null; } else { List<SolrEntity> list = new ArrayList<>(); //遍历结果集 for (SolrDocument doc : results) { //得到每条数据的map集合 Map<String, Object> map = doc.getFieldValueMap(); //添加到list for (String key : map.keySet()) { System.out.println("KEY:" + key + ",VALUE:" + map.get(key).toString()); } SolrEntity demo = new SolrEntity(); demo.setId(map.get("id").toString()); demo.setName(map.get("name").toString()); list.add(demo); } return list; } } }
-
配置Controller
package beanchan.cn.springbootdemo.controller; import beanchan.cn.springbootdemo.entity.SolrEntity; import beanchan.cn.springbootdemo.service.ISolrService; import org.apache.solr.client.solrj.SolrServerException; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.io.IOException; import java.util.List; /** * @Classname searchController * @Author ChenBin * @Description * @CreateDate 2021/2/20 10:57 AM */ @RestController public class SearchController { @Resource ISolrService solrService; /** * 新增 * * @return flag */ @RequestMapping("add") public String add() { solrService.add(); return "success"; } @RequestMapping("update") public String update() { solrService.update(); return "success"; } @RequestMapping("delete") public String delete() { solrService.delete(); return "success"; } @RequestMapping("queryList") public String queryList() { List<SolrEntity> solrEntities = null; try { solrEntities = solrService.quertList(""); } catch (IOException | SolrServerException e) { e.printStackTrace(); } if (solrEntities != null) return solrEntities.toString(); return "empty"; } }
-
测试还请自行实验,下图简单展示一下