[前车之鉴] SpringBoot原生使用Hikari数据连接池升级到动态多数据源的深坑解决方案 RocketMQ吞掉异常问题排查

文章目录

    • 背景说明
    • 蒙蔽双眼
    • 口说无凭
      • 修补引发的新问题
      • 解决配置问题
    • 本地监控佐证
    • 万法归元

背景说明

当前业务场景我们使用原生SpringBoot整合Hikari数据源连接池提供服务,但是近期业务迭代需要使用动态多数据源,很自然想到dynamic-source,结果一系列惨案离奇发生。。。

蒙蔽双眼

原生SpringBoot整合HikariCp数据源连接池配置【这个是没问题的配置】

spring.datasource.hikari.allow-pool-suspension = true
spring.datasource.hikari.connection-timeout = 10000
spring.datasource.hikari.pool-name = HikariPool
spring.datasource.hikari.idle-timeout = 60000
spring.datasource.hikari.maximum-pool-size = 300
spring.datasource.hikari.max-lifetime = 120000
spring.datasource.hikari.minimum-idle = 30spring.datasource.type = com.zaxxer.hikari.HikariDataSource
spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver
spring.datasource.url = jdbc:mysql://a.com:4000/payment?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
spring.datasource.username = xx
spring.datasource.password = sx

而升级后的动态多数据源配置如下:【有严重问题】


spring.datasource.dynamic.primary = tidb-payment
spring.datasource.dynamic.strict = false
spring.datasource.dynamic.hikari.idle-timeout = 60000
spring.datasource.dynamic.hikari.max-lifetime = 120000
spring.datasource.dynamic.hikari.connection-timeout = 10000
spring.datasource.dynamic.hikari.minimum-idle = 30
spring.datasource.dynamic.hikari.maximum-pool-size = 300spring.datasource.url = jdbc:mysql://a.com:4000/payment?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
spring.datasource.username = xxx
spring.datasource.password = xxx
spring.datasource.type = com.zaxxer.hikari.HikariDataSourcemysql-payment.username = root
mysql-payment.password = xxx
mysql-payment.url = jdbc:mysql://xxx:3306/payment?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghaimysql-cashier.username = xxx
mysql-cashier.password = xx
mysql-cashier.url = jdbc:mysql://xxx:3306/cashier?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghaispring.datasource.dynamic.primary = tidb-payment
spring.datasource.dynamic.datasource.tidb-payment.url = ${spring.datasource.url}
spring.datasource.dynamic.datasource.tidb-payment.username = ${spring.datasource.username}
spring.datasource.dynamic.datasource.tidb-payment.password = ${spring.datasource.password}
spring.datasource.dynamic.datasource.tidb-payment.type = ${spring.datasource.type}spring.datasource.dynamic.datasource.mysql-payment.url = ${mysql-payment.url}
spring.datasource.dynamic.datasource.mysql-payment.username = ${mysql-payment.username}
spring.datasource.dynamic.datasource.mysql-payment.password = ${mysql-payment.password}
spring.datasource.dynamic.datasource.mysql-payment.type = ${spring.datasource.type}spring.datasource.dynamic.datasource.mysql-cashier.url = ${mysql-cashier.url}
spring.datasource.dynamic.datasource.mysql-cashier.username = ${mysql-cashier.username}
spring.datasource.dynamic.datasource.mysql-cashier.password = ${mysql-cashier.password}
spring.datasource.dynamic.datasource.mysql-cashier.type = ${spring.datasource.type}

来,无论几年经验的道友看看此配置有什么问题?刚使用的童鞋很难发现,因为没有一定的并发量, 几乎很难发现其中 很致命的2个问题

  1. 全局配置是各自独享,不是共享
  2. 当前配置的最大活跃连接数和最小活跃连接数实际运行都是10,即配置是错误的

实话说,我也是遇到我人生第一个职业滑铁卢:

  1. 只要服务一发版,消息服务一直处于积压状态,而这个服务业务逻辑又很单一就是消费数据写TIDB,加上匮乏的测试人员,非生产环境根本看不出任何问题
  2. 只要一回滚就正常
  3. 服务消息积压根本没有任何错误
    这期间一直怀疑是新升级代码过多创建线程,但是几经确认是规范的创建线程池,自信注释掉所有可能过多创建线程地方,发布后继续消息积压,几经尝试无果

最搞笑的是,在期间做的修补策略还因为看不到异常,而引入一个新的问题:

