目录
1. 悲观并发控制(Pessimistic concurrency control)
2. 乐观并发控制(Optimistic concurrency control)
1、文档概述
通常情况下,对象和文档时等价相通的。不过也有所差别:对象是一个 JSON 结构体——类似于哈希、hashmap、字典或者关联数据;对象中还可能包含其他对象。在Elasticsearch 中,文档特指最顶层结构或者 跟对象序列化成的 JSON数据(以唯一 ID 标识并存储于 Elasticsearch 中)。
元数据
一个文档不只有数据,它还包含了 元数据(metadata)—— 关于文档的信息。三个必须的元数据节点是:
节点 | 说明 |
---|---|
_index | 文档存储的地方 |
_type | 文档代表的对象的类 |
_id | 文档的唯一标识 |
索引(index) —— 相当于关系型数据库里的 数据库 ——它是存储和索引关联数据的地方。
类型(Type)—— 每个文档(对象)都属于某一个类型(type),相同类型(Type)的文档表示相同的“事物" ,因为他们的数据结构也是相同的。相当于数据库中的表。
id 仅仅是一个字符串,它与 _index 和 _type 组合时,就可以在 Elasticsearch 中唯一标识一个文档。当创建一个文档,你可以自定义 _id,也可以让 Elasticsearch 帮你自动生成。
其他元数据
待续
2、索引(存储数据)
文档通过 index API 被索引 —— 使数据可以被存储和搜索。
创建 website索引
curl -H "Content-Type: application/json" -XPUT 'http://10.24.54.241:9200/website' -d '{"settings" : {"number_of_shards" : 2,"number_of_replicas" : 1}}'
自定义 ID
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"
}
对应 命令
curl -H "Content-Type: application/json" -XPUT 'http://10.24.54.241:9200/website/blog/123' -d '{"title": "My first blog entry","text": "Just trying this out...","date": "2014/01/01"}'
结果:
{
"_index":"website",
"_type":"blog",
"_id":"123",
"_version":1,
"result":"created",
"_shards":{
"total":2,
"successful":2,
"failed":0
},
"_seq_no":0,
"_primary_term":1
}
响应指出请求的索引已经被成功创建,这个索引中包含_index
、_type
和_id
元数据,以及一个新元素:_version
。
自增 ID
Elasticsearch 自动生成 ID。请求结构发生了变化:PUT 方法 —— “在这个URL中存储文档” 变成了 POST 方法 —— “在这个类型下存储文档”,通俗讲:原来把文档存储到某个 ID 对应的空间,现在是把这个文档添加到 某个 _type 下。
命令变为
POST /website/blog/
{
"title": "My second blog entry",
"text": "Still trying this out...",
"date": "2014/01/01"
}
改成命令行模式:
curl -H "Content-Type: application/json" -XPOST 'http://10.24.54.241:9200/website/blog' -d '{"title": "My second blog entry","text": "Still trying this out...","date": "2014/01/01"}'
结果:
{
"_index":"website",
"_type":"blog",
"_id":"BBVwkGoB1YPGGlWYBvT-",
"_version":1,
"result":"created",
"_shards":{
"total":2,
"successful":2,
"failed":0
},
"_seq_no":0,
"_primary_term":1
}
_id 为自动生成的值。
3、查询刚才创建的文档
curl -XGET 'http://10.24.54.241:9200/website/blog/123?pretty'
结果:
{
"_index" : "website",
"_type" : "blog",
"_id" : "123",
"_version" : 2,
"_seq_no" : 1,
"_primary_term" : 1,
"found" : true,
"_source" : {
"title" : "My second blog entry",
"text" : "Still trying this out...",
"date" : "2014/01/01"
}
}
_source:包含了创建索引时发送给 Elasticsearch 的原始文档。
_found:找到此文档 true,不存在为 false。
检索文档的一部分
curl -XGET 'http://10.24.54.241:9200/website/blog/123?pretty&_source=title,text'
结果:
{
"_index" : "website",
"_type" : "blog",
"_id" : "123",
"_version" : 2,
"_seq_no" : 1,
"_primary_term" : 1,
"found" : true,
"_source" : {
"text" : "Still trying this out...",
"title" : "My second blog entry"
}
}
_source 数据被过滤了
只要 _source 数据
curl -XGET 'http://10.24.54.241:9200/website/blog/123/_source?pretty'
结果:
{
"title" : "My second blog entry",
"text" : "Still trying this out...",
"date" : "2014/01/01"
}
4、检测文档是否存在
使用 HEAD 请求不会返回响应体,只有 HTTP 头:
curl -i -XHEAD 'http://10.24.54.241:9200/website/blog/123'
结果:
HTTP/1.1 200 OK
content-type: application/json; charset=UTF-8
content-length: 205
不存在的返回结果:
HTTP/1.1 404 Not Found
content-type: application/json; charset=UTF-8
content-length: 61
5、更新文档
文档在 Elasticsearch 中是不可变得 —— 我们不能修改他们。如果需要更新已存在的文档,我们需要重建索引或者替换掉它。
curl -H "Content-Type: application/json" -XPUT 'http://10.24.54.241:9200/website/blog/123' -d '{"title": "Update My first blog entry","text": "Update Just trying this out...","date": "2014/01/01"}'
运行后,再次查询这个记录,如下:
{
"_index" : "website",
"_type" : "blog",
"_id" : "123",
"_version" : 3,
"_seq_no" : 2,
"_primary_term" : 1,
"found" : true,
"_source" : {
"title" : "Update My first blog entry",
"text" : "Update Just trying this out...",
"date" : "2014/01/01"
}
}
_version 自增了
_source 内容改变了。
6、创建一个新文档(如果已存在则不创建)
想要保证文档是新加入的最简单的方式是使用 POST 方法让 Elasticsearch 自动生成唯一 ID。
如果想使用自定义的 ID,我们必须判断此文档是否存在,不存在时才创建,存在时则抛出异常,这里可以使用 op_type 查询参数:
PUT /website/blog/123?op_type=create
{ ... }
或者第二种方法是在URL后加/_create
做为端点:
PUT /website/blog/123/_create
{ ... }
如果请求成功的创建了一个新文档,Elasticsearch将返回正常的元数据且响应状态码是201 Created
。
另一方面,如果包含相同的_index
、_type
和_id
的文档已经存在,Elasticsearch将返回409 Conflict
响应状态码,错误信息类似如下:
{
"error" : "DocumentAlreadyExistsException[[website][4] [blog][123]:
document already exists]",
"status" : 409
}
7、删除文档
使用 DELETE 方法:
curl -XDELETE 'http://10.24.54.241:9200/website/blog/124'
8、版本控制(乐观锁)
冲突问题
例如:我们使用 Elasticsearch 存储大量在线商店的库存信息,每当销售一个商品,Elasticsearch 中的库存就要减一。如果两个同时运行的 Web 进程,两者同时减一件商品的库存:
如图,就可能造成库存减一,实际卖了两件。
在传统数据库中有两种方式解决这种问题:
1. 悲观并发控制(Pessimistic concurrency control)
在关系型数据库中典型的例子是在读一行数据前锁定这行,然后确保只有加锁的那个线程可以修改这行数据。
2. 乐观并发控制(Optimistic concurrency control)
被 Elasticsearch 使用,假如冲突不经常发生,如果在读写过程中数据发生了变化,更新操作将失败。这时候由程序决定在失败后如何解决冲突。实际情况中,可以重新尝试更新,刷新数据或者直接返回给用户。
乐观并发控制
Elasticsearch 是分布式的。当文档被创建、更新或删除,文档的新版本会被复制到集群的其他节点。
Elasticsearch 即是同步的又是异步的,意思是这些复制请求都是平行发送的,并无序的到达目的地。这就需要一种方法确保老版本的文档永远不会覆盖新的版本。
Elasticsearch 使用 _version 保证所有修改都被正确排序。当一个旧版本出现在新版本之后,它会被简单的忽略。
第 6 创建一个新文档 章节,已经讲了如何指定版本已经存在则会报错,这时就要获取新的文档版本号,重试更新库存。
9、文档局部更新
更新文档部分内容,例如新增字段:
curl -H "Content-Type: application/json" -XPOST 'http://10.24.54.241:9200/website/blog/123/_update' -d '{"doc": {"title": "doc title","tags": ["tag1"],"views": 0}}'
更新后结果:
{
"_index" : "website",
"_type" : "blog",
"_id" : "123",
"_version" : 4,
"_seq_no" : 3,
"_primary_term" : 2,
"found" : true,
"_source" : {
"title" : "doc title",
"text" : "Update Just trying this out...",
"date" : "2014/01/01",
"views" : 0,
"tags" : [
"tag1"
]
}
}
更新了 title 字段;
新增了 tags、views字段。
10、检索多个文档(Mget)
合并多个请求可以避免每个请求单独的网络开销。
POST /_mget
{
"docs" : [
{
"_index" : "website",
"_type" : "blog",
"_id" : 2
},
{
"_index" : "website",
"_type" : "pageviews",
"_id" : 1,
"_source": "views"
}
]
}
11、批量操作
mget
允许我们一次性检索多个文档一样,bulk
API允许我们使用单一请求来实现多个文档的create
、index
、update
或delete
。这对索引类似于日志活动这样的数据流非常有用,它们可以以成百上千的数据为一个批次按序进行索引。
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
删除一个文档。见《删除文档》
例子:
在 linux 进入某个目录下,创建一个 bulk.json 文件,文件内容为:
{ "update": { "_index": "website", "_type": "blog", "_id": "123", "_retry_on_conflict" : 3}}
{ "doc" : {"title" : "My updated blog post"} }
运行命令:
curl -i -H "Content-Type: application/json" -XPOST 'http://10.24.54.241:9200/website/blog/_bulk?pretty' --data-binary @bulk.json
批量运行后的返回值:
HTTP/1.1 200 OK
Warning: 299 Elasticsearch-6.7.2-56c6e48 "Deprecated field [_retry_on_conflict] used, expected [retry_on_conflict] instead"
content-type: application/json; charset=UTF-8
content-length: 412
{
"took" : 191,
"errors" : false,
"items" : [
{
"update" : {
"_index" : "website",
"_type" : "blog",
"_id" : "123",
"_version" : 5,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 4,
"_primary_term" : 2,
"status" : 200
}
}
]
}
文档更新后的结果:
{
"_index" : "website",
"_type" : "blog",
"_id" : "123",
"_version" : 5,
"_seq_no" : 4,
"_primary_term" : 2,
"found" : true,
"_source" : {
"title" : "My updated blog post",
"text" : "Update Just trying this out...",
"date" : "2014/01/01",
"views" : 0,
"tags" : [
"tag1"
]
}
}
title的值改变了。