Проблема с подключением JDBC MySQL - попытка повторного подключения 3 раза. Сдаваться

У меня есть приложение службы отдыха, работающее на платформе Java Spring. Приложение зависит от подключения к внешней базе данных MySQL, которая подключается через JDBC.

Моя проблема заключается в поддержании прочной связи между остальной службой и базой данных MySQL. У меня есть то, что я считаю рудиментарным отказоустойчивым соединением, которое выглядит примерно так:

public Connection getConnection() throws SQLException {
    if(connection == null){
         this.buildConnection();
    }
    else if(!connection.isValid(10)){ //Rebuild connection if it is no longer valid
        connection.close();
        this.buildConnection();
    }
    return connection;
}

Использование этого метода должно гарантировать, что соединение действительно до выполнения любого запроса. Моя проблема в том, что я периодически получаю исключение при вызове этого метода:

Не удалось создать соединение с сервером базы данных. Пытался переподключиться 3 раза. Сдаваться. SQLState: 08001. Код ошибки: 0.

Вещи, которые меня очень озадачили по этому поводу:

  1. Эта ошибка возникает только периодически. Много раз соединение работает, просто найдите.
  2. Я тестирую это же приложение на своей машине разработчика, и эта ошибка никогда не возникает.

Я специально настроил базу данных MySQL на своем собственном сервере, поэтому я контролирую все ее параметры конфигурации. Из этого я знаю, что эта проблема не связана с максимальным количеством разрешенных подключений или тайм-аутом подключения.

Изменить — Обновление 1:

  • Эта служба размещается как облачная служба на платформе Microsoft Azure.
  • Я случайно поставил его как экземпляр в Северной Европе, а БД находится в Северной Америке - возможно, не связано, но пытаюсь нарисовать всю картину.
  • Пробовал совет по этой ссылке безуспешно. Не использовать пулы потоков, и все ResultSets и Statements/PreparedStatements после этого закрываются.

Изменить — Обновление 2

После некоторого рефакторинга мне удалось успешно реализовать пул соединений HikariCP, как описано @M.Deinum ниже. К сожалению, такая же проблема сохраняется. На моем локальном компьютере все отлично работает, и все модульные тесты проходят, но как только я отправляю его в Azure и жду между запросами более нескольких минут, я получаю эту ошибку при попытке получить соединение из пула:

springHikariCP - Соединение недоступно, время запроса истекло через 38268 мс. SQLState: 08S01. Код ошибки: 0.

Моя конфигурация HikariCP выглядит следующим образом:

//Set up connection pool
HikariConfig config = new HikariConfig();
config.setDriverClassName("com.mysql.jdbc.Driver"); 
config.setJdbcUrl("jdbc:mysql://dblocation");

//Connection pool properties
Properties prop = new Properties();
prop.setProperty("user", "Username");
prop.setProperty("password", "Password");
prop.setProperty("verifyServerCertificate", "false");
prop.setProperty("useSSL","true");
prop.setProperty("requireSSL","true");
config.setDataSourceProperties(properties);

config.setMaximumPoolSize(20);
config.setConnectionTestQuery("SELECT 1");
config.setPoolName("springHikariCP");
config.setLeakDetectionThreshold(5000); 

config.addDataSourceProperty("dataSource.cachePrepStmts", "true");
config.addDataSourceProperty("dataSource.prepStmtCacheSize", "250");
config.addDataSourceProperty("dataSource.prepStmtCacheSqlLimit", "2048");
config.addDataSourceProperty("dataSource.useServerPrepStmts", "true");

dataSource = new HikariDataSource(config);

Любая помощь будет принята с благодарностью.