WARN com.baomidou.dynamic.datasource.DynamicRoutingDataSource [240] - dynamic-datasource initial loaded [0] datasource,Please add your primary datasource or check your configuration

当你第一次看到这个警告切记不要忽略,因为此时服务虽然只是启动告警,但是只要一尝试sql连接,直接异常:Caused by: com.baomidou.dynamic.datasource.exception.CannotFindDataSourceException: dynamic-datasource can not find primary datasource
本来我不需要单独讲,因为自测是基本的素养,但是因为在当时上线修补过程中是缺少测试【过于自信】,所以任务服务发版没问题忽略,而异常还是我后来从rocketmq_client.log找到,还不是自身配置logback-spring.xml对应日志文件,所以一直没在意,关键RocketMQ还吃掉了异常,直接当回滚处理.


口说无凭

修补引发的新问题

首先对着回滚前最后一次修补代码分支先直接在本地压测,瞬间发现baomidou.dynamic.datasource.exception.CannotFindDataSourceException:ception
但问题来了,线上为什么没有这个异常,搜遍了日志无果,后来想到当前直接监听RocketMQ消费,统一在consumeMessage方法处理,如下在这里插入图片描述
坑啊,当时没发现是因为程序没有任何错误还傻傻以为是程序处理正常,只是线程积压了

话说回来,这个错误算比较低级了,因为引入了dynamic-datasource 数据源但是却没有配置好数据源,而默认引入依赖就会在业务的sql操作中使用改配置数据源连接池【当时回滚代码逻辑是不清晰的,只回滚配置注释代码是不够的,要么基于老分支直接重写逻辑本地验证后再试,要么所有新代码一起移除,包括mave依赖】

  <dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.4.0</version></dependency>

这里可以推理得到:既然这个错误是RocketMQ捕获了,那么自然打在了RocketMQ配置的日志文件中:rockemq_client.log 注意这个不配置就自动生成,关键还只保留8小时,最终本地验证是找到了,生产因为过了一天所以看不到

解决配置问题

现在我们回来看看配置两个问题是怎么回事,这个比较隐晦了,我加好了数据源后拷贝生产一份配置到本地,开始debug定位发现,配置最大活跃连接、最小活跃连接数首先是-1 然后在校验合法性时改成了默认值10
在这里插入图片描述
what?没生效本能想到这不可能,因为生产一直这么使用的,甚至怀疑生产一直是错误的,但是生产让SRE查询监控信息确认是正确的,瞬间再次怀疑自己,索性仔细比对生产老配置发现和源代码排查
才知道maximum-pool-size minimum-idle在升级使用dynamic-source是不对的,属性名发生了变更分别变成了max-pool-size 和 min-idle , 本以为原路拷贝即可谁知在dynamic-datasource源码中配置HikariCp做了替换,真的坑爹
在这里插入图片描述
这里就可以解释,线上是并发比较高的,所以很快把10个连接占满,甚至已经抛出了连接不可用的异常由于被RocketMQ捕获,所以很难发现,于是修正了属性值再次Debug正常设置成功。

修正了属性值还不够,接下来有第二个问题,请回到开头再次观察连接池配置是全局配置,最初也是没有好好看源码以为是三个数据源共享配置,直到我在调试过程中看到源码确实是独自设置,我才恍然
在这里插入图片描述

是否允许全局独享取决你的业务场景,如果你的数据库的所在数据源都是独立部署的那么 共享除了失去定制的灵活性没啥性能问题,但是如果你的本质是一个数据源多个数据库 这么配置会撑爆数据库连接,使用时需要谨慎!

有人要问了谁叫你不看文档,这里要diss 一下 dynamic-source官方文档说明这一块是真的黑心
在这里插入图片描述

所以经过上面分析最正确的配置模版如下,注意我只保证属性一定设置生效,但是value数值需要各自工业实践结果:

