首页 > 代码库 > 一次hibernate+c3p0+mysql连接池java.net.SocketException: Connection reset故障的解决笔记

一次hibernate+c3p0+mysql连接池java.net.SocketException: Connection reset故障的解决笔记

hibernate 有自带的连接池。但大家用起来颇有微词,由于其稳定性以及性能都不太好。c3p0 连接池的性能和稳定性久经考验,所以用 hibernate 的朋友一般都使用 c3p0 的连接池。

那么是不是把 c3p0 的包导进来,然后 hibernate.cfg.xml 里把 c3p0 的各种属性加进来就万事大吉了呢?不见得,非常可能你的项目上线以后。发现你的连接池不仅性能低下,并且可靠性差,c3p0 并没有表现出它传说中应该具有的那些特性。你在咒骂 c3p0 的可靠性以及性能的时候,有没有想过你用的非常可能还是 hibernate 默认的连接池,由于你的 c3p0 的那些配置压根儿就没起作用呢?你有没有考虑过。怎么样才干让你配置的 hibernate 中 c3p0 配置真正起作用?如何验证你的 c3p0 配置真正起了作用了?本文结合作者亲自经历的一次 java.net.SocketException: Connection reset 故障的排除体验,来回答一下这些问题。
本文所用 mysql 版本号是 5.0。 hibernate 版本号是 3.3.2。c3p0 版本号是 0.9.1.1:
技术分享
笔者的项目 dao 层组合是 hibernate+c3p0+mysql,hibernate.cfg.xml 中 c3p0 连接池配置例如以下:

		<property name="hibernate.c3p0.max_size">10</property>
		<property name="hibernate.c3p0.min_size">5</property>
		<property name="hibernate.c3p0.checkoutTimeout">15000</property>
		<property name="hibernate.c3p0.max_statements">200</property>
		<property name="hibernate.c3p0.idle_test_period">0</property>
		<property name="hibernate.c3p0.acquire_increment">1</property>
		<property name="hibernate.c3p0.validate">true</property>
		<property name="hibernate.c3p0.timeout">0</property>

上线以后,发现偶尔会有下面错误:
org.hibernate.exception.JDBCConnectionException: Cannot open connection
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:97)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:52)
at org.hibernate.jdbc.ConnectionManager.openConnection(ConnectionManager.java:449)
at org.hibernate.jdbc.ConnectionManager.getConnection(ConnectionManager.java:167)
at org.hibernate.jdbc.JDBCContext.connection(JDBCContext.java:142)
at org.hibernate.transaction.JDBCTransaction.begin(JDBCTransaction.java:85)
at org.hibernate.impl.SessionImpl.beginTransaction(SessionImpl.java:1354)
at sun.reflect.GeneratedMethodAccessor263.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.hibernate.context.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:342)
at com.sun.proxy.$Proxy23.beginTransaction(Unknown Source)
at com.defonds.dao.PayAntharDao.updateBalance(PayAntharDao.java:595)
at com.defonds.interImpl.BatchLogAdmin$BatchUpdateThread.run(BatchLogAdmin.java:49)
at java.lang.Thread.run(Thread.java:745)
Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure

The last packet successfully received from the server was 5,226,622 milliseconds ago.  The last packet sent successfully to the server was 1 milliseconds ago.
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:1116)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3589)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3478)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4019)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2490)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2651)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2677)
at com.mysql.jdbc.ConnectionImpl.setTransactionIsolation(ConnectionImpl.java:5315)
at org.hibernate.connection.DriverManagerConnectionProvider.getConnection(DriverManagerConnectionProvider.java:126)
at org.hibernate.jdbc.ConnectionManager.openConnection(ConnectionManager.java:446)
... 12 more
Caused by: java.net.SocketException: Connection reset
at java.net.SocketInputStream.read(SocketInputStream.java:196)
at java.net.SocketInputStream.read(SocketInputStream.java:122)
at com.mysql.jdbc.util.ReadAheadInputStream.fill(ReadAheadInputStream.java:114)
at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:161)
at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:189)
at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:3036)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3489)
... 20 more

一開始仅仅是一些定时查询的线程曝出这个错误,由于没有影响业务。所以也没太在意。


后来项目变更,为了增强用户体验,一些更新操作由同步等待,换为异步操作。也就是主线程马上返回给用户结果,另起线程去更新。


响应速度是提上去了,可是这个更新却偶尔失败。原因就是上边那个 Connection reset 故障,这个更新失败频率太高了,基本每天一次(大都是早上第一次出现频率最高)。造成客户不满。


尽管手头还有巨多任务,但客户至上。优先级拉高。

异常信息里边说的非常明确。造成这个故障的原因是数据库服务器的超时断开机制,也就是说假设你没有不论什么查询语句(这里。增删改也属于查询的范畴),超过数据库配置的时间( "wait_timeout" 默觉得八小时)后。就把当前连接断开。而连接池却不知道,还觉得当前连接是有效的,当有新的 sql 过来时。启用当前数据库已经废弃的连接,造成以上结果。怎么办呢?

增大数据库超时治标不治本;
连接字符串加上 &autoReconnect=true&failOverReadOnly=false&maxReconnects=10 也无济于事;
加上下面空暇验证也不起作用:
<property name="hibernate.c3p0.idle_test_period">120</property>
<property name="hibernate.c3p0.validate">true</property>
<property name="hibernate.c3p0.preferredTestQuery">select 1</property>

c3p0 的稳定性应该没这么差吧?会不会是根本就没用 c3p0 的连接池呢?我们開始怀疑我们对 c3p0 的配置对不正确。于是我们用 SHOW PROCESSLIST; 命令对我们的连接池进行验证,发现果然没实用上 c3p0 的配置(我们配的连接数最小为 5):
技术分享
原因在哪里呢?hibernate 核心包里有下面类:
技术分享
可见 hibernate 原生态是支持 c3p0 的。那么怎么才干让 hibernate 去认这些配置呢?
查看 hibernate 核心包里的 org.hibernate.connection.ConnectionProviderFactory 类的源代码。能够看到下面凝视:
Instantiates a connection provider given either System properties or a java.util.Properties instance. The ConnectionProviderFactory first attempts to find a name of a ConnectionProvider subclass in the property hibernate.connection.provider_class. If missing, heuristics are used to choose either DriverManagerConnectionProvider, DatasourceConnectionProvider, C3P0ConnectionProvider or DBCPConnectionProvider. 
事情到了这里。已经非常清晰明朗了:假设我们没有配置 ConnectionProvider 的实现类,hibernate 默认去找 DriverManagerConnectionProvider、DatasourceConnectionProvider、C3P0ConnectionProvider 或者 DBCPConnectionProvider 当中的一个作为连接池的实现。通过上面的日志能够看出。非常不幸,hibernate 没有去找 C3P0ConnectionProvider,它找的是 DriverManagerConnectionProvider。


既然找到了问题症兆所在,接下来的事就顺理成章了,我们仅仅需把 C3P0ConnectionProvider 加到 hibernate 配置里就可以,于是我们 hibernate 连接池配置变成了下面的样子:

<property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
<property name="hibernate.c3p0.max_size">10</property>
<property name="hibernate.c3p0.min_size">5</property>
<property name="hibernate.c3p0.checkoutTimeout">15000</property>
<property name="hibernate.c3p0.max_statements">200</property>
<property name="hibernate.c3p0.idle_test_period">120</property>
<property name="hibernate.c3p0.timeout">300</property>
<property name="hibernate.c3p0.acquire_increment">1</property>
<property name="hibernate.c3p0.validate">true</property>
<property name="hibernate.c3p0.preferredTestQuery">select 1</property>

部署上去。世界安静了,客户急匆匆的电话声没了:上个月的 18 日部署上这个配置。直到作者发本篇博客为止。再也没有遇见 Connection reset 故障。


后记
事实上另一个办法验证你的 c3p0 有没有配置成功。以上异常信息里有这一句:
at org.hibernate.connection.DriverManagerConnectionProvider.getConnection(DriverManagerConnectionProvider.java:126)
这也说明了依照当时的配置。hibernate 用的实际是 hibernate 自己的连接池。不是 c3p0 的。

一次hibernate+c3p0+mysql连接池java.net.SocketException: Connection reset故障的解决笔记