线上问题总结-获取不到连接池(logback 配置+代码问题)

news/2024/11/16 0:55:40/文章来源:https://www.cnblogs.com/lingyejun/p/18306486

原文链接

出错问题复盘

1、问题回顾

早上 6:00 多开始报警,数据库连接池获取不到连接,日志的报错如下:

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms.
### The error may exist in class path resource [com/ xxx/xxxMapper.xml]
### The error may involve xxx.getListByOrgIdOrRealName
### The error occurred while executing a query
### Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms  

导致的结果就是:

40 多笔提现失败,因为那个时候刚好有客户要提现

2、定位问题

2.1 首先看应用日志

看应用日志,最主要的信息就是 Failed to obtain JDBC Connection、HikariPool-1 - Connection is not available,request timed out after 30000ms

1、看这个信息的第一反应就是数据库连接是不是被打满了,先去看了数据库监控,发现连接数很平稳,没有太大的变化(zabbix 的监控)。

2、然后数据库连接没有很多的话,第 2 步去看看 Hikari 连接池的配置,配置如下:

spring.datasource.hikari.connectionTimeout = 100000
spring.datasource.hikari.validationTimeout = 300000
spring.datasource.hikari.maximum-pool-size = 200
spring.datasource.hikari.minimum-idle = 50

3、当时是想着把 maximum-pool-size=500(就是 200 调整为 500),当然此时还没有改,也就没有发布、重启

2.2 看 jstack 日志

2.1 jsatck 日志如下

“xxxx_Worker-1” #206 prio=5 os_prio=0 tid=0x00007f508f6ab000 nid=0x1705 waiting on condition [0x00007f4f61818000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)parking to wait for <0x0000000720ab5208> (a java.util.concurrent.locks.AbstractQueuedSynchronizerC o n d i t i o n O b j e c t ) a t j a v a . u t i l . c o n c u r r e n t . l o c k s . L o c k S u p p o r t . p a r k ( L o c k S u p p o r t . j a v a : 175 ) a t j a v a . u t i l . c o n c u r r e n t . l o c k s . A b s t r a c t Q u e u e d S y n c h r o n i z e r ConditionObject) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at java.util.concurrent.locks.AbstractQueuedSynchronizerConditionObject)atjava.util.concurrent.locks.LockSupport.park(LockSupport.java:175)atjava.util.concurrent.locks.AbstractQueuedSynchronizerConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.ArrayBlockingQueue.put(ArrayBlockingQueue.java:353)
at ch.qos.logback.core.AsyncAppenderBase.putUninterruptibly(AsyncAppenderBase.java:181)
at ch.qos.logback.core.AsyncAppenderBase.puto r i g i n a l originaloriginalXaWJEOe0(AsyncAppenderBase.java:172)
at ch.qos.logback.core.AsyncAppenderBase.puto r i g i n a l originaloriginalXaWJEOe0$accessor67 X f D E R 7 ( A s y n c A p p e n d e r B a s e . j a v a ) a t c h . q o s . l o g b a c k . c o r e . A s y n c A p p e n d e r B a s e 67XfDER7(AsyncAppenderBase.java) at ch.qos.logback.core.AsyncAppenderBase67XfDER7(AsyncAppenderBase.java)atch.qos.logback.core.AsyncAppenderBaseauxiliary$fPj3kqdc.call(Unknown Source)
at org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstMethodsInter.intercept(InstMethodsInter.java:86)

2.2 观察 jstack 日志分析

通过看 jstack 日志发现,有几百个 parking to wait for <0x0000000720ab5208> 这个日志,而且下面也是相同的输出,调用栈是在 logback 的一个 putUniteruptibly 方法上,然后去追踪应用里的调用栈,发现都是卡在了打印日志的输出上(log.info("{}", xxx))。

2.3 猜测

2.3.1 看日志配置

因为都是卡在打印日志上,所以想打印日志是不是有什么问题。然后就想到了前一天刚上线了应用日志的异步打印配置,配置如下 :

<!--设置为异步打印-->
<appender name="debug-async-appender" class="ch.qos.logback.classic.AsyncAppender"><!-- 更改默认的队列的深度,该值会影响性能.默认值为256 --><queueSize>512</queueSize><appender-ref ref="debugAppender"/>
</appender><!--设置为异步打印-->
<appender name="error-async-appender" class="ch.qos.logback.classic.AsyncAppender"><discardingThreshold >0</discardingThreshold><!-- 更改默认的队列的深度,该值会影响性能.默认值为256 --><queueSize>512</queueSize><appender-ref ref="errorAppender"/>
</appender>
2.3.2 追踪 logback 异步打印方法

因为看 jstack 日志的时候发现调用栈是:

at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.ArrayBlockingQueue.put(ArrayBlockingQueue.java:353)
at ch.qos.logback.core.AsyncAppenderBase.putUninterruptibly(AsyncAppenderBase.java:181)

可以看到是有一个阻塞队列在阻塞排队。我们直接看源码

2.3.3 看源码
//这个就是阻塞队列的声明。
BlockingQueue<E> blockingQueue;private void putUninterruptibly(E eventObject) {boolean interrupted = false;//可以看到这里就是一个 while true 的死循环,然后不断的往 blockingQueue 中放对象try {while(true) {try {this.blockingQueue.put(eventObject);return;} catch (InterruptedException var7) {interrupted = true;}}} finally {if (interrupted) {Thread.currentThread().interrupt();}}
}//再看一下 ArrayBlockingQueue 的 put 操作
public void put(E e) throws InterruptedException {Objects.requireNonNull(e);//可以看到这里就是一个锁操作,加锁final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == items.length)notFull.await();//一直阻塞等待enqueue(e);} finally {lock.unlock();}
}

由此可以看出,因为配置的是有界队列,512,然后此时可能满了,然后新来的对象就要阻塞等待出队,然后自己才能入队。所以才会在 jstack 日志中看到 400 多个 wait for 同一个对象的日志

2.4 尝试解决

2.4.1 修改 logback 配置
<!--设置为异步打印-->
<appender name="debug-async-appender" class="ch.qos.logback.classic.AsyncAppender"><!-- 更改默认的队列的深度,该值会影响性能.默认值为256 --><queueSize>512</queueSize><appender-ref ref="debugAppender"/><neverBlock>true</neverBlock>
</appender>
<!–设置为异步打印–>
<appender name=“error-async-appender” class=“ch.qos.logback.classic.AsyncAppender”>
<discardingThreshold >0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<appender-ref ref=“errorAppender”/>
<neverBlock>true</neverBlock>
</appender>
 
添加 neverBlock 的配置,添加这个配置后,队列默认走的不是 put 文件,是 offer 方法
//AsyncAppenderBase 的方法
private void put(E eventObject) {//配置了之后,就走 offer 方法if (this.neverBlock) {this.blockingQueue.offer(eventObject);} else {this.putUninterruptibly(eventObject);}
}//看一下 offer 方法
public boolean offer(E e) {
Objects.requireNonNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
//如果列队满了,直接返回 false
if (count == items.length)
return false;
else {
//否则入队
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}

也就是说,设置了 neverBlock 之后,就会把这些丢弃掉,不入队,也就是说可能会丢日志

2.5 测试环境还原

测试环境还原的时候,因为猜测是数据库连接池的问题,所以修改了连接池配置,最小、最大设置为 5,去压测,还是没有还原这个情况。然后继续看应用日志有新发现

2.6 日志大小跟其他同区间的比很大

相同的日志切割,发现 6 点的那个时间点特别大,其他的就几十 M,但是 6 点区间的有 100 多 M,快 200M了,所以就看看到底是啥情况。然后就发现了,有一个身份证认证的异常信息,返回的异常信息特别大,导致了 log 的时候卡了。

就是说,本来 log.error("", e),这个最多几十 ms 左右就左右了,但是呢,这个对象特别大,然后就导致最终 io 写入日志信息的时候耗时太长。

2.7 找到原因,分析验证

2.7.1 找出代码中这种情况的日志打印,然后把这段代码去除
2.7.2 分析

1)、因为这个是调用第 3 方的人脸识别的接口,接口返回的 Base64 的照片信息,这个信息很大,导致 logback 打印日志写这个日志的时候很耗时。

2)、因为有些类、方法上加了 @Transactional,导致在 logback 执行的时候(logger.info/logger.error 在 finally 中执行),连接没有被释放,所以其他的请求执行的时候获取不到连接。

2.8 发布上线

上线之后,第 2 天相同时间发现没有再出现这样的问题,至此解决

