在使用c3p0连接池

时间:2016-09-13 12:16:11

标签: java mysql connection-pooling database-replication c3p0

我使用Mysql复制驱动程序和c3p0连接池配置了主从复制。有时在奴隶面临以下连接失败问题。在当前设置中,有一个主设备和一个从设备。

   org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction; nested exception is javax.persistence.PersistenceException: org.hibernate.TransactionException: JDBC begin transaction failed:
            at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:431)
            at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:373)
            at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:427)
            at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:276)
            at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
            at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
            at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208)
            at com.sun.proxy.$Proxy264.get(Unknown Source)
    /* 
    getSomeDataFromSlave()
    */      java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
            at java.util.concurrent.FutureTask.run(FutureTask.java:262)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
            at java.lang.Thread.run(Thread.java:745)
    Caused by: javax.persistence.PersistenceException: org.hibernate.TransactionException: JDBC begin transaction failed:
            at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1763)
            at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1677)
            at org.hibernate.jpa.spi.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:1771)
            at org.hibernate.jpa.internal.TransactionImpl.begin(TransactionImpl.java:64)
            at org.springframework.orm.jpa.vendor.HibernateJpaDialect.beginTransaction(HibernateJpaDialect.java:170)
            at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:380)
            ... 16 more
    Caused by: org.hibernate.TransactionException: JDBC begin transaction failed:
            at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.doBegin(JdbcTransaction.java:76)
            at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.begin(AbstractTransactionImpl.java:162)
            at org.hibernate.internal.SessionImpl.beginTransaction(SessionImpl.java:1435)
            at org.hibernate.jpa.internal.TransactionImpl.begin(TransactionImpl.java:61)
            ... 18 more
    Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failureThe last packet successfully received from the server was 5,804 milliseconds ago.  The last packet sent successfully to the server was 3,206 milliseconds ago.
            at sun.reflect.GeneratedConstructorAccessor897.newInstance(Unknown Source)
            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:404)
            at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:981)
            at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3465)
            at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3365)
            at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3805)
            at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2478)
            at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2625)
            at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2547)
            at com.mysql.jdbc.ConnectionImpl.setAutoCommit(ConnectionImpl.java:4874)
            at com.mysql.jdbc.MultiHostMySQLConnection.setAutoCommit(MultiHostMySQLConnection.java:2064)
            at sun.reflect.GeneratedMethodAccessor367.invoke(Unknown Source)
            at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
            at java.lang.reflect.Method.invoke(Method.java:606)
            at com.mysql.jdbc.LoadBalancedConnectionProxy.invokeMore(LoadBalancedConnectionProxy.java:484)
            at com.mysql.jdbc.MultiHostConnectionProxy.invoke(MultiHostConnectionProxy.java:452)
            at com.sun.proxy.$Proxy232.setAutoCommit(Unknown Source)
            at com.mysql.jdbc.MultiHostMySQLConnection.setAutoCommit(MultiHostMySQLConnection.java:2064)
            at sun.reflect.GeneratedMethodAccessor367.invoke(Unknown Source)
            at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
            at java.lang.reflect.Method.invoke(Method.java:606)
            at com.mysql.jdbc.ReplicationConnectionProxy.invokeMore(ReplicationConnectionProxy.java:293)
            at com.mysql.jdbc.MultiHostConnectionProxy.invoke(MultiHostConnectionProxy.java:452)
            at com.sun.proxy.$Proxy233.setAutoCommit(Unknown Source)
            at com.mchange.v2.c3p0.impl.NewProxyConnection.setAutoCommit(NewProxyConnection.java:881)
            at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.doBegin(JdbcTransaction.java:72)
            ... 21 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:100)
            at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:143)
            at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:173)
            at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:2954)
            at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3375)
            ... 43 more

目前的配置如下:

**c3p0 properties:**

db.maxPoolSize=20
db.minPoolSize=10
db.maxConnectionIdleTimeInSec=300
db.idleConnectionTestPeriodInSec=300
db.testConnectionOnCheckin=true
db.testConnectionOnCheckout=true
db.connectionTestQuery=select 1

** DB config **

jdbc.driverClassName=com.mysql.jdbc.ReplicationDriver
jdbc.url=jdbc:mysql:replication://url1,url2/schema

**I have done some c3p0 finer logging following are some traces**
.....
FINER] MBean: com.mchange.v2.c3p0:type=PooledDataSource[z8kflt9j9jerlpms8xe0|8ac49e] registered.

2016-09-13 12:39:51 [localhost-startStop-1] INFO o.s.o.j.LocalContainerEntityManagerFactoryBean -

Building JPA container EntityManagerFactory for persistence unit 'default'

[FINEST] incremented pending_acquires: 1

[FINEST] incremented pending_acquires: 2

[FINEST] incremented pending_acquires: 3

