二 基础入门
3、数据输入和输出
3.1、什么是文档
- 多数实体或对象可以被序列化为包含键值对的 JSON 对象
{
"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"
}
]
}
通常情况下,我们使用的术语 对象 和 文档 是可以互相替换的
3.2 文档元数据
-
三个必须的元数据元素如下:
-
_index
: 文档在哪存放 -
_type
: 文档表示的对象类别 -
_id
:文档唯一标识
-
3.2.1. _index
-
一个 索引 应该是因共同的特性被分组到一起的文档集合。
-
例如,你可能存储所有的产品在索引
products
中,而存储所有销售的交易到索引sales
中。 虽然也允许存储不相关的数据到一个索引中,但这通常看作是一个反模式的做法。
3.2.2. _type
- 数据可能在 索引 中只是松散的组合在一起,但是通常明确定义一些数据中的子分区是很有用的。
- 例如,所有的产品都放在一个索引中,但是你有许多不同的产品类别,比如 "electronics" 、 "kitchen" 和 "lawn-care"。
3.2.3. _id
- ID 是一个字符串,当它和
_index
以及_type
组合就可以唯一确定 Elasticsearch 中的一个文档。 当你创建一个新的文档,要么提供自己的_id
,要么让 Elasticsearch 帮你生成。
3.3、 索引文档
通过使用 index
API ,文档可以被 索引 —— 存储和使文档可被搜索。 但是首先,我们要确定文档的位置。正如我们刚刚讨论的,一个文档的 _index
、 _type
和 _id
唯一标识一个文档。 我们可以提供自定义的 _id
值,或者让 index
API 自动生成。
3.3.1、 使用自定义的 ID
如果你的文档有一个自然的标识符 (例如,一个 user_account
字段或其他标识文档的值),你应该使用如下方式的 index
API 并提供你自己 _id
:
PUT /{index}/{type}/{id}
{
"field": "value",
...
}
举个例子,如果我们的索引称为 website
,类型称为 blog
,并且选择 123
作为 ID ,那么索引请求应该是下面这样:
PUT /website/blog/123
{
"title": "My first blog entry",
"text": "Just trying this out...",
"date": "2014/01/01"
}
Elasticsearch 响应体如下所示:
{
"_index": "website",
"_type": "blog",
"_id": "123",
"_version": 1,
"created": true
}
该响应表明文档已经成功创建,该索引包括 _index
、 _type
和 _id
元数据, 以及一个新元素: _version
3.3.2、 Autogenerating IDs
- 自动生成的 ID 是 URL-safe、 基于 Base64 编码且长度为20个字符的 GUID 字符串
3.4、 取回一个文档
GET /website/blog/123?pretty
{
"_index" : "website",
"_type" : "blog",
"_id" : "123",
"_version" : 1,
"found" : true,
"_source" : {
"title": "My first blog entry",
"text": "Just trying this out...",
"date": "2014/01/01"
}
}
3.4.1. 返回文档的一部分
GET /website/blog/123?_source=title,text
{
"_index" : "website",
"_type" : "blog",
"_id" : "123",
"_version" : 1,
"found" : true,
"_source" : {
"title": "My first blog entry" ,
"text": "Just trying this out..."
}
}
3.5、 检查文档是否存在
curl -i -XHEAD http://localhost:9200/website/blog/123
- 正确返回200
- 错误返回404
3.6. 更新整个文档
PUT /website/blog/123
{
"title": "My first blog entry",
"text": "I am starting to get the hang of this...",
"date": "2014/01/02"
}
在响应体中,我们能看到 Elasticsearch 已经增加了 _version
字段值:
{
"_index" : "website",
"_type" : "blog",
"_id" : "123",
"_version" : 2,
"created": false (1)
}
created
标志设置成false
,是因为相同的索引、类型和 ID 的文档已经存在。
-
在内部,Elasticsearch 已将旧文档标记为已删除,并增加一个全新的文档。 尽管你不能再对旧版本的文档进行访问,但它并不会立即消失。当继续索引更多的数据,Elasticsearch 会在后台清理这些已删除文档。
-
在本章的后面部分,我们会介绍
update
API, 这个 API 可以用于部分更新。 虽然它似乎对文档直接进行了修改,但实际上 Elasticsearch 按前述完全相同方式执行以下过程:
- 从旧文档构建 JSON
- 更改该 JSON
- 删除旧文档
- 索引一个新文档
- 唯一的区别在于,
update
API 仅仅通过一个客户端请求来实现这些步骤,而不需要单独的get
和index
请求。
3.7. 创建新文档
当我们索引一个文档,怎么确认我们正在创建一个完全新的文档,而不是覆盖现有的呢?
请记住, _index
、 _type
和 _id
的组合可以唯一标识一个文档。所以,确保创建一个新文档的最简单办法是,使用索引请求的 POST
形式让 Elasticsearch 自动生成唯一 _id
:
POST /website/blog/
{ ... }
然而,如果已经有自己的 _id
,那么我们必须告诉 Elasticsearch ,只有在相同的 _index
、 _type
和 _id
不存在时才接受我们的索引请求。这里有两种方式,他们做的实际是相同的事情。使用哪种,取决于哪种使用起来更方便。
第一种方法使用 op_type
查询-字符串参数:
PUT /website/blog/123?op_type=create
{ ... }
第二种方法是在 URL 末端使用 /_create
:
PUT /website/blog/123/_create
{ ... }
如果创建新文档的请求成功执行,Elasticsearch 会返回元数据和一个 201 Created
的 HTTP 响应码。
另一方面,如果具有相同的 _index
、 _type
和 _id
的文档已经存在,Elasticsearch 将会返回 409 Conflict
响应码,以及如下的错误信息:
{
"error": {
"root_cause": [
{
"type": "document_already_exists_exception",
"reason": "[blog][123]: document already exists",
"shard": "0",
"index": "website"
}
],
"type": "document_already_exists_exception",
"reason": "[blog][123]: document already exists",
"shard": "0",
"index": "website"
},
"status": 409
}
3.8. 删除文档
删除文档的语法和我们所知道的规则相同,只是使用 DELETE
方法:
DELETE /website/blog/123
如果找到该文档,Elasticsearch 将要返回一个 200 ok
的 HTTP 响应码,和一个类似以下结构的响应体。注意,字段 _version
值已经增加:
{
"found" : true,
"_index" : "website",
"_type" : "blog",
"_id" : "123",
"_version" : 3
}
如果文档没有找到,我们将得到 404 Not Found
的响应码和类似这样的响应体:
{
"found" : false,
"_index" : "website",
"_type" : "blog",
"_id" : "123",
"_version" : 4
}
即使文档不存在( Found
是 false
), _version
值仍然会增加。这是 Elasticsearch 内部记录本的一部分,用来确保这些改变在跨多节点时以正确的顺序执行。
3.9. 处理冲突
- 多个客户端同时修改的时候,会产生冲突
- 可以通过乐观锁和悲观锁进行控制
- 在 es 主要通过乐观锁进行控制
3.10. 乐观并发控制
- 通过版本号的 cas 操作
3.11. 文档的部分更新
- 使用
update
API 我们还可以部分更新文档
3.12. 取回多个文档
mget
API 要求有一个 docs
数组作为参数,每个元素包含需要检索文档的元数据, 包括 _index
、 _type
和 _id
。如果你想检索一个或者多个特定的字段,那么你可以通过 _source
参数来指定这些字段的名字:
GET /_mget
{
"docs" : [
{
"_index" : "website",
"_type" : "blog",
"_id" : 2
},
{
"_index" : "website",
"_type" : "pageviews",
"_id" : 1,
"_source": "views"
}
]
}