数据
对象(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)的文档表示相同的“事物”,因为他们的数据结构也是相同的。(相当于表)
_id
id仅仅是一个字符串,它与_index和_type组合时,就可以在Elasticsearch中唯一标识一个文档。当创建一个文档,你可以自定义_id,也可以让Elasticsearch帮你自动生成。
索引
索引一个文档
文档通过其_index、_type、_id唯一确定。们可以自己提供一个_id,或者也使用index API 为我们生成一个。
使用自己的ID
PUT /{index}/{type}/{id}
{
"field": "value",
...
}
Elasticsearch中每个文档都有版本号,每当文档变化(包括删除)都会使_version增加。
自增ID
如果我们的数据没有自然ID,我们可以让Elasticsearch自动为我们生成。请求结构发生了变化:PUT方法——“在这个URL中存储文档”变成了POST方法——”在这个类型下存储文档”。
POST /website/blog/
{
"title": "My second blog entry",
"text": "Still trying this out...",
"date": "2014/01/01"
}
自动生成的ID有22个字符长,URL-safe, Base64-encoded string universally unique identifiers, 或者叫 UUIDs。
获取
检索文件
GET /website/blog/123?pretty
pretty:在任意的查询字符串中增加pretty参数,类似于上面的例子。会让Elasticsearch美化输出(pretty-print)JSON响应以便更加容易阅读。_source字段不会被美化,它的样子与我们输入的一致。
检索文档的一部分
GET请求将返回文档存储在_source参数中的全部。但只想得到source中若干字段。多个字段可以使用逗号分隔:
GET /website/blog/123?_source=title,text
只得到_source字段而不要其他的元数据:
GET /website/blog/123/_source
存在
检查文档是否存在
使用head方法来代替get方法,此时返回的结果只有HTTP头,没有具体的内容。curl -i -XHEAD http://localhost:9200/website/blog/123
更新整个文档
如果需更新文档,那么我们需要使用index API的reindex或者直接替换。
PUT /website/blog/123
{
"title": "My second blog entry",
"text": "Still trying this out...",
"date": "2014/01/01"
}
注:返回结果中version加一、create为false。
更新操作步骤:
- 从旧的数据检索出文档
- 修改他
- 删除旧的文档
- 索引新的文档
创建一个新的文档
如何保证新建的数据而不是覆盖原来的数据。
- 使用POST方法,以自增id的方式,创建文档。
- 如果使用指定id的当时的话,需要告诉es只有index、type和id不一样时才接受请求。有以下两种方法。
- 使用op_type指定
PUT /website/blog/123?op_type=create
- 使用_create作为后缀。
PUT /website/blog/123/_create
- 使用op_type指定
删除文档
语法如下: DELETE /website/blog/123
同样delete操作会导致version加一。
版本控制
处理冲突
当多个人修改同一文档时,就会发生冲突。比如商品减一操作。
有两种方法保证并发更新的时候不丢失。
悲观并发控制(Pessimistic concurrency control)
这在关系型数据库中被广泛的使用,假设冲突的更改经常发生,为了解决冲突我们把访问区块化。典型的例子是在读一行数据前锁定这行,然后确保只有加锁的那个线程可以修改这行数据。
乐观并发控制(Optimistic concurrency control)
被Elasticsearch使用,假设冲突不经常发生,也不区块化访问,然而,如果在读写过程中数据发生了变化,更新操作将失败。这时候由程序决定在失败后如何解决冲突。实际情况中,可以重新尝试更新,刷新数据(重新读取)或者直接反馈给用户。
Elasticsearch是分布式的。当文档被创建、更新或删除,文档的新版本会被复制到集群的其它节点。Elasticsearch即是同步的又是异步的,意思是这些复制请求都是平行发送的,并无序(out of sequence)的到达目的地。这就需要一种方法确保老版本的文档永远不会覆盖新的版本。
我们利用_version的这一优点确保数据不会因为修改冲突而丢失。我们可以指定文档的version来做想要的更改。如果那个版本号不是现在的,我们的请求就失败了。(类似于CAS)
使用外部版本控制系统
一种常见的结构是使用一些其他的数据库做为主数据库,然后使用Elasticsearch搜索数据,这意味着所有主数据库发生变化,就要将其拷贝到Elasticsearch中。如果有多个进程负责这些数据的同步,就会遇到上面提到的并发问题。
如果主数据库有版本字段——或一些类似于timestamp等可以用于版本控制的字段——是你就可以在Elasticsearch的查询字符串后面添加version_type=external
来使用这些版本号。版本号必须是整数,大于零小于9.2e+18——Java
中的正的long。
外部版本号与之前说的内部版本号在处理的时候有些不同。它不再检查_version是否与请求中指定的一致,而是检查是否小于指定的版本。如果请求成功,外部版本号就会被存储到_version中。
PUT /website/blog/2?version=5&version_type=external
{
"title": "My first external blog entry",
"text": "Starting to get the hang of this..."
}
文档局部更新
最简单的update请求表单接受一个局部文档参数doc,它会合并到现有文档中——对象合并在一起,存在的标量字段被覆盖,新字段被添加。
POST /website/blog/1/_update
{
"doc" : {
"tags" : [ "testing" ],
"views": 0
}
}
使用脚本局部更新
默认的脚本语言是Groovy,一个快速且功能丰富的脚本语言,语法类似于Javascript。它在一个沙盒(sandbox)中运行,以防止恶意用户毁坏Elasticsearch或攻击服务器。
脚本能够使用update API改变_source字段的内容,它在脚本内部以ctx._source表示。例如,我们可以使用脚本增加博客的views数量:
POST /website/blog/1/_update
{
"script" : "ctx._source.views+=1"
}
我们还可以使用脚本增加一个新标签到tags数组中。在这个例子中,我们定义了一个新标签做为参数而不是硬编码在脚本里。这允许Elasticsearch未来可以重复利用脚本,而不是在想要增加新标签时必须每次编译新脚本:
POST /website/blog/1/_update
{
"script" : "ctx._source.tags+=new_tag",
"params" : {
"new_tag" : "search"
}
}
通过设置ctx.op为delete我们可以根据内容删除文档:
POST /website/blog/1/_update
{
"script" : "ctx.op = ctx._source.views == count ? 'delete' : 'none'",
"params" : {
"count": 1
}
}
更新可能不存在的文档
使用upsert参数来执行script使其不存在时被创建
POST /website/pageviews/1/_update
{
"script" : "ctx._source.views+=1",
"upsert": {
"views": 1
}
}
注意脚本默认被禁用。
解决的方法是:
在安装目录下config文件夹找到elasticsearch.yml文件,然后启用:
script.inline: true script.indexed: true
- 重启
再次运行上面的更新命令就可以成功。
更新和冲突
增加的顺序我们并不关心。
可以通过retry_on_conflict参数设置重试次数来自动完成,这样update操作将会在发生错误前重试——这个值默认为0。
POST /website/pageviews/1/_update?retry_on_conflict=5 <1> { "script" : "ctx._source.views+=1", "upsert": { "views": 0 } }
顺序非常重要的情况。例如index API,使用“保留最后更新(last-write-wins)”的update API,但它依旧接受一个version参数以允许你使用乐观并发控制(optimistic concurrency control)来指定你要更新文档的版本。
Mget
检索多个文档
- mget API参数是一个docs数组,数组的每个节点定义一个文档的_index、_type、_id元数据。如果你只想检索一个或几个确定的字段,也可以定义一个_source参数:
POST /_mget
{
"docs" : [
{
"_index" : "website",
"_type" : "blog",
"_id" : 2
},
{
"_index" : "website",
"_type" : "pageviews",
"_id" : 1,
"_source": "views"
}
]
}
这样的响应与单独使用get request响应体相同。
- 如果你想检索的文档在同一个_index中(甚至在同一个_type中),你就可以在URL中定义一个默认的/_index或者/_index/_type。你依旧可以在单独的请求中使用这些值:
POST /website/blog/_mget
{
"docs" : [
{ "_id" : 2 },
{ "_type" : "pageviews", "_id" : 1 }
]
}
- 如果所有文档具有相同_index和_type
POST /website/blog/_mget
{
"ids" : [ "2", "1" ]
}
批量操作
bulk请求体如下:
{ action: { metadata }}\n
{ request body }\n
{ action: { metadata }}\n
{ request body }\n
...
这种格式类似于用”\n”符号连接起来的一行一行的JSON文档流(stream)。两个重要的点需要注意:
- 每行必须以”\n”符号结尾,包括最后一行。这些都是作为每行有效的分离而做的标记。
- 每一行的数据不能包含未被转义的换行符,它们会干扰分析——这意味着JSON不能被美化打印。
action/metadata这一行定义了文档行为(what action)发生在哪个文档(which document)之上。
行为(action)必须是以下几种:create、index、update、delete。
注意
- delete行为(action)没有请求体,它紧接着另一个行为(action)
- 记得最后一个换行符
每个子请求都被独立的执行,所以一个子请求的错误并不影响其它请求。如果任何一个请求失败,顶层的error标记将被设置为true,然后错误的细节将在相应的请求中被报告
注意
bulk请求不是原子操作——它们不能实现事务。每个请求操作时分开的,所以每个请求的成功与否不干扰其它操作。
参考资料:
Elasticsearch权威指南
备注:
转载请注明出处:http://blog.csdn.net/wsyw126/article/details/70761698
作者:WSYW126