es-并发写入报错及解决

一、现象报错日志

VersionConflictEngineException[[XXX][1234]: version conflict, current version [2

问题产生背景


众所周知,es经常被用于存储日志数据,其中在某些场景下,日志产生的时机不同,并且需要将多类具备关联关系的日志写入同一个document,就会带来同一个文档可能会被其它文档覆盖,或者missing等问题。

大家都知道es是不支持事务的,同时也不具备像关系型数据库那样可以关联查询的能力。所以我们如何将一个document在被完成后具备关联关系呢?我们需要遵循以下流程:

基础document创建------> 更新文档(_update/id) ------> 提交保存

而其中针对多类日志并发写入时,可能基础document结构还未创建,这是直接做update会直接报出document missing的问题,所以这就需要我们针对第一个接收到的日志数据在做update更新之前先做创建,并且需要文档锁级别的控制,才能保证在创建文档时不被后续涌入的数据直接update而报错document missing。我们通常使用

PUT http://ip:port/index/_doc/id?version=0&version_type=external
其中version代表当前document的版本号,所属范围时document级别,而version_type则为版本号类型,如果为external则需要后续更新的版本号需要比上一个版本号大,否则会报出异常。这就是我们今天要解决的问题,虽然用文档锁级别控制住了基础document的生成,但是后续的update操作无法控制由于并发而带来的版本号冲突问题。

问题表象
{
    "error":{
        "reason":"[20KB9MFIODLRPYET110091800X1169C685D4E1675646532216]: version conflict, required seqNo [3], primary term [1]. current document has seqNo [5] and primary term [1]",
        "index_uuid":"eHmesKN8RNC3KUSEFQE0Tw",
        "index":"logclient",
        "type":"version_conflict_engine_exception",
        "shard":"0",
        "root_cause":[
            {
                "reason":"[20KB9MFIODLRPYET110091800X1169C685D4E1675646532216]: version conflict, required seqNo [3], primary term [1]. current document has seqNo [5] and primary term [1]",
                "index_uuid":"eHmesKN8RNC3KUSEFQE0Tw",
                "index":"logclient",
                "type":"version_conflict_engine_exception",
                "shard":"0"
            }
        ]
    },
    "status":409
}
当Elasticsearch在写入或者删除的时候出现错误version_conflict_engine_exception时,表示当前写入或删除存在版本冲突。此时,status为409。其中,_seq_no和_primary_term分别是:

_seq_no:文档版本号,作用同_version,文档级别。

_primary_term:文档所在位置。索引级别

问题分析
在并发冲突的时候,我们有常用的两种策略:

悲观锁并发策略
        在关系型数据库中,通过阻塞并排队的方式,来避免发生冲突。例如在读取数据时阻塞,来保证正在修改行数据的请求完成正常操作后,能够读取到最新的数据。这种方式的前提假设是数据冲突更有可能发生。

优点:方便,直接加锁,对应用程序来说透明,不需要额外的操作。

缺点:并发能力很低,同一时间只有一条线程操作数据。

乐观锁并发策略
        乐观锁假设多用户并发的事务在处理时不会互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据,在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其它事务有更新的话,正在提交的事务会进行回滚。相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般实现乐观锁的方式就是记录数据版本。

        数据版本是为数据增加一个版本标识,当读取数据时,将版本标识的值一同读出。数据每更新一次,同时对版本标识进行更新。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,如果数据库表当前版本号与第一次取出来的版本标识值相当,则予以更新,否则认为是过期数据。

实现数据版本有两种方式,第一种是使用版本号,第二种是使用时间戳。

优点:并发能力很高,不给数据加锁,可以进行大量线程并发操作。

缺点:麻烦,每次更新的时间都要先比对版本号,然后可能需要重新加载数据,再次修改,再写;这个过程可能要重复好几次。

Elasticsearch中采用的是乐观锁的并发策略,这种方式的前提假设是数据冲突一般不会发生,从而避免阻塞数据请求。然而,在读和写之间,如果数据发生改变,更新就失败了,然后由程序决定如何进行后续的处理。

Elasticsearch内部如何基于_version进行乐观锁并发控制?
Elasticsearch的修改,如果没有带上version的时候,直接替换原来的文档,没有查询过程,多线程操作,不区分执行的先后顺序。这时候Elasticsearch并不阻止别的线程修改这条数据,极大的可能会出现数据回流或数据覆盖。

Elasticsearch是分布式的,文档的创建/变更等都会同步到其他节点。由于其异步性和并发的特点,这些同步请求都是并行的,因此并不能保证数据是按照修改顺序依次到达的。Elasticsearch保证了一个老版本的数据永远无法重写或覆盖新版本的数据。

在索引的Get和Delete请求中,都存在一个_version 字段。数据的变更会导致_version的值递增。第一次创建一个document的时候,它的_version内部版本号如果不指定就是1,后续,每次对这个document执行修改或者删除操作,都会对这个_version版本号自动加1。哪怕是删除,也对这条数据的版本号加1。

其实,每次删除一个document并不是立即进行物理删除的,因为它的一些版本号信息还保留着,比如先删除一条document,再重新创建这条document,会发现新建的这条document的_version版本号的值取自删除时_version的值再加1。

问题解决
那么,如何基于Es的version从而解决并发的更新时所带来的版本冲突问题呢?

方案一:retry策略
Elasticsearch再次获取document数据和最新版本号,成功就更新,失败再试;比如,设置尝试5次更新,retry_on_conflict=5,代表着最大更新5次,5次后不再尝试写入并且抛出异常。需要注意的是,retry策略在增量操作的无关顺序的场景中更适用,比如计数操作,数据写入的先后顺序对最终结果关系不大。其它的一些场景,比如库存的变化,订单的状态,直接更新为指定数值的,retry策略都不适用。除非可以在业务中将数据写入由顺序有关转化成顺序无关,才可以使用retry策略。

具体写法:http://ip:port/index/_doc/id/_update?retry_on_conflict=5

方案二:延时写入
直接回调写入会造成更多的版本冲突发生,虽然可以解决问题但依然存在风险。结合对Elasticsearch的写入机制的深入理解,尝试跳出Elasticsearch本身,在业务侧解决Elasticsearch频繁更新同一document时出现的版本冲突异常。延时写入,将写入请求延迟处理。

一、使用Redis作为中间缓存,将一段时间内的同一document的写入请求缓存,key为document ID,value为变更字段的k-v格式。在这段时间内,后写入的覆盖先写入的,也就是将写入请求合并,只更新一次。

二、将写入失败的数据发送到rocketmq的队列中,按照document ID分区,做延时写入,顺序消费。

优点

实现顺序写入,写入频次降低,大大降低并发冲突的发生。
将数据存储在缓存和消息队列,保证数据不会丢失。
缺点

在本就存在显示延迟的基础上,加大了延迟,具体的延迟指标需要结合集群规格,数据量级等综合考虑。
容易造成消息堆积,如果消费出现异常需要重新消费。
方案三:回调写入
如果遇到status=409的异常直接回调当前写入方法重新写入,直到写入成功为止。以java代码为例,捕获到的异常为ElasticsearchStatusException。本以为当前方案会对Elasticsearch集群造成一定不良影响,但经过一周的观察,Elasticsearch集群的监控指标(包含GC频次)并无异常,但不能保证在更大的数据量级中不会发生其它异常。

优点

开发成本低,可以解决retry策略无法解决的顺序写入的问题,回调写入方法即可。
缺点

尝试写入的次数很高,且无法预估在大数据量级下中的直接回调写入会对集群性能造成何种影响,即使对集群进行扩容升级,也会变相提高成本。
线程消亡或者等待队列溢出会造成数据丢失。
如果当前写入执行了多次的回调写入,那么势必会影响分配到当前线程的其它写入,造成数据延迟,当然也可以通过加大线程池、升级服务器的方式提高性能,但毕竟是治标不治本的方法,一旦产生业务侧不可接受的延迟依然很麻烦。
方案四:Redis外部版本号 + setNx锁 + Rocketmq
采集到的binlog写入rocketmq的队列中(多队列),然后并发消费这些数据。使用表名为key,binlogID为value,在每次写入之前校验当前binlogID与Redis中存储的binlogID的大小,作为判断是否同步的条件,之后用Redis的setNx锁,锁住docID,目的是保证并发条件下,同一doc只有一次更新。如果以上两次校验有任何一次发生版本号异常,那么将此条消息原封不动的发送至队尾,并设置延迟消费(延迟级别根据具体情况分析),执行写入方法后catch出版本号异常,如仍存在版本号异常,即同样将消息发送至队列队尾,延迟消费。

以上简述的方案,虽然不能从根本上解决版本号异常的问题,但是却可以大大减少版本号异常发生的频次,同时也可以降低写入次数,并且当前文档更新的阻塞不会影响后续的数据同步,基本上解决了版本号异常对数据同步造成的堆积延迟。

目前这种方案,适用于外部控制版本号,针对互联网订单,消费的场景较多。

以上四种方案,根据不同的场景来使用,由于我的场景时并发多类日志的存储,并且每类日志在同一个document上所更新的字段不同且不要求顺序更新写入,所以针对文档锁级别的控制,方案一就足够了。
 

参考

Elasticsearch并发写入版本冲突解决方案_es 并发写入_我教你啊的博客-CSDN博客

ElasticSearch学习(六) --版本控制与冲突解决_elasticsearch 冲突-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/130834.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

shell综合项目

主菜单 http和Nginx分别的install的菜单&#xff0c;安装过程通过重定向到/dev/null达到看不见的效果 输入非整数或者大于4的数字都会提示错误 代码如下: [rootserver ~]# vim install_menu.sh #!/bin/bash function menu() { cat << EOF …

变更数据捕获 (CDC) 的七种使用方法

变更数据捕获 (CDC) 是数据工程中的强大工具&#xff0c;在过去几年中在各种组织中得到了巨大的应用。这是因为它能够以非常低的延迟将事务数据库紧密集成到您企业中的许多其他系统中。 CDC 对事务数据库中发生的更改&#xff08;例如插入、更新和删除&#xff09;做出响应&am…

前端小案例 | 喵喵大王立大功 | 一个带便利贴功能的todolist面板

文章目录 &#x1f4da;html&#x1f4da;css&#x1f4da;js&#x1f407;stickynote.js&#x1f407;todolist.js&#x1f407;clock.js &#x1f4da;html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><m…

配置Raspberry自动连接WIFI,在无法查看路由器的校园网情况下使用自己电脑热点

1、开启电脑热点&#xff0c;并共享电脑WLAN2 打开控制面板->网络和Internet->网络连接 选择自己的校园网&#xff0c;我这里是WLAN2&#xff0c;右键属性&#xff0c;如下操作&#xff1a; 如果没有看到 本地连接*10类似的图标 则按如下操作&#xff1a;winx键&#x…

文本内容转换成语音播放的工具:Speech Mac

Speech Mac版是一款适用于Mac电脑的语音合成工具。它将macOS语音合成器的所有功能整合到一个易于使用的界面中。通过Speech Mac版&#xff0c;用户可以选择40多种声音和语言&#xff0c;方便地将文本转换为语音。用户可以将文本拖放或粘贴到Speech中&#xff0c;并随时更改语音…

Linux安装配置awscli命令行接口工具及其从aws上传下载数据

官网技术文档有全面介绍&#xff1a;安装或更新 AWS CLI 的最新版本 - AWS Command Line Interface在系统上安装 AWS CLI。https://docs.aws.amazon.com/zh_cn/cli/latest/userguide/getting-started-install.html#getting-started-install-instructionsawscli常用命令参考&…

Transformer:开源机器学习项目,上千种预训练模型 | 开源日报 No.66

huggingface/transformers Stars: 113.5k License: Apache-2.0 这个项目是一个名为 Transformers 的开源机器学习项目&#xff0c;它提供了数千种预训练模型&#xff0c;用于在文本、视觉和音频等不同领域执行任务。该项目主要功能包括&#xff1a; 文本处理&#xff1a;支持…

PGD(projected gradient descent)算法源码解析

论文链接&#xff1a;https://arxiv.org/abs/1706.06083 源码出处&#xff1a;https://github.com/Harry24k/adversarial-attacks-pytorch/tree/master 源码 import torch import torch.nn as nnfrom ..attack import Attackclass PGD(Attack):r"""PGD in the …

全球规模最大!5G建得好还要用得好

在日前举行的2023年中国5G发展大会上&#xff0c;工信部宣布中国已建成全球规模最大、技术领先的5G网络。当务之急&#xff0c;“建得好”还需切实迈向“用得好”&#xff0c;尽快形成更多可大规模复制的成熟应用&#xff0c;才能真正释放5G力量&#xff0c;夯实数字经济的发展…

js调整table表格上下相邻元素顺序

有时候我们会遇到要通过箭头控制table表格上下顺序的需求,如下: 点击向下就将该元素下移一位,下面的一位元素就移上来,点击向上就将该元素上移一位,上面的一位元素就移下来,也就是相邻元素互换位置顺序: <el-table :data="targetTable" border style=&quo…

[自学记录08*]LDR、HDR与ToneMapping

一、Dynamic Range—动态范围 Dynamic Range表示动态范围&#xff0c;检测DR&#xff0c;DR又分为LDR&#xff08;Low Dynamic Range&#xff09;和HDR&#xff08;High Dynamic Range&#xff09;。它们都是表示亮度值范围的一种方式&#xff0c;那么有什么区别呢。 1.LDR …

如何搭建一个Spring MVC和Vue3的应用程序

要搭建一个基于Spring MVC框架和Vue3框架的前端应用程序&#xff0c;可以按照以下步骤进行&#xff1a; 创建Java项目并添加Spring MVC依赖 使用Maven或Gradle等构建工具创建一个Java项目&#xff0c;并在项目的pom.xml或build.gradle文件中添加Spring MVC依赖。例如&#xf…

【RabbitMQ】RabbitMQ 消息的堆积问题 —— 使用惰性队列解决消息的堆积问题

文章目录 一、消息的堆积问题1.1 什么是消息的堆积问题1.2 消息堆积的解决思路 二、惰性队列解决消息堆积问题2.1 惰性队列和普通队列的区别2.2 惰性队列的声明方式2.3 演示惰性队列接收大量消息2.4 惰性队列的优缺点 一、消息的堆积问题 1.1 什么是消息的堆积问题 消息的堆积…

Java反射机制详解

CONTENTS 1. 为什么需要反射2. Class对象2.1 类字面量2.2 泛型类的引用2.3 cast()方法 反射使我们摆脱了只能在编译时执行面向类型操作的限制&#xff0c;并且让我们能够编写一些非常强大的程序。本文将讨论 Java 是如何在运行时发现对象和类的信息的&#xff0c;这通常有两种形…

基于Qt命令行处理XML文件读写

Qt源码在后面,文本介绍Qt国际化语言和XML # XML基础(一) ## 1、概述 ### 1.1 定义(xml是个啥玩意儿?) XML(extensible Markup Language)俗称差妹儿,专业称之为:可拓展标记语言。 (1)何为标记,指的是一种标记语言,或者标签语言,即用一系列的标签来对数据进行…

C++中声明和实现析构函数

C中声明和实现析构函数 与构造函数一样&#xff0c;析构函数也是一种特殊的函数。构造函数在实例化对象时被调用&#xff0c;而析构函数在对象销毁时自动被调用。 析构函数看起来像一个与类同名的函数&#xff0c;但前面有一个腭化符号&#xff08; &#xff5e;&#xff09;…

Linux常用命令——chage命令

在线Linux命令查询工具 chage 修改帐号和密码的有效期限 补充说明 chage命令是用来修改帐号和密码的有效期限。 语法 chage [选项] 用户名选项 -m&#xff1a;密码可更改的最小天数。为零时代表任何时候都可以更改密码。 -M&#xff1a;密码保持有效的最大天数。 -w&…

数字媒体技术基础之:ICC 配置文件

ICC 配置文件&#xff08;也称为 ICC 色彩配置文件或 ICC 色彩描述文件&#xff09;是由国际色彩联盟&#xff08;International Color Consortium, ICC&#xff09;制定的一种标准文件格式&#xff0c;用于在不同的设备和软件之间保持颜色的一致性。 ICC 配置文件包含有关设备…

Ubuntu中nano使用

1.nano配置文件 sudo nano /etc/nanorc2.显示每行得数字 //etc/nanorc配置文件中设置 set linenumbers //取消注释使用Nano编辑器入门指南 引言 1.1 关于Nano编辑器 Nano是一个简单易用的文本编辑器&#xff0c;适用于终端环境。它具有轻量级、快速启动和基本功能的特点&…

项目实战:通过axios加载水果库存系统的首页数据

1、创建静态页面 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><link rel"stylesheet" href"style/index.css"><script src"script/axios.mi…