2.9 模拟那种场景,还原异常

2.9.1 代码
1.application.properties
#这里为了模拟极限场景,直接将连接数置为 1 
spring.datasource.hikari.connectionTimeout = 10000
spring.datasource.hikari.validationTimeout = 300000
spring.datasource.hikari.maximum-pool-size = 1
spring.datasource.hikari.minimum-idle = 1
2.logback-spring.xml
<appender name="error-async-appender" class="ch.qos.logback.classic.AsyncAppender"><discardingThreshold >0</discardingThreshold><!-- 更改默认的队列的深度,该值会影响性能.默认值为256 --><queueSize>512</queueSize><appender-ref ref="errorAppender"/>
</appender>
 
3.controller/service/mapper
//Controller
@Slf4j
@RequestMapping("/hikari-demo/")
@RestController
public class TestController {
@Resource
private TaskOrderService taskOrderService;@GetMapping(“list”)
public List<TaskOrder> getList() {
return taskOrderService.selectByAll();
}@GetMapping(“order”)
public TaskOrder selectById() {
return taskOrderService.selectById();
}
}
@Service
@Slf4j
public class TaskOrderServiceImpl implements TaskOrderService {
@Resource
private OrderMapper orderMapper;@Transactional(readOnly = true)
@Override
public List<TaskOrder> selectByAll() {
List<TaskOrder> taskOrders = orderMapper.selectAll();
try {
log.info(“{}”, 1);
//这里就是模拟日志写很大文件的操作(大于数据库连接池的连接超时时间)
TimeUnit.SECONDS.sleep(15);
} catch (Exception e) {
} finally {
}
return taskOrders;
}@Override
public TaskOrder selectById() {
Long id = RandomUtil.randomLong(1000);
TaskOrder taskOrder = orderMapper.selectById(id);
try {
log.info(“{}”, id);
TimeUnit.SECONDS.sleep(15);
} catch (Exception e) {
} finally {
}
return taskOrder;
}
}
 
//Mapper/Mapper.xml
public interface OrderMapper {List<TaskOrder> selectAll();TaskOrder selectById(Long id);
}
<mapper namespace="com.bamboo.hikaridemo.mapper.OrderMapper"><resultMap id="allMap" type="com.bamboo.hikaridemo.TaskOrder"><id column="id" property="id"/><result column="create_date" property="createDate"/></resultMap><select id="selectAll" resultMap="allMap">select *from task_orderlimit 10</select><select id="selectById" resultMap="allMap">select *from task_orderWHERE id = #{id}</select>
</mapper>
2.9.2 压测
--同时用 10 个用户 100 个请求去测试
ab -c 10 -n 100 http://localhost:8089/hikari-demo/order
ab -c 10 -n 100 http://localhost:8089/hikari-demo/list
2.9.3 场景还原

2021-07-15 11:25:34.915 ERROR 31485 — [nio-8089-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:

Error querying database. Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 10004ms.

The error may exist in file [/Users/zhuzi/Desktop/hikari-demo/target/classes/mapper/OrderMapper.xml]

The error may involve com.bamboo.hikaridemo.mapper.OrderMapper.selectById

The error occurred while executing a query

Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 10004ms.] with root cause

java.sql.SQLTransientConnectionException**: HikariPool-1 - Connection is not available, request timed out after 10004ms**.

可以看到压测开始 1 次执行后,第 2 次的时候就报了连接超时,获取不到连接的错误

2.10 总结

至此,这个问题解决了,然后也还原了问题发出的场景,所以以后还是要注意引入一个技术的时候要多了解更多的核心原理。

2.10.1 打印日志一定要注意,不能打印太大的信息

尤其是调用第 3 方的接口里,一定要搞清楚他的返回信息,再抛出相应的信息,要不出现这种的就可能会出问题。

2.10.2 引入新的东西后,一定要多测试,多考虑极端情况

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

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

相关文章

linux: 使用 setfacl 命令管理文件访问控制列表

linux: 使用 setfacl 命令管理文件访问控制列表 原创 王义杰 AI学者王义杰 2024年07月12日 21:25 广东 听全文在Linux系统中,管理文件和目录的权限通常通过传统的chmod命令来设置。然而,随着系统复杂度的增加,有时需要更灵活的权限管理方式。此时,setfacl命令提供了强大的功…

更改类的属性类型后发现的坑org.apache.ibatis.type.TypeException: Could not set parameters for mapping

背景 本次将一个类的属性从Integer改成String,上线后发现有这种报错org.apache.ibatis.type.TypeException: Could not set parameters for mappingorg.apache.ibatis.type.TypeException: Could not set parameters for mapping: ParameterMapping{property=brandName, mode=…

Easyexcel导出时如何隐藏掉某些字段

方案 //不显示字段注解 @ExcelIgnore 背景只要一个注解 @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.IDENTITY) //设置主键生成策略 属性是自动生成 @ExcelProperty(value = "序号") //不显示字段注解 @ExcelIgnore private Inte…

嗅探器(割点)

https://www.luogu.com.cn/problem/P5058 第2题 嗅探器 查看测评数据信息某军搞信息对抗实战演习,红军成功地侵入了蓝军的内部网络。蓝军共有两个信息中心,红军计划在某台中间服务器上安装一个嗅探器,从而能够侦听到两个信息中心互相交换的所有信息。 但是蓝军的网络相当…

在excel中快速定位到具体的行

1.在这个位置输入A6 到这行2.在这个位置输入A6 到这行 快捷键 Crtl+G 本篇文章如有帮助到您,请给「翎野君」点个赞,感谢您的支持。 首发链接:https://www.cnblogs.com/lingyejun/p/18306473作者:翎野君 出处:http://www.cnblogs.com/lingyejun/ 若本文如对您有帮助,不妨…

[天堂之门+模拟执行]2021羊城杯oddcode

天堂之门 学习文章 https://taardisaa.github.io/2021/09/25/HeavensGate/ 前言 Windows判别位的方式,是根据cs段寄存器的。 在32位程序中,cs的值是0x23; 在64位程序中,cs的值是0x33。 所以只要修改cs的值,就能实现切换。而实现这个切换,使用的是ljmp长跳转指令(在Intel语法中也…

全面对比:天工大模型 vs 紫东太初大模型

在当今信息时代,人工智能(AI)技术的飞速发展正以前所未有的速度和深度改变着我们的生活和工作方式。随着大数据、计算能力和算法的不断提升,各类大模型的涌现使得AI的应用领域日益广泛,从自然语言处理到图像识别,从推荐系统到自动驾驶,AI正逐渐渗透到各行各业,成为推动…

读人工智能全传15意向立场

读人工智能全传15意向立场1. 物理立场 1.1. 可以解释一个实体行为 1.2. 在物理立场中,我们使用自然法则(物理、化学等)来预测系统的行为结果 1.3. 虽然物理立场在解释这种行为的时候非常有效,但无法应用于理解或者预测人类行为 1.3.1. …

Microsoft宣布将在开发人员会议上专注于.NET Aspire

2024年7月15日微软宣布,其开发执行团队将在下个月的开发者大会上聚焦于使用 .NET Aspire 的云原生开发,以及结合人工智能的“现代 SQL”在 Microsoft Fabric 中的应用。微软的 Visual Studio LIVE! 2024 大会不仅是一个会议,而是创新、学习和社区庆祝的盛会。大会将于 8 月 …

PDF 分割拆分 API 数据接口

PDF 分割拆分 API 数据接口 文件处理,PDF 高效的 PDF 分割工具,高效处理,可永久存储。1. 产品功能高效处理大文件; 支持多语言字符识别; 支持 formdata 格式 PDF 文件流传参; 支持设置每个 PDF 文件的页数; 输出文件永久 CDN 存储; 全接口支持 HTTPS(TLS v1.0 / v1.1 …

拯救SQL Server数据库事务日志文件损坏的终极大招

拯救SQL Server数据库事务日志文件损坏的终极大招在数据库的日常管理中,我们不可避免的会遇到服务器突然断电(没有进行电源冗余),服务器故障或者 SQL Server 服务突然停掉, 头大的是ldf事务日志文件也损毁了,SQL Server服务器起来之后,发现数据库处于"Recovery Pen…

On July

STAYING IN HOME 7月3日放假回家,这段时间大致是起床挖洞、晚上打球、外卖度日。完全的"宅",计划去练车,不巧的是打球崴脚了,只能闭关一下了。刚回家的那几天全是凌晨四五点睡,这几天好转一些,有意控制早点,不然窗帘一拉,起床就是下午三四点。每次起床就是吃完…