Пул соединений с Java и MySQL в веб-приложении Tomcat - PullRequest
3 голосов
/ 19 сентября 2009

Недавно я написал и развернул веб-приложение Java на сервере, и обнаружил необычную проблему, которая не появлялась во время разработки или тестирования.

Когда пользователь входит в систему так долго и начинает отображать данные из базы данных, на странице указывается, что нет записей для просмотра. Но при обновлении страницы первые x записи отображаются в соответствии с правилами нумерации страниц.

Проверяя логи, нахожу:

ERROR|19 09 2009|09 28 54|http-8080-4|myDataSharer.database_access.Database_Metadata_DBA| - Error getting types of columns of tabular Dataset 12

com.mysql.jdbc.CommunicationsException: Communications link failure due to underlying exception: 

** BEGIN NESTED EXCEPTION ** 

java.io.EOFException

STACKTRACE:

java.io.EOFException
    at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:1956)
    at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:2368)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2867)
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1616)

И так по нескольким сотням строк.

Приложение в настоящее время настроено примерно на 100 пользователей, но еще не используется полностью. Он использует пул соединений между сервлетами / jsps Apache Tomcat и базой данных MySQL со следующим примером кода, формирующим общее расположение операции базы данных, которых на странице обычно несколько:

// Gets a Dataset.
public static Dataset getDataset(int DatasetNo) {
    ConnectionPool_DBA pool = ConnectionPool_DBA.getInstance();
    Connection connection = pool.getConnection();
    PreparedStatement ps = null;
    ResultSet rs = null;

    String query = ("SELECT * " +
                    "FROM Dataset " +
                    "WHERE DatasetNo = ?;");

    try {
        ps = connection.prepareStatement(query);
        ps.setInt(1, DatasetNo);
        rs = ps.executeQuery();
        if (rs.next()) {
            Dataset d = new Dataset();
            d.setDatasetNo(rs.getInt("DatasetNo"));
            d.setDatasetName(rs.getString("DatasetName"));
            ...

            }

            return d;
        }
        else {
            return null;
        }
    }
    catch(Exception ex) {
        logger.error("Error getting Dataset " + DatasetNo + "\n", ex);            
        return null;
    }
    finally {
        DatabaseUtils.closeResultSet(rs);
        DatabaseUtils.closePreparedStatement(ps);
        pool.freeConnection(connection);
    }
}

Кто-нибудь может посоветовать способ исправления этой проблемы?

Я полагаю, что это связано с тем, что MySQL оставляет соединения для опросов открытыми до восьми часов, но я не уверен.

Спасибо

Мартин О'Ши.


Просто, чтобы прояснить один момент, касающийся моего метода пула соединений, я использую не Oracle в своем приложении, а собственный класс следующим образом:

package myDataSharer.database_access;

import java.sql.*;
import javax.sql.DataSource;
import javax.naming.InitialContext;
import org.apache.log4j.Logger;

public class ConnectionPool_DBA {

    static Logger logger = Logger.getLogger(ConnectionPool_DBA.class.getName());

    private static ConnectionPool_DBA pool = null;
    private static DataSource dataSource = null;


    public synchronized static ConnectionPool_DBA getInstance() {
        if (pool == null) {
            pool = new ConnectionPool_DBA();
        }
        return pool;
    }

    private ConnectionPool_DBA() {
        try {
            InitialContext ic = new InitialContext();
            dataSource = (DataSource) ic.lookup("java:/comp/env/jdbc/myDataSharer");
        }
        catch(Exception ex) {
            logger.error("Error getting a connection pool's datasource\n", ex);
        }
    }

    public void freeConnection(Connection c) {
        try {
            c.close();
        }
        catch (Exception ex) {
            logger.error("Error terminating a connection pool connection\n", ex);           
        }
    }

    public Connection getConnection() {
        try {
            return dataSource.getConnection();
        }
        catch (Exception ex) {
            logger.error("Error getting a connection pool connection\n", ex);            
            return null;
        }
    }    
}

Я думаю, что упоминание Oracle связано с тем, что я использовал похожее имя.

Ответы [ 5 ]

4 голосов
/ 19 сентября 2009

