ES 文档
在很多程序中,大部分实体或者对象都被序列化为包含键和值的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"
}
]
}
通常情况下,我们使用可以互换对象和文档。然而,还是有一个区别的。对象(object )仅仅是一个JSON对象,类似于哈希,哈希映射,字典或关联数组。对象(Objects)则可以包含其他对象(Objects)。
在Elasticsearch中,文档这个单词有特殊的含义。它指的是在Elasticsearch中被存储到唯一ID下的由最高级或者根对象 (root object )序列化而来的JSON。
*文档元数据*
一个文档不只包含了数据。它还包含了元数据(metadata) —— 关于文档的信息。有三个元数据元素是必须存在的,它们是:
名字 | 说明 |
_index | 文档存储的地方 |
_type | 文档代表的对象种类 |
_id | 文档的唯一编号 |
*_index*
索引 类似于传统数据库中的"数据库"——也就是我们存储并且索引相关数据的地方。
在Elasticsearch中,我们的数据都在分片中被存储以及索引,索引只是一个逻辑命名空间,它可以将一个或多个分片组合在一起。然而,这只是一个内部的运作原理——我们的程序可以根本不用关心分片。对于我们的程序来说,我们的文档存储在索引中。剩下的交给Elasticsearch就可以了。
*_type*
在程序中,我们使用对象代表“物品”,比如一个用户、一篇博文、一条留言或者一个邮件。每一个对象都属于一种类型,类型定义了对象的属性或者与数据的关联。用户类的对象可能就会包含名字、性别、年龄以及邮箱地址等。
在传统的数据库中,我们总是将同类的数据存储在同一个表中,因为它们的数据格式是相同的。同理,在Elasticsearch中,我们使用同样类型的文档来代表同类“事物”,也是因为它们的数据结构是相同的。
每一个类型都拥有自己的映射(mapping)或者结构定义,它们定义了当前类型下的数据结构,类似于数据库表中的列。所有类型下的文档会被存储在同一个索引下,但是映射会告诉Elasticsearch不同的数据应该如何被索引。
我们将会在《映射》中探讨如何制定或者管理映射,但是目前为止,我们只需要依靠Elasticsearch来自动处理数据结构。
*_id*
id是一个字符串,当它与_index以及_type组合时,就可以来代表Elasticsearch中一个特定的文档。我们创建了一个新的文档时,你可以自己提供一个_id,或者也可以让Elasticsearch帮你生成一个。
*其他元数据*
在文档中还有一些其他的元数据,我们将会在《映射》章节中详细讲解。使用上面罗列的元素,我们已经可以在Elasticsearch中存储文档或者通过ID来搜索已经保存的文档了。
我们通常用用_cat API检测集群是否健康。 确保9200端口号可用:
curl 'localhost:9200/_cat/health?v'
绿色表示一切正常, 黄色表示所有的数据可用但是部分副本还没有分配,红色表示部分数据因为某些原因不可用.
通过如下语句,我们可以获取集群的节点列表:
curl 'localhost:9200/_cat/nodes?v'
通过如下语句,列出所有索引:
curl 'localhost:9200/_cat/indices?v'
创建索引
现在我们创建一个名为“customer”的索引,然后再查看所有的索引:
curl -XPUT 'localhost:9200/customer?pretty'
索引一个文档
文档通过索引API被索引——存储并使其可搜索。但是最开始我们需要决定我们将文档存储在哪里。正如之前提到的,一篇文档通过_index, _type以及_id来确定它的唯一性。我们可以自己提供一个_id,或者也使用indexAPI 帮我们生成一个。
使用自己的ID
如果你的文档拥有天然的标示符(例如user_account字段或者文档中其他的标识值),这时你就可以提供你自己的_id,这样使用indexAPI:
PUT /{index}/{type}/{id}
{
"field": "value",
...
}
如:
curl -XPUT "http://localhost:9200/megacorp/employee/1" -d '
{
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}'
Elasticsearch返回内容:
{
"_index": "megacorp",
"_type": "employee",
"_id": "1",
"_version": 1,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"created": false
}
这个返回值意味着我们的索引请求已经被成功创建,其中还包含了_index, _type以及_id的元数据,以及一个新的元素_version。
自增ID
如果我们的数据中没有天然的标示符,我们可以让Elasticsearch为我们自动生成一个。请求的结构发生了变化:我们把PUT——“把文档存储在这个地址中”变量变成了POST——“把文档存储在这个地址下”。
这样一来,请求中就只包含 _index和_type了:
curl -XPOST "http://localhost:9200/megacorp/employee/" -d '
{
"first_name" : "Douglas",
"last_name" : "Fir",
"age" : 35,
"about": "I like to build cabinets",
"interests": [ "forestry" ]
}'
这次的反馈和之前基本一样,只有_id改成了系统生成的自增值:
{
"_index": "megacorp",
"_type": "employee",
"_id": "AVpkks-zosJrEdXVKk5N",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"created": true
}
自生成ID是由22个字母组成的,安全 universally unique identifiers 或者被称为UUIDs。
搜索文档
要从Elasticsearch中获取文档,我们需要使用同样的_index,_type以及 _id但是不同的HTTP变量GET:
curl -XGET "http://localhost:9200/megacorp/employee/1?pretty"
返回结果:
{
"_index" : "megacorp",
"_type" : "employee",
"_id" : "1",
"_version" : 2,
"found" : true,
"_source" : {
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "I love to go rock climbing",
"interests" : [
"sports",
"music"
]
}
}
*pretty*
在任意的查询字符串中添加pretty参数,类似上面的请求,Elasticsearch就可以得到优美打印的更加易于识别的JSON结果。_source字段不会执行优美打印,它的样子取决于我们录入的样子。
GET请求的返回结果中包含{"found": true}。这意味着这篇文档确实被找到了。如果我们请求了一个不存在的文档,我们依然会得到JSON反馈,只是found的值会变为false。
同样,HTTP返回码也会由'200 OK'变为'404 Not Found'。我们可以在curl后添加-i,这样你就能得到反馈头文件:
curl -i -XGET "http://localhost:9200/megacorp/employee/1?pretty"
反馈结果就会是这个样子:
HTTP/1.1 200 OK
content-type: application/json; charset=UTF-8
content-length: 294
{
"_index" : "megacorp",
"_type" : "employee",
"_id" : "1",
"_version" : 2,
"found" : true,
"_source" : {
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "I love to go rock climbing",
"interests" : [
"sports",
"music"
]
}
}
检索文档中的一部分
通常,GET请求会将整个文档放入_source字段中一并返回。但是可能你只需要title字段。你可以使用_source得到指定字段。如果需要多个字段你可以使用逗号分隔:
curl -XGET "http://localhost:9200/megacorp/employee/_search?q=last_name:Smith"
现在_source字段中就只会显示你指定的字段:
{
"took": 11,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 2,
"max_score": 0.6931472,
"hits": [
{
"_index": "megacorp",
"_type": "employee",
"_id": "1",
"_score": 0.6931472,
"_source": {
"first_name": "John",
"last_name": "Smith",
"age": 25,
"about": "I love to go rock climbing",
"interests": [
"sports",
"music"
]
}
},
{
"_index": "megacorp",
"_type": "employee",
"_id": "2",
"_score": 0.2876821,
"_source": {
"first_name": "Jane",
"last_name": "Smith",
"age": 32,
"about": "I like to collect rock albums",
"interests": [
"music"
]
}
}
]
}
}
或者你只想得到_source字段而不要其他的元数据,你可以这样请求:
curl -XGET "http://localhost:9200/megacorp/employee/_search"
这样结果就只返回:
{
"took" : 5,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"failed" : 0
},
"hits" : {
"total" : 3,
"max_score" : 1.0,
"hits" : [
{
"_index" : "megacorp",
"_type" : "employee",
"_id" : "2",
"_score" : 1.0,
"_source" : {
"first_name" : "Jane",
"last_name" : "Smith",
"age" : 32,
"about" : "I like to collect rock albums",
"interests" : [
"music"
]
}
},
{
"_index" : "megacorp",
"_type" : "employee",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "I love to go rock climbing",
"interests" : [
"sports",
"music"
]
}
},
{
"_index" : "megacorp",
"_type" : "employee",
"_id" : "AVpkks-zosJrEdXVKk5N",
"_score" : 1.0,
"_source" : {
"first_name" : "Douglas",
"last_name" : "Fir",
"age" : 35,
"about" : "I like to build cabinets",
"interests" : [
"forestry"
]
}
}
]
}
}
检查文档是否存在
如果确实想检查一下文档是否存在,你可以试用HEAD来替代GET方法,这样就是会返回HTTP头文件:
curl -i -XHEAD "http://localhost:9200/megacorp/employee/1"
如果文档存在,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/megacorp/employee/9"
HTTP/1.1 404 Not Found
content-type: text/plain; charset=UTF-8
content-length: 0
更新整个文档
在Documents中的文档是不可改变的。所以如果我们需要改变已经存在的文档,我们可以使用《索引》中提到的indexAPI来重新索引或者替换掉它:
curl -XPUT "http://localhost:9200/megacorp/employee/1" -d '
{
"first_name" : "John",
"last_name" : "Aires",
"age" : 24,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}'
在反馈中,我们可以发现Elasticsearch已经将_version数值增加了:
{
"_index": "megacorp",
"_type": "employee",
"_id": "1",
"_version": 2,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"created": false
}
created被标记为 false是因为在同索引、同类型下已经存在同ID的文档。
在内部,Elasticsearch已经将旧文档标记为删除并且添加了新的文档。旧的文档并不会立即消失,但是你也无法访问他。Elasticsearch会在你继续添加更多数据的时候在后台清理已经删除的文件。
在本章的后面,我们将会在《局部更新》中介绍最新更新的API。这个API允许你修改局部,但是原理和下方的完全一样:
从旧的文档中检索JSON
修改它
删除修的文档
索引一个新的文档
唯一不同的是,使用了updateAPI你就不需要使用get然后再操作index请求了。
创建一个文档
当我们索引一个文档时,如何确定我们是创建了一个新的文档还是覆盖了一个已经存在的文档呢?
请牢记_index,_type以及_id组成了唯一的文档标记,所以为了确定我们创建的是全新的内容,最简单的方法就是使用POST方法,让Elasticsearch自动创建不同的_id:
POST /website/blog/
{ ... }
然而,我们可能已经决定好了_id,所以需要告诉Elasticsearch只有当_index,_type以及_id这3个属性全部相同的文档不存在时才接受我们的请求。实现这个目的有两种方法,他们实质上是一样的,你可以选择你认为方便的那种:
第一种是在查询中添加op_type=create参数:
curl -XPUT "http://localhost:9200/megacorp/employee/11?op_type=create" -d '
{
"first_name" : "John",
"last_name" : "Aires",
"age" : 24,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}'
或者在请求最后添加 /_create:
curl -XPUT "http://localhost:9200/megacorp/employee/15/_create" -d '
{
"first_name" : "John",
"last_name" : "Aires",
"age" : 24,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}'
如果成功创建了新的文档,Elasticsearch将会返回常见的元数据以及201 Created的HTTP反馈码。
而如果存在同名文件,Elasticsearch将会返回一个409 Conflict的HTTP反馈码,以及如下方的错误信息:
{
"error" : "DocumentAlreadyExistsException[[website][4] [blog][123]:
document already exists]",
"status" : 409
}
删除一个文档
删除文档的基本模式和之前的基本一样,只不过是需要更换成DELETE方法:
curl -XDELETE "http://localhost:9200/megacorp/employee/3"
如果文档存在,那么Elasticsearch就会返回一个200 OK的HTTP响应码,返回的结果就会像下面展示的一样。请注意_version的数字已经增加了。
HTTP/1.1 200 OK
content-type: application/json; charset=UTF-8
content-length: 144
{"found":true,"_index":"megacorp","_type":"employee","_id":"15","_version":2,"result":"deleted","_shards":{"total":2,"successful":1,"failed":0}}
如果文档不存在,那么我们就会得到一个404 Not Found的响应码,返回的内容就会是这样的:
HTTP/1.1 404 Not Found
content-type: application/json; charset=UTF-8
content-length: 146
{"found":false,"_index":"megacorp","_type":"employee","_id":"3","_version":1,"result":"not_found","_shards":{"total":2,"successful":1,"failed":0}}
尽管文档并不存在("found"值为false),但是_version的数值仍然增加了。这个就是内部管理的一部分,它保证了我们在多个节点间的不同操作的顺序都被正确标记了。