Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 435|回复: 0

DBCP重连的问题及解决办法(转)

[复制链接]
  • TA的每日心情
    奋斗
    2024-4-6 11:05
  • 签到天数: 748 天

    [LV.9]以坛为家II

    2034

    主题

    2092

    帖子

    70万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    705612
    发表于 2021-5-8 12:27:22 | 显示全部楼层 |阅读模式

    本文转载:http://lc87624.iteye.com/blog/1734089,欢迎大家阅读原文。

    使用数据库连接池时,免不了会遇到断网、数据库挂掉等异常状况,当网络或数据库恢复时,若无法恢复连接池中的连接,那必然会是一场灾难。

    关于dbcp的自动重连配置,网上相关的资料也不少,通过以下资料,并对照官方文档中的参数说明,大致能了解各项配置的含义,我就不冗诉了,本文的目的主要是对问题排查的经过做个简单的记录。
    参考资料:

    测试环境:

    • dbcp版本——1.4
    • 数据库——postgresSQL 9.10(简称pg)
    • 本地(以下称为client)操作系统及数据库服务器(以下称为server)操作系统均为linux
    • server位于内网环境,client需要通过vpn或网线直连内网才能访问数据库


    首先模拟的是断网的情况
    在本地测试dbcp的重连配置时,发现断网后,连接池无法重建连接,分别试过testOnBorrow和testWhileIdle两种validate方式,都没能解决,现象如下:
    1. 正常启动应用,在server端通过"select * from pg_stat_activity"查看连接数,会有initialSize个来自client的IDLE连接。——正常
    2. 在client端执行各种查询操作,连接数保持不变,且在server端的db log中能看到validate query。——正常
    3. 手动切断vpn,client与server断开,查询无法返回结果;然后重连,再次查看连接数,连接数仍保持不变,且连接的创建时间为断网前,即是说连接池认为之前的连接仍然有效,没有销毁旧连接&创建新连接。
    4. 此时在应用中执行各种查询操作,均无响应,等待一段时间后(分钟级),超时抛出异常:
    Caused by: org.postgresql.util.PSQLException: An I/O error occured while sending to the backend.
    Caused by: java.net.SocketException: Connection timed out.
    5. 继续通过"select * from pg_stat_activity"查看连接数,隔一段时间后,连接消失。

    问题:断网后,仍留在线程池内的连接是否有效?若有效,为什么网络恢复后查询无响应?若无效,为何线程池没有发现并重新创建有效连接?
    排查过程:
    1.重连vpn后,通过netstat查看client至server的连接

    Java代码   收藏代码
    1. sudo netstat -antop | grep :5432  | grep java  


    注:5432为pg端口,grep java是为了过滤client上的其他形式的连接。
    发现连接数和在server端看到的连接数一致,且均为ESTABLISH状态。
    2. 但在client上执行查询时,通过tcpdump查看client发往server的tcp请求,并无任何请求产生。

    Java代码   收藏代码
    1. sudo tcpdump -s 65535 -X -i eth0 host xxx.xxx.xxx  


    可见当前线程池中的连接实际上已经失效了,但dbcp仍认为它是有效的,因此仍在尝试用旧连接访问数据库,直至网络超时。

    于是,开始怀疑是vpn的问题,将client接上网线直连内网后,再次重试上述步骤,只是把断网的方式由切断vpn换成了拔网线,发现这次使用断网前的连接能够正常访问数据库,于是断定是vpn的问题,猜测是重连vpn后,虽然client端ip没有变,但路由的路径已经变了,之前的连接无法复用,但dbcp并不知道。对网络细节不是太熟悉,就不多加揣测了。

    接下来模拟数据库断开client连接的情况
    由于pg采用的是进程模型,与数据库建立的每一个连接都是单独的一个进程,故尝试采用kill进程的方式模拟数据库断开连接。
    预期的结果是:kill掉一个连接进程后,dbcp通过validate query发现该连接失效,将销毁该连接并重新创建新连接。
    但实际情况确是:kill掉一个连接后,所有连接全部被销毁。
    问题:究竟是数据库还是dbcp销毁了所有连接?
    排查过程:
    熟悉pg的同事认为pg之所以采用进程模型,就是为了避免连接之间的影响,因此不可能发生kill一个连接,其他连接也被销毁的情况。在这个理论前提下,问题就变得很诡异,因为dbcp的validate肯定是针对一个连接的,也不可能会在validate一个连接失效的情况下销毁所有连接,于是越想越偏,甚至开始怀疑是pg的jdbc driver有问题,最终放弃了深究。
    但我总觉得有点不太对劲,于是推翻之前的前提,开始怀疑是pg销毁了所有连接。于是,在使用连接池的应用之外,通过pg的数据库客户端psql连接db,这就建立了一个与dbcp无关的连接,接着继续在server端kill了一个连接池中的连接,继而发现psql创建的连接也被销毁了,这就能确定是pg在销毁连接,因为dbcp不可能控制自身范围之外的连接。
    后来才知道,pg之所以会这么做,是因为我们kill连接时使用的是kill -9(简称9杀),9杀太过粗暴,pg会重启很多内部进程,以保证所有进程正常,之前的连接也将会丢失,换用普通的kill命令,则不会发生以上情况。可见9杀很多情况下是十分危险的,试想一个线上db,若是9杀一个连接,后果不堪设想。。。

    总结
    说是dbcp问题排查,但大家可以看到最终问题的根源都跟dbcp没有什么关系。实际工作中的很多问题,关联的因素众多,需要有各方面的知识储备才能找到真正问题根源,否则就会把问题归结到一个自己不太了解的领域。
    另外,看到dbcp基本配置和重连配置这篇文章中对连接池重连有两句不错的总结,引用一下:

    引用
    1. 数据库意外重启后,原先的数据库连接池能自动废弃老的无用的链接,建立新的数据库链接
    2. 网络异常中断后,原先的建立的tcp链接,应该能进行自动切换



    最后附上测试使用的dbcp配置。
    testOnBorrow配置:

    Xml代码   收藏代码
    [html]  view plain  copy
     
    1. <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">    
    2.         <property name="driverClassName" value="${jdbc.driver}" />    
    3.         <property name="url" value="${jdbc.url}"/>    
    4.         <property name="username" value="${jdbc.user}" />    
    5.         <property name="password" value="${jdbc.passwd}" />    
    6.         <property name="removeAbandoned" value="true"/>    
    7.           <property name="initialSize" value="10" />    
    8.           <property name="maxIdle" value="10" />    
    9.           <property name="minIdle" value="10" />    
    10.            <property name="maxActive" value="30" />    
    11.            <property name="maxWait" value="30000" />    
    12.            <property name= "testWhileIdle" value="false" />    
    13.         <property name= "testOnBorrow" value="true" />    
    14.         <property name= "testOnReturn" value="false" />    
    15.         <property name= "validationQuery" value="select 1" />    
    16.         <!-- <property name= "validationQueryTimeout" value="1" /> 配置已失效-->    
    17.     </bean>    



    testWhileIdle配置:

    Xml代码   收藏代码
    [html]  view plain  copy
     
    1. <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">    
    2.         <property name="driverClassName" value="${jdbc.driver}" />    
    3.         <property name="url" value="${jdbc.url}"/>    
    4.         <property name="username" value="${jdbc.user}" />    
    5.         <property name="password" value="${jdbc.passwd}" />    
    6.         <property name="removeAbandoned" value="true"/>    
    7.           <property name="initialSize" value="10" />    
    8.           <property name="maxIdle" value="10" />    
    9.           <property name="minIdle" value="10" />    
    10.            <property name="maxActive" value="30" />    
    11.            <property name="maxWait" value="30000" />    
    12.            <property name= "testWhileIdle" value="true" />    
    13.         <property name= "testOnBorrow" value="false" />    
    14.         <property name= "testOnReturn" value="false" />    
    15.         <property name= "validationQuery" value="select 1" />    
    16.         <!-- <property name= "validationQueryTimeout" value="1" /> 配置已失效-->    
    17.         <property name= "timeBetweenEvictionRunsMillis" value="30000" />    
    18.         <property name= "numTestsPerEvictionRun" value="30" />    
    19.         <property name="minEvictableIdleTimeMillis" value="1800000" />    
    20.     </bean>    



    注:testOnBorrow只会发现当前连接失效,再创建一个连接供当前查询使用,而testWhileIdle会定时校验numTestsPerEvictionRun个连接,只要发现连接失效,就将其移除再重新创建。

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-5-15 23:48 , Processed in 0.065935 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表