Реализация пула соединений: Java - PullRequest
13 голосов
/ 06 июля 2011

В одном из интервью, с которым я столкнулся, меня попросили реализовать пул соединений.Таким образом, подход был такой:

  1. Создать List или HashMap
  2. Создать предварительно определенное количество соединений
  3. Добавить их в коллекцию.
  4. Теперь, когда вызывается метод ConnectionImpl getConnection() класса ConnectionPoolingImpl, возвращается ссылка на соединение.

Теперь, когда кто-то возвращает соединение (releaseConnection(ConnectionImpl O)), как я могу гарантировать, что когдато же самое приложение снова пытается повторно использовать объект подключения, моя реализация выдает исключение?

Этот же объект подключения мог быть возвращен новому приложению, и он должен иметь возможность его использовать.

Моя точка зрения состояла бы в том, чтобы поддерживать флаговую переменную в структуре другого типа массива для каждого Connectionimpl объекта и устанавливать эту переменную в допустимое значение.Когда пользователь возвращает объект соединения, я бы сделал это недопустимым значением.Для каждой операции в моем ConnectionImpl мне нужно будет проверить, был ли у пользователя действительный флаг.

Что бы вы сказали по этому подходу?

Ответы [ 4 ]

20 голосов
/ 06 июля 2011

Я бы не возвращал "реальный" объект соединения из пула, но обертку, которая вместо * клиента предоставляет контроль жизненного цикла соединения pool .

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

interface Connection {
    int read(); // reads an int from the connection
    void close(); // closes the connection
}

Чтение реализации из потока может выглядеть следующим образом (игнорирование исключений, обработка EOF,и т.д.):

class StreamConnection implements Connection {
    private final InputStream input;
    int read(){ return input.read(); }
    void close(){ input.close(); }
}

Кроме того, давайте предположим, что у вас есть пул для StreamConnection s, который выглядит следующим образом (опять же, игнорируя исключения, параллелизм и т. д.):

class StreamConnectionPool {
    List<StreamConnection> freeConnections = openSomeConnectionsSomehow();
    StreamConnection borrowConnection(){ 
        if (freeConnections.isEmpty()) throw new IllegalStateException("No free connections");
        return freeConnections.remove(0); 
    }
    void returnConnection(StreamConnection conn){
        freeConnections.add(conn);
    }
}

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

Решением является (конечно) еще один уровень косвенности: создайте пул, который возвращает оболочку Connection, которая вместо закрытия базового соединения при вызове close() возвращает его в пул:

class ConnectionPool {

    private final StreamConnectionPool streamPool = ...;

    Connection getConnection() {
        final StreamConnection realConnection = streamPool.borrowConnection();
        return new Connection(){
            private boolean closed = false;
            int read () {
                if (closed) throw new IllegalStateException("Connection closed"); 
                return realConnection.read();
            }
            void close() {
                if (!closed) {
                    closed = true;
                    streamPool.returnConnection(realConnection);
                }
            }
            protected void finalize() throws Throwable {
                try {
                    close();
                } finally {
                    super.finalize();
                }
            }
        };
    }

}

Это ConnectionPool будет единственной вещью, которую когда-либо увидит код клиента.Предполагая, что он является единственным владельцем StreamConnectionPool, этот подход имеет несколько преимуществ:

Снижение сложности и минимальное влияние на код клиента - единственная разница между открытием соединений самостоятельно и использованием пулаявляется то, что вы используете фабрику, чтобы получить Connection s (что вы, возможно, уже сделали, если вы используете внедрение зависимостей).Самое главное, вы всегда очищаете свои ресурсы одним и тем же способом, т. Е. Звоните close().Точно так же, как вас не волнует, что делает read, пока он предоставляет вам необходимые данные, вам все равно, что делает close(), пока он высвобождает ресурсы, на которые вы заявили.Вам не нужно думать, является ли это соединение из пула.

