Реализация пула соединений: Java

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

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

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

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

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

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


person crackerplace    schedule 06.07.2011    source источник
comment
Я бы попробовал использовать правильный английский для начала. ;) Проблема с флагом заключается в том, что соединение может быть действительным, поскольку оно используется другим потоком, когда вы пытаетесь использовать его повторно.   -  person Peter Lawrey    schedule 06.07.2011
comment
Теперь .. вам нужно лучше, чем приведенное выше подробное объяснение .. :-) .. это будет статья, а не вопрос ..   -  person crackerplace    schedule 06.07.2011
comment
@Joachim, проделал хорошую работу по переводу на английский язык, не превратив его в статью. ;) Я объяснил, почему этот подход не сработает. Есть ли у вас какие-либо другие предложения относительно того, как проблема может быть решена?   -  person Peter Lawrey    schedule 06.07.2011
comment
Схема моего решения: никогда не возвращать фактическое соединение, всегда возвращать оболочку. И возвращайте каждый объект-оболочку только один раз.   -  person Joachim Sauer    schedule 06.07.2011
comment
@ Иоахим, да, это имеет смысл ..   -  person crackerplace    schedule 06.07.2011
comment
@Joachim sourcemaking.com/design_patterns/object_pool/java# .. это реализовано здесь, но объект соединения возвращается напрямую без какой-либо оболочки. В этом случае я думаю, что пользователь может повторно использовать соединение даже после его освобождения?   -  person crackerplace    schedule 06.07.2011
comment
@whokares: это правильно. Это не очень хорошая реализация пула соединений. Кстати: если вы не делаете это в качестве домашнего задания и/или для обучения, я бы посоветовал вам не реализовывать собственный пул соединений, а вместо этого использовать существующий (например, Apache DBCP, или c3p0).   -  person Joachim Sauer    schedule 06.07.2011
comment
@Joachim. Спасибо за информацию, кстати, это просто для понимания внутренностей, а не для какого-либо пользовательского использования .. Ура   -  person crackerplace    schedule 06.07.2011


Ответы (4)


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

Предположим, у вас действительно простое соединение, из которого вы можете считывать значения 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, который выглядит так (опять же, игнорируя исключения, параллелизм и т. д.):

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 (что вы, возможно, уже сделали, если используете внедрение зависимости). Самое главное, вы всегда очищаете свои ресурсы одним и тем же способом, т. е. вызывая close(). Точно так же, как вам все равно, что делает read, пока он дает вам нужные данные, вас не волнует, что делает close(), пока он освобождает ресурсы, которые вы потребовали. Вам не нужно думать, является ли это соединение из пула или нет.

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

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

person gustafc    schedule 06.07.2011
comment
В любом случае, мы не будем закрывать соединение как пул, за исключением редких случаев. Но как вы обрабатываете логику(), когда вы отменяете доступ к объекту соединения после его возврата? - person crackerplace; 06.07.2011
comment
Это в read(), часть, которая говорит if (closed) throw new IllegalStateException("Connection closed"). По сути, это ваша идея флагов, проверяющих, возвращается ли соединение или нет, просто оно находится в другом месте (обертка вместо пула). - person gustafc; 06.07.2011
comment
Другая проблема заключается в том, что когда вы возвращаете оболочку, вам придется издеваться над всеми методами реального объекта соединения, переопределяя, что немного громоздко.... - person crackerplace; 06.07.2011
comment
Может быть громоздким, но это также единственный безопасный способ. - person gustafc; 06.07.2011
comment
Я думаю, что это не сработает, так как... будет много потоков, обращающихся к объектам соединения... поэтому после того, как поток освободит объект соединения, если вы установите Closed = true, как другой поток после того, как получит соединение, сможет читать. .и вы в любом случае создаете новые объекты подключения, чем повторно используете .. - person crackerplace; 06.07.2011
comment
При каждом заимствовании соединения создается новая оболочка. Нас не волнуют обертки, их создание смехотворно дешево. Основные соединения, которые нам действительно важны, и они используются повторно. Я бы посоветовал вам немного поиграть с моим кодом, запустить его в отладчике и посмотреть, как он работает во время выполнения. - person gustafc; 06.07.2011
comment
Как правильно указывает @whokares, приведенное выше решение игнорирует проблемы параллелизма. - person Cody; 24.05.2015

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

person Chris Dennett    schedule 06.07.2011

Реализация пула подключений

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();
    }
}
person Premraj    schedule 27.05.2018

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

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

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

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

person Neil Coffey    schedule 06.07.2011