在本博客中,本来想使用 bing 搜索或者 Google 搜索来实现站内搜索的功能,尽管我已经设置了在发布和更新文章的时候通知不同的搜索引擎,但是它们什么时候更新索引却不能受到我的控制,所以,我最后选择使用 Elasticsearch 来实现站内搜索的功能。
这篇文章很多地方的实现参考了 使用 Elasticsearch 实现博客站内搜索 | QuQu QuQu 老师的这篇文章,在此表示感谢。
安装 Elasticsearch
下载和安装 Elasticsearch
下载和安装 Elasticsearch 的步骤很简单,就是安装包下载下来解压就行,具体的步骤可以查看官方文档 Installation Elasticsearch
然后运行 <es>/bin/elasticsearch
脚本,就可以启动 Elasticsearch 了。
使用 curl 来验证 Elasticsearch 是否运行成功:
1 | curl -XGET http://127.0.0.1:9200/\?pretty |
如果返回值和上面类似,就是运行成功了。
在配置的时候遇到了以下几个错误:
JVM 内存不足
Elasticsearch 默认情况下最小需要 2G 内存才能运行,但是我的虚拟机最多只有 2G 内存,所以,需要设置以下 Elasticsearch 启动的内存大小:
1 | <es-root>/config/jvm.options |
将对应最小启动内存设置成 1G,就可以启动了
Elasticsearch 不能 root 运行
Elasticsearch 由于安全的原因,在 >= 5.0 版本之后,不允许使用 root 用户来运行,现有的解决方法有:
修改源代码并且编译来支持使用 root 用户:How to run Elasticsearch 5.2.1 as root user in linux machine
或者创建一个新的用户来执行 Elasticsearch,我这里选择的是第二种方法。CENTOS安装ElasticSearch
配置启动和关闭脚本
后面为了使用 root 用户来运行 Elasticsearch,我最后使用了 docker 来进行 Elasticsearch。
具体安装 docker Elasticsearch 的步骤可以参考 Install Elasticsearch
在官网中下载的 Elasticsearch 默认开启了 X-Pack 权限验证,我在运行 docker 的时候取消了该权限验证,具体可以参考 Security Settings:
1 | docker run -p 9200:9200 -e "http.host=0.0.0.0" -e "transport.host=127.0.0.1" -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" -e "xpack.security.enabled=false" -e "plugins=/usr/share/elasticsearch/plugin" docker.elastic.co/elasticsearch/elasticsearch:5.4.0 |
安装 IK Analysis
Elasticsearch 自带的分词器会粗暴地把每个汉字直接分开,没有根据词库来分词。为了处理中文搜索,还需要安装中文分词插件。所以需要使用 IK Analysis for Elasticsearch 来支持 Elasticsearch 的中文分词能力。
具体的下载以及安装过程可以参考 IK Analysis for Elasticsearch 文档中的内容。
首先查看 Elasticsearch 的版本号,然后在 elasticsearch-analysis-ik | release 中找到对应的版本下载。
查看版本号的方法,就和上面 使用 curl 来验证 Elasticsearch 是否运行成功 的方法相同,其中,返回值里面有一个
1 | "number" : "5.4.0" |
表示当前 Elasticsearch 的版本,就可以下载对应的 elasticsearch-analysis-ik 版本。
根据要求,解压 target/releases/elasticsearch-analysis-ik-{version}.zip
到 your-es-root/plugins/ik
然后重启 Elasticsearch。
在启动的时候,看到下面的信息,就说明 analysis-ik 安装成功
1 | [o.e.p.PluginsService ] [Yy0Pe-N] loaded plugin [analysis-ik] |
在 elasticsearch-analysis-ik 中有两种不同的分词方法,根据官方分档的解释,分为:
ik_max_word
: 会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合;ik_smart
: 会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”。
我在使用的时候使用的是最细粒度的拆分
创建 Mapping 和 Query DSL
在使用 Elastic 存储需要被检索的数据之前,我们要先为其创建一个 Index,然后在该 Index 下定义 Document 中数据的类型,所使用的 analyzer、是否索引等属性。创建 Mapping 的内容如下:
1 | curl -XDELETE 'http://localhost:9200/articles' |
其中,相关配置的含义为:
1 | "term_vector": "with_positions_offsets" // 保存值和token位置信息和Token的offset |
设置 title
和 content
,使用 ik_max_word
作为分词器。然后将 create_data
和 categories
设置不分词。
然后在保存新的文章和更新文章的时候,可以将数据写入到 Elasticsearch 以供搜索:
1 | res.es.index({ |
在我们的博客中,需要实现对于 content
、title
、link
、categories
的复杂搜索,并且为不同检索区域的内容涉及不同的权重,具体的权重以及复杂检索如下,参考的是 QuQu 老师的检索策略:
1 | { |
因为我的原文没有保存只有文字格式的,而是直接编辑 Markdown 来呈现文章的,所以,我在检索的时候,直接检索的是原文的 Markdown 文件。
当然,检索得到的 Markdown 格式不适合直接在浏览器上进行显示,所以需要将其中的 Markdown 语法相关的标记都删除,这里我使用的是 remove-markdown 这个库来实现将标记溢出的功能。当然,这个库中有一些处理的地方不合我的要求,比如:
- Markdown 原文中,如果搜索出来的结果包含
<!--more-->
标记,则通过该库的编辑后,会成为!--more--
格式 - 通过 Elasticsearch 中,
higtlight
返回的字段有<b>
和</b>
标签的包裹,但是通过该库的编辑后,会变成b
和/b
所以针对以上两点,我也使用了正则表达式进行了修正:
1 | (value) => { |