Solr之搜索引擎的搭建

前言

学习和使用 Solr 有挺长一段时间了,所以我想着也应该写几篇博客记录一下。网上关于 Solr 的文章有一大堆,但是很多都是比较老的,还有很多都是互相抄来抄去,真正可以学习的却不多。但是有一个名叫嘿↗你的益达 的博客里面的东西还是很不错的,我从他那里学到了很多。

我也不是说我能写出多牛逼的东西,毕竟关于搜索引擎或者 Solr 又或者 Lucene,我也只是个菜鸟。我就想写几篇博客,一来自己以后可以回过头来看看,二来也可以给别人提供一些帮助。

这一次我准备先写两篇,分别是利用 Solr 搭建搜索引擎,以及手动修改和编译 Solr 源代码,本文讲的是前者,我希望我能够将搭建搜索引擎的整个过程讲清楚。

下载 Solr

因为 Solr 是 Apache 的开源项目,所以我们可以去 Apache 官网下载 Solr,Solr 现在已经和 Lucene 合在了一个项目里面。

虽然现在 Solr 的最新版本已经是 6.5.0,而且去年就已经有了 6.x 的版本,但是考虑到稳定性以及文档等原因,去年在选择 Solr 版本的时候,我们还是选择了 5.x 的版本,最终选择的是当时 5.x 版本中最新的 5.5.2。所以我今天就以 5.5.2 为例来讲。

我们可以去 http://archive.apache.org/dist/lucene/solr/ 下载 Solr,进去之后找到 5.5.2,然后点进去。

之后我们可以上面这些内容,其中既有 Solr 源码,又有 Solr 编译好的版本,还有一些校验文件。
我们下载上图中红色圈出来的文件中的任意一个,它们其实是一样的,只不过压缩打包方式不一样,一个是 tgz 格式,另一个是 zip 格式。假设我们下载的是下面那个 zip 格式的 Solr 好了。

运行 Solr

我们的 Solr 一般都部署在 Linux 服务器上,本来我是想直接以 Linux 上运行 Solr 为例来说。但后来想想,今天要说的很基础,而且又不是要搭集群什么的,以本地运行为例也一样。我是在 Mac 上运行的 Solr,不过反正我也是通过命令行的方式运行的,大家也可以把它当成是在 Linux 上跑的 Solr。

下载之后进行解压,解压之后,里面大概有这些文件:

如果现在想要让 Solr 跑起来,那特别简单,因为现在 Solr 里面默认集成了 Jetty,所以要跑起来只是一条命令的事情。网上有很多文章还是说要将 Solr 跑在 Tomcat 下,以前确实只能这样,现在不用了,要运行在 Tomcat 下当然现在也可以,但是比较麻烦,所以我们不那么做。

bin目录下有一个可执行文件叫做 solr,可以通过它直接让 Solr 跑起来,使用下面的命令

1
$ bin/solr start

可以看到 Solr 运行在 8983 端口,我们打开 http://localhost:8983,如果看到了如下界面,说明 Solr 成功运行。

我们使用 start 来启动 Solr,自然而然地我们可以想到可以用 stop 来关闭 Solr,以及用 restart 来重启 Solr。

1
$ bin/solr stop

我们现在运行的是单机版的 Solr,当然现在它还不能使用,只是跑起来了,我们需要为 Solr 创建 Core

创建 Core

所谓的 Core 就是包含不同封装的物理索引形成的一个实例。这样说也比较抽象,简单来理解就是,一个 Core 包含了配置文件,包含了索引文件,包含了数据,所以一个 Core 就相当于是一个搜索的实例了。我们要对某些文件或者数据进行搜索,首先要创建一个 Core,然后在这个 Core 里面进行一系列配置,之后将那些数据索引起来,就可以搜索了。

我们可以在 Solr 的管理界面上看到一个 Core Admin 的选项,但我们不能直接通过它来创建 Core,会报错。

它说它没找到配置文件,这里我就觉得很奇怪,既然都做了这个选项在这里,就不能给我们创建好默认的配置文件么,还要我们先提供配置文件,很不符合逻辑啊,不知道是不是因为我哪里做的不对。

所以我们需要手动去创建 Core。
进入 Solr 根目录下面的 server/solr 目录,这是我们存放 Core 的地方,每个 Core 一个文件夹。我们先创建一个文件夹名为 test,这也是我们的 Core 的名字。

1
$ mkdir test

solr 目录下面有个名为 configsets 的目录,里面提供了一些配置文件模板,然后我们去 configsets/data_driven_schema_configs 目录下,将里面的 conf 目录拷贝到我们刚刚创建的 test 目录下。

回到 Solr 的 web 管理界面,回到 Core Admin 那边,在 nameinstanceDir 上输入 test,最后选择 Add Core,这次我们就能够成功创建 Core 了。

创建 Core 之后,左下角多出了我们刚刚创建的 Core,对比一下没创建之前那里显示的是 No cores availables。中间部分也显示了当前 Core 的信息。

回到 test 目录,里面多出了一些东西,conf 是我们自己放进去的配置文件,data 是 Solr 为我们创建的存放数据和索引的目录,而 core.properties 则是 Solr 给我们配置 Core 属性的文件。

core.properties 里面的内容如下:

1
2
3
4
5
6
#Written by CorePropertiesLocator
#Wed Apr 05 05:16:51 UTC 2017
name=test
config=solrconfig.xml
schema=schema.xml
dataDir=data

我们可以手动修改 Core 读取的配置文件,同样,我们可以修改索引数据存放的路径,不一定要放在 Solr 给我们创建的 data 目录下面。

再回到 Solr 管理界面,点击左下角的 test。会出现以下界面

左下角的 Core 菜单中,Analysis 用来测试分词,Dataimport 用来索引数据,Query 用来进行搜索,这几个比较常用。这些都是 Solr 以 http 形式提供给我们操作当前 Core 的接口。

我们可以试着打开 Query 菜单进行一下搜索。
在中间的 q 输入框中输入一个“大数据”,然后点击下面的 Execute Query 进行搜索,结果什么结果也没查到,这也是意料之中的事情,我们都没有索引数据,肯定什么也搜不出来。这里 q 代表的是搜索词,其他还有 fq,fl,sort 之类的都是 Solr 的搜索参数。

所以我们真正要可以搜索,还是要做很多事情的。

索引数据

我们之前说了左侧菜单中的 Dataimport 用来索引数据,点进去,我们就发现现在我们还不能索引数据,所以我们必定还需要一番配置。

假设我们要搜索的数据存储在 MySQL 数据库中,那么我们就要对 MySQL 中的数据进行索引。Solr 也是用 Java 写的,要连接 MySQL数据库,我们需要 JDBC 的驱动文件。我用的是 mysql-connector-java-5.1.39-bin.jar,这个可以网上去下载,例如从这里下载。

下载之后,将其放入 Solr 根目录下的 server/solr-webapp/webapp/WEB-INF/lib 目录下面。

还需要将两个文件 solr-dataimporthandler-5.5.2.jarsolr-dataimporthandler-extras-5.5.2 一起放入server/solr-webapp/webapp/WEB-INF/lib 目录下。好在这两个文件不用专门去网上找,Solr 本身就将这两个 jar 包提供给了我们,就在 Solr 根目录下面的 dist 目录下,所以我们直接让这两个文件拷贝过去。下面的命令在 Solr 根目录下执行:

1
cp dist/solr-dataimporthandler-5.5.2.jar dist/solr-dataimporthandler-extras-5.5.2.jar server/solr-webapp/webapp/WEB-INF/lib

准备好 jar 文件之后,我们去修改配置文件。
回到我们之前创建的 test 目录下,进入 conf 目录:

这里面我们经常要修改的配置文件是 managed-schemasolrconfig.xml

默认 Dataimport 功能在 Solr 5.x 中是被禁用的,所以我们需要将其开启,用 vim 打开 solrconfig.xml,在里面添加一个 dataimport 的 handler 配置项:

1
2
3
4
5
6
<!-- Data Importer -->
<requestHandler name="/dataimport" class="org.apache.solr.handler.dataimport.DataImportHandler">
<lst name="defaults">
<str name="config">data-config.xml</str>
</lst>
</requestHandler>

可以先 solrconfig.xml 文件里面搜索一下 requestHandler,然后将上面的配置项和其他的放在一起,便于管理。

上面的配置项里面提到了一个名为 data-config.xml 的配置文件,之前我们并没有在当前 Core 的配置文件中找到,所以我们需要自己来创建。

这个 data-config.xml 文件要根据实际的数据库要索引的表及其字段来配置,我这里先给出一个示例配置。

1
2
3
4
5
6
7
8
9
10
11
12
<dataConfig>
<dataSource type="JdbcDataSource" driver="com.mysql.jdbc.Driver" url="jdbc:mysql://xx.xx.xx.xx/database" user="user" password="password" batchSize="-1"/>
<document>
<entity name="project" query="select PROJECT_ID, name, leader, member, unit from project">
<field column="PROJECT_ID" name="id" />
<field column="name" name="name" />
<field column="leader" name="leader" />
<field column="member" name="member" />
<field column="unit" name="unit" />
</entity>
</document>
</dataConfig>

里面的数据库地址以及用户名密码改成你们自己的,另外提一下 batchSize 这个值,这里设为 -1 表示无限制,不这么设置的话,如果索引的数据太大,可能会报错。

假如我要索引的数据是一张名为 project 的表,那我就在这里配置了一个 entity 也名为 project,后面跟上 SQL 查询语句。下面的 field 表示了数据库表中的字段与 Solr 中字段的对应情况。左边表示数据表中的字段,后边是 Solr 中的字段。
那么 Solr 中的字段在哪呢?我们就要去 managed-schema 中配置了。

用 vim 打开 managed-schema 文件,在里面添加如下 field,field后边的属性可以更改,暂时我们就这样写。这些 field 就是我们之前的 data-config.xml 中写的 Solr 中的字段。实际上应该是这边先定义了的 filed,那边才能用。当然我们现在无所谓。

注意我们不用添加 id 这个 field,因为默认就有,而且这个 id 比较特殊,这相当于 Solr 的主键,用来判别唯一性,当然这个也是可以改的,默认就是 id。相信一般我们数据库中也是用 id 字段作为主键的,所以一般不用改。

修改完配置之后,保存。重启 Solr。

1
$ bin/solr restart

打开 Solr 管理界面,再次进入 test 的 Dataimport 菜单,如果不出意外地话,这次我们就能看到索引数据的界面了。

点开 Configuration 我们还能看到之前配置的配置文件,和图中红色圈出来的那样,点击中间的 Entity,如果可以看到我们配置的 project,说明一切正常,现在我们就可以点击 Execute 来进行数据索引了。

出现上图,表示数据索引成功。

接下来我们尝试搜索一下,搜索“数据挖掘”:

终于可以搜出东西来啦!

这样就好了吗?当然不是,这个搜索其实还是有很大的问题的。如果还记得的话,我们之前为每个 field 都配了一个 type 属性,当时属性统一设置为 string。 string 的意思就是把所有的查询词都当成一个完整的字符串,不分词,我们可以去 Analysis 里面测试一下

它表明索引和查询的时候,都把“数据挖掘人工智能”这个词当成一个完整的词。但实际上这显然不止一个词,理应被拆开来。

Solr 是可以指定字段查询的,比如我们指定搜索 name 字段中包含“数据挖掘”的文档:

What?什么都没搜出来?这是正常的!
虽然在之前的搜索结果中,我们看到了有些文档的 name 字段中确实有“数据挖掘”这个词,但是现在直接指定却搜不出来。原因就在于我们之前说的问题了,我们索引的时候把 name 字段的 type 配置成了 string,这就导致了 Solr 把整一段 name 当成了一整个词,存入到了倒排索引中。由于并没有一个 name 字段的值仅仅是“数据挖掘”这四个字,所以导致刚才我们去 name 里面搜“数据挖掘”什么也没搜出来。

那么问题来了,之前不指定字段,又为什么能搜出来呢?
让我们回到 managed-schema

重点关注我红色圈出来的部分,不指定字段,默认搜索的是 _text_字段。我们的配置文件中,* 字段的数据都会被拷贝到目标字段_text_ 中,* 就是任意字段的意思。copyField 的意思就是将当前字段的值拷贝到目标字段上去,目标字段的 multiValued值必须设置为 true,这样可以使得只要搜索目标字段就可以同时搜索其他拷贝到它上面的字段。这里的意思是所有的字段都被拷贝到了 _text_ ,所以我们不指定字段的时候,是搜索的 _text_,而 _text_ 的 type 是 text_general,text_general 会把中文一个个拆开来,变成一个个字,然后在所有字段里面搜索,所以我们能搜索到结果,这也解释了为什么我们之前搜索“数据挖掘”,出来的第一个结果中并没有完整包含“数据挖掘”这个词,而是“数据流挖掘”。

要验证不指定字段的时候,默认搜索的是 _text_,也很简单,在配置文件 managed-schema 中将我刚刚红圈圈出来的两行注释掉,然后重启 Solr,再次搜索“数据挖掘”:

会报错,然后提示 _text_ 字段不存在,这就证明了我之前所说的。

所以更好的做法是自己配置一个中文分词器,因为 Solr 本身是不能对中文分词的,并且创建一个对应分词器的 fieldType 然后将要分词的字段的 type 改为该 filedType。

配置分词器

我使用的中文分词器是 HanLP,当然其他还有很多。
Solr 配置 Hanlp 中文分词的步骤在 HanLP 作者的 GitHub 上参考。

那里写的比较简略,还有很多步骤其实没写出来,我这里具体的讲一下。

首先下载 hanlp-portable.jarhanlp-lucene-plugin.jar 两个 jar 包,上面的 GitHub 上给出了编译好的 jar 包的下载地址,同样放在 ${webapp}/WEB-INF/lib 下,即我们之前放 JDBC 驱动的目录。

光有这两个插件其实是不够的,上面提供的分词器是Portable版的,比较简陋,所以还需要 HanLP 的核心数据集,可以去https://github.com/hankcs/HanLP/releases 下载。以及配置文件hanlp.properties

${webapp}/WEB-INF下新建一个目录 classes,然后将刚才下载的配置文件hanlp.properties放进去,再在 classes 目录下新建一个文件夹 hanlp,然后将下载的数据集解压出来的 data 文件夹放到 hanlp 目录下面。

最后的目录结构像这样

1
2
3
4
5
6
7
8
9
10
WEB-INF

├─lib
├─...
└─classes

├─hanlp.properties
└─hanlp

└─data

修改配置文件 hanlp.properties,默认如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#本配置文件中的路径的根目录,根目录+其他路径=绝对路径
#Windows用户请注意,路径分隔符统一使用/
root=D:/JavaProjects/HanLP/
#核心词典路径
CoreDictionaryPath=data/dictionary/CoreNatureDictionary.txt
#2元语法词典路径
BiGramDictionaryPath=data/dictionary/CoreNatureDictionary.ngram.txt
#停用词词典路径
CoreStopWordDictionaryPath=data/dictionary/stopwords.txt
#同义词词典路径
CoreSynonymDictionaryDictionaryPath=data/dictionary/synonym/CoreSynonym.txt
#人名词典路径
PersonDictionaryPath=data/dictionary/person/nr.txt
#人名词典转移矩阵路径
PersonDictionaryTrPath=data/dictionary/person/nr.tr.txt
#繁简词典路径
TraditionalChineseDictionaryPath=data/dictionary/tc/TraditionalChinese.txt
#自定义词典路径,用;隔开多个自定义词典,空格开头表示在同一个目录,使用“文件名 词性”形式则表示这个词典的词性默认是该词性。优先级递减。
#另外data/dictionary/custom/CustomDictionary.txt是个高质量的词库,请不要删除
CustomDictionaryPath=data/dictionary/custom/CustomDictionary.txt; 现代汉语补充词库.txt; 全国地名大全.txt ns; 人名词典.txt; 机构名词典.txt; 上海地名.txt ns;data/dictionary/person/nrf.txt nrf
#CRF分词模型路径
CRFSegmentModelPath=data/model/segment/CRFSegmentModel.txt
#HMM分词模型
HMMSegmentModelPath=data/model/segment/HMMSegmentModel.bin
#分词结果是否展示词性
ShowTermNature=true

不添加自定义词典等改动的话,只要修改最上面的 root 即可,比如,我要改成 root=/Users/Mercy/Desktop/solr-5.5.2/server/solr-webapp/webapp/WEB-INF/classes/hanlp/

最后去配置文件 managed-schema 中进行配置。
增加一个字段 text_hanlp:

1
2
3
4
5
6
7
8
<fieldType name="text_hanlp" class="solr.TextField">
<analyzer type="index">
<tokenizer class="com.hankcs.lucene.HanLPTokenizerFactory" enableIndexMode="true"/>
</analyzer>
<analyzer type="query">
<tokenizer class="com.hankcs.lucene.HanLPTokenizerFactory" enableIndexMode="false"/>
</analyzer>
</fieldType>

并且把你认为需要进行分词的 field 的 type 改成 text_hanlp。比如我这里认为 name 字段和 member 字段需要分词,就把它们的 type 改成 text_hanlp。

重启 Solr,测试一下 HanLP 分词器是否可用。

再次用 Analysis 来测试,FieldType 选 textHanlp。

把“数据挖掘人工智能”给分开来了,说明 HanLP 分词器配置一切正常。

搜索字段配置

之前我们说了,不指定搜索字段,默认搜索 _text_ 字段,而且 _text_ 字段默认包含了所有的字段的数据。有时候我们不需要从所有的字段里面去搜索,比如说,如果我有一个年份的字段,我并不想搜索该字段,只是用来显示,所以我们可以自定一个字段,然后将需要搜索的字段 copy 到它上面去。

最关键的一步就是修改默认搜索字段,指定我们刚才创建的 for_select 为默认搜索字段。这个要在 solrconfig.xml 中配置。

找到名为 /select 的 requestHandler,修改 df 的值为 for_select,这个默认被注释掉了。

一切准备就绪了,我们再次重启 Solr。

由于分词器修改过了,而且添加了新的字段,所以需要重新索引一遍数据。

索引好数据之后,再次搜索,这次效果就好很多啦!

同样我们指定字段搜索也是可以搜出来的

这里结果数量和上面一样是因为我们这么点数据,数据挖掘词这个词都在 name 字段里面,其他字段没有,自然指定和不指定字段搜出来的数量都一样了。但是排序结果并不一样。