Защита от злонамеренного / неправильного использования - клиенты могут возвращать только те ресурсы, которые они извлекли из пула;они не могут закрыть основные связи;они не могут использовать соединения, которые уже вернули ... и т. д.

"Гарантированный" возврат ресурсов - благодаря нашей реализации finalize, даже если все ссылки на заимствованыConnection потеряно, оно все еще возвращается в пул (или, по крайней мере, имеет шанс быть возвращенным).Разумеется, соединение будет поддерживаться дольше, чем необходимо, возможно, бесконечно, поскольку завершение не гарантируется, но это небольшое улучшение.

5 голосов
/ 06 июля 2011

Я бы просто сказал им, что буду использовать класс JdbcConnectionPool ( здесь ), который поставляется с H2 (вы, вероятно, можете его скопировать).Винт пытается реализовать один :) Это может быть вопрос с подвохом.

0 голосов
/ 27 мая 2018

Реализация ConnectionPool

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;



/** A Connection Pool with 5 Available Connections **/
class ConnectionPool {

    private List<Connection>availableConnections = 
                                new ArrayList<Connection>();
    private List<Connection>usedConnections = new ArrayList<Connection>();
    private final int MAX_CONNECTIONS = 5;

    private String URL;
    private String USERID;
    private String PASSWORD;


    /** Initialize all 5 Connections and put them in the Pool **/
    public ConnectionPool(String Url, String UserId, String password)
            throws SQLException {
        this.URL = Url;
        this.USERID = UserId;
        this.PASSWORD = password;

        for (int count = 0; count <MAX_CONNECTIONS; count++) {
            availableConnections.add(this.createConnection());
        }

    }






/** Private function, 
    used by the Pool to create new connection internally **/

    private Connection createConnection() throws SQLException {
        return DriverManager
                .getConnection(this.URL, this.USERID, this.PASSWORD);
    }




    /** Public function, used by us to get connection from Pool **/
    public Connection getConnection() {
        if (availableConnections.size() == 0) {
            System.out.println("All connections are Used !!");
            return null;
        } else {
            Connection con = 
            availableConnections.remove(
                availableConnections.size() - 1);
            usedConnections.add(con);
            return con;
        }
    }



    /** Public function, to return connection back to the Pool **/
    public boolean releaseConnection(Connection con) {
        if (null != con) {
            usedConnections.remove(con);
            availableConnections.add(con);
            return true;
        }
        return false;
    }





    /** Utility function to check the number of Available Connections **/
    public int getFreeConnectionCount() {
        return availableConnections.size();
    }
}
0 голосов
/ 06 июля 2011

ОК, поэтому, если я правильно понимаю, ваш вопрос в основном «как мы можем гарантировать, что поток не вернет соединение с пулом, а затем продолжит его использовать?».При условии, что вы не передадите «необработанный» объект Connection вызывающей стороне, тогда ответ по сути будет «вы можете поместить некоторый элемент управления где-нибудь, если хотите».

Фактическая проверка может включать пометку каждого соединения с помощьюкакой поток «владеет» им в данный момент, а затем удостоверится, что это всегда Thread.currentThread () во время любого вызова для использования соединения.

Не имеет большого значения, к какому объекту вы возвращаетесьпользователь соединения для представления соединения: это может быть ваша собственная реализация-обертка Connection или просто какой-то другой объект-обертка с вашими методами для выполнения запросов.Независимо от того, что вы используете, вам просто нужно сделать вышеупомянутую проверку перед выполнением любого запроса.Имейте в виду, что в целях безопасности обычно не следует разрешать выполнение «сырого» произвольного SQL, но все запросы должны основываться на четко определенном PreparedStatement.Поэтому нет особого принуждения возвращать фактическую реализацию Connection, кроме того, что в некоторых случаях это может помочь вам перенести существующий код (и / или если вы решили, что действительно хотите разрешить выполнение произвольного SQL).

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

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