01-Solr
Solr01
1.Solr简介
- Solr是基于Apache Lucene构建的用于搜索和分析的开源框架,提供搜索、高亮显示和文字解析等功能。
- Solr是一个Java Web项目,且内嵌Jetty服务器。Solr6之前是一个war包,需要放在Tomcat上运行。Solr8+,内嵌Jetty服务器,Solr8+的启动流程:通过批处理文件(Solr安装目录下bin目录的.cmd或者.sh文件),先启动Jetty服务器,然后在Jetty启动后运行war包。
- 正向索引,Forward Index,也叫正排索引。从文档内容到词组的过程,每次搜索的时候需要搜索所有文档,每个文档比较搜索条件和词组。
- 反向索引,Inverted Index,也叫倒排索引。反向索引是正向索引的逆向,通过建立词组和文档的映射关系,通过词组就可以找到文档。
2.Solr搜索和存储原理
-
搜索原理。Solr高效搜索的主要是分词和索引(反向索引)。
- 分词。对搜索条件和存储内容进行分词,分成日常所使用的词语。
- 索引。存储数据时按照某些要求建立索引,如果要求建立索引,会对存储内容中的关键字(分词)建立索引。
-
Solr数据存储原理。
- Solr中使用索引库保存数据(可以理解为索引库对应MySQL中的一章表),没有表的概念,一个索引库中可以有n个字段。
- Solr数据存储在Document文档对象中,文档对象包含的属性和属性类型都定义在schema.xml中,如果需要自定义属性或者自定义属性类型都需要修改schema.xml配置文件。从Solr5开始schema.xml更名为managed-schema(没有扩展名)。
- Solr中一条数据,就相当于MySQL中的一行数据,一条数据就是一个文档,文档可以使用json描述,也可以使用XML描述。
- MySQL中user表有10个字段,u1u10。对应到Solr就是有一个user的索引库,索引库有10个字段,u1u10。user索引库中的数据使用json描述就是
{u1:'', u2:'', u3:'', u4:''..., u10:''}
;user索引库的数据使用xml描述就是:
<dot> <u1></u1> <u2></u2> ... <u10></u10> </dot>
3.Solr的安装
-
https://solr.apache.org/
,Solr官网;https://solr.apache.org/downloads.html
,Solr下载地址。 -
Solr压缩包解压之后的目录说明。
- bin,可执行文件目录,包括启动和停止的脚本。
- contrib,Solr核心jar包。
- dist,常用插件的jar包。如需要使用数据导入则需要将这个目录中的solr-dataimporthandler-8.9.0.jar和solr-dataimporthandler-extras-8.9.0.jar拷贝到server\solr-webapp\webapp\WEB-INF\lib。
- docs,文档目录。
- example,案列目录。
- server,Solr搜索应用服务器核心目录。Solr是基于Web写的Java项目,通过bin目录下的脚本来启动server这个Web写的Java项目。
- start.jar。bin下的可执行文件通过启动start.jar来启动Jerry,start.jar中封装Jetty。
- contexts,上下文配置文件。
- etc,Solr核心配置文件。
- lib,常用的Jar包。
- logs,日志目录,包含GC日志、慢请求日志等。
- modules,用于加载插件。
- resources,日志配置信息。
- scripts,一些可执行文件。
- solr,保存索引库,保存索引库配置(字段类型和字段名称等)和文档数据。
- solr-webapp,Solr的war的核心。Solr-webapp是Solr完整的war包,war运行需要解压,解压是需要时间的,所以这么目录保存的是加压后的war包。
-
修改Solr配置文件关闭启动用户检查。
- 如果启动过多的线程会占用大量的空间。Centos低版本,一个线程占用128K;Centos高版本,一个线程占用256K。
- Solr中需要使用多线程进行分析和查询,Linux下root用户可以无限的创建线程,所以使用root用户启动Solr时,会抛出很多警告,而ES则不允许使用root用户启动。
bin/solr.in.sh
文件中SOLR_ULIMIT_CHECKS=true,默认开启用户检查,当使用root用户启动Solr时,就有很多的警告信息;当SOLR_ULIMIT_CHECKS=false,即关闭用户检查,即使使用root用户启动,也不会有告警信息。
-
Solr启动。
- Solr内嵌Jetty,可直接启动,默认监听8983端口。
- Solr默认不推荐使用root用户启动,如果需要使用root启动,则需要制定-force参数。普通用户启动
solr/bin/solr start
;root用户启动solr/bin/solr start -force
。 http://127.0.0.1:8983/solr/#/
,启动之后的访问地址。
4.创建索引库
- 服务器操作。
- 在server/solr下创建user文件夹,user就是索引库。然后将server/solr/configsets/_default(索引库的默认配置文件)下的所有文件复制到server/solr/user文件夹下。
http://127.0.0.1:8983/
地址的页面操作。- 选择Core Admin,然后Add Core,即初始化创建的user索引库。
- Add Core的一些配置参数。
- name,user。
- instanceDir,user。
- dataDir data,data。会自动在server/solr/user下创建data文件夹。
- config,solrconfig.xml。会寻找server/solr/user/conf下的solrconfig.xml。
- schema,managed-schema。会寻找server/solr/user/conf下的managed-schema文件。
- Solr页面中选择user索引库后中的选项解释。
- Overview,索引库信息。
- Analysis,分词信息,可以获取到一段文字的分词结果。
- Dataimport,数据导入,可以将MySQL中的数据导入。
- Document,文档的增删改查。
- Files,server\solr\user\conf\lang下的配置文件信息。
- Ping,服务连通性测试。
- Plugins,插件信息。
- Query,查询、搜索。
- Replication,副本信息。
- Schema,用于添加索引库的字段。
- Segments info,提高查询效率生产的一个过度文件。
- Solr数据分析完成后,先将数据写入到Segment,然后由操作系统将Segment写入到磁盘中。
- Segments info中可以看到Deletinos(文件碎片的百分比)。这个百分比记录的是Solr文件中的碎片。Solr中要删除一个文件时,先将其标记为要删除的状态,然后统一删除。Deletinos表示要删除文件的多少。
- Solr实际开发中使用的过程。
- 创建索引库。
- 索引库和MySQL中的表相互对应,对于复杂的查询,索引库和MySQL中的视图对应。
- 索引库会被单独作为一个项目,并且保存到Git上(方面维护Solr字段和MySQL列的关系)。在使用时只需要将整个索引库放到
server\solr
下即可。 - 如果Solr是第一次部署,则需要去Web页面初始化索引库(Web页面初始化数据的本质是发送Http请求,所以也可以使用curl初始化)。
- 数据导入。索引库创建并且初始化完成之后,就需要进行数据导入,可以使用Web页面进行数据导入(Web页面数据导入的本质是发送Http请求,所以也可以使用curl进行数据导入)。
- 创建索引库。
5.安装ik分词器
- 下载ik分词器。Maven仓库中下载和solr版本对应的ik分词器的jar包,下载地址:
https://mvnrepository.com/artifact/com.github.magese/ik-analyzer
。 - 将ik分词器的jar包放到solr-8.9.0\server\solr-webapp\webapp\WEB-INF\lib下。
- 修改
server\solr\user\conf\managed-schema
配置文件,为user索引库中需要中文分词的字段添加ik分词配置。
<schema name="default-config" version="1.6">
<!-- 定义字段,字段类型为not_user_smart,当前字段就会使用ik分词器。 -->
<field name="not_smart" type="not_user_smart" indexed="true" stored="true" />
<!-- 定义字段,字段类型为user_smart,当前字段就会使用ik分词器。 -->
<field name="smart" type="user_smart" indexed="true" stored="true" />
<!-- 定义使用ik分词器的字段类型,并且字段没有useSmart。 -->
<fieldType name="not_use_smart" 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="false" conf="ik.conf"/>
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
</fieldType>
<!-- 定义使用ik分词器的字段类型,并且字段useSmart。 -->
<fieldType name="use_smart" class="solr.TextField">
<analyzer type="index">
<tokenizer class="org.wltea.analyzer.lucene.IKTokenizerFactory" useSmart="true" 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>
</schema>
-
先停止solr(solr.cmd stop -all),然后再启动(solr.cmd start)。
-
useSmart值的区别(以
我们去吃饭
为例)。useSmart="false",分词量多,分的细致,分为:我们、我、们、去吃、吃饭;userSmart="true",分词量少,更智能化,分为:我们、去、吃饭。
6.Solr页面中数据的增删改查
-
增加数据。选择Documents,Request-Handler为/update。
- Document Type为XML,Commit Within=1000,Overwrite=true。
<doc> <field name="id">100</field> <field name="not_smart">我们去吃饭</field> <field name="smart">我们去吃饭</field> </doc>
- Document Type为JSON,Commit Within=1000,Overwrite=true。
{"id": "101", "not_smart": "我们去吃饭", "smart": "我们去吃饭"}
-
修改数据。修改数据和新增数据的操作一样,当id不存在是为新增;当id存在时为修改数据。
-
查询数据。选择Query进行数据查询,Request-Handler (qt)为/select。
- q,查询条件。
*:*
,:左边是查询的key,:右边是查询的value,*表示查询所有数据。 - q.op,默认or,即查询条件之间是or的关系。
q=id:100 not_smart:我们去吃饭,q.op=or
,查询id=100或者not_smart为我们去吃饭的数据;q=id:100 not_smart:我们去吃饭,q.op=and
,查询id=100并且not_smart为我们去吃饭的数据。 - sort,排序。id desc,按照id降序;id asc,按照id升序。
- start, rows,分页。start,数据开始位置,从0开始;rows,查询数据数量,相当与每页返回的数据数量。0,10,查询前10条数据。
- hl,高亮。hl.fl,需要高亮的字段;hl.simple.pre,高亮词的前缀;hl.simple.post,高亮词后缀。高亮需要配置q查询一起使用,如
q=not_smart:我们
,hl.fl为not_smart,则返回的高亮数据为<em>我们</em>去吃饭
,并且q=not_smart:我们
查询条件中的我们
需要被分词。
- q,查询条件。
-
删除数据。选择Documents,Request-Handler为/update。
- Document Type为XML,Commit Within=1000,Overwrite=true。
<!-- 通过id删除数据。 --> <delete> <id>100</id> </delete> <!-- 通过查询条件删除数据。 --> <delete> <query>not_smart:我们</query> </delete>
- Solr在服务器使用curl删除数据。
# 删除数据 curl http://localhost:8080/update --data-binary "<delete><id>100</id></delete>" -H 'Content-type:text/xml; charset=utf-8' # 提交命令 curl http://localhost:8080/update --data-binary "<commit/>" -H 'Content-type:text/xml; charset=utf-8'
- 使用
example\exampledocs\post.jar
删除数据。
# 删除数据 java -Ddata=args -jar post.jar "<delete><id>100</id></delete>" # 查看post.jar帮助文档 java -jar post.jar -help
7.dataimport数据导入
- 修改server\solr\user\conf\solrconfig.xml配置文件。
<config>
...
<!-- 请求http://127.0.0.1:8983/solr/user/dataimport时,
使用DataImportHandler处理。
-->
<requestHandler name="/dataimport" class="org.apache.solr.handler.dataimport.DataImportHandler">
<lst name="defaults">
<str name="config">data-config.xml</str>
</lst>
</requestHandler>
...
</config>
- server\solr\user\conf\下新建data-config.xml,配置数据库信息和字段的映射信息。
<?xml version="1.0" encoding="UTF-8"?>
<dataConfig>
<dataSource type="JdbcDataSource"
driver="com.mysql.cj.jdbc.Driver"
url="jdbc:mysql://127.0.0.1:3306/solr?serverTimeZone=Asia/Shanghai"
user="root"
password="123456"/>
<document>
<!-- 数据库的name字段,映射solr的not_smart字段。 -->
<entity name="user" query="select id, from user">
<field column="id" name="id"/>
<field column="name" name="not_smart"/>
<field column="address" name="smart"/>
</entity>
</document>
</dataConfig>
- 将MySQL驱动包(mysql-connector-java-8.0.28.jar)复制到server\solr-webapp\webapp\WEB-INF\lib下。
- Solr数据导入需要使用DataImportHandler,因此需要将dist下的
solr-dataimporthandler-8.9.0.jar
和solr-dataimporthandler-extras-8.9.0.jar
拷贝到server\solr-webapp\webapp\WEB-INF\lib下,然后重启。
8.SolrJ实现数据的增删改查
- 导入solrj依赖,solr-solrj的版本尽量和Solr版本一致。
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-solrj</artifactId>
<version>8.9.0</version>
</dependency>
- 新增和更新数据。
public static void test01() {
// 基础路径,Solr服务和索引库所在路径。
String baseSolrUrl = "http://127.0.0.1:8983/solr/user";
// 通过基础路径构建客户端连接。
HttpSolrClient httpSolrClient = new HttpSolrClient.Builder(baseSolrUrl).build();
// id存在则更新数据,id不存在则新增数据。
SolrInputDocument document = new SolrInputDocument();
document.addField("id", "100");
document.addField("not_smart", "我们一起去吃饭。");
try {
// 更新的响应结果和web页面更新的操作的响应结果一致。
UpdateResponse updateResponse = httpSolrClient.add(document);
// 返回的状态。
System.out.println(updateResponse.getStatus());
// 执行时间。
System.out.println(updateResponse.getQTime());
} catch (Exception e) {
// 发生异常回滚
try {
httpSolrClient.rollback();
} catch (Exception solrServerException) {
solrServerException.printStackTrace();
}
}
// 在solr服务中,数据的写也是有事务的。
// Web管理平台,一次操作就是一次事务,默认提交事务;
// solr-solrj事务需要手动提交事务。
// 执行成功,提交事务。
try {
httpSolrClient.commit();
} catch (Exception e) {
e.printStackTrace();
}
// 回收资源。
try {
httpSolrClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
- 删除数据。
public static void test02() {
String baseSolrUrl = "http://127.0.0.1:8983/solr/user";
HttpSolrClient httpSolrClient = new HttpSolrClient.Builder(baseSolrUrl).build();
try {
UpdateResponse updateResponse = httpSolrClient.deleteById("001");
System.out.println(updateResponse.getStatus());
} catch (Exception e) {
e.printStackTrace();
}
try {
httpSolrClient.commit();
} catch (Exception e) {
e.printStackTrace();
}
}
- 查询。条件查询、排序、分页。
public static void test03() {
String baseSolrUrl = "http://127.0.0.1:8983/solr/user";
HttpSolrClient httpSolrClient = new HttpSolrClient.Builder(baseSolrUrl).build();
try {
SolrQuery solrQuery = new SolrQuery();
// 查询条件。
solrQuery.setQuery("id:*1*");
// 排序
solrQuery.setSort("id", SolrQuery.ORDER.asc);
// 分页
solrQuery.setStart(0);
solrQuery.setRows(20);
QueryResponse queryResponse = httpSolrClient.query(solrQuery);
// 对应Web页面查询的响应头。
NamedList<Object> namedList = queryResponse.getHeader();
/*
status=0
QTime=118
params={q=id:*1*,start=0,sort=id asc,rows=20,wt=javabin,version=2}
*/
Iterator<Map.Entry<String, Object>> iterator = namedList.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// 响应体,SolrDocumentList是ArrayList的子类。
SolrDocumentList results = queryResponse.getResults();
// 查询到的总数。
System.out.println("NumFound" + results.getNumFound());
// 遍历查询结果,result.getFieldValue("")用于获取字段对应的值。
for (SolrDocument result : results) {
System.out.print(result.getFieldValue("id") + "\t");
System.out.println(result.getFieldValue("not_smart"));
}
} catch (Exception e) {
e.printStackTrace();
}
}
- 高亮查询。
public static void test04() {
String baseSolrUrl = "http://127.0.0.1:8983/solr/user";
HttpSolrClient httpSolrClient = new HttpSolrClient.Builder(baseSolrUrl).build();
try {
SolrQuery solrQuery = new SolrQuery();
// 查询条件。
solrQuery.setQuery("not_smart:*吃饭*");
// 排序
solrQuery.setSort("id", SolrQuery.ORDER.asc);
// 分页
solrQuery.setStart(0);
solrQuery.setRows(20);
// 设置开启高亮
solrQuery.setHighlight(true);
// 高亮字段。
solrQuery.addHighlightField("not_smart");
// 高亮前缀
solrQuery.setHighlightSimplePre("<em>");
// 高亮后缀。
solrQuery.setHighlightSimplePost("</em>");
QueryResponse queryResponse = httpSolrClient.query(solrQuery);
// 高亮数据。
Map<String, Map<String, List<String>>> highlighting = queryResponse.getHighlighting();
SolrDocumentList results = queryResponse.getResults();
for (SolrDocument result : results) {
System.out.print(result.getFieldValue("id"));
System.out.print(result.getFieldValue("not_smart"));
// 高亮数据需要通过唯一id值获取。
Map<String, List<String>> map = highlighting.get(result.getFieldValue("id"));
if (map != null) {
System.out.print("有高亮数据 " + map.get("not_smart"));
}
System.out.println();
}
} catch (Exception e) {
e.printStackTrace();
}
}
{
"responseHeader":{
"status":0,
"QTime":13,
"params":{
"q":"not_smart:*吃饭*",
"hl":"true",
"hl.simple.post":"</em>",
"start":"0",
"q.op":"OR",
"hl.fl":"zh_all",
"sort":"id asc",
"rows":"2",
"hl.simple.pre":"<em>",
"_":"1681473821442"}},
"response":{"numFound":380,"start":0,"numFoundExact":true,"docs":[
{
"not_smart":"我们去吃饭",
"id":"127",
"_version_":1762052434657542155},
{
"not_smart":"我们去吃饭",
"id":"128",
"_version_":1762052434657542156}]
},
"highlighting":{
"127":{ # 高亮数据字段,127是id的值。
"not_smart":["我们去<em>吃饭</em>"]},
"128":{
"not_smart":["我们去<em>吃饭</em>"]}}}
9.spring-boot-starter-data-solr
- spring-boot-starter-data-solr提供了两种操作solr的方式,使用SolrTemplate或者继承SolrCrudRepository。
- SolrTemplate比较灵活,可以实现SolrCrudRepository的全部功能。当前端查询条件过多,需要进行条件拼接时,就可以使用SolrTemplate,如需要通过姓名、手机号、身份证、时间、地址等条件进行查询时,这些条件有可能全部使用,也有可能只使用了其中的一个或者两个,即需要根据前端传值判断是否为空后进行条件拼接,就可以使用SolrTemplate。
- SolrCrudRepository的特点是方便简单,适用于指定条件的查询,如通过姓名查询。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix