seata事务回滚引起的skywalking数据库存储空间剧增的问题排查

  • 基本信息

产品名称:ATS3.0

问题分类:编码问题

环境类型:环境无关

  • 问题现象

             11月1日上午华润DBA收到数据库磁盘空间告警,检查后发现skywalking连接的mysql数据库占用空间从之前一直是比较稳定的,但是10月31日开始激增,一天左右原来剩250G+的磁盘空间只剩下了50G左右。进一步排查发现数据主要集中在skywalking的segment表中。
  • 问题原因

              seata回滚一个数据库操作较多的事务,由于seata客户端的问题,没有正常关闭PrepareStatement最终触发了ORA-01000-超出打开游标的最大数问题(生产库游标单个session最大300),导致回滚失败。然后seata每秒都会再次触发重试,又因为是一个大事务,导致每秒都有大量的事务回滚的sql操作。因为我们安装了skywalking的oracle插件,程序调用oracle的请求也会被记录,最终导致大量的链路信息被记录到segment表中造成了skywalking的存储数据库空间暴增。
  • 排查过程

        4.1 紧急处置

        1.先让客户删除了mysql库今天之前的binlog,释放出部分空间,否正剩余50G很可能几个小时候就会耗尽,释放后剩余空间回到了250G左右,有了比较充足的排查时间。

        2.预案:如果空间持续上升,且还没找到原因的话可以尝试临时删除部分skywalking的数据,因为只是用于监控,不影响业务。或者暂时停止下skywalking。

        4.2 定位skywalking的空间是被什么请求占用

        1.segment表里的endpoint_id表示请求服务及地址的信息,根据这个字段分类排序可以判断一段时间主要是哪个请求占用了空间。

sql: select count(*) as total , endpoint_id from segment s where start_time > 1667286000000 group by endpoint_id order by total desc;

        start_time要根据查询的时间调整这个毫秒数

2.   发现主要是有两个请求触发的频率比较高

3.   endponit_id下划线左边是服务名(最后的.1也要去掉才是服务名),右边是请求URL,查看skywalking源码,确定这串字符的编码规则,写了一个转换的逻辑new String(Base64.getDecoder().decode(待转换内容), StandardCharsets.UTF_8);

4.   转换后发现这两个请求分别是ntms-financing的Oracle/JDBI/PreparedStatement/executeQuery和Oracle/JDBI/PreparedStatement/executeUpdate

5.   发现都是数据库操作后去ELK查了下financing服务的日志发现有大量的数据库操作日志,都是seata回滚线程操作的,此时基本可以判断是seata不断尝试回滚事务产生的日志,接下来就需要具体是哪些事务回滚失败不停重试,以及回滚失败的原因。

        4.3 确认为什么会触发大量的问题请求

触发大量请求的原因:

        seata事务失败,如果能回滚成功,则会直接释放掉,如果回滚失败,则会一直尝试重试回滚,每1s重试一次,我们没有配置停止时间,所以会一直重试。回滚的时候会去做undo_log表的查询、根据undo_log表的数据做业务表的查询等操作,如果一个事务操作的数据量很大,那么每次回滚都会打印很多sql语句,也就会对skywalking记录的数据有影响了。

        假设一个全局事务是插入1000条数据到表中,那么回滚的时候,会执行一次查询undo_log,对于每行数据,执行一次select for update,执行一次delete,共2001条sql。一次回滚正常要执行这么多sql,如果回滚失败了,过1s后还要再执行,可以看到数据量是很大的。一天之内回滚日志就有五千多万条:

查找回滚失败原因:

通过查看日志找到每次执行一堆回滚sql后都会有一个报错:

报错日志为”ORA-01000: maximum open cursors exceeded“,也就是说seata很可能因为这个问题导致数据无法回滚。

对这个异常的解释:oracle ORA-01000: maximum open cursors exceeded问题的解决方法-CSDN博客

简单地说就是conn.prepareStatement()会打开一个游标,如果循环里调用这段代码,并且没调用close方法的话,就有可能造成上面的问题。

一些命令:oracle怎么查询游标-Oracle-PHP中文网

# 查看当前打开的游标总数
select count(*) from v$open_cursor;
# 每个连接能打开的最大游标数
select value from v$parameter where name = 'open_cursors';
# 更改连接能开启的最大游标数(改成1000)
 alter system set open_cursors=1000 scope=both;

​​​​​​定位问题:

        通过条件"100.100.3.253:8091:4602887063828890271" and "error" 可以看到有一个异常日志,通过调用栈可以大概找到报错的地方

再结合上面的”branchRollback failed. branchType:“可以大概定位到报错位置。

        在io.seata.rm.datasource.undo.AbstractUndoLogManager#undo这段代码的位置,是对当前事务分支做回滚的操作。先获取undo_log的中该分支的数据,做反序列化后可以得到执行的每条sql的undo_log。(一个本地事务每次执行sql都会生成一个SQLUndoLog,在连接最后提交时会把该本地事务操作的所有SQLUndoLog封装为BranchUndoLog,序列化到undo_log表中)

        再遍历该BranchUndoLog的SQLUndoLog的list,对每一条SQLUndoLog执行io.seata.rm.datasource.undo.AbstractUndoExecutor#executeOn回滚操作。问题就出在这个方法

public void executeOn(Connection conn) throws SQLException {
    //dataValidationAndGoOn执行一下select xxx for update,对该数据加锁。
    if (IS_UNDO_DATA_VALIDATION_ENABLE && !dataValidationAndGoOn(conn)) {
        return;
    }
    PreparedStatement undoPST = null;
    try {
        String undoSQL = buildUndoSQL();
        //在这里开启一个游标
        undoPST = conn.prepareStatement(undoSQL);
        TableRecords undoRows = getUndoRows();
        for (Row undoRow : undoRows.getRows()) {
            ArrayList<Field> undoValues = new ArrayList<>();
            List<Field> pkValueList = getOrderedPkList(undoRows, undoRow, getDbType(conn));
            for (Field field : undoRow.getFields()) {
                if (field.getKeyType() != KeyType.PRIMARY_KEY) {
                    undoValues.add(field);
                }
            }
 
            undoPrepare(undoPST, undoValues, pkValueList);
            //做undolog的回滚
            undoPST.executeUpdate();
        }
 
    } catch (Exception ex) {
        if (ex instanceof SQLException) {
            throw (SQLException) ex;
        } else {
            throw new SQLException(ex);
        }
    }
    //处理完并没有关闭游标
}

        看到该方法只开启了游标,并没有关闭。外面还有一个undo_log的循环,当BranchUndoLog的list过多时,会超过游标数量。

代码模拟:

  public void add() {
        for (int i1 = 0; i1 < 10000; i1++) {
            TestPO testPO = new TestPO();
            testPO.setUrid("" + i1);
            testPO.setName("name" + i1);
            testPO.setAge(i1);
            testMapper.insert(testPO);
        }
    }
 
    @GlobalTransactional
    public void update() {
        ((TestService)AopContext.currentProxy()).doupdate();
        int i = 1/0;
 
    }
 
    @Transactional
    public void doupdate(){
        for (int i1 = 0; i1 < 10000; i1++) {
            TestPO testPO = new TestPO();
            testPO.setUrid("" + i1);
            testPO.setName("name2" + i1);
            testPO.setAge(i1);
            testMapper.updateById(testPO);
        }
    }
 
 
create table TSYS_TEST
(
    URID VARCHAR2(64) not null
        constraint TSYS_TEST_PK
            primary key,
    NAME VARCHAR2(64),
    AGE  NUMBER(38)
)
/

执行完add后,再执行update,会报上面超过游标数量的问题。

该问题seata新版本已经更改。就是在上面的方法后面把statement关闭了。

        现场的解决方案时先把游标数调成了3000,该事务可以正常回滚了,把资源都释放掉了。等下次xx发版的时候,再把这段逻辑加上。

        现场的最大游标是300,也就是说每次失败大概会打印300(回滚条数)+300(select for update)+1(select from undo_log)条日志,一天要打印601*60*60*24=51,926,400 ,基本上就是上面所有命中次数了。所以该结论没什么问题。

  • 解决方案

        1.临时调大了生产oracle库的游标数,从300调整到了3000,因为游标数不够而一直重试的回滚操作回滚成功了,暂时解决了问题

        2.seata client没有正常关闭PrepareStatement的问题已修复,等下下次华润升级的时候更新过去

        3. seata事务没有正常结束的情况需要监控起来,不然只能等到引起问题才发现,如果此时存在大量的未完成事务可能就非常难以解决了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

绿光集团荣获美业科技创新大奖,杨全军董事长荣获杰出人物

近日&#xff0c;在2023中国&#xff08;南昌&#xff09;国际美发美容节之“凤凰之夜&#xff0c;美业盛典”上&#xff0c;香港绿光国际科技集团股份有限公司董事长杨全军先生荣获了2023年度“凤凰”杰出人物奖。同时&#xff0c;绿光集团也因其研发的AI人工智能数字光磁床、…

pycharm pro v2023.2.4(Python开发)

PyCharm是一种Python集成开发环境&#xff08;IDE&#xff09;&#xff0c;PyCharm提供了强大的功能&#xff0c;包括语法突出显示、智能代码完成、代码检查、自动重构和调试等特性&#xff0c;这些都可以帮助Python开发人员更加高效地编写代码。 PyCharm Pro是PyCharm的高级版…

[C/C++]数据结构 链表OJ题: 反转链表

描述: 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表 示例: 方法一: 让链表指向反向 如图所示: 代码思路: struct ListNode* reverseList(struct ListNode* head) {struct ListNode* n1NULL;struct ListNode* n2head;struct ListNode*…

使用Python从零实现多分类SVM

本文将首先简要概述支持向量机及其训练和推理方程&#xff0c;然后将其转换为代码以开发支持向量机模型。之后然后将其扩展成多分类的场景&#xff0c;并通过使用Sci-kit Learn测试我们的模型来结束。 SVM概述 支持向量机的目标是拟合获得最大边缘的超平面(两个类中最近点的距…

Go语言用Colly库编写的图像爬虫程序

下面是一个使用Colly库编写的Go语言图像爬虫程序&#xff0c;该程序会爬取news.qq上的图片&#xff0c;并使用proxy_host:duoip和proxy_port:8000的爬虫IP服务器进行抓取。 package mainimport ("fmt""net/http""github.com/crawlab-collective/go-co…

YOLOv4: Optimal Speed and Accuracy of Object Detection(2020.4)

文章目录 AbstractIntroductionRelated workObject detection modelsBag of freebiesBag of specials MethodologySelection of architectureSelection of BoF and BoSAdditional improvementsYOLOv4 ExperimentsResults表8列出了使用Maxwell GPU的帧率对比结果表9列出了使用Pa…

asp.net生产线远程故障诊断系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio

一、源码特点 asp.net 生产线远程故障诊断系统是一套完善的web设计管理系统&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为vs2010&#xff0c;数据库为sqlserver2008&#xff0c;使用 c#语言开发 asp.net生产线远程故障诊断…

ARMday03(寄存器读写、栈、程序状态寄存器、软中断和异常、混合编程)

单寄存器内存读写指令 将一个寄存器中的数值写入到内存&#xff0c;或者从内存中读取数据放在某一个指定寄存器中 指令码和功能 1.向内存中写&#xff1a; str{条件码} 目标寄存器,[目标地址]&#xff1a;将目标寄存器的4字节数值写入到目标地址为首地址的空间中 strh{条件码…

openvino学习(一)ubuntu20.04安装openvino2022

安装openvino2022要求 操作系统 Ubuntu 18.04 长期支持 (LTS)&#xff0c;64 位 Ubuntu 20.04 长期支持 (LTS)&#xff0c;64 位 软件 CMake 3.13 或更高版本&#xff0c;64 位 GCC 7.5.0&#xff08;适用于 Ubuntu 18.04&#xff09;或 GCC 9.3.0&#xff08;适用于 Ubunt…

[ Linux Busybox ] flash_eraseall 命令解析

文章目录 相关结构体flash_eraseall 函数实现flash_eraseall 实现流程图 文件路径&#xff1a;busybox-1.20.2/miscutils/flash_eraseall.c 相关结构体 MTD 相关信息结构体 struct mtd_info_user {__u8 type; // MTD 设备类型__u32 flags; // MTD设…

一篇文章带你搞懂DNS全流程

1.DNS与CDN DNS是域名系统的缩写&#xff0c;它是一种将域名和IP地址相互映射的分布式数据库&#xff0c;能够使人更方便地访问互联网。 DNS的主要功能是将域名解析为IP地址。当你在浏览器中输入一个网址时&#xff0c;浏览器会向DNS服务器发送一个请求&#xff0c;以获取该网…

如何使用 JMeter 进行 HTTPS 请求测试?

本文将介绍如何使用 JMeter 测试 HTTPS 请求&#xff0c;并提供相关的技巧和注意事项。 在进行性能测试时&#xff0c;很多网站都采用了 HTTPS 协议。当我们测试 HTTPS 请求&#xff0c;如果服务端开启了双向认证&#xff0c;则需要客户端发送请求时带上证书。本文介绍如何在 …

换服还是掀桌?哪条才是程序员的出路?

站在时代的风口浪尖&#xff0c;猪都能起飞。 大数据互联网正是时代的宠儿&#xff0c;IT行业的发展也正如火如荼。 人人都眼红程序员的高薪资&#xff0c;认为他们吃着时代的红利。 但是三百六十行&#xff0c;行行出社畜。”996“也好&#xff0c;甚至"007"也罢…

软件测试面试题【2023最新合集】

收集了各大公司的面试经验&#xff0c;现整理出来&#xff0c;希望能给正在找工作的志同道合的小伙伴一些指引&#xff0c;本文会持续更新的哦。 1、 CPU 和 GPU的区别 一个是通用计算&#xff0c;一个是专用计算。 CPU主要负责操作系统和应用程序&#xff0c;GPU主要负责跟…

TensorFlow学习笔记--(1)张量的随机生成

张量的生成 如何判断一个张量的维数&#xff1a;看张量的中括号有几层 0 1 2 &#xff1a;零维数列 [2 4 6] : 一维向量 [ [1 2 3] [4 5 6] ] : 二维数组 两行三列 第一行数据为 1 2 3 第二行数据为 4 5 6 以此类推 n维张量有n层中括号 tf.zeros(%指定一个张量的维数%) 生成一…

汽车标定技术(二)--基于XCP的标定测量实战

目录 1.工程创建 1.1 新建工程 1.2 设备配置 1.3 标定观测 1.4 刷写 2.原始hex文件与标定文件的合并 2.1 修改memory segment file 2.2 标定量地址偏移 ​编辑 2.3 标定后与原始hex文件合并 2.4 标定后直接merge 2.5 不用对ram地址进行偏移实现hex文件合并 本文使用…

【机器学习】梯度下降预测波士顿房价

文章目录 前言一、数据集介绍二、预测房价代码1.引入库2.数据3.梯度下降 总结 前言 梯度下降算法学习。 一、数据集介绍 波士顿房价数据集&#xff1a;波士顿房价数据集&#xff0c;用于线性回归预测 二、预测房价代码 1.引入库 from sklearn.linear_model import Linear…

【Docker】设置容器系统字符集zh_CN.UTF-8退出失效:关于Docker容器配置环境变量,再次进入失效问题

设置容器系统字符集zh_CN.UTF-8退出失效&#xff1a;关于Docker容器配置环境变量&#xff0c;再次进入失效问题 修改正在运行的Docker容器内的字符集: 先进入Docker容器&#xff1a;docker exec -it 容器ID /bin/bash查看是否支持中文字符集&#xff1a;locale -a | grep zh&a…

案例 | 3D可视化工具HOOPS助力SolidWorks edrawings成功引入AR/VR技术

HOOPS中文网慧都科技是HOOPS全套产品中国地区指定授权经销商&#xff0c;提供3D软件开发工具HOOPS售卖、试用、中文试用指导服务、中文技术支持。http://techsoft3d.evget.com/达索系统SolidWorks面临的挑战 达索系统SolidWorks公司开发和销售三维CAD设计软件、分析软件和产品…