ElasticSearch(四)ElasticSearch文档操作

ElasticSearch(四)ElasticSearch文档操作

作者:lomtom

个人网站:lomtom.cn

个人公众号:博思奥园

你的支持就是我最大的动力。

ES系列:

  1. ElasticSearch(一) ElasticSearch入门
  2. ElasticSearch(二)在ElasticSearch 中使用中文分词器
  3. ElasticSearch(三)ElasticSearch索引操作
  4. ElasticSearch(四)ElasticSearch文档操作
  5. ElasticSearch(五)ElasticSearch字段类型

1、什么是文档?

在大多数应用中,多数实体或对象可以被序列化为包含键值对的 JSON 对象。

一个 可以是一个字段或字段的名称,

一个 可以是一个字符串,一个数字,一个布尔值, 另一个对象,一些数组值,或一些其它特殊类型诸如表示日期的字符串,或代表一个地理位置的对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"name": "John Smith",
"age": 42,
"confirmed": true,
"join_date": "2014-06-01",
"home": {
"lat": 51.5,
"lon": 0.1
},
"accounts": [
{
"type": "facebook",
"id": "johnsmith"
},
{
"type": "twitter",
"id": "johnsmith"
}
]
}

通常情况下,我们使用的术语 对象 和 文档 是可以互相替换的。

不过,有一个区别: 一个对象仅仅是类似于 hash 、 hashmap 、字典或者关联数组的 JSON 对象,对象中也可以嵌套其他的对象。
对象可能包含了另外一些对象。

在 Elasticsearch 中,术语 文档 有着特定的含义。它是指最顶层或者根对象, 这个根对象被序列化成 JSON 并存储到 Elasticsearch 中,指定了唯一 ID。

字段的名字可以是任何合法的字符串,但 不可以 包含英文句号(.)

2、文档元数据

一个文档不仅仅包含它的数据 ,也包含 元数据 —— 有关 文档的信息。 三个必须的元数据元素如下:

_index :文档在哪存放
_type :文档表示的对象类别
_id :文档唯一标识

更多见官方文档:官网

3、文档添加

通过使用 index API ,文档可以被 索引 —— 存储和使文档可被搜索。 但是首先,我们要确定文档的位置。正如我们刚刚讨论的,一个文档的 _index 、 _type 和 _id 唯一标识一个文档。 我们可以提供自定义的 _id 值,或者让 index API 自动生成。

1、使用自定义的 ID:如果你的文档有一个自然的标识符 (例如,一个 user_account 字段或其他标识文档的值),你应该使用如下方式的 index API 并提供你自己 _id :

1
2
3
4
5
PUT /{index}/{type}/{id}
{
"field": "value",
...
}

例如:
举个例子,如果我们的索引称为 blog ,类型称为 type ,并且选择 1 作为 ID ,那么索引请求应该是下面这样:

1
2
3
4
5
6
7
PUT /blog/type/1

{
"title": "my first type",
"name": "java",
"date": "2021/02/21"
}

返回,该响应表明文档已经成功创建,该索引包括 _index 、 _type 和 _id 元数据, 以及 _version 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_index": "blog",
"_type": "type",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": {
"total": 3,
"successful": 3,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 6
}
  • _index 表示文档索引。
  • _type 表示文档的类型。
  • _id 表示文档的 id。
  • _version 表示文档的版本(更新文档,版本会自动加 1,针对一个文档的)。
  • result 表示执行结果。
  • _shards 表示分片信息。
  • _seq_no 和 _primary_term 这两个也是版本控制用的(针对当前 index)。

2、使用自动化的ID

如果你的数据没有自然的 ID, Elasticsearch 可以帮我们自动生成 ID 。 请求的结构调整为: 不再使用 PUT 谓词(“使用这个 URL 存储这个文档”), 而是使用 POST 谓词(“存储文档在这个 URL 命名空间下”)。

现在该 URL 只需包含 _index 和 _type :

1
2
3
4
5
6
7
POST	 /blog/type/