person JackB    schedule 02.02.2016    source источник
comment
Почему бы вам просто не использовать пул соединений с проверочным запросом и некоторыми функциями автоматического повторного подключения? Похоже, вы пытаетесь обойти себя.   -  person M. Deinum    schedule 02.02.2016
comment
У меня нет веских причин, по которым я не использую пулы потоков (все еще дергаюсь за ноги с JDBC), но из того, что я прочитал, автоматическое переподключение к MySQL ненадежно. Насколько я знаю, метод connection.isValid(int) надежен. Ваш комментарий подразумевает, что использование пула соединений потенциально может устранить подобные детали низкого уровня - так ли это на самом деле?   -  person JackB    schedule 02.02.2016
comment
да. Также в зависимости от пула соединений, который вы используете, запрос, который он использует, действителен (я рекомендую HikariCP в качестве пула). Который должен автоматически удалять недопустимое соединение из пула, создавая новые при необходимости. Следовательно, вам действительно не нужно автоматическое переподключение, просто правильный пул   -  person M. Deinum    schedule 02.02.2016
comment
Вы не должны использовать комбинацию драйвера/jdbcurl, вместо этого следует использовать класс источника данных (как также объясняется в вики HikariCP), что действительно рекомендуется. (Я изменю свой ответ с вашими настройками. Следует отметить, что HikariDataSource расширяет HikariConfig, что экономит вам некоторый код.   -  person M. Deinum    schedule 03.02.2016
comment
Также для тестового запроса используйте /* ping */ select 1 или только /* ping */, так как это вызовет пинг вместо выполнения запроса. Может немного отличаться в поведении.   -  person M. Deinum    schedule 03.02.2016
comment
На самом деле вам вообще не следует использовать тестовый запрос, так как он не будет использовать механизм JDBC4, который с isValid будет использоваться по умолчанию, если запрос не задан.   -  person M. Deinum    schedule 03.02.2016


Ответы (3)


Я предлагаю использовать правильный пул соединений JDBC, такой как HikariCP, который вместе с проверочным запросом, который будет выполняться с правильными интервалами, должен дать вам свежие и правильные связи каждый раз.

Предполагая, что вы используете Spring и xml для настройки источника данных.

<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
    <property name="poolName" value="springHikariCP" />
    <property name="dataSourceClassName"       value="com.mysql.jdbc.jdbc2.optional.MysqlDataSource" />
    <property name="dataSourceProperties">
        <props>
            <prop key="url">${jdbc.url}</prop>
            <prop key="user">${jdbc.username}</prop>
            <prop key="password">${jdbc.password}</prop>
        </props>
    </property>
</bean>

По умолчанию он проверяет соединения при оформлении заказа. Предлагаю попробовать.

Поскольку вы используете конфигурацию java bases, я предлагаю следующее

@Bean
public DataSource dataSource() {
    HikariDataSource ds = new HikariDataSource();
    ds.setPoolName("springHikariCP");
    ds.setMaxPoolSize(20);
    ds.setLeakDetectionThreshold(5000);
    ds.setDataSourceClassName("com.mysql.jdbc.jdbc2.optional.MysqlDataSource");
    ds.addDataSourceProperty("url", url);
    ds.addDataSourceProperty("user", username);
    ds.addDataSourceProperty("password", password);
    ds.addDataSourceProperty("cachePrepStmts", true);
    ds.addDataSourceProperty("prepStmtCacheSize", 250);
    ds.addDataSourceProperty("prepStmtCacheSqlLimit", 2048);
    ds.addDataSourceProperty("useServerPrepStmts", true);
    ds.addDataSourceProperty("verifyServerCertificate", false);
    ds.addDataSourceProperty("useSSL", true);
    ds.addDataSourceProperty("requireSSL", true);

    return ds;
}
person M. Deinum    schedule 02.02.2016
comment
Спасибо за помощь. Я реализовал это, но все еще не решает проблему. Добавлены дополнительные детали к вопросу выше. - person JackB; 02.02.2016
comment
На самом деле вы не должны использовать connectionTestQuery, так как это отключит проверку JDBC4 isValid. Вы по-прежнему можете видеть ошибку, но ваше приложение должно по-прежнему работать. - person M. Deinum; 03.02.2016
comment
Итак, я понял это, и ответ очень близок к этому, но отсутствует ключевой параметр конфигурации, который помог. Кошерно ли редактировать свой ответ, чтобы включить его, а затем принять его? Или я должен добавить свой собственный ответ вместе? - person JackB; 03.02.2016
comment
Что ж, если вы скажете, что такое ключевой параметр конфигурации, я добавлю его в ответ. - person M. Deinum; 04.02.2016
comment
Основные параметры конфигурации, которые заставили его работать: ds.setMaximumPoolSize(5); ds.setMaxLifetime(30000); ds.setMinimumIdle(5); ds.setIdleTimeout(30000); Это в основном гарантирует, что в пуле всегда есть 5 подключений, которые никогда не простаивают более 30 секунд. - person JackB; 04.02.2016

Кажется, это вызвано системной переменной wait_timeout MySQL.

Для MySQL 5.0 5.1, 5.5, 5.6, значение по умолчанию для wait_timeout составляет 28 800 секунд (8 часов), а максимальное значение для wait_timeout:

  • Linux: 31536000 секунд (365 дней, один год)
  • Windows: 2147483 секунды (2^31 миллисекунда, 24 дня 20 часов 31 мин 23 секунды)

Количество секунд, в течение которых сервер ожидает активности в неинтерактивном соединении, прежде чем закрыть его. Этот тайм-аут применяется только к подключениям к файлам сокетов TCP/IP и Unix, но не к подключениям, выполненным с использованием именованных каналов или общей памяти.

Поэтому я думаю, что вы можете попробовать использовать соединение jdbc, чтобы поддерживать интервал проверки связи в несколько секунд, или напрямую использовать своего рода структуру пула соединений JDBC для автоматического управления соединениями jdbc.

Надеюсь, это поможет. С уважением.

person Peter Pan    schedule 02.02.2016

предполагая, что код, который у вас сейчас есть для '//Настроить пул соединений', вызывается только один раз, как и при создании bean-компонента, для инициализации источника данных.

при этом ваш getConnection() будет просто:

public Connection getConnection() throws SQLException {
   return dataSource.getConnection();
}

и убедитесь, что для параметра wait_timeout в mysql установлено значение минуты больше, чем для maxLifetime в hikaricp, которое по умолчанию составляет 30 минут.

Надеюсь это поможет.

person Nitin    schedule 03.02.2016