Существует несколько советов по предотвращению этой ситуации, полученных из других источников, особенно из реализаций пулов соединений других драйверов и с других серверов приложений. Некоторая информация уже доступна в документации Tomcat по источникам данных JNDI.

  1. Установите расписание очистки / повторного использования, которое закроет соединения в пуле, если они неактивны после определенного периода. Не рекомендуется оставлять соединение с базой данных открытым в течение 8 часов (по умолчанию MySQL). На большинстве серверов приложений значение тайм-аута неактивного соединения настраивается и обычно составляет менее 15 минут (т. Е. Соединения нельзя оставлять в пуле более 15 минут, если они не используются повторно). В Tomcat, при использовании источника данных JNDI, используйте параметры removeAbandoned и removeAbandonedTimeout , чтобы сделать то же самое.
  2. Когда новое соединение возвращается из пула в приложение, убедитесь, что оно сначала протестировано. Например, большинство серверов приложений, которые я знаю, могут быть настроены таким образом, чтобы соединение с базой данных Oracle проверялось с помощью команды «ВЫБОР 1 ИЗ двойного». В Tomcat используйте свойство validationQuery, чтобы установить соответствующий запрос для MySQL - я считаю, что это «SELECT 1» (без кавычек). Причина, по которой установка значения свойства validationQuery помогает, заключается в том, что если запрос не выполняется, соединение удаляется из пула и на его месте создается новое.

Что касается поведения вашего приложения, пользователь, вероятно, видит результат пула, впервые возвращающего устаревшее соединение с приложением. Во второй раз пул, вероятно, возвращает другое соединение, которое может обслуживать запросы приложения.

Источники данных Tomcat JNDI основаны на Commons DBCP, поэтому свойства конфигурации , применимые к DBCP, будут применяться и к Tomcat.

3 голосов
/ 19 сентября 2009

Я бы удивился, почему вы используете ConnectionPool_DBA в своем коде, а не позволяете Tomcat обрабатывать пул и просто просматриваете соединение с помощью JNDI.

Почему вы используете пул соединений Oracle с MySQL? Когда я выполняю поиск JNDI и пул соединений, я предпочитаю библиотеку Apache DBCP. Я считаю, что это работает очень хорошо.

Я бы также спросил, генерируют ли ваши методы DatabaseUtils какие-либо исключения, потому что, если один из вызовов до вашего вызова pool.freeConnection () сгенерирует один, вы никогда не освободите это соединение.

Мне не очень нравится ваш код, потому что класс, выполняющий операции SQL, должен передавать в него свой экземпляр Connection и не должен нести двойную ответственность за получение и использование Connection. Класс персистентности не может знать, используется ли он в более крупной транзакции. Лучше иметь отдельный сервисный уровень, который получает Соединение, управляет транзакцией, маршализирует классы постоянства и очищает, когда она завершена.

UPDATE:

Google обнаружил класс Oracle с тем же именем, что и у вас. Теперь мне действительно не нравится ваш код, потому что вы написали что-то свое, когда была легко доступна лучшая альтернатива. Я бы сразу бросил вашу и переделал это, используя DBCP и JNDI.

2 голосов
/ 20 сентября 2009

Эта ошибка означает, что сервер неожиданно закрывает соединение. Это может произойти в следующих 2 случаях,

  1. MySQL закрывает простаивающее соединение через определенное время (по умолчанию 8 часов). Когда это происходит, ни один поток не отвечает за закрытие соединения, поэтому оно становится устаревшим. Это, скорее всего, причина, если эта ошибка возникает только после длительного простоя.

  2. Если вы не полностью прочитали все ответы, соединение может быть возвращено в пул в состоянии занятости. В следующий раз команда отправляется в MySQL и закрывает соединение из-за неправильного состояния. Если ошибка возникает довольно часто, это, вероятно, причина.

Между тем, создание ветки о выселении поможет облегчить проблему. Добавьте что-то вроде этого в источник данных,

          ...
          removeAbandoned="true"
          removeAbandonedTimeout="120"
          logAbandoned="true"
          testOnBorrow="false"
          testOnReturn="false"
          timeBetweenEvictionRunsMillis="60000"
          numTestsPerEvictionRun="5"
          minEvictableIdleTimeMillis="30000"
          testWhileIdle="true"
          validationQuery="select now()"
0 голосов
/ 26 марта 2010

Если вы не нашли свой ответ, я имел дело с этим последний день. По сути, я делаю то же, что и вы, за исключением того, что я основываю свой пул на apache.commons.pool. Та же самая ошибка, которую вы видите EOF. Проверьте файл журнала ошибок mysqld, который, скорее всего, находится в вашем каталоге данных. Ищите mysqld сбой. mysqld_safe быстро перезапустит ваш mysqld, если он выйдет из строя, поэтому не будет очевидным, что это так, если вы не заглянете в его лог-файл. / var / log не помогает в этом сценарии.

Соединения, созданные до сбоя, будут EOF после сбоя.

0 голосов
/ 19 сентября 2009

Существует ли маршрутизатор между веб-сервером и базой данных, который прозрачно закрывает незанятые соединения TCP / IP?

Если это так, ваш пул соединений должен либо отбрасывать неиспользуемые соединения более чем на XX минут из пула, либо выполнять какой-либо пинг каждые YY минуты для соединения, чтобы поддерживать его активным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...