Elasticsearch并发控制

悲观锁

悲观锁,就是各种情况下,都上锁,上锁之后,就只有一个线程可以操作这一条数据了,当然,不同的场景下,上的锁不同,有行级锁,表级锁,读锁,写锁。

乐观锁

乐观锁,就是不加锁,通过版本号来控制。

比如线程 A 去读取库存,发现库存为 100 件,同时线程 B 也去读取库存,发现库存同样是 100 件。

线程 A 消耗一件库存,将库存变为 99 件,此时库存的版本号为 1,线程 A 的版本号也为 1,线程 A 将库存 99 写入库存,版本号一致,写入成功,此时将库存版本号变为 2。

线程 B 同样消耗一件,库存变为 99,此时线程B的版本号也为 1,线程 B 准备向库存写入 99 件,但线程 B 此时的版本号为 1,库存的版本号为 2,版本库不一致,写入失败。然后会重新加载数据,重新计算,再次写入。

悲观锁与乐观锁对比

优缺点 悲观锁 乐观锁
优点 方便,直接加锁,对应用程序来说,透明,不需要做额外的操作。 并发能力很高,不给数据加锁,大量线程可以并发。
缺点 并发能力低,同一时间只能有一条线程操作数据 麻烦,每次更新的时候,都要先比对版本号,然后写。可能需要重新加载数据,再次修改,再次写入。这个过程可能要反复好几遍。

Elasticsearch的锁

  • Elasticsearch 是基于乐观锁的并发控制。
  • Elasticsearch 的乐观锁是基于内部的 _version 进行乐观锁并发控制的。

Elasticsearch _version元数据

我们在 Kibana 的控制台上,输入以下代码,新建一个文档

PUT haicodernet/_doc/1 { "index": "www.haicoder.net" }

输入完成后,我们点击运行按钮,输出了最终的运行结果,如下图所示:

01_Elasticsearch并发控制.png

我们看到,第一次创建一个 document 的时候,它的 _version 内部版本号就是 1,现在,我们修改该条记录,具体命令如下:

PUT haicodernet/_doc/1 { "index": "haicoder.net" }

输入完成后,我们点击运行按钮,输出了最终的运行结果,如下图所示:

02_Elasticsearch并发控制.png

现在,我们看到,此时的 _version 变成了 2,即以后,每次对这个 document 执行修改或者删除操作,都会对这个 _version 版本号自动加 1;哪怕是删除,也会对这条数据的版本号加 1。

我们会发现,在删除一个 document 之后,可以从一个侧面证明,它不是立即物理删除掉的,因为它的一些版本号等信息还是保留着的。先删除一条 document,再重新创建这条 document,其实会在 delete version 基础之上,再把 version 号加 1。

Elasticsearch _version并发控制

我们在 Kibana 的控制台上,输入以下代码,新建一个文档:

PUT haicodernet/_doc/1 { "index": "www.haicoder.net" }

输入完成后,我们点击运行按钮,输出了最终的运行结果,如下图所示:

03_Elasticsearch并发控制.png

我们看到,第一次创建一个 document 的时候,它的 _version 内部版本号就是 1,现在,我们模拟两个客户端都同时获取这同一条数据,具体命令如下:

GET haicodernet/_doc/1

我们看到,我们获取了刚创建的数据,现在,一个客户端带上了版本号更新了该条记录,具体命令如下:

PUT /haicodernet/_doc/1?version=1 { "index": "haicoder.net" }

带上版本号,确保说,es 中的数据的版本号,跟客户端中的数据的版本号是相同的,才能修改,现在修改成功,版本号变成了 2,此时,另外一个客户端,尝试基于 version=1 的数据去进行修改,同样带上 version 版本号,进行乐观锁的并发控制,具体命令如下:

PUT /haicodernet/_doc/1?version=1 { "index": "haicoder.net" }

此时,会报错,因为,版本号冲突了,基于最新的数据和版本号,去进行修改,修改后,带上最新的版本号,可能这个步骤会需要反复执行好几次,才能成功,特别是在多线程并发更新同一条数据很频繁的情况下。

乐观锁并发控制

external version

Elasticsearch 提供了一个 feature,就是说,你可以不用它提供的内部 _version 版本号来进行并发控制,可以基于你自己维护的一个版本号来进行并发控制。举个列子,假如你的数据在 mysql 里也有一份,然后你的应用系统本身就维护了一个版本号,无论是什么自己生成的,程序控制的。

这个时候,你进行乐观锁并发控制的时候,可能并不是想要用 es 内部的 _version 来进行控制,而是用你自己维护的那个 version 来进行控制。

?version=1 ?version=1&version_type=external

其中,version_type=external,唯一的区别在于,_version,只有当你提供的 version 与 es 中的 _version 一模一样的时候,才可以进行修改,只要不一样,就报错;当 version_type=external 的时候,只有当你提供的 version 比 es 中的 _version 大的时候,才能完成修改。

比如 es 中,_version=1,?version=1,才能更新成功,而 es 中 _version=1,?version>1&version_type=external,才能成功。