简介
Elasticsearch是一个高度可扩展的开源的分布式Restful全文搜索和分析引擎。它允许用户快速的(近实时的)存储、搜索和分析海量数据。它通常用作底层引擎技术,为具有复杂搜索功能和要求的应用程序提供支持。
它可以被下面这样准确地形容:
- 一个分布式的实时文档存储,每个字段可以被索引与搜索;
- 一个分布式实时分析搜索引擎;
- 能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据。
以下是ES可用于的一些场景:
- 电商网站提供搜索功能:可使用ES来存储产品的目录和库存,并为它们提供搜索和自动填充建议。
- 收集日志和交易数据,并进行分析:可使用Logstash来收集、聚合和解析数据, 然后让Logstash将此数据提供给ES。然后可在ES中搜索和聚合开发者感兴趣的信息。
- 需要快速调查、分析、可视化查询大量数据的特定问题:可以使用ES存储数据,然后使用Kibana构建自定义仪表板,来可视化展示数据。还可以使用ES的聚合功能针对这些数据进行复杂的商业分析。
安装
1、安装 Elasticsearch
安装 Elasticsearch 之前,你需要先安装一个较新版本的 Java,最好的选择是,你可以从 www.java.com 获得官方提供的最新版本的Java。
在Mac上可以使用brew快速安装Elasticsearch
$ brew install elasticsearch
==> elasticsearch
Data: /usr/local/var/lib/elasticsearch/
Logs: /usr/local/var/log/elasticsearch/elasticsearch_liufan.log
Plugins: /usr/local/var/elasticsearch/plugins/
Config: /usr/local/etc/elasticsearch/
To have launchd start elasticsearch now and restart at login:
brew services start elasticsearch
Or, if you don't want/need a background service you can just run:
elasticsearch
PS: brew 是MacOS上的包管理工具,可以简化 macOS 和 Linux 操作系统上软件的安装。官网给出的安装方法,并不适用国内的Mac用户,因为网络资源的原因,电脑下载是龟速,所以我们考虑替换镜像源,将下载资源改为国内镜像资源即可(推荐),这里自行百度即可。
安装完成之后通过elasticsearch --version查看版本信息
$ elasticsearch
Version: 7.10.2, Build: oss/tar/unknown/2021-01-16T01:02:01.721195Z, JVM: 16.0.1
运行elasticsearch
$ elasticsearch
此时,Elasticsearch运行在本地的9200端口,在浏览器中输入网址“http://localhost:9200/”
2、安装Kibana
$ brew install kibana
==> Summary
🍺 /usr/local/Cellar/kibana/7.10.2: 29,226 files, 313MB
==> Caveats
==> kibana
Config: /usr/local/etc/kibana/
If you wish to preserve your plugins upon upgrade, make a copy of
/usr/local/opt/kibana/plugins before upgrading, and copy it into the
new keg location after upgrading.
To have launchd start kibana now and restart at login:
brew services start kibana
Or, if you don't want/need a background service you can just run:
kibana
启动kibana
$ kibana
此时,Kibana运行在本地的5601端口,在浏览器中输入网址“http://localhost:5601”,
3、安装扩展工具(Elasticsearch Head)
在谷歌商店中,搜索Elasticsearch Head并添加到扩展中
Elasticsearch Head
下面,让我们来了解Elasticsearch的一些基本概念,这有助于我们更好地理解和使用Elasticsearch。
基础概念
Elasticsearch是面向文档型数据库,一条数据在这里就是一个文档,用JSON作为文档序列化的格式,比如下面这条用户数据
{
"name" : "John",
"sex" : "Male",
"age" : 25,
"birthDate": "1990/05/01",
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
用Mysql这样的数据库存储就会容易想到建立一张User表,有xxxxx的字段等,在Elasticsearch里这就是一个文档,当然这个文档会属于一个User的类型,各种各样的类型存在于一个索引当中。这里有一份简易的将Elasticsearch和关系型数据术语对照表:
关系数据库 ⇒ 数据库 ⇒ 表 ⇒ 行 ⇒ 列(Columns)
Elasticsearch ⇒ 索引(Index) ⇒ 类型(type) ⇒ 文档(Docments) ⇒ 字段(Fields)
一个 Elasticsearch 集群可以包含多个索引(数据库),也就是说其中包含了很多类型(表)。这些类型中包含了很多的文档(行),然后每个文档中又包含了很多的字段(列)。Elasticsearch的交互,可以使用Java API,也可以直接使用HTTP的Restful API方式,比如我们打算插入一条记录,可以简单发送一个HTTP的请求:
PUT /megacorp/employee/1
{
"name" : "John",
"sex" : "Male",
"age" : 25,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
更新,查询也是类似这样的操作,具体操作手册可以参见权威指南
索引
Elasticsearch最关键的就是提供强大的索引能力了,Elasticsearch索引的精髓:
一切设计都是为了提高搜索的性能
为了提高搜索的性能,难免会牺牲某些其他方面,比如插入/更新,否则其他数据库不用混了。前面看到往Elasticsearch里插入一条记录,其实就是直接PUT一个json的对象,这个对象有多个fields,比如上面例子中的name, sex, age, about, interests,那么在插入这些数据到Elasticsearch的同时,Elasticsearch还默默的为这些字段建立索引--倒排索引,因为Elasticsearch最核心功能是搜索。
Elasticsearch是如何做到快速索引的
什么是倒排索引?
倒排索引(Inverted Index)也叫反向索引,有反向索引必有正向索引。通俗地来讲,正向索引是通过key找value,反向索引则是通过value找key
倒排索引
假设有这么几条数据,id是Elasticsearch自建的文档id
id | name | gender | age |
---|---|---|---|
1 | 张三 | 男 | 18 |
2 | 李四 | 男 | 20 |
3 | 王五 | 女 | 20 |
Term(单词):一段文本经过分析器分析以后就会输出一串单词,这一个一个的就叫做Term(直译为:单词)
Term Dictionary(单词字典):顾名思义,它里面维护的是Term,可以理解为Term的集合
Term Index(单词索引):为了更快的找到某个单词,我们为单词建立索引
Posting List(倒排列表):倒排列表记录了出现过某个单词的所有文档的文档列表及单词在该文档中出现的位置信息,每条记录称为一个倒排项(Posting)。根据倒排列表,即可获知哪些文档包含某个单词
上面的例子,Elasticsearch建立的索引大致如下:
name字段:
Term | Posting List |
---|---|
张三 | 1 |
李四 | 2 |
王五 | 3 |
gender字段:
Term | Posting List |
---|---|
男 | [1,2] |
女 | 3 |
age字段:
Term | Posting List |
---|---|
18 | 1 |
20 | [2,3] |
Elasticsearch分别为每个字段都建立了一个倒排索引。比如,在上面“张三”、“男”、20 这些都是Term,而[1,2]就是Posting List。Posting list就是一个数组,存储了所有符合某个Term的文档ID。
只要知道文档ID,就能快速找到文档。可是,要怎样通过我们给定的关键词快速找到这个Term呢?
当然是建索引了,为Terms建立索引,最好的就是B-Tree索引(PS:MySQL就是B树索引最好的例子)。
首先,让我们来回忆一下MyISAM存储引擎中的索引是什么样的:
MyISAM存储引擎
我们查找Term的过程跟在MyISAM中记录ID的过程大致是一样的
MyISAM中,索引和数据是分开,通过索引可以找到记录的地址,进而可以找到这条记录
在倒排索引中,通过Term索引可以找到Term在Term Dictionary中的位置,进而找到Posting List,有了倒排列表就可以根据ID找到文档了
PS:可以这样理解,类比MyISAM的话,Term Index相当于索引文件,Term Dictionary相当于数据文件
PS:其实,前面我们分了三步,我们可以把Term Index和Term Dictionary看成一步,就是找Term。因此,可以这样理解倒排索引:通过单词找到对应的倒排列表,根据倒排列表中的倒排项进而可以找到文档记录
为了更进一步理解,下面从网上摘了两张图来具现化这一过程:
倒排索引
文档管理(CRUD)
增加:
POST /db/user/1
{
"username": "ceshi1",
"password": "123456",
"age": "22"
}
POST /db/user/2
{
"username": "ceshi2",
"password": "123456",
"age": "22"
}
这里就是往索引为 db
类型为 user
的数据库中插入一条 id
为 1 的一条数据,这条数据其实就相当于一个拥有 username/password/age
三个属性的一个实体,就是 JSON 数据
执行命令后,Elasticsearch 返回如下数据:
# POST /db/user/1
{
"_index": "db",
"_type": "user",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 2,
"_primary_term": 1
}
# POST /db/user/2
{
"_index": "db",
"_type": "user",
"_id": "2",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 1,
"_primary_term": 1
}
version
是版本号的意思,当我们执行操作会自动加 1
删除:
DELETE /db/user/1
执行命令后,Elasticsearch 返回如下数据:
{
"_index": "db",
"_type": "user",
"_id": "1",
"_version": 2,
"result": "deleted",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 1,
"_primary_term": 1
}
这里就可以看到 version
变成了 2
修改:
PUT /db/user/2
{
"username": "ceshi3",
"password": "123456",
"age": "22"
}
执行命令后,Elasticsearch 返回如下数据:
{
"_index": "db",
"_type": "user",
"_id": "2",
"_version": 2,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 2,
"_primary_term": 1
}
查询:
GET /db/user/2
执行命令后,Elasticsearch 返回如下数据:
{
"_index": "db",
"_type": "user",
"_id": "2",
"_version": 2,
"found": true,
"_source": {
"username": "ceshi3",
"password": "123456",
"age": "22"
}
}
搜索
上面我们已经演示了基本的文档 CRUD 功能,然而 Elasticsearch 的核心功能是搜索,所以在学习之前,为更好的演示这个功能,我们先往 Elasticsearch 中插入一些数据:
PUT /movies/movie/1
{
"title": "The Godfather",
"director": "Francis Ford Coppola",
"year": 1972,
"genres": [
"Crime",
"Drama"
]
}
PUT /movies/movie/2
{
"title": "Lawrence of Arabia",
"director": "David Lean",
"year": 1962,
"genres": [
"Adventure",
"Biography",
"Drama"
]
}
PUT /movies/movie/3
{
"title": "To Kill a Mockingbird",
"director": "Robert Mulligan",
"year": 1962,
"genres": [
"Crime",
"Drama",
"Mystery"
]
}
PUT /movies/movie/4
{
"title": "Apocalypse Now",
"director": "Francis Ford Coppola",
"year": 1979,
"genres": [
"Drama",
"War"
]
}
PUT /movies/movie/5
{
"title": "Kill Bill: Vol. 1",
"director": "Quentin Tarantino",
"year": 2003,
"genres": [
"Action",
"Crime",
"Thriller"
]
}
PUT /movies/movie/6
{
"title": "The Assassination of Jesse James by the Coward Robert Ford",
"director": "Andrew Dominik",
"year": 2007,
"genres": [
"Biography",
"Crime",
"Drama"
]
}
现在已经把一些电影信息放入了索引,可以通过搜索看看是否可找到它们。 为了使用 ElasticSearch 进行搜索,我们使用 _search
端点,可选择使用索引和类型。也就是说,按照以下模式向URL发出请求:<index>/<type>/_search
。其中,index
和 type
都是可选的。
换句话说,为了搜索电影,可以对以下任一URL进行POST请求:
- http://localhost:9200/_search - 搜索所有索引和所有类型。
- http://localhost:9200/movies/_search - 在电影索引中搜索所有类型
- http://localhost:9200/movies/movie/_search - 在电影索引中显式搜索电影类型的文档。
基本自由文本搜索:
现在尝试在两部电影的标题中搜索有“kill”这个词的电影信息:
GET /_search
{
"query": {
"query_string": {
"query": "kill"
}
}
}
执行结果:
正如预期的,得到两个命中结果,每个电影的标题中都带有“kill”单词。再看看另一种情况,在特定字段中搜索。
指定搜索的字段:
查询字符串查询有一些可以指定设置,设置称为“fields”,可用于指定要搜索的字段列表。如果不使用“fields”字段,ElasticSearch查询将默认自动生成的名为 “_all” 的特殊字段,来基于所有文档中的各个字段匹配搜索。
GET /_search
{
"query": {
"query_string": {
"query": "ford",
"fields": [
"title"
]
}
}
}
执行上面查询它,看看会有什么结果(应该只匹配到 1 行数据):
正如预期的得到一个命中,电影的标题中的单词“ford”。现在,从查询中移除fields属性,应该能匹配到 3 行数据:
过滤:
GET /_search
{
"query": {
"filtered": {
"query": {
"query_string": {
"query": "drama"
}
},
"filter": {
"term": {
"year": 1962
}
}
}
}
}
过滤的查询是具有两个属性(query
和filter
)的查询。执行时,它使用过滤器过滤查询的结果。
当执行上面请求,因为在索引中有五部电影在 _all
字段(从类别字段)中包含单词 "drama"
,所以得到5 个命中,而添加过滤器要求 "year"
字段等于 1962
,只得到两个命中,这个两个命中的数据的 year
字段的值都是等于 1962
。
总结和思考
Elasticsearch的索引思路:
将磁盘里的东西尽量搬进内存,减少磁盘随机读取次数(同时也利用磁盘顺序读特性),结合各种奇技淫巧的压缩算法,用及其苛刻的态度使用内存
对于使用Elasticsearch进行索引时需要注意:
- 不需要索引的字段,一定要明确定义出来,因为默认是自动建索引的
- 同样的道理,对于String类型的字段,不需要analysis的也需要明确定义出来,因为默认也是会analysis的
- 选择有规律的ID很重要,随机性太大的ID(比如java的UUID)不利于查询
参考资料
https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html
https://blog.csdn.net/shenchaohao12321/article/details/101178091
https://www.jianshu.com/p/1df1529aaca7
https://elasticsearch.cn/
网友评论