spring.datasource.url = jdbc:mysql://a.com:4000/payment?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
spring.datasource.username = xxx
spring.datasource.password = xxx
spring.datasource.type = com.zaxxer.hikari.HikariDataSourcemysql-payment.username = root
mysql-payment.password = xxx
mysql-payment.url = jdbc:mysql://xxx:3306/payment?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghaimysql-cashier.username = xxx
mysql-cashier.password = xx
mysql-cashier.url = jdbc:mysql://xxx:3306/cashier?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghaispring.datasource.dynamic.primary = tidb-payment
spring.datasource.dynamic.datasource.tidb-payment.url = ${spring.datasource.url}
spring.datasource.dynamic.datasource.tidb-payment.username = ${spring.datasource.username}
spring.datasource.dynamic.datasource.tidb-payment.password = ${spring.datasource.password}
spring.datasource.dynamic.datasource.tidb-payment.type = ${spring.datasource.type}
spring.datasource.dynamic.datasource.tidb-payment.hikari.max-pool-size = 50
spring.datasource.dynamic.datasource.tidb-payment.hikari.min-idle = 4
spring.datasource.dynamic.datasource.tidb-payment.hikari.max-lifetime = 120000
spring.datasource.dynamic.datasource.tidb-payment.hikari.connection-timeout = 10000
spring.datasource.dynamic.datasource.tidb-payment.hikari.idle-timeout = 60000
spring.datasource.dynamic.datasource.tidb-payment.hikari.allow-pool-suspension = truespring.datasource.dynamic.datasource.mysql-payment.url = ${mysql-payment.url}
spring.datasource.dynamic.datasource.mysql-payment.username = ${mysql-payment.username}
spring.datasource.dynamic.datasource.mysql-payment.password = ${mysql-payment.password}
spring.datasource.dynamic.datasource.mysql-payment.type = ${spring.datasource.type}
spring.datasource.dynamic.datasource.mysql-payment.hikari.max-pool-size = 25
spring.datasource.dynamic.datasource.mysql-payment.hikari.min-idle = 4
spring.datasource.dynamic.datasource.mysql-payment.hikari.max-lifetime = 120000
spring.datasource.dynamic.datasource.mysql-payment.hikari.connection-timeout = 10000
spring.datasource.dynamic.datasource.mysql-payment.hikari.idle-timeout = 60000
spring.datasource.dynamic.datasource.mysql-payment.hikari.allow-pool-suspension = truespring.datasource.dynamic.datasource.mysql-cashier.url = ${mysql-cashier.url}
spring.datasource.dynamic.datasource.mysql-cashier.username = ${mysql-cashier.username}
spring.datasource.dynamic.datasource.mysql-cashier.password = ${mysql-cashier.password}
spring.datasource.dynamic.datasource.mysql-cashier.type = ${spring.datasource.type}
spring.datasource.dynamic.datasource.mysql-cashier.hikari.max-pool-size = 25
spring.datasource.dynamic.datasource.mysql-cashier.hikari.min-idle = 3
spring.datasource.dynamic.datasource.mysql-cashier.hikari.max-lifetime = 120000
spring.datasource.dynamic.datasource.mysql-cashier.hikari.connection-timeout = 10000
spring.datasource.dynamic.datasource.mysql-cashier.hikari.idle-timeout = 60000
spring.datasource.dynamic.datasource.mysql-cashier.hikari.allow-pool-suspension = true

本地监控佐证

至此问题排查和解决已经确定,但是这么debug修改我还是不太放心,比较之前自信修改的教训让我历历在目,有了解到SpringBoot自带监控肯定有关于数据源连接池的信息,如果能看到自己期望的结果,那么一定不会有问题了

所以这里参考网上如何打开本地健康检查【不推荐生产环境使用】:Springboot整合Prometheus本地监控多数据源 ,这一篇不仅给出了方案,还发现了SpringBoot监控多数据源的bug,即只监控到一个问题:配置之后把之前的流程走一遍确实走到了默认值10
在这里插入图片描述

不用不知道,我又陷入另一个自我怀疑阶段:在本地和测试环境启动参数、apollo配置、代码完全一致的情况,都使用错误的数据连接池配置后, 测试和本地展现两种不同的数据源监控结果:云服务器是-1,而本地一直都是10,详情分析请看这一篇:【沉淀之华】SpringBoot使用HikariCP数据源两次初始化过程 & 服务器与本地完全一致却不同数据源结果定位


万法归元

从上面坎坷的排查过程看,需要注意3点

  1. 平时迭代一定要尽可能做好自测,甚至是压测
  2. 不要定式思维,按技术文档或者源码配置【无奈官方文档都成了资本手下,只恨无开源精神】
  3. 不要让RocketMQ去处理我们的业务异常,一定要手动捕获处理,否则很多未知的问题很难定位发现

持续分享,持续输出…

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

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

相关文章

如何把本地项目一次性上传github(避免一个一个上传)

一、方法如下&#xff08;亲测有效&#xff09;&#xff1a; 如何把本地项目上传github_本地已有工程上传github-CSDN博客 执行成功截图如下&#xff1a; 二、如果按照上面链接遇到问题&#xff1a; 问题0&#xff1a; 上面链接方法最后一步执行后&#xff0c;提示不能访问g…

OpenssH 漏洞修复

文章目录 OpenSSH 漏洞修复需求&#xff1a;准备环境配置阿里云yum源关闭防火墙 && SELinux安装 telnet-server安装 zlib 软件包安装OpenssL安装 OpenssH报错信息 OpenSSH 漏洞修复 场景&#xff1a; CentOS Stream 9 系统ssh默认版本一般是OpenSSH_8.7p1 &#xff0c;…

【QUARTZ】springboot+quartz动态配置定时任务

Quartz 介绍 Quartz 定时任务可分为Trigger&#xff08;触发器&#xff09;、Job&#xff08;任务&#xff09;和Scheduler&#xff08;调度器&#xff09;&#xff0c;定时任务的逻辑大体为&#xff1a;创建触发器和任务&#xff0c;并将其加入到调度器中&#xff0c;如下图所…

CMake+大漠插件的应用开发——处理dm.dll,免注册调用大漠插件

文章目录 CMake大漠插件的应用开发——处理dm.dll&#xff0c;免注册调用大漠插件说明环境项目结构配置编译环境编码-直接调用 dll编码-生成tlh文件&#xff0c;便于提示 CMake大漠插件的应用开发——处理dm.dll&#xff0c;免注册调用大漠插件 说明 网上有一种使用方式是&am…

Playwright 结合 Selenium Grid - 1.windows 环境使用教程

Playwright 可以连接到运行 Selenium 4 的 Selenium Grid Hub 来启动 Google Chrome 或 Microsoft Edge 浏览器,而不是在本地机器上运行浏览器。 下载Selenium Grid 打开selenium官方https://www.selenium.dev/downloads/下载Selenium Server (Grid) 目前最新版本4.16.1 下…

Windows 项目从0到1的部署

目录 一. 安装jdk 1.1 安装jdk 1.2 配置jdk的环境配置jdk 1.3 配置成功 二. 配置tomcat 2.1 启动tomcat 2.2 防火墙设置 三. 安装MySQL 3.1 安装步骤 3.2 内部连接 3.3 外部连接 四. 部署项目 4.1 项目部署 4.2 修改mysql的用户密码 一. 安装jdk 这里给大家准备好了jdk和…

数据库SELECT语句

文章目录 一、检索数据二、排序检索三、过滤数据四、数据过滤4.1 组合WHERE子句1. AND操作符2. OR操作符3. 计算次序 4.2 IN操作符4.3 NOT操作符 五、用通配符过滤LIKE操作符1. 百分号&#xff08;%&#xff09;通配符2. 下划线&#xff08;_&#xff09;通配符 使用通配符的技…

what is BERT?

BERT Introduction Paper 参考博客 9781838821593_ColorImages.pdf (packt-cdn.com) Bidirectional Encoder Representation from Transformer 来自Transformer的双向编码器表征 基于上下文&#xff08;context-based&#xff09;的嵌入模型。 那么基于上下文&#xff08;…

apache、nginx、php 隐藏版本号

apache、nginx、php 隐藏版本号 针对的系统都是CentOS 1、没配置之前 1.1 Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/7.2.24 mod_wsgi/3.4 Python/2.7.5 1.2 Server: nginx/1.16.0 1.3 X-Powered-By&#xff1a;7.2.24 2、配置信息 不知道具体位置&#xff0c;可…

小程序系列-5.WXML 模板语法

一、数据绑定 1、在 data 中定义页面的数据 动态绑定内容&#xff1a; 动态绑定属性&#xff1a; 2. Mustache 语法的格式 3. Mustache 语法的应用场景 4. 三元运算 5.算数运算 二、 事件绑定 1. 什么是事件&#xff1f; 2. 小程序中常用的事件 3. 事件对象的属性列表 4.…

【Java SE语法篇】6.数组

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更新的动力❤️ 文章目录 1.数组的基本概念1.1 为什么使用数组&#xff1f;1.…

【期末不挂科-C++考前速过系列P3】大二C++第3次过程考核(20道选择题&12道判断题&2道代码题)【解析,注释】

前言 大家好吖&#xff0c;欢迎来到 YY 滴C考前速过系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的《…