Elasticsearch全文搜索的使用和原理
之前使用了下MongoDB的中文全文搜索,结果惨不忍睹。很多文中明明存在的词就是搜索不到,查文档才发现MongoDB免费版并没有提供针对中文的分词器,所以全文搜索的结果就可想而知了。查了一圈觉得免费的中文全文搜索解决方案里,最好的应该是elasticsearch了吧。所以最近学习了下,并把它用到了项目里,效果还不错。
Elasticsearch是一个基于Apache Lucene(TM)的开源搜索引擎。无论在开源还是专有领域,Lucene可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库。
Elasticsearch可以简单的理解为是为Lucene套上了一层RESTful的接口,和一层分布式的扩展的包装层,使它不受语言限制地通过HTTP请求操作,也不受硬件性能限制地随意横向扩展。
安装与调错
安装教程其实到处都是,但是我在几台机器上安装都没能一次就启动起来,如果不是在配置不错的服务器上安装,多半也会踩些坑,这里记录一下。用的是Elasticsearch-5.5.1版本。
安装
$ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.5.1.zip
$ unzip elasticsearch-5.5.1.zip
$ cd elasticsearch-5.5.1/
不能用root用户启动:
sudo chown -R noroot:noroot elasticsearch-5.5.1/ # 这里的noroot为一个非root用户
如果没有java8环境:
sudo apt-get install default-jdk #安装java8
sudo apt-get install oracle-java8-installer #或者更新到java8
安装中文分词器ik
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v5.5.1/elasticsearch-analysis-ik-5.5.1.zip
如果下载缓慢,可以先用其他vps下载,再本地安装
sudo ./bin/elasticsearch-plugin install file:///tmp/elasticsearch-analysis-i5.5.1.zip
启动:
$ ./bin/elasticsearch
如果一起正常,访问localhost:9200就可以看到Elasticsearch的基本信息了:
curl localhost:9200
{
"name" : "admgvq_",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "JfseDbLfTZe8U0nJDUtmxA",
"version" : {
"number" : "5.5.1",
"build_hash" : "19c13d0",
"build_date" : "2017-07-18T20:44:24.823Z",
"build_snapshot" : false,
"lucene_version" : "6.6.0"
},
"tagline" : "You Know, for Search"
}
调错
遇到问题时google了一堆别人博客里的方法,照着弄了一遍反而把自己弄晕了,大多数文章都只给了一个不知道哪里看来的解决办法,但并没有说是啥原因。后来发现英文文档其实说的挺清楚的,要是开始耐心看看反而会节约不少时间。相关的配置文档在这里。我遇到的问题是用户可用的文件描述符不够和虚拟内存不够。
- 用户可用文件描述符不够:
max file descriptors [4096] for elasticsearch process likely too low, increase to at least [65536]
相关文档的解决办法是:
sudo su
ulimit -n 65536
su elasticsearch # Elasticsearch 可以改为其他非root用户
但是ulimit -n
的修改只在当前shell的session有效,登出用户就失效了。更多关于ulimit
`和文件描述符的信息可以看看这篇文章。
还可以编辑文件 /etc/security/limits.conf:
sudo nano /etc/security/limits.conf
添加
* hard nofile 65536
* soft nofile 65536
不过这只在下次登录时有效,因为init.d
会忽略上面的修改。所以终极办法是编辑/etc/pam.d/su
:
session required pam_limits.so
相当于是每次登陆时都去读取limits.conf
中的配置。
然而,当我想把es的启动写到supervisor里,想让它随supervisor开机启动的时候,问题又回来了。因为supervisor开机启动并没有用户登录的过程,所以可用文件描述符并没有被修改到。暂时没有找到如何永久修改可用文件描述符,让es开机启动的方法,如果你刚好看到这篇文章,并找到了方法,恳请留言告诉我 :)
- 虚拟内存不够
# 暂时
切换root用户:
sysctl -w vm.max_map_count=262144
# 永久
nano /etc/sysctl.conf
添加
vm.max_map_count=655360
重启后验证:
sysctl vm.max_map_count
搜索原理
Elasticsearch中的存储结构和关系型数据库有些区别:
Relational DB -> Databases -> Tables -> Rows -> Columns
Elasticsearch -> Indices -> Types -> Documents -> Fields
Elasticsearch集群可以包含多个索引(indices)(数据库),每一个索引可以包含多个类型(types)(表),每一个类型包含多个文档(documents)(行),然后每个文档包含多个字段(Fields)(列)。
倒排索引
Elasticsearch会为每一个字段创建一个倒排索引(Inverted index),所谓倒排索引,就是将文档->词
的对应关系变为词->文档
的对应关系,比如:
Docs | brown | fox | quick | the |
------------------------------------
Doc 1 | X | X | X | X |
Doc 2 | | X | X | |
Doc 3 | X | X | | X |
... | .. | .. | .. | .. |
倒排索引就是将这个对应关系矩阵作转置:
Term | Doc 1 | Doc 2 | Doc 3 | ...
------------------------------------
brown | X | | X | ...
fox | X | X | X | ...
quick | X | X | | ...
the | X | | X | ...
所以倒排索引(Inverted index)叫反向索引或者转置索引可能还更容易理解些。有了词->文档
的对应关系,当我们拿到搜索词时就可以很容易的找到包含他的文档了。搜索词可能也不止一个,这时候就把搜索词先分词,再逐个匹配,根据匹配程度打分,最后依据打分值返回搜索结果。
分析器
看到这里你就会发现,创建词->文档
的对应关系是搜索的关键一步,一份文档中可能不是所有的内容都需要被搜索,比如标点、HTML标签、停用词等。而且每个词可能还有时态、单复数等形态的变化。针对中文还需要专门的分词器。这些工作都需要在分析器中完成。
一个分析器需要包含字符过滤器、分词器、标记过滤器三个部分:
- 字符过滤器:过滤HTML标签等字符。
- 分词器:根据标点或空格分割单词,当然中文需要运用其他的分词技术。
- 标记过滤器:过滤停用词,替换大小写、时态、单复数等。
映射
Elasticsearch会在为索引创建映射(mapping)的时候指定分析器,一个映射定义了字段类型,每个字段的数据类型,以及字段被Elasticsearch处理的方式。映射还用于设置关联到类型上的元数据。
-
type
规定了字段的数据类型,常用的数据类型:数据类型 type 字符串 string 整型 byte, short, integer, long 浮点型 float, double Bool boolean 时间 date -
index
表示数据以什么方式被索引:analyzed(分析此字段,默认)
,not_analyzed(不分析此字段)
,no(不能被搜索)
。 -
analyzer
指定了用什么分析器。 -
search_analyzer
表示搜索内容用什么分析器。
curl -X PUT 'localhost:9200/blog' -d '
{
"mappings": {
"post": {
"properties": {
"url": {
"type": "string",
"index": "not_analyzed"
},
"created_at": {
"type": "date"
},
"content": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word"
},
"title": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word"
}
}
}
}
}'
上面的例子中,url
不需要经过分析器分析,所以设置它的index
为not_analyzed
,而content
和title
字段需要全文搜索,并且是中文内容,所以analyzer
使用中文分析器ik_max_word
。
使用
Elasticsearch的操作包括请求体查询和结构化查询两种,都是通过HTTP请求进行操作。不同的是前者更像是调用api,把请求内容都放在url里。而后者是把请求内容放到body中,更像是mongo的查询方式。结构化查询的功能较前者要强大很多,而且其他语言封装的es库也大多使用这种查询方式。
使用结构化查询添加索引:
PUT /blog/post/
{
"title" : "...",
"content" : "...",
"url" : "http://...",
"created_at" : "2017-11-1"
}
调用python的Elasticsearch包:
from elasticsearch import Elasticsearch
es = Elasticsearch([{'host': '127.0.0.1', 'port': 9200}])
es.index(index='blog', doc_type='post', body={'title': title, 'content': content, 'url': url, 'created_at': created_at})
查询用得最多的是match
全文搜索,当然还有很多其他类型的查询,不过如果不是全文搜索直接在关系型数据库中就完成了。
GET /_search
{
"query": {"match": {'content': '...'}},
"sort": [
{"_score": {"order": "desc"}},
{"created_at": {"order": "desc"}}
]
}
python:
res = es.search(index="blog",
body={"query": {"match": {'content': keyword.encode('utf-8')}}, "sort": [
{"_score": {"order": "desc"}},
{"created_at": {"order": "desc"}}
]})
数据同步
由于Elasticsearch只负责全文搜索功能,数据主要还是存储在SQL或者NoSQL里的,这时就需要将数据库里的数据实时同步到es里去,最简单的方法是在向数据库插入数据时,也同时向es插入一条索引,前提是数据库不会对这些数据经常做修改。
当然更好的办法是直接同步数据库操作。这个操作基本是利用数据库的操作日志完成的。比如mongo的mongo-connector.
使用mongo-connector需要先开启mongo的复制集:
mongod --replSet myDevReplSet
然后初始化复制集:
rs.initiate()
最后启动mongo-connector:
mongo-connector -m 127.0.0.1:27017 -t 127.0.0.1:9200 -d elastic_doc_manager
这时mongo的所有操作就会同步到es。但这样会把整个数据库的操作同步到es,而且es中的映射都是用的默认设置。所以还需要按照配置文档写一份配置文件,决定需要同步的库和配置映射。
我这里用到的只是Elasticsearch的全文搜索功能,当然es最厉害的分布式实时搜索由于自己的数据量没达到那个量级,也就没有尝试了。
请问博客有rss吗?想订阅一下,以便查看更新~
@nearg1e https://jiayi.space/feed ^_^
博主文章质量很高,清晰简洁。帮帮的~