1、Elasticsearch 版本冲突复现先让大家直观的看到 Elasticsearch 文档版本冲突。 1.1 场景1:create 场景
1.2 场景2:批量更新场景模拟模拟脚本1:循环写入数据 index.sh。 模拟脚本2:循环update_by_query 批量更新数据 update.sh。 由于:写入脚本 index.sh 比更新脚本 update.sh (执行一次,休眠1秒)执行要快,所以更新获取的版本较写入的最新版本要低,会导致版本冲突如下图所示: 1.3 场景3:批量删除场景模拟写入脚本 index.sh 不变。 删除脚本 delete.sh 如下: 和更新原因一致,由于:写入脚本 index.sh 比删除脚本 delete.sh (执行一次,休眠1秒)执行要快,所以删除获取的版本较写入的最新版本要低,会导致版本冲突如下图所示: 2、Elasticsearch 文档版本定义执行:
召回结果如下: 这里的 version 代表文档的版本。
一句话:Elasticsearch 使用_version来鉴别文档是否已更改。 3、Elasticsearch 文档版本产生背景试想一下,如果没有文档版本?当有并发访问会怎么办? 前置条件:Elasticsearch 从写入到被检索的时间间隔是由刷新频率 refresh_interval 设定的,该值可以更新,但默认最快是 1 秒。 如上图所示,假设我们有一个人们用来评价 T 恤设计的网站。网站很简单,仅列出了T恤设计,允许用户给T恤投票。如果顺序投票,没有并发请求,直接发起update更新没有问题。 但是,在999累计投票数后,碰巧小明同学和小红同学两位同时(并发)发起投票请求,这时候,如果没有版本控制,将导致最终结果不是预期的1001,而是1000。 所以,为了处理上述场景以及比上述更复杂的并发场景,Elasticsearch 亟需一个内置的文档版本控制系统。这就是 _version 的产生背景。 https://kb./elasticsearch/elasticsearch-version-history-what-it-does-and-doesnt-do-501 https://www./cn/blog/elasticsearch-versioning-support 4、常见的并发控制策略并发控制可以简记为:“防止两个或多个用户同时编辑同一记录而导致最终结果和预期不一致”。 常见的并发控制策略:悲观锁、乐观锁。 4.1 悲观锁悲观锁,又名:悲观并发控制,英文全称:"Pessimistic Concurrency Control",缩写“PCC”,是一种并发控制的方法。
4.2 乐观锁乐观锁,又名:乐观并发控制,英文全称:“Optimistic Concurrency Control”,缩写OCC”,也是一种并发控制的方法。
这里要强调的是,Elasticsearch 采用的乐观锁的机制来处理并发问题。 Elasticsearch 乐观锁本质是:没有给数据加锁,而是基于 version 文档版本实现。每次更新或删除数据的时候,都需要对比版本号。 5、Elasticsearch 文档版本冲突的本质一句话,Elasticsearch 文档冲突的本质——老版本覆盖掉了新版本。 6、如何解决或者避免 Elasticsearch 文档版本冲突?6.1 external 外部控制版本号“external”——我的理解就是“简政放权”,交由外部的数据库或者更确切的说,是写入的数据库或其他第三方库来做控制。 版本号可以设置为外部值(例如,如果在数据库中维护)。要启用此功能,version_type应设置为 external。 使用外部版本类型 external 时,系统会检查传递给索引请求的版本号是否大于当前存储文档的版本。
好处:不论何时,ES 中只有最新版本的数据,借助 external 相对有效的解决版本冲突问题。 实战一把: 如果没有 external,执行如下命令:
报错如下:
啥意思呢?内部版本控制(internal)不能用于乐观锁,也就是直接使用 version 是不可以的。需要使用: 如果用 external,执行如下命令:
执行结果如下:
相比于之间没有加 external,加上 external 后,可以实现基于version的文档更新操作。 external_gt 和 external_gte的用法见官方文档,本文不展开,原理同 external。 https://www./guide/en/elasticsearch/reference/8.1/docs-index_.html#index-versioning 6.2 通过if_seq_no 和 if_primary_term 唯一标识避免冲突索引操作(Index,动词)是有条件的,并且只有在对文档的最后修改分配了由 if_seq_no 和 if_primary_term 参数指定的序列号和 primary term specified(翻译起来拗口,索性用英文)才执行。 如果检测到不匹配,该操作将产生一个 VersionConflictException 409 的状态码。 Step1:写入数据
返回:
Step2:以这种方式更新,前提是先拿到 if_seq_no 和 if_primary_term
返回:
step2 更新数据的时候,是在 step1 的获取已写入文档的 if_seq_no=0 和 if_primary_term=1 基础上完成的。 这样能有效避免冲突。 6.3 批量更新和批量删除忽略冲突实现如下是在开篇的基础上加了:conflicts=proceed。 conflicts 默认值是终止,而 proceed 代表继续。
conflicts=proceed 的本质——告诉进程忽略冲突并继续更新其他文档。 开篇不会报 409 错误了,但依然会有版本冲突。但,某些企业级场景是可以用的。 同理,delete_by_query 参数及返回结果均和 update_by_query 一致。 扩展:单个更新 update (区别于批量更新:update_by_query)有 7、关于频繁更新带来的性能问题正如文章开篇演示的,并发更新或者并发删除可能会导致版本冲突。 除了并发性和正确性之外,请注意,非常频繁地更新文档可能会导致性能下降。 如果更新了尚未写入段(segment)的文档,将会导致刷新操作。而刷新频率越小(企业级咨询我见过设置小于1s的,不推荐),势必会导致写入低效。 |
|