1.处理冲突
当使用 index API更新文档的时候,我们读取原始文档,做修改,然后将整个文档(whole document)一次性重新索引。
最近的索引请求会生效——Elasticsearch中只存储最后被索引的任何文档。如果其他人同时也修改了这个文档,他们的修改将会丢失。
很多时候,这不是问题。也许我们的主数据存储是关系数据库,我们只是将数据复制到Elasticsearch中以使其可搜索。
也许两个人几乎不可能同时更改同一个文档。或者,如果偶尔失去变化,对我们的业务来说也许并不重要。
但有时失去变化非常重要。想象一下,我们正在使用Elasticsearch来存储我们在线商店中库存的小部件数量。每次我们出售小部件时,我们都会减少Elasticsearch中的库存数量。
有一天,管理层决定进行销售。突然间,我们每秒都在销售几个小部件。想象一下,两个并行运行的Web进程,每个进程都处理一个小部件的销售,如下图显示没有并发控制的结果。
在数据库领域,通常使用两种方法来确保在并发更新时不会丢失更改:
- 悲观的并发控制
- 这种方法广泛用于关系数据库,它假定可能发生冲突的更改,因此阻止对资源的并发访问以防止冲突。
- 一个典型的例子是在读取数据之前锁定一行,确保只有放置锁的线程才能对该行中的数据进行更改。
- 乐观的并发控制
- 由Elasticsearch使用, 这种方法假定冲突不太可能发生,并且不会阻止尝试操作。
- 但是,如果在读取和写入之间修改了基础数据,则更新将失败。然后由应用程序决定如何解决冲突。例如,它可以使用新数据重新尝试更新,或者可以向用户报告情况。
2.乐观并发控制
Elasticsearch是分布式的。
如果要创建,更新或删除新版本的文档,则必须将其复制到群集中的其他节点。
Elasticsearch也是异步和并发的,这意味着这些复制请求是并行发送的,并且可能不按顺序到达目的地。
Elasticsearch需要一种方法来确保旧版本的文档永远不会覆盖较新的版本。
当我们之前讨论过index
,get
和delete
请求时,我们指出每个文档都有一个_version
数字,只要文档被更改,该数字就会递增。
Elasticsearch使用此_version
数字来确保以正确的顺序应用更改。
如果在新版本之后旧版本文档到达,则可以简单地忽略它。
我们可以利用这个_version
数字来确保 我们的应用程序所做的冲突的更改不会导致数据丢失。
我们希望通过指定更改的文档的version编号来完成此操作。如果该版本不再是最新版本,我们的请求将失败。
让我们创建一个新的博客文章:
curl -X PUT "localhost:9200/website/blog/1/_create" -H 'Content-Type: application/json' -d'
{
"title": "My first blog entry",
"text": "Just trying this out..."
}
'
响应正文告诉我们这个新创建的文档_version
为1
。现在假设我们要编辑文档:我们将其数据加载到Web表单中,进行更改,然后保存新版本。
首先我们检索文档:
curl -X GET "localhost:9200/website/blog/1"
响应正文包含相同的版本_version
1
:
{
"_index" : "website",
"_type" : "blog",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"title": "My first blog entry",
"text": "Just trying this out..."
}
}
现在,当我们尝试通过重新索引文档来保存更改时,我们指定此次修改对应的版本号version
:
curl -X PUT "localhost:9200/website/blog/1?version=1" -H 'Content-Type: application/json' -d'
{
"title": "My first blog entry",
"text": "Starting to get the hang of this..."
}
'
此请求成功(当前此文档的版本为1),响应正文告诉我们_version
已增加到2(因为数据更新了)
:
{
"_index": "website",
"_type": "blog",
"_id": "1",
"_version": 2
"created": false
}
但是,如果我们要重新运行相同的索引请求,仍然指定 version=1
,Elasticsearch将使用409 Conflict
HTTP响应代码进行响应,响应内容大致如下:
{
"error": {
"root_cause": [
{
"type": "version_conflict_engine_exception",
"reason": "[blog][1]: version conflict, current [2], provided [1]",
"index": "website",
"shard": "3"
}
],
"type": "version_conflict_engine_exception",
"reason": "[blog][1]: version conflict, current [2], provided [1]",
"index": "website",
"shard": "3"
},
"status": 409
}
这告诉我们Elasticsearch中当前的文档的_version
编号是2
,但我们指定更新版本为1
。
我们现在所做的工作取决于我们的应用要求。我们可以告诉用户其他人已经对文档进行了更改,并在尝试再次保存之前查看更改。或者,与stock_count
先前窗口小部件的情况一样,我们可以检索最新文档并尝试重新应用更改。
更新或删除文档的所有修改API都接受一个version
参数,该参数允许您将乐观并发控制应用于代码中有需要的部分。
3.使用外部系统中的版本
常见的设置是使用其他数据库作为主数据存储,使用Elasticsearch使数据可搜索, 这意味着对主数据库的所有更改都需要在发生时复制到Elasticsearch。
如果多个进程负责此数据同步,则可能会遇到类似于前面描述的并发问题。
如果您的主数据库已经具有版本号 - 或者诸如 timestamp
可以用作版本号的值 - 那么您可以通过添加version_type=external
到查询字符串来在Elasticsearch中重用这些相同的版本号。
版本号必须是大于零且小于9.2e+18的整数 - 在Java中为正值
long
。
处理外部版本号的方式与我们之前讨论的内部版本号略有不同。
Elasticsearch检查当前是否小于指定版本,而不是检查当前_version
是否与请求中指定的当前相同。如果请求成功,则外部版本号将存储为文档的新版本。_version
外部版本号不仅可以在索引和删除请求中指定,还可以在创建新文档时指定。
例如,要创建一个外部版本号为5的新博客帖子,我们可以执行以下操作:
curl -X PUT "localhost:9200/website/blog/2?version=5&version_type=external" -H 'Content-Type: application/json' -d'
{
"title": "My first external blog entry",
"text": "Starting to get the hang of this..."
}
'
在回复中,我们可以看到当前的_version
数字是5
:
{
"_index": "website",
"_type": "blog",
"_id": "2",
"_version": 5,
"created": true
}
现在我们更新这个文档,指定一个新的version
数字10
:
curl -X PUT "localhost:9200/website/blog/2?version=10&version_type=external" -H 'Content-Type: application/json' -d'
{
"title": "My first external blog entry",
"text": "This is a piece of cake..."
}
'
请求成功并将当前设置_version
为10
:
{
"_index": "website",
"_type": "blog",
"_id": "2",
"_version": 10,
"created": false
}
如果您要重新运行此请求,它将产生版本冲突错误,因为指定的外部版本号不高于Elasticsearch中的当前版本。