[FINER] com.mchange.v2.resourcepool.BasicResourcePool@37ca3e27 config: [start -> 3; min -> 3; max -> 10; inc -> 3; num_acq_attempts -> 30; acq_attempt_delay -> 1000; check_idle_resources_delay -> 60000; mox_resource_age -> 0; max_idle_time -> 100000; excess_max_idle_time -> 0; destroy_unreturned_resc_time -> 10000; expiration_enforcement_delay -> 2500; break_on_acquisition_failure -> false; debug_store_checkout_exceptions -> true]

[INFO] Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, dataSourceName -> z8kflt9j9jerlpms8xe0|8ac49e, debugUnreturnedConnectionStackTraces -> true, description -> null, driverClass -> com.mysql.jdbc.ReplicationDriver, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> z8kflt9j9jerlpms8xe0|8ac49e, idleConnectionTestPeriod -> 60, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql:replication://url1,url2/schema, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 100, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 10, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 3, numHelperThreads -> 3, numThreadsAwaitingCheckoutDefaultUser -> 0, preferredTestQuery -> select 1, properties -> {user=******, password=******}, propertyCycle -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 10, usesTraditionalReflectiveProxies -> false ]

[FINER] acquire test -- pool size: 0; target_pool_size: 3; desired target? 1

[FINE] awaitAvailable(): [unknown]

[
....

我的假设是池中的从连接从Mysql端关闭但在池中它仍未更新且未标记为非活动状态。假设它的活动连接应用程序尝试从slave获取并失败。 知道这里有什么问题吗?连接池是否存在问题,它不测试从属连接并在使用之前定期刷新连接?

尝试了connectionTest的自定义连接类,但没有运气。连接测试类如下

public class QueryReplicationConnectionTester扩展了DefaultConnectionTester {

private static final long serialVersionUID = -3450145378350470297L;

/**
 * during testing we need to make sure, that not only master
 * but also the slave connection is used. Therefore we need to set
 * the connection to "readonly" to make sure, that the slave 
 * connection is used.
 * 
 * CAUTION: this will only work for ONE SLAVE ENVIRONMENT, since
 * this does not make sure all slaves are checked.
 */
@Override
public int activeCheckConnection(Connection connection, String arg1, Throwable[] arg2) {

    // Initially set to ok
    int status = CONNECTION_IS_OKAY;

    try {

        // remember state and 
        boolean autoCommit = connection.getAutoCommit();
        boolean readOnly = connection.isReadOnly();

        // switch to slave and check slave
        connection.setReadOnly(true);
        connection.setAutoCommit(false);
        status = super.activeCheckConnection(connection, arg1, arg2);

        // if slave is fine, lets check the master
        if ( status == CONNECTION_IS_OKAY ){
            connection.setReadOnly(false);
            connection.setAutoCommit(autoCommit);
            status = super.activeCheckConnection(connection, arg1, arg2);
        }

        connection.setAutoCommit(autoCommit);
        connection.setReadOnly(readOnly);

    } catch (SQLException e) {
        status = CONNECTION_IS_INVALID;
    }

    // return final state
    return status;
}

}

也检查了Mysql日志。我可以看到preferredtestquery(选择1)被激活为master但由于某种原因它没有被激发到奴隶。

1 个答案:

答案 0 :(得分:1)

如果问题出现在您认为的问题上,那么一个简单的解决方法可能就是设置Connection property readFromMasterWhenNoSlaves。然后你可以使用c3p0的内置DefaultConnectionTester,只要主服务器可用,Connections就可以工作。如果主服务器出现故障,那么连接测试将失败,客户端将无法从主服务器获取Connections,直到主服务器恢复生命。但除非您对Connections的所有使用都是只读的,否则可能就是您想要的行为。如果主服务器关闭并且c3p0释放了与应用程序的连接,则它无法知道这些连接是否将用于只读目的,因此应该考虑这些连接已断开。在这种情况下,您可以通过复制获得一些负载分配,但是您无法进行故障恢复"如果主机关闭,则为从机。但是,当奴隶关闭时,你应该故障回到主人。

如果您对应用程序的所有应用程序都是只读的,则可以编写一个ConnectionCustomizer来调用setReadOnly(true)方法中的onAcquire(...)。 c3p0将跟踪setReadOnly(...)的覆盖,并确保客户端看到您设置的值,即使在签入/签出周期后也是如此。然后,默认情况下,大概是连接到奴隶。如果设置readFromMasterWhenNoSlaves,那么您的应用程序应该正确地故障回复到主服务器,因为服务器不可用。请注意,如果客户端的连接完全是只读的,则客户端永远不应设置setReadOnly(false)

但是,您的客户端更可能不是普遍只读的,因此您应该使用普通的连接测试而不先设置只读,但设置为readFromMasterWhenNoSlaves。当主服务器关闭时,连接将无效,但应该可以解决从服务器的问题。

我不确定您为什么没有看到针对奴隶的连接测试,但您可能会尝试使用setAutoCommit(true)而非setAutoCommit(false)进行针对奴隶的测试在您的自定义ConnectionTester中。但我想最后,你不想使用这个连接测试仪,默认的连接测试仪就足够了。

您可能还想将Connection属性autoReconnect设置为true。

注意:我还没有使用过MySQL的ReplicationDriver,这只是快速阅读docs的猜测。 < / p>