前言
传统上, 我们以行和列的形式把数据存储在关系型数据库中, 相当于使用电子表格。 这种固定的存储方式导致对象的灵活性不复存在了。
对象(object)是一种语言相关, 记录在内存中的的数据结构。 为了在网络间发送, 或者存储它, 我们需要一些标准的格式来表示它。 JSON (JavaScript Object Notation)是一种可读的以文本来表示对象的方式。 它已经成为NoSQL世界中数据交换的一种事实标准。 当对象被序列化为JSON, 它就成为JSON文档(JSON document)了。
Elasticsearch是一个分布式的文档(document)存储引擎。 它可以实时存储并检索复杂数据结构——序列化的JSON文档。 换言说, 一旦文档被存储在Elasticsearch中, 它就可以在集群的任一节点上被检索。在Elasticsearch中, 每一个字段的数据都是默认被索引的。 也就是说, 每个字段专门有一个反向索引用于快速检索。 而且, 与其它数据库不同, 它可以在同一个查询中利用所有的这些反向索引, 以惊人的速度返回结果。
文档
程序中大多的实体或对象能够被序列化为包含键值对的JSON对象, 键(key)是字段(field)或属性(property)的名字, 值(value)可以是字符串、 数字、 布尔类型、 另一个对象、 值数组或者其他特殊类型。
通常, 我们可以认为对象(object)和文档(document)是等价相通的。 不过, 他们还是有所差别: 对象(Object)是一个JSON结构体——类似于哈希、 hashmap、 字典或者关联数组; 对象(Object)中还可能包含其他对象(Object)。 在Elasticsearch中, 文档(document)这个术语有着特殊含义。 它特指最顶层结构或者根对象(root object)序列化成的JSON数据( 以唯一ID标识并存储于Elasticsearch中) 。
一个文档中主要有数据和元数据两大部分。 元数据(metadata)是关于文档的信息。 元数据中三个必须的(重要的)元数据节点是:
节点 | 说明 |
_index | 文档存储的地方 |
_type | 文档代表的对象的类 |
_id | 文档的唯一标识 |
_index:索引(index)类似于关系型数据库里的“数据库”——它是我们存储和索引关联数据的地方。事实上, 我们的数据被存储和索引在分片(shards)中, 索引只是一个把一个或多个分片分组在一起的逻辑空间。 然而, 这只是一些内部细节——我们的程序完全不用关心分片。 对于我们的程序而言, 文档存储在索引(index)中。 剩下的细节由Elasticsearch关心既可。
_type:在Elasticsearch中, 我们使用相同类型(type)的文档表示相同的“事物”, 因为他们的数据结构也是相同的。每个类型(type)都有自己的映射(mapping)或者结构定义, 就像传统数据库表中的列一样。 所有类型下的文档被存储在同一个索引下, 但是类型的映射(mapping)会告诉Elasticsearch不同的文档如何被索引。
_id:id仅仅是一个字符串, 它与 _index 和 _type 组合时, 就可以在Elasticsearch中唯一标识一个文档。 当创建一个文档, 你可以自定义 _id , 也可以让Elasticsearch帮你自动生成。
添加文档
文档通过 index API被索引——使数据可以被存储和搜索。 但是首先我们需要决定文档所在,文档通过其 _index 、 _type 、 _id 唯一确定。要在索引中添加文档,通常可以通过指定ID和不指定ID两种形式。
指定ID添加文档
例如我们的索引叫做 “website” , 类型叫做 “blog” , 我们选择的ID是 “123” , 那么这个索引请求就像这样:
#指定添加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 字段变成了自动生成的值,自动生成的ID有22个字符长, 叫URL-safe, Base64-encoded string universally unique identifiers, 即 UUIDs :
{
"_index": "website",
"_type": "blog",
"_id": "wM0OSFhDQXGZAWDf0-drSA",
"_version": 1,
"created": true
}
检索文档
获取文档全部内容
想要从Elasticsearch中获取文档, 我们使用同样的 _index 、 _type 、 _id , 但是HTTP方法改为 GET :
GET /website/blog/123?pretty
注:在任意的查询字符串中增加 pretty 参数, 类似于上面的例子。 会让Elasticsearch美化输出(pretty-print)JSON响应以便更加容易阅读。
响应包含了现在熟悉的元数据节点, 增加了 _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"
}
}
GET请求返回的响应内容包括 {"found": true} 。 这意味着文档已经找到。 如果我们请求一个不存在的文档, 依旧会得到一个JSON, 不过 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 :
HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=UTF-8
Content-Length: 0
更新文档
如果需要更新已存在的文档, 我们可以使用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
}
在内部, 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将返回正常的元数据且响应状态码是 201Created 。另一方面, 如果包含相同的 _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将会在你之后添加更多索引的时候才会在后台进行删除内容的清理。
小结
现在你知道如何把Elasticsearch当作一个分布式的文件存储了。 你可以存储、 更新、 检索和删除它们, 而且你知道如何安全的进行这一切。 这确实非常非常有用, 尽管我们还没有看到更多令人激动的特性, 例如如何在文档内搜索。