ElasticSearch 6.x 学习笔记:9.版本控制

2022-05-06 19:09:58 浏览数 (1)

9.1 冲突处理

如果两个线程同时修改一个文档,这时就会发生冲突。

比如某件商品存货100件,用户1下单买走1件,剩余99件;与此同时用户2也下单买走1件,但是用户2不知道用户1已经下单,看到剩余商品仍然是99件。这样造成系统中显示商品总数比实际数量要多,这种情况在商业系统中肯定是不能容忍的。

在数据库领域中,有两种方法通常被用来确保并发更新时变更不会丢失: 1、悲观并发控制 这种方法被关系型数据库广泛使用,它假定有变更冲突可能发生,因此阻塞访问资源以防止冲突。 一个典型的例子是读取一行数据之前先将其锁住,确保只有放置锁的线程能够对这行数据进行修改。 2、乐观并发控制 Elasticsearch 中使用的这种方法假定冲突是不可能发生的,并且不会阻塞正在尝试的操作。 然而,如果源数据在读写当中被修改,更新将会失败。应用程序接下来将决定该如何解决冲突。 例如,可以重试更新、使用新的数据、或者将相关情况报告给用户。

9.2 乐观并发控制

当我们之前讨论 index , GET 和 delete 请求时,我们指出每个文档都有一个 _version (版本)号,当文档被修改时版本号递增。 Elasticsearch 使用这个 _version 号来确保变更以正确顺序得到执行。如果旧版本的文档在新版本之后到达,它可以被简单的忽略。

我们可以利用 _version 号来确保 应用中相互冲突的变更不会导致数据丢失。我们通过指定想要修改文档的 version 号来达到这个目的。 如果该版本不是当前版本号,我们的请求将会失败。

代码语言:javascript复制
PUT website
代码语言:javascript复制
{
  "acknowledged": true,
  "shards_acknowledged": true,
  "index": "website"
}

添加一个文档

代码语言:javascript复制
PUT website/blog/1/_create
{
  "title": "My first blog entry",
  "text":  "Just trying this out..."
}
代码语言:javascript复制
{
  "_index": "website",
  "_type": "blog",
  "_id": "1",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 2,
    "failed": 0
  },
  "created": true
}

响应体告诉我们,这个新创建的文档 _version 版本号是 1 。 现在假设我们想编辑这个文档:我们加载其数据到 web 表单中, 做一些修改,然后保存新的版本。

代码语言:javascript复制
GET website/blog/1
代码语言:javascript复制
{
  "_index": "website",
  "_type": "blog",
  "_id": "1",
  "_version": 1,
  "found": true,
  "_source": {
    "title": "My first blog entry",
    "text": "Just trying this out..."
  }
}

现在,当我们尝试通过重建文档的索引来保存修改,我们指定 version 为我们的修改会被应用的版本

代码语言:javascript复制
PUT /website/blog/1?version=1 
{
  "title": "My first blog entry",
  "text":  "Starting to get the hang of this..."
}

此请求成功,并且响应体告诉我们 _version 已经递增到 2 :

代码语言:javascript复制
{
  "_index": "website",
  "_type": "blog",
  "_id": "1",
  "_version": 2,
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 2,
    "failed": 0
  },
  "created": false
}

然而,如果我们重新运行相同的索引请求,仍然指定 version=1 , Elasticsearch 返回 409 Conflict HTTP 响应码,和一个如下所示的响应体:

代码语言:javascript复制
{
  "error": {
    "root_cause": [
      {
        "type": "version_conflict_engine_exception",
        "reason": "[blog][1]: version conflict, current version [2] is different than the one provided [1]",
        "index_uuid": "ekHPTnDgRH63lHUpvxqQBA",
        "shard": "3",
        "index": "website"
      }
    ],
    "type": "version_conflict_engine_exception",
    "reason": "[blog][1]: version conflict, current version [2] is different than the one provided [1]",
    "index_uuid": "ekHPTnDgRH63lHUpvxqQBA",
    "shard": "3",
    "index": "website"
  },
  "status": 409
}

响应体告诉我们在 Elasticsearch 中这个文档的当前 _version 号是 2 ,但我们指定的更新版本号为 1

我们现在怎么做取决于我们的应用需求。我们可以告诉用户说其他人已经修改了文档,并且在再次保存之前检查这些修改内容。 或者,在之前的商品 stock_count 场景,我们可以获取到最新的文档并尝试重新应用这些修改。

所有文档的更新或删除 API,都可以接受 version 参数,这允许你在代码中使用乐观的并发控制,这是一种明智的做法。

9.3 通过外部系统使用版本控制

一个常见的设置是使用其它数据库作为主要的数据存储,使用 Elasticsearch 做数据检索, 这意味着主数据库的所有更改发生时都需要被复制到 Elasticsearch

Elasticsearch 中通过增加 version_type=external 方式指定外部版本号,如果外部版本号是否大于当前文档版本,则可以执行更新操作。

例如,要更新一个新的具有外部版本号 5 的博客文章,我们可以按以下方法进行:

代码语言:javascript复制
PUT /website/blog/2?version=5&version_type=external
{
  "title": "My first external blog entry",
  "text":  "Starting to get the hang of this..."
}

在响应中,我们能看到当前的 _version 版本号是 5

代码语言:javascript复制
{
  "_index": "website",
  "_type": "blog",
  "_id": "2",
  "_version": 5,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 2,
    "failed": 0
  },
  "created": true
}

当我们再次执行上面的外部版本号更新时,报错。因为外部版本号不大于当前版本号5.

代码语言:javascript复制
{
  "error": {
    "root_cause": [
      {
        "type": "version_conflict_engine_exception",
        "reason": "[blog][2]: version conflict, current version [5] is higher or equal to the one provided [5]",
        "index_uuid": "ekHPTnDgRH63lHUpvxqQBA",
        "shard": "2",
        "index": "website"
      }
    ],
    "type": "version_conflict_engine_exception",
    "reason": "[blog][2]: version conflict, current version [5] is higher or equal to the one provided [5]",
    "index_uuid": "ekHPTnDgRH63lHUpvxqQBA",
    "shard": "2",
    "index": "website"
  },
  "status": 409
}

0 人点赞