数据吞吐
无论程序怎么写,意图是一样的:组织数据为我们的目标所服务。但数据并不只是由随机比特和字节组成,我们在数据节点间建立关联来表示现实世界中的实体或者“某些东西”。属于同一个人的名字和Email地址会有更多的意义。
在现实世界中,并不是所有相同类型的实体看起来都是一样的。一个人可能有一个家庭电话号码,另一个人可能只有一个手机号码,有些人可能两者都有。一个人可能有三个Email地址,其他人可能没有。西班牙人可能有两个姓氏,但是英国人(英语系国家的人)可能只有一个。
面向对象编程语言流行的原因之一,是我们可以用对象来表示和处理现实生活中那些有着潜在关系和复杂结构的实体。到目前为止,这种方式还不错。
但当我们想存储这些实体时问题便来了。传统上,我们以行和列的形式把数据存储在关系型数据库中,相当于使用电子表格。这种固定的存储方式导致对象的灵活性不复存在了。
但是如何能以对象的形式存储对象呢?相对于围绕表格去为我们的程序去建模,我们可以专注于使用数据,把对象本来的灵活性找回来。
对象(object)是一种语言相关,记录在内存中的的数据结构。为了在网络间发送,或者存储它,我们需要一些标准的格式来表示它。JSON (JavaScript Object Notation)是一种可读的以文本来表示对象的方式。它已经成为NoSQL世界中数据交换的一种事实标准。当对象被序列化为JSON,它就成为JSON文档(JSON document)了。
Elasticsearch是一个分布式的文档(document)存储引擎。它可以实时存储并检索复杂数据结构——序列化的JSON文档。换言说,一旦文档被存储在Elasticsearch中,它就可以在集群的任一节点上被检索。
当然,我们不仅需要存储数据,还要快速的批量查询。虽然已经有很多NoSQL的解决方案允许我们以文档的形式存储对象,但它们依旧需要考虑如何查询这些数据,以及哪些字段需要被索引以便检索时更加快速。
在Elasticsearch中,每一个字段的数据都是默认被索引的。也就是说,每个字段专门有一个反向索引用于快速检索。而且,与其它数据库不同,它可以在同一个查询中利用所有的这些反向索引,以惊人的速度返回结果。
在这一章我们将探讨如何使用API来创建、检索、更新和删除文档。目前,我们并不关心数据如何在文档中以及如何查询他们。所有我们关心的是文档如何安全在Elasticsearch中存储,以及如何让它们返回。
什么是文档?
程序中大多的实体或对象能够被序列化为包含键值对的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