一次Cannot create PoolableConnectionFactory(无法从套接字读取更多的数据) 问题的排查过程

今天项目出现了无法连接数据库的异常,错误描述如下(此处只列出部分错误信息):

org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is org.apache.commons.dbcp.SQLNestedException: Cannot create PoolableConnectionFactory (无法从套接字读取更多的数据)
at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:80)
Caused by: org.apache.commons.dbcp.SQLNestedException: Cannot create PoolableConnectionFactory (无法从套接字读取更多的数据)
at org.apache.commons.dbcp.BasicDataSource.createPoolableConnectionFactory(BasicDataSource.java:1549)
at org.apache.commons.dbcp.BasicDataSource.createDataSource(BasicDataSource.java:1388)
at org.apache.commons.dbcp.BasicDataSource.getConnection(BasicDataSource.java:1044)
at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:111)
at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:77)
… 42 more
Caused by: java.sql.SQLRecoverableException: 无法从套接字读取更多的数据
at oracle.jdbc.driver.SQLStateMapping.newSQLException(SQLStateMapping.java:101)
at oracle.jdbc.driver.DatabaseError.newSQLException(DatabaseError.java:133)
at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:199)
at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:263)

1.问题描述

上午刚到公司就听运维人员反馈某个接口无法提供服务,出问题的这个项目,主要是对外提供接口查询和插入数据等服务。通过查看后台日志,发现了如上所示的错误信息。本人也是第一次遇到这种问题,起初一直在怀疑是数据库方面做了改动,DBA说近期未做任何改动,且有其他项目在正常使用这个数据库。查看日志发现前一天接口还能正常接收请求,并完成插入数据库的操作,并且另一套项目环境(测试环境)中调用了同样的接口(两套环境中使用同一套数据库)。
经过从数据库、服务器、项目各个方面分析排查,最后终于解决了此问题。
简单介绍一下项目结构,下文中将会使用proxy_interface、proxy_db指代:
proxy_interface: 主要负责将内网的请求转发到外网的某些接口中
proxy_db:主要负责将内网的数据插入到另一个网络环境中的公共数据库中(插入之后,由其他程序数据数据,也是本次出问题的项目
在这里插入图片描述

2.排查过程

       起初查看tomcat中的日志,发现日志中文乱码。我第一次时间没有动手解决这个问题,以为通过抛出的错误类型可以解决本次的问题,最后浪费了自己很长时间,导致了我在错误的方向排查了很久。好在出问题的这个项目还没有正式对外提供服务,只有个别客户处于接口对接阶段,不然停机带来的影响就太大了。

2.1 排查客户端与服务端连通性

       网上搜了造成Cannot create PoolableConnectionFactory这个错误的原因,每个人遇到的也都不相同,有的是写错了数据库连接url, 有的是写错用户名等信息,把网友出错的点都检查一遍之后,还是没有解决我的问题。此时,我开始把问题甩给了DBA(总觉得是他们做了数据库的改动,不可能昨天还能用,今天就创建不了数据库连接,这明显属于无能狂怒)。
       经过再三确认,数据库近期未作任何改动。且从客户端使用telnet验证数据库端口连通性,发现也是正常的。最后为了打消顾虑,DBA说可以重启数据库,配合验证一下是不是数据库问题。

结论:一般出现这种问题,日志中都很详细的给出了原因Cannot create PoolableConnectionFactory (无法从套接字读取更多的数据),千万要仔细分析日志。如果我早点看到中文错误提示,也不会把锅甩给数据库

2.2 查看客户端端口使用情况

       排除数据库的问题后,只能从项目和服务器层面找问题了。此前这个项目在2个环境部署了,但在另一个运行环境中就没有出现问题,这两个项目的运行环境,除了tomcat版本有差别,其他都一致(都是从同一个虚拟机模板系统克隆出来的,而且此时我忽视了出问题的这套环境有大量业务请求……,这也是导致本次问题的原因之一)。因此我决定将两处运行的tomcat版本保持一致,我直接把另一台服务器上的tomcat复制到了出问题的这个服务器上,停止了原先tomcat进程。启动新的tomcat之后,发现接口正常了,通过接口可以向数据库中插入数据(还好最近这个项目提供的服务没人用,不然我这瞎折腾可就出大事了)。
       自认为问题解决之后,决定把原先tomcat中的其他项目(proxy_interface)也移动到新tomcat中,一切完成之后,再次测试接口是否正常。非常不幸的是,又出现了同样的问题,对比前后两次运行环境的区别,不同点在于另一个项目(proxy_interface)也与本次的问题proxy_db项目一同启动。通过查看日志发现,proxy_interface项目主要是用户把请求从内网服务器转发到外网,且有大量的请求没有正常收到响应。proxy_interface项目最近的业务量很大,目标接口的处理能力有限,且proxy_interface的连接方法中设置了请求的超时时延(类似于设置了http的响应超时时延),这导致proxy_interface发起的大量request没有收到response就断开了连接,查看proxy_interface的日志发现,由proxy_interface发起的请求有大量java.net.SocketTimeoutException: Read timed out错误。
       发现是由proxy_interface项目影响之后,就查看了本服务器的端口状态:
在服务器上执行netstat命令之后,通过控制台输出的端口使用情况,发现大量 time_wait 状态的 TCP 连接,此处列出部分连接信息,如下:

[root@ logs]# netstat
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State   
tcp6       0      0 172.16.30.41:http       172.16.8.8:40098        TIME_WAIT  
tcp6       0      0 172.16.30.41:http       172.16.8.8:37938        TIME_WAIT  
tcp6       0      0 172.16.30.41:http       172.16.8.8:6555         TIME_WAIT  
tcp6       0      0 172.16.30.41:http       172.16.8.8:montage-lm   TIME_WAIT  
tcp6       0      0 172.16.30.41:http       172.16.8.8:53871        TIME_WAIT  
tcp6       0      0 172.16.30.41:http       172.16.8.8:47711        TIME_WAIT  
tcp6       0      0 172.16.30.41:http       172.16.8.8:aequus-alt   TIME_WAIT  
tcp6       0      0 172.16.30.41:http       172.16.8.8:49468        TIME_WAIT  
tcp6       0      0 172.16.30.41:http       172.16.8.8:30310        TIME_WAIT  
tcp6       0      0 172.16.30.41:http       172.16.8.8:30259        TIME_WAIT  
tcp6       0      0 172.16.30.41:http       172.16.8.8:56396        TIME_WAIT  
tcp6       0      0 172.16.30.41:http       172.16.8.8:54549        TIME_WAIT  
tcp6       0      0 172.16.30.41:http       172.16.8.8:23352        TIME_WAIT  
tcp6       0      0 172.16.30.41:http       172.16.8.8:28003        TIME_WAIT  
tcp6       0      0 172.16.30.41:http       172.16.8.8:34517        TIME_WAIT  
tcp6       0      0 172.16.30.41:http       172.16.8.8:52585        TIME_WAIT  
tcp6       0      0 172.16.30.4:http       172.16.8.8:26494        TIME_WAIT  

如下图所示:
在这里插入图片描述
       此时想到是否因为proxy_interface项目占用了过多的系统资源没有正常释放,而导致处于同一个tomcat服务中的其他进程无法再获取系统资源?咨询了一个大佬之后,大佬让我修改linux内核网络参数,及时的释放掉该链接,修改了linux内核参数之后,问题终于得到了解决。

3.linux内核参数设置

3.1 通过调整内核参数,允许TIME-WAIT sockets重新用于新的TCP连接

vi /etc/sysctl.conf
编辑文件,加入以下内容:
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
#net.ipv4.tcp_tw_reuse = 1表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
#net.ipv4.tcp_tw_recycle = 1表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。

还可根据需要自行添加其他参数:

net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_fin_timeout = 30
#net.ipv4.tcp_syncookies = 1表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
#net.ipv4.tcp_fin_timeout修改系統默认的TIMEOUT时间

最后执行此命令使配置生效

sysctl -p

虽然解决了这个问题,但是又有其他疑问:服务启动后,proxy_interface项目非常快速的导致同一个tomcat中的其他项目服务无法打开socket连接,系统中应该需要设置某个进程可以打开文件数量的限制(系统为每个TCP连接都要创建一个socket句柄,每个socket句柄同时也是一个文件句柄)。

3.2 TIME_WAIT状态及存在原因

       客户端与服务器端建立TCP/IP连接后关闭SOCKET后,主动关闭的一方在发送最后一个ack 后就会进入TIME_WAIT 状态停留2MSL(MSL就是maximum segment lifetime,最大分节生命期)。这个时间是TCP/IP必不可少的,也就是“解决”不了的,这是一个IP数据包能在互联网上生存的最长时间,超过这个时间将在网络中消失。
        MSL在RFC 1122上建议是2分钟,而源自berkeley的TCP实现传统上使用30秒,因而,TIME_WAIT状态一般维持在1-4分钟。
TIME_WAIT状态存在的理由:
1)防止上一次连接中的包,迷路后重新出现,影响新连接
(经过2MSL,上一次连接中所有的重复包都会消失)
2)可靠的关闭TCP连接
在进行关闭连接四路握手协议时,最后的ACK是由主动关闭端发出的,如果这个最终的ACK丢失,服务器将重发最终的FIN,因此客户端必须维护状态信息允 许它重发最终的ACK。
如果不维持这个状态信息,那么客户端将响应RST分节,服务器将此分节解释成一个错误(在java中会抛出connection reset的SocketException)。
因而,要实现TCP全双工连接的正常终止,必须处理终止序列四个分节中任何一个分节的丢失情况,主动关闭的客户端必须维持状态信息进入TIME_WAIT状态。

4.结论

       这次遇到的问题虽然通过修改服务器参数解决了,但是程序中依然还有很多问题,在平时开发中,还是不能一味的追求完成功能、完成任务,而忽视了写下的代码会带来哪些影响。遇到问题需要根据错误日志逐步分析可能造成这些问题的原因,绝大多数的错误都是可以通过自己的分析找出错误原因,而且要从每一个疑难bug中收获知识。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值