Elasticsearch 学习:入门篇
Elasticsearch 是一个分布式搜索引擎,底层基于 Lucene 实现。Elasticsearch 屏蔽了 Lucene 的底层细节,提供了分布式特性,同时对外提供了 Restful API。Elasticsearch 以其易用性迅速赢得了许多用户,被用在网站搜索、日志分析等诸多方面。由于 ES 强大的横向扩展能力,甚至很多人也会直接把 ES 当做 NoSQL 来用。
本文主要记录了 ES 的一些必要的基础知识,也是自己在学习和使用 ES 的一些总结。当然,要系统和深入学习还是要依靠官方文档:Elasticsearch Reference 和不断地实践。
本文会涉及以下内容:
- ES 的基本概念讲解
- 如何通过 ES 增删数据以及批量修改
- ES 基本的查询和搜索功能、高亮关键词搜索以及多索引查询功能
基本概念
在正式学习,有一些名词和概念需要简单的了解下。
- Document (文档)
- Index (索引)
- Type [已废弃]
Document (文档)
文档指的是用户提交给 ES 的一条数据。需要注意的是,这里的文档并非指的是一个纯字符串文本,在 ES 中文档指的是一条 JSON 数据。如果对 MongoDB 有了解的话,这里文档的含义和 MongoDB 中的基本类似。
JSON 数据中可以包含多个字段,这些字段可以类比为 MySQL 中每个表的字段。
例如:
{
"message": "this is my blog",
"author": "cyhone"
}
这样我们后期进行搜索和查询的时候,也可以分别针对 message
字段和 author
字段进行搜索。
Index (索引)
Index(索引) 可以理解为是文档的集合,同在一个索引中的文档共同建立倒排索引。
也有很多人会把索引类比于 MySQL 中 schema 的概念。但在 ES 中 Index 更加灵活,用起来也更加方便。
此外,提交给同一个索引中的文档,最好拥有相同的结构。这样对于 ES 来说,不管是存储还是查询,都更容易优化。
Type [已废弃]
Type 可以理解为是 Index 的子集,类似于 MySQL 中 schema 和 table 的关系。Type 原来存在的目的是为了在同一个 Index 存储异构数据。但其实 ES 中的索引用起来足够方便和灵活,对于异构数据,完全可以再建另外单独的 Index 存储。
所以在 Elasticsearch 的新版本中,已经逐步淡化和移除了 Type 的概念。在 7.0 版本中,对于每个 Index,ES 直接内置了一个 _doc
的 Type,且一个 Index 只能包含一个 Type。如果用户在添加数据时用到了其他 Type,则会报错。
所以不管是新旧版本,大家在使用 ES 的时候,也忘记 Type 这个存在就好,用 _doc
即可。
我们接下来看下如何在 ES 中存储和查询一个文档,也是常说的 CRUD
操作。
在 ES 中,用户的一切操作和行为都是围绕 REST 风格的 HTTP API 进行的。ES 中所有接口的语义都严格遵守 REST 规范。
新增 / 更新文档
要想搜索内容的前提肯定是先把内容交给 ES 进行存储和索引。
我们有两种方法向对应索引中新增文档:
通过 POST 新增文档
POST /es-test/_doc
{
"message": "this is my blog",
"author": "cyhone"
}
对于以上请求来说,我们通过 POST
把对应的数据存储在了索引 es-test
中。
这里需要注意的是,Index 并不需要提前建好。对于用户指定的 Index,如果不存在,ES 会自动建立对应的 Index。
通过 PUT 新增文档
PUT /es-test/_doc/1
{
"message": "this is my blog",
"author": "cyhone"
}
在上面例子里面,我们通过 PUT
在索引 es-test
中,新增了一条数据。与 POST 不一样的是,通过 PUT 新增数据需要手动指定该条数据的唯一 id。也就是上述的 /es-test/_doc/1
中的 1
。这个唯一 id 不必要是数字,任何合法字符串均可。
POST 和 PUT 的行为都非常符合 REST 风格:
- PUT 保证幂等性。因此在提交的时候需要指定一个唯一 id,对于同一个唯一 id 来说,无论 PUT 多少次,ES 只会修改这个 id 对应文档的内容,而不会新增文档。
- POST 不保证幂等性。因此每次的 POST 请求都会在系统新增一条文档。对于新增的文档,系统会自动生成一个唯一 ID。
这也意味着,我们可以用 PUT + 指定唯一 id 的方式,来修改和更新文档。
删除文档
我们可以使用 DELETE 来删除一个文档。例如:
DELETE /es-test/_doc/1
DELETE 也是幂等性操作,在使用的时候也需要指定唯一 ID。
查询 / 搜索文档
查询和搜索文档相对来说非常复杂,不过这也是很多人使用 ES 的原因。作为一个搜索引擎,自然需要提供足够强大的查询功能。
本文仅介绍几种常用的查询方法,其他复杂的查询方式和聚合、分析等操作,以后会单独写一篇文章总结。
简单查询
我们可以通过以下语法,提供关键词,搜索所有字段进行查询。
GET /es-test/_search?q=blog
或者我们也可以指定查询某个字段,如下:
GET /es-test/_search
{
"query":{
"match": {
"message": "elasticsearch"
}
}
}
以上例子中,我们指定查询 message 字段中包含有 elasticsearch 的文档。
分页查询
对于查询得到的结果,数目过多的情况下,es 默认会进行分页。分页主要有两个参数进行控制:
- size 显示应该返回的结果数量,默认是 10
- from 显示应该跳过的初始结果数量,默认是 0
我们可以通过直接在 url 中指定分页参数,如下:
GET /es-test/_search?size=5&from=10
也可以在请求体中指定分页参数,如下:
GET /es-test/_search
{
"query":{
"match": {
"message": "elasticsearch"
}
},
"size": 10,
"from": 5
}
关键词高亮显示
我们通常自己开发搜索引擎的时候,往往需要对搜索结果中的关键词高亮这种功能。如下:
ES 可以非常简单的实现关键词的高亮。我们可以构建如下请求体:
{
"query": {
"match": {
"message": "blog"
}
},
"highlight": {
"fields": {
"message": {}
}
}
}
其实就是增加一个 highlight 属性,里面指明了要高亮的字段。其返回的消息体如下:
{
"took" : 41,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.2876821,
"hits" : [
{
"_index" : "es-test",
"_type" : "_doc",
"_id" : "PNyBzHABTSSzPOmql8i9",
"_score" : 0.2876821,
"_source" : {
"message" : "this is my blog",
"author" : "cyhone"
},
"highlight" : {
"message" : [
"this is my <em>blog</em>"
]
}
}
]
}
}
在返回体中有一个 highlight 字段,里面对 message 字段进行高亮处理: 关键词使用了 <em></em>
标签包围了。
我们可以利用 css 修改对 <em>
标签的样式,以实现其关键词高亮效果。
多索引查询
在 ES 中可以非常方便地在多个索引中通过搜索文档。
例如你有两个索引: es-test-1
和 es-test-2
。
你可以这样直接在 URL 中指明两个索引:
GET /es-test-1,es-test-2/_search
或者如下的模糊搜索的方式
GET /es-test-*/_search
如果有必要的话,甚至可以这样:
GET /a*, b*/_search
以上方式都可以在多个索引中同时搜索文档,把多个索引看做一个使用。
其实这也意味着,我们在存储的时候,没必要把所有的文档都存在一个 Index 中。
很常见的一个操作是,我们可以将文档按天分索引存储。例如: es-test-2020-03-11
,es-test-2020-03-12
等,
在查询的时候,指定 es-test-*
查询即可,这样对外看来,文档似乎还是存储在一起,同时也减轻了 Index 的存储压力。(一个 ES 分片最多能存储 Integer.MAX_VALUE - 128
个文档)
批量操作
上文讲到的通过 POST、PUT 来新增或修改数据,都是基于单条数据的。但是我们知道网络 IO 是网络操作中最耗时的部分,对于大数据量写入的场景下,我们通常希望写入方可以提供批量修改的接口,以避免频繁的网络交互,更大限度地提升写入性能。
ES 当然也提供了批量修改的接口。在批量接口中,我们一次可以进行多个新增、更新和删除等修改行为的动作。例如:
POST _bulk
{"index" : { "_index" : "es-test"} }
{"message" : "this is my blog"}
{"create" : { "_index" : "es-test", "_id" : "3"} }
{"message" : "this is my blog"}
{"delete" : { "_index" : "es-test", "_id" : "2"} }
{"update" : {"_id" : "1", "_index" : "test"} }
{"message" : "this is my blog"}
以上这个批量操作有些复杂。里面包含了 4 种操作 index
、create
、delete
和 update
。
其中 index
、create
和 update
都包含两行,一行是具体的操作,一行是文档内容。
index
和 create
的区别在于,create 会携带一个唯一 id,如果该 id 存在,则插入失败。
动态映射
有一点值得注意的是,本文中的例子都是用了 message
字段来进行 match 搜索,如果换成字段名换成了其他,例如 content
可能就不行。
这是因为在我这边的 ES 有一个默认的动态映射,将长度低于 2048 的字符串认定为 keyword
类型。但是字段名是 message
的话,则为 text
类型。keyword
类型不进行分词处理,不适合进行关键词搜索处理。
这样就需要我们不得不关注 ES 的动态映射。此部分内容以后会再单独分一篇文章讲解。