原创

Elasticsearch基础(五)——Document并发控制

我们知道,在数据库中,如果多个客户端线程同时对一条记录进行增删改查,数据库会有锁机制保证并发访问的数据一致性。在Elasticsearch中,document就表示一条数据记录,那么Elasticsearch是如何对document的并发访问进行控制的呢?

本章,我们就以一个电商下单的案例为背景,介绍document的并发控制机制。

一、案例背景

我们现在有一个商品管理系统,底层采用Elasticsearch存储商品信息(包括商品ID、商品库存等)。现在,用户需要进行下单操作,流程如下:

  1. 用户浏览商品,此时发送请求到商品管理系统,查询商品信息;
  2. 用户下单购买;
  3. 支付成功后,发送请求到商品管理系统,扣减商品库存(库存-1)。

二、version版本号

Elasticsearch内部采用了版本号机制对document的并发修改进行控制 。所谓版本号,本质是一种乐观锁。

3.1 _version

我们往Elasticsearch写入document数据时,ES会自动为document生成一个版本号_version,比如我们插入一条id为6的数据,返回结果里的_version就表示这条数据的版本号:

PUT /test_index/test_type/6
{
  "test_field": "test test"
}

返回:
{
  "_index": "test_index",
  "_type": "test_type",
  "_id": "6",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "created": true
}

第一次创建document时,它的_version版本号是1;以后,每次对这个document修改或删除时,_version版本号会自动加1(哪怕是删除,也会对这条数据的版本号加1)。

3.2 版本控制

我们来看下ES的数据版本控制流程,整个业务执行流程如下:

  1. 假设我们现在有两个线程A和B,同时读取到了商品信息,此时获取到的document的_version版本号是相同的,假设都是1;
  2. A线程在本地将库存减1后,发送PUT更新请求,请求里带上了版本号PUT /product/storage/1?version=1
  3. ES收到请求后,对数据进行更新,然后将版本号+1;
  4. B线程在本地将库存减1后,发送PUT更新请求,请求里也带上了版本号1,PUT /product/storage/1?version=1
  5. ES收到请求后,发现id为1的document的版本号已经是2了,而B线程的更新请求里带的版本号还是1,两者不一样,于是拒绝更新,返回报错。

3.3 external version

Elasticsearch还提供了一个外部版本号的功能,就是说可以不用它提供的内部_version版本号来进行并发控制,可以基于你自己维护的一个版本号来进行并发控制。

内部版本语法:

?version=1

自定义版本号语法:

?version=1&version_type=external

注意,当version_type=external的时,只有当提供的version比Elasticsearch中的_version大的时候,才能完成修改。

我们来看个例子,比如现在写入了这样一条记录,,可以看到_version版本号是1:

PUT /test_index/test_type/8
{
  "test_field": "test"
}

{
  "_index": "test_index",
  "_type": "test_type",
  "_id": "8",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "created": true
}

然后,A线程先进行修改,带上了外部版本号2:

PUT /test_index/test_type/8?version=2&version_type=external
{
  "test_field": "test client 1"
}

{
  "_index": "test_index",
  "_type": "test_type",
  "_id": "8",
  "_version": 2,
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "created": false
}

接着,B线程也进行修改,带上了外部版本号2,可以看到报错了,因为当version_type=external的时,只有当提供的version比Elasticsearch中的_version大的时候,才能完成修改:

PUT /test_index/test_type/8?version=2&version_type=external
{
  "test_field": "test client 2"
}

{
  "error": {
    "root_cause": [
      {
        "type": "version_conflict_engine_exception",
        "reason": "[test_type][8]: version conflict, current version [2] is higher or equal to the one provided [2]",
        "index_uuid": "6m0G7yx7R1KECWWGnfH1sw",
        "shard": "1",
        "index": "test_index"
      }
    ],
    "type": "version_conflict_engine_exception",
    "reason": "[test_type][8]: version conflict, current version [2] is higher or equal to the one provided [2]",
    "index_uuid": "6m0G7yx7R1KECWWGnfH1sw",
    "shard": "1",
    "index": "test_index"
  },
  "status": 409
}

三、总结

本章,我们讲解了document的并发控制原理,其核心就是基于内部的版本号机制,下一章,我们来看下document数据的路由原理。

正文到此结束
本文目录