{
"title": "my first type",
"name": "java",
"date": "2021/02/21"
}

相应体除了 _id 是 Elasticsearch 自动生成的,响应的其他部分和前面的类似:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_index": "blog",
"_type": "type",
"_id": "uPiIw3cBOjvzCSsirgZV",
"_version": 1,
"result": "created",
"_shards": {
"total": 3,
"successful": 3,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 7
}

自动生成的 ID 是 URL-safe、 基于 Base64 编码且长度为20个字符的 GUID 字符串。 这些 GUID 字符串由可修改的 FlakeID 模式生成,这种模式允许多个节点并行生成唯一 ID ,且互相之间的冲突概率几乎为零。

当我们索引一个文档,怎么确认我们正在创建一个完全新的文档,而不是覆盖现有的呢?

请记住, _index 、 _type 和 _id 的组合可以唯一标识一个文档。所以,确保创建一个新文档的最简单办法是,使用索引请求的 POST 形式让 Elasticsearch 自动生成唯一 _id

4、读取文档

1、获取单个

为了从 Elasticsearch 中检索出文档,我们仍然使用相同的 _index , _type , 和 _id ,但是 HTTP 谓词更改为 GET :

例如,我们需要取回我们刚刚新建的第一个type,也就是ID为1的type

1
GET		/blog/type/1

返回体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_index": "blog",
"_type": "type",
"_id": "1",
"_version": 1,
"_seq_no": 0,
"_primary_term": 6,
"found": true,
"_source": {
"title": "my first type",
"name": "java",
"date": "2021/02/21"
}
}

GET 请求的响应体包括 {"found": true} ,这证实了文档已经被找到。 如果我们请求一个不存在的文档,我们仍旧会得到一个 JSON 响应体,但是 found 将会是 false 。 此外, HTTP 响应码将会是 404 Not Found ,而不是 200 OK

例如,读取一个不存在的文档

1
GET		/blog/type/1

返回体:

1
2
3
4
5
6
{
"_index": "blog",
"_type": "type",
"_id": "2",
"found": false
}

2、获取多个文档

Elasticsearch 的速度已经很快了,但甚至能更快。 将多个请求合并成一个,避免单独处理每个请求花费的网络延时和开销。 如果你需要从 Elasticsearch 检索很多文档,那么使用 multi-get 或者 mget API 来将这些检索请求放在一个请求中,将比逐个文档请求更快地检索到全部文档。

mget API 要求有一个 docs 数组作为参数,每个元素包含需要检索文档的元数据, 包括 _index 、 _type 和 _id 。如果你想检索一个或者多个特定的字段,那么你可以通过 _source 参数来指定这些字段的名字:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET		/_mget
{
"docs" : [
{
"_index" : "blog",
"_type" : "type",
"_id" : 1
},
{
"_index" : "blog",
"_type" : "type",
"_id" : 2,
"_source": "views"
}
]
}

该响应体也包含一个 docs 数组, 对于每一个在请求中指定的文档,这个数组中都包含有一个对应的响应,且顺序与请求中的顺序相同。

其中的每一个响应都和使用单个 get request 请求所得到的响应体相同:

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
27
28
29
30
31
{
"docs": [
{
"_index": "blog",
"_type": "type",
"_id": "1",
"_version": 3,
"_seq_no": 5,
"_primary_term": 6,
"found": true,
"_source": {
"title": "my first type,and i rewrote it",
"name": "java",
"date": "2021/02/21",
"views": 1
}
},
{
"_index": "blog",
"_type": "type",
"_id": "2",
"_version": 1,
"_seq_no": 1,
"_primary_term": 7,
"found": true,
"_source": {
"views": 1
}
}
]
}

如果想检索的数据都在相同的 _index 中(甚至相同的 _type 中),则可以在 URL 中指定默认的 /_index 或者默认的 /_index/_type 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GET		/blog/_mget

{
"docs" : [
{
"_type": "type",
"_id": 1
},
{
"_type": "type",
"_id": 2
}
]
}

或者

1
2
3
4
5
GET		/blog/type/_mget

{
"ids": [1,2]
}

5、查看文档是否存在

如果只想检查一个文档是否存在–根本不想关心内容—​那么用 HEAD 方法来代替 GET 方法。

HEAD 请求没有返回体,只返回一个 HTTP 请求报头:

1、查看一个存在的文档:

1
HEAD	/blog/type/1

返回200 ok

2、查看一个不存在的文档

1
HEAD	/blog/type/2

返回404 not found

6、更新整个文档

在 Elasticsearch 中文档是 不可改变 的,不能修改它们。相反,如果想要更新现有的文档,需要 重建索引 或者进行替换, 我们可以使用相同的 index API 进行实现

1
2
3
4
5
6
PUT		/blog/type/1
{
"title": "my first type,and i rewrote it",
"name": "java",
"date": "2021/02/21"
}

在响应体中,我们能看到 Elasticsearch 已经增加了 _version 字段值,_version变成了2,代表我们已经修改了一次,并且_result变成updated

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_index": "blog",
"_type": "type",
"_id": "1",
"_version": 2,
"result": "updated",
"_shards": {
"total": 3,
"successful": 3,
"failed": 0
},
"_seq_no": 1,
"_primary_term": 6
}

底层:在内部,Elasticsearch 已将旧文档标记为已删除,并增加一个全新的文档。 尽管你不能再对旧版本的文档进行访问,但它并不会立即消失。当继续索引更多的数据,Elasticsearch 会在后台清理这些已删除文档。

重新获取修改后的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_index": "blog",
"_type": "type",
"_id": "1",
"_version": 2,
"_seq_no": 1,
"_primary_term": 6,
"found": true,
"_source": {
"title": "my first type,and i rewrote it",
"name": "java",
"date": "2021/02/21"
}
}

7、更新整个文档

1、直接更新

update 请求最简单的一种形式是接收文档的一部分作为 doc 的参数, 它只是与现有的文档进行合并。

对象被合并到一起,覆盖现有的字段,增加新的字段。 例如,我们增加views 到我们的博客文章,如下所示:

1
2
3
4
5
6
POST	/blog/type/1/_update
{
"doc" : {
"views": 0
}
}

重新查看,新的字段已被添加到 _source 中。

2、使用脚本进行部分更新

脚本可以在 update API中用来改变 _source 的字段内容, 它在更新脚本中称为 ctx._source 。 例如,我们可以使用脚本来增加该分类中 views 的数量:

1
2
3
4
POST 		/blog/type/1/_update
{
"script" : "ctx._source.views+=1"
}

加入我们对一个新的文档进行更新,因为不存在会导致报错,所以我们可以使用upsert 参数,指定如果文档不存在就应该先创建它:

例如ID为2的分类不存在,我们要更新他里面的views参数,使用upsert就不会报错,并且会新建一个_id为2,_type为type,_index为blog的文档

1
2
3
4
5
6
7
POST 		/blog/type/2/_update
{
"script" : "ctx._source.views+=1",
"upsert": {
"views": 1
}
}

我们第一次运行这个请求时, upsert 值作为新文档被索引,初始化 views 字段为 1 。 在后续的运行中,由于文档已经存在, script 更新操作将替代 upsert 进行应用,对 views 计数器进行累加。

8、删除文档

删除文档我们只需要DELETE请求即可达到删除一个文档

1
DELETE	/blog/type/1

返回体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_index": "blog",
"_type": "type",
"_id": "1",
"_version": 3,
"result": "deleted",
"_shards": {
"total": 3,
"successful": 3,
"failed": 0
},
"_seq_no": 2,
"_primary_term": 6
}

正如已经在更新文档中提到的,删除文档不会立即将文档从磁盘中删除,只是将文档标记为已删除状态。随着你不断的索引更多的数据,Elasticsearch 将会在后台清理标记为已删除的文档。

ElasticSearch(四)ElasticSearch文档操作

https://lomtom.cn/f202f94d.html

作者

lomtom

发布于

2021-12-04

更新于

2021-12-10

许可协议