索引一个文档
文档通过 索引 API被索引——存储并使其可搜索。但是最开始我们需要决定我们将文档存储 在哪里。正如之前提到的,一篇文档通过 _index , _type 以及 _id 来确定它的唯一性。我们 可以自己提供一个 _id ,或者也使用 index API 帮我们生成一个。
使用自己的ID
如果你的文档拥有天然的标示符(例如 user_account 字段或者文档中其他的标识值),这时 你就可以提供你自己的 _id ,这样使用 index API:
PUT /{index}/{type}/{id} { "field": "value", ... } PUT /website/blog/123 { "title": "My first blog entry", "text": "Just trying this out...", "date": "2014/01/01" }
自增ID
如果我们的数据中没有天然的标示符,我们可以让Elasticsearch为我们自动生成一个。请求 的结构发生了变化:我们把 PUT ——“把文档存储在这个地址中”变量变成了 POST ——“把文 档存储在这个地址下”。
POST /website/blog/ { "title": "My second blog entry", "text": "Still trying this out...", "date": "2014/01/01" }
自生成ID是由22个字母组成的,安全 universally unique identifiers 或者被称为UUIDs。
文档是什么?
在很多程序中,大部分实体或者对象都被序列化为包含键和值的JSON对象。键是一个字段或 者属性的名字,值可以是一个字符串、数字、布尔值、对象、数组或者是其他的特殊类型, 比如代表日期的字符串或者代表地理位置的对象:
文档元数据
一个文档不只包含了数据。它还包含了元数据(metadata) —— 关于文档的信息。有三个元数 据元素是必须存在的,它们是:
名字 | 说明 |
_index | 文档存储的地方 |
_type | 文档代表的对象种类 |
_id | 文档的唯一编号 |
_index
索引 类似于传统数据库中的"数据库"——也就是我们存储并且索引相关数据的地方。
TIP:
在Elasticsearch中,我们的数据都在分片中被存储以及索引,索引只是一个逻辑命名空间, 它可以将一个或多个分片组合在一起。然而,这只是一个内部的运作原理——我们的程序可 以根本不用关心分片。对于我们的程序来说,我们的文档存储在索引中。剩下的交给 Elasticsearch就可以了。
_type
每一个类型都拥有自己的映射(mapping)或者结构定义,它们定义了当前类型下的数据结构, 类似于数据库表中的列。所有类型下的文档会被存储在同一个索引下,但是映射会告诉 Elasticsearch不同的数据应该如何被索引。
_id
id是一个字符串,当它与 _index 以及 _type 组合时,就可以来代表Elasticsearch中一个特定 的文档。我们创建了一个新的文档时,你可以自己提供一个 _id ,或者也可以让 Elasticsearch帮你生成一个。
搜索文档
要从Elasticsearch中获取文档,我们需要使用同样的 _index , _type 以及 _id 但是不同的 HTTP变量 GET :
{ "_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 美化打印数据
_source 字段不会执行优美打印,它的样子取决于我们录入 的样子
GET请求的返回结果中包含 {"found": true} 。这意味着这篇文档确实被找到了。如果我们请 求了一个不存在的文档,我们依然会得到JSON反馈,只是 found 的值会变为 false 。
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 字段中就只会显示你指定的字段:
{ "_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
检查文档是否存在
如果确实想检查一下文档是否存在,你可以试用 HEAD 来替代 GET 方法,这样就是会返回 HTTP头文件:
curl -i -XHEAD /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 /website/blog/124
HTTP/1.1 404 Not Found Content-Type: text/plain; charset=UTF-8 Content-Length: 0
更新整个文档
在Documents中的文档是不可改变的。所以如果我们需要改变已经存在的文档,我们可以使 用《索引》中提到的 index API来重新索引或者替换掉它:
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 }
created 被标记为 false 是因为在同索引、同类型下已经存在同ID的文档
在内部,Elasticsearch已经将旧文档标记为删除并且添加了新的文档。旧的文档并不会立即消失,但是你也无法访问他。Elasticsearch会在你继续添加更多数据的时候在后台清理已经 删除的文件。
- 从旧的文档中检索JSON
- 修改它
- 删除修的文档
- 索引一个新的文档
唯一不同的是,使用了 update API你就不需要使用 get 然后再操作 index 请求了。
创建一个文档
请牢记 _index , _type 以及 _id 组成了唯一的文档标记,所以为了确定我们创建的是全新的 内容,最简单的方法就是使用 POST 方法,让Elasticsearch自动创建不同的 _id :
POST /website/blog/ { ... }
然而,我们可能已经决定好了 _id ,所以需要告诉Elasticsearch只有当 _index , _type 以及 _id 这3个属性全部相同的文档不存在时才接受我们的请求。实现这个目的有两种方法,他 们实质上是一样的,你可以选择你认为方便的那种:
第一种是在查询中添加 op_type 参数:
PUT /website/blog/123?op_type=create { ... }
或者在请求最后添加 /_create :
PUT /website/blog/123/_create { ... }
1. 创建成功,Elasticsearch将会返回常见的元数据以及 201 Created 的HTTP反馈码。
2. 存在同名文件 ,Elasticsearch将会返回一个 409 Conflict 的HTTP反馈码,以及如下方的错误信息:
{ "error" : "DocumentAlreadyExistsException[[website][4] [blog][123]: document already exists]", "status" : 409 }
删除一个文档
DELETE /website/blog/123
如果文档存在,那么Elasticsearch就会返回一个 200 OK 的HTTP响应码
{ "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将会在你之后添加更多索引的时候才会在后台进行删除内容的清理。
处理冲突
当你使用 索引 API来更新一个文档时,我们先看到了原始文档,然后修改它,最后一次性地 将整个新文档进行再次索引处理。Elasticsearch会根据请求发出的顺序来选择出最新的一个 文档进行保存。但是,如果在你修改文档的同时其他人也发出了指令,那么他们的修改将会 丢失。
并发处理
以下是两种能避免在并发更新时丢失数据的方法:
- 悲观并发控制(PCC)
这一点在关系数据库中被广泛使用。假设这种情况很容易发生,我们就可以阻止对这一资源 的访问。典型的例子就是当我们在读取一个数据前先锁定这一行,然后确保只有读取到数据 的这个线程可以修改这一行数据。 - 乐观并发控制(OCC)
Elasticsearch所使用的。假设这种情况并不会经常发生,也不会去阻止某一数据的访问。然 而,如果基础数据在我们读取和写入的间隔中发生了变化,更新就会失败。这时候就由程序 来决定如何处理这个冲突。例如,它可以重新读取新数据来进行更新,又或者它可以将这一 情况直接反馈给用户。
乐观并发控制
Elasticsearch是分布式的。当文档被创建、更新或者删除时,新版本的文档就会被复制到集 群中的其他节点上。Elasticsearch即是同步的又是异步的,也就是说复制的请求被平行发送 出去,然后可能会混乱地到达目的地。这就需要一种方法能够保证新的数据不会被旧数据所 覆盖。
我们在上文提到每当有 索引 、 put 和 删除 的操作时,无论文档有没有变化,它 的 _version 都会增加。Elasticsearch使用 _version 来确保所有的改变操作都被正确排序。如 果一个旧的版本出现在新版本之后,它就会被忽略掉。
我们可以利用 _version 的优点来确保我们程序修改的数据冲突不会造成数据丢失。我们可以 按照我们的想法来指定 _version 的数字。如果数字错误,请求就是失败。
下面是一个示例
- 创建一个新的博文
PUT /website/blog/1/_create { "title": "My first blog entry", "text": "Just trying this out..." }
- 首先我们先要得到文档:
{ "_index" : "website", "_type" : "blog", "_id" : "1", "_version" : 1, "found" : true, "_source" : { "title": "My first blog entry", "text": "Just trying this out..." } }
返回结果显示 _version 为 1 :
- 现在,我们试着重新索引文档以保存变化,我们这样指定了 version 的数字:
PUT /website/blog/1?version=1 { "title": "My first blog entry", "text": "Starting to get the hang of this..." }
- 我们只希望当索引中文档的 _version 是 1 时,更新才生效。请求成功相应,返回内容告诉我们 _version 已经变成了 2 :
{ "_index": "website", "_type": "blog", "_id": "1", "_version": 2 "created": false }
- 然而,当我们再执行同样的索引请求,并依旧指定 version=1 时,Elasticsearch就会返回一 个 409 Conflict 的响应码,返回内容如下:
{ "error" : "VersionConflictEngineException[[website][2] [blog][1]: version conflict, current [2], provided [1]]", "status" : 409 }
- 所有的有关于更新或者删除文档的API都支持 version 这个参数,有了它你就通过修改你的程 序来使用乐观并发控制。