我们知道,在数据库中,如果多个客户端线程同时对一条记录进行增删改查,数据库会有锁机制保证并发访问的数据一致性。在Elasticsearch中,document就表示一条数据记录,那么Elasticsearch是如何对document的并发访问进行控制的呢?
本章,我们就以一个电商下单的案例为背景,介绍document的并发控制机制。
一、案例背景
我们现在有一个商品管理系统,底层采用Elasticsearch存储商品信息(包括商品ID、商品库存等)。现在,用户需要进行下单操作,流程如下:
- 用户浏览商品,此时发送请求到商品管理系统,查询商品信息;
- 用户下单购买;
- 支付成功后,发送请求到商品管理系统,扣减商品库存(库存-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的数据版本控制流程,整个业务执行流程如下:
- 假设我们现在有两个线程A和B,同时读取到了商品信息,此时获取到的document的
_version
版本号是相同的,假设都是1; - A线程在本地将库存减1后,发送PUT更新请求,请求里带上了版本号
PUT /product/storage/1?version=1
; - ES收到请求后,对数据进行更新,然后将版本号+1;
- B线程在本地将库存减1后,发送PUT更新请求,请求里也带上了版本号1,
PUT /product/storage/1?version=1
; - 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数据的路由原理。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。