什么是文档?
程序中大多的实体或对象能够被序列化为包含键值对的JSON对象,键(key) 是字段(field) 或属性(property) 的名字,值(value) 可以是字符串、数字、布尔类型、另一个对象、值数组或者其他特殊类型,比如表示日期的字符串或者表示地理位置的对象。
{
"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"
}
]
}
通常,我们可以认为对象(object) 和文档(document) 是等价相通的。不过,他们还是有所差别:对象(Object)是一个JSON结构体——类似于哈希、hashmap、字典或者关联数组;对象(Object)中还可能包含其他对象(Object)。 在Elasticsearch中,文档(document) 这个术语有着特殊含义。它特指最顶层结构或者根对象(root object) 序列化成的JSON数据(以唯一ID标识并存储于Elasticsearch中)。
文档元数据
一个文档不只有数据。它还包含了元数据(metadata) ——关于 文档的信息。三个必须的元数据节点是:
节点 | 说明 |
---|---|
_index |
文档存储的地方 |
_type |
文档代表的对象的类 |
_id |
文档的唯一标识 |
_index
- 索引(index) 类似于关系型数据库里的“数据库”——它是我们存储和索引关联数据的地方。
提示:
事实上,我们的数据被存储和索引在分片(shards) 中,索引只是一个把一个或多个分片分组在一起的逻辑空间。然而,这只是一些内部细节——我们的程序完全不用关心分片。对于我们的程序而言,文档存储在索引(index) 中。剩下的细节由Elasticsearch关心既可。
- 我们将会在 索引管理 章节中探讨如何创建并管理索引,但现在,我们将让Elasticsearch为我们创建索引。我们唯一需要做的仅仅是选择一个索引名。这个名字必须是全部小写,不能以下划线开头,不能包含逗号。让我们使用
website
做为索引名。
_type
- 在应用中,我们使用对象表示一些“事物”,例如一个用户、一篇博客、一个评论,或者一封邮件。每个对象都属于一个类(class) ,这个类定义了属性或与对象关联的数据。
user
类的对象可能包含姓名、性别、年龄和Email地址。 - 在关系型数据库中,我们经常将相同类的对象存储在一个表里,因为它们有着相同的结构。同理,在Elasticsearch中,我们使用相同类型(type) 的文档表示相同的“事物”,因为他们的数据结构也是相同的。
- 每个类型(type) 都有自己的映射(mapping) 或者结构定义,就像传统数据库表中的列一样。所有类型下的文档被存储在同一个索引下,但是类型的映射(mapping) 会告诉Elasticsearch不同的文档如何被索引。 我们将会在《映射》章节探讨如何定义和管理映射,但是现在我们将依赖Elasticsearch去自动处理数据结构。
_type
的名字可以是大写或小写,不能包含下划线或逗号。我们将使用blog
做为类型名。
_id
id 仅仅是一个字符串,它与_index
和_type
组合时,就可以在Elasticsearch中唯一标识一个文档。当创建一个文档,你可以自定义_id
,也可以让Elasticsearch帮你自动生成。
其它元数据
还有一些其它的元数据,我们将在 映射和分析 章节探讨。使用上面提到的元素,我们已经可以在Elasticsearch中存储文档并通过ID检索——换言说,把Elasticsearch做为文档存储器使用了。
索引一个文档
文档通过index
API被索引——使数据可以被存储和搜索。但是首先我们需要决定文档所在。正如我们讨论的,文档通过其_index
、_type
、_id
唯一确定。们可以自己提供一个_id
,或者也使用index
API 为我们生成一个。
使用自己的ID
- 如果你的文档有自然的标识符(例如
user_account
字段或者其他值表示文档),你就可以提供自己的_id
,使用这种形式的index
API:PUT /{index}/{type}/{id} { "field": "value", ... }
- 例如我们的索引叫做
“website”
,类型叫做“blog”
,我们选择的ID是“123”
,那么这个索引请求就像这样: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
。 - Elasticsearch 中每个文档都有版本号,每当文档变化(包括删除)都会使
_version
增加。在《版本控制》章节中我们将探讨如何使用_version
号确保你程序的一部分不会覆盖掉另一部分所做的更改。
自增ID
如果我们的数据没有自然ID,我们可以让Elasticsearch自动为我们生成。请求结构发生了变化:PUT
方法——“在这个URL中存储文档”
变成了POST
方法——"在这个类型下存储文档"
。(译者注:原来是把文档存储到某个ID对应的空间,现在是把这个文档添加到某个_type
下)。
- URL现在只包含
_index
和_type
两个字段:POST /website/blog/ { "title": "My second blog entry", "text": "Still trying this out...", "date": "2014/01/01" }
- 响应内容与刚才类似,只有
_id
字段变成了自动生成的值:{ "_index": "website", "_type": "blog", "_id": "wM0OSFhDQXGZAWDf0-drSA", "_version": 1, "created": true }
- 自动生成的ID有22个字符长,URL-safe, Base64-encoded string universally unique identifiers, 或者叫 UUIDs。
检索文档
- 想要从Elasticsearch中获取文档,我们使用同样的
_index
、_type
、_id
,但是HTTP方法改为GET
:GET /website/blog/123?pretty
- 响应包含了现在熟悉的元数据节点,增加了
_source
字段,它包含了在创建索引时我们发送给Elasticsearch的原始文档。{ "_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" } }
pretty
:在任意的查询字符串中增加pretty
参数,类似于上面的例子。会让Elasticsearch美化输出(pretty-print) JSON响应以便更加容易阅读。_source
字段不会被美化,它的样子与我们输入的一致。 - GET请求返回的响应内容包括
{"found": true}
。这意味着文档已经找到。如果我们请求一个不存在的文档,依旧会得到一个JSON,不过found
值变成了false
。 - 此外,HTTP响应状态码也会变成
'404 Not Found'
代替'200 OK'
。我们可以在curl
后加-i
参数得到响应头:curl -i -XGET http://localhost:9200/website/blog/124?pretty
- 现在响应类似于这样:
HTTP/1.1 404 Not Found Content-Type: application/json; charset=UTF-8 Content-Length: 83 { "_index" : "website", "_type" : "blog", "_id" : "124", "found" : false }
检索文档的一部分
- 通常,
GET
请求将返回文档的全部,存储在_source
参数中。但是可能你感兴趣的字段只是title
。请求个别字段可以使用_source
参数。多个字段可以使用逗号分隔:GET /website/blog/123?_source=title,text
_source
字段现在只包含我们请求的字段,而且过滤了date
字段:{ "_index" : "website", "_type" : "blog", "_id" : "123", "_version" : 1, "exists" : true, "_source" : { "title": "My first blog entry" , "text": "Just trying this out..." } }
- 或者你只想得到
_source
字段而不要其他的元数据,你可以这样请求:GET /website/blog/123/_source
- 它仅仅返回:
{ "title": "My first blog entry", "text": "Just trying this out...", "date": "2014/01/01" }
检查文档是否存在
- 如果你想做的只是检查文档是否存在——你对内容完全不感兴趣——使用
HEAD
方法来代替GET
。HEAD
请求不会返回响应体,只有HTTP头:curl -i -XHEAD http://localhost:9200/website/blog/123
- Elasticsearch将会返回
200 OK
状态如果你的文档存在:HTTP/1.1 200 OK Content-Type: text/plain; charset=UTF-8 Content-Length: 0
- 如果不存在返回
404 Not Found
:curl -i -XHEAD http://localhost:9200/website/blog/124
HTTP/1.1 404 Not Found Content-Type: text/plain; charset=UTF-8 Content-Length: 0
- 当然,这只表示你在查询的那一刻文档不存在,但并不表示几毫秒后依旧不存在。另一个进程在这期间可能创建新文档。
更新整个文档
文档在Elasticsearch中是不可变的——我们不能修改他们。如果需要更新已存在的文档,我们可以使用 索引管理 提到的index
API 重建索引(reindex) 或者替换掉它。
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>
}
- <1>
created
标识为false
因为同索引、同类型下已经存在同ID的文档。
在内部,Elasticsearch已经标记旧文档为删除并添加了一个完整的新文档。旧版本文档不会立即消失,但你也不能去访问它。Elasticsearch会在你继续索引更多数据时清理被删除的文档。
在本章的后面,我们将会在 局部更新 中探讨update
API。这个API 似乎 允许你修改文档的局部,但事实上Elasticsearch遵循与之前所说完全相同的过程,这个过程如下:
- 从旧文档中检索JSON
- 修改它
- 删除旧文档
- 索引新文档
唯一的不同是update
API完成这一过程只需要一个客户端请求既可,不再需要get
和index
请求了。
创建一个新文档
当索引一个文档,我们如何确定是完全创建了一个新的还是覆盖了一个已经存在的呢?
- 请记住
_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
。 - 另一方面,如果包含相同的
_index
、_type
和_id
的文档已经存在,Elasticsearch将返回409 Conflict
响应状态码,错误信息类似如下:{ "error" : "DocumentAlreadyExistsException[[website][4] [blog][123]: document already exists]", "status" : 409 }
删除文档
- 删除文档的语法模式与之前基本一致,只不过要使用
DELETE
方法:DELETE /website/blog/123
- 如果文档被找到,Elasticsearch将返回
200 OK
状态码和以下响应体。注意_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将会在你之后添加更多索引的时候才会在后台进行删除内容的清理。