Otwarcie połączenia z bazą danych jest kosztowną operacją, a buforowanie połączeń służy do utrzymywania otwartych połączeń z bazą danych, aby można było ich ponownie użyć. Pozwala to uniknąć konieczności wielokrotnego otwierania sesji sieciowych, uwierzytelniania i sprawdzania autoryzacji. Tworzenie puli powoduje, że połączenia są aktywne, dzięki czemu w przypadku późniejszego żądania połączenia używane jest jedno z aktywnych, zamiast tworzyć je od zera.
Zespół połączeń
Pula połączeń stała się jedną z najczęstszych metod obsługi połączeń z bazą danych przed żądaniem zapytania. Zwykle uważamy, że połączenie z bazą danych jest szybkie, ale tak nie jest, zwłaszcza gdy łączy się duża liczba klientów. Bez puli połączeń połączenie zajęłoby żądaniu do 35-50 ms, ale 1-2 ms w przypadku zastosowania puli połączeń. Pula połączeń polega zatem na wstępnym przydzielaniu połączeń z bazą danych, a następnie odtwarzaniu ich, gdy łączą się nowi klienci
Powody łączenia połączeń
- Aby uniknąć awarii serwera. Serwery PostgreSQL są ograniczone do liczby klientów, które obsługują jednocześnie, w zależności od parametru pamięci. Jeśli ta liczba zostanie przekroczona, zakończy się awarią serwera. W przypadku puli połączeń klienci używają określonej liczby połączeń.
- Ułatwienie przetwarzania zapytań. Zwykle żądania bazy danych są wykonywane w sposób szeregowy z kryterium pierwsze weszło, pierwsze wyszło. W przypadku dużej liczby klientów osiągnięcie tego procesu zajęłoby wieki. Dlatego podejście powinno polegać na nawiązaniu pojedynczego połączenia z żądaniami potokowymi, które mogą być wykonywane jednocześnie, a nie każde na raz.
- Poprawa bezpieczeństwa. Często połączenie obejmuje uzgadnianie, które może trwać średnio 25-35 ms, w którym ustanawiany jest SSL, sprawdzane są hasła i udostępniane informacje konfiguracyjne. Cała ta praca dla każdego podłączonego użytkownika spowoduje znaczne wykorzystanie pamięci. Jednak w przypadku puli połączeń liczba połączeń jest zmniejszona, a tym samym oszczędza się pamięć.
Rodzaje puli połączeń
Zasadniczo istnieją dwa rodzaje puli połączeń, jednak istnieje trzeci rodzaj obejścia, który działa jak strategia puli połączeń znana jako trwałe połączenia.
Stałe łączenie połączeń
To podejście ma na celu utrzymanie aktywnego połączenia początkowego od momentu jego zainicjowania. Nie w pełni obsługuje funkcje puli połączeń, ale wystarczająco dobrze, aby zapewnić ciągłe połączenie. Jest to bardzo pomocne w przypadku małego zestawu połączeń klienckich, których narzut może wynosić od 25 do 50 ms. Ograniczeniem tego podejścia jest to, że jest ono ograniczone do liczby połączeń z bazą danych, przy czym zwykle jest to jedno połączenie na wejście do serwera.
Łączenie połączeń ramowych
Pula połączeń szkieletowych występuje na poziomie aplikacji, dzięki czemu za każdym razem, gdy uruchamiany jest skrypt serwera, pula połączeń jest ustanawiana w celu obsługi żądań zapytań, które nadejdą później.
Samodzielne łączenie połączeń
Dla każdego połączenia z bazą danych wykorzystywana jest dodatkowa pamięć o wielkości od 5 do 10 MB, aby zaspokoić żądanie zapytania. Nie jest to dobre dla dużej liczby połączeń. Korzystanie z puli połączeń struktury może być ograniczone przez tę liczbę połączeń, ponieważ może to spowodować duże użycie pamięci. Dlatego decydujemy się na korzystanie z puli połączeń Standalone, która jest skonfigurowana zgodnie z sesjami, wyciągami i transakcjami Postgres. Podstawową zaletą tego podejścia jest:minimalny koszt około 2kb dla każdego połączenia.
Kiedy tworzysz klasę puli połączeń, powinna ona spełniać następujące czynniki w celu zwiększenia wydajności bazy danych:
- Wstępnie przydziel połączenia
- Nadzór nad dostępnymi połączeniami
- Przypisz nowe połączenia
- Poczekaj, aż połączenie będzie dostępne
- Zamknij połączenie
Wstępna alokacja połączeń
Zapewnienie większej liczby połączeń z wyprzedzeniem ułatwi obsługę zgłoszeń w momencie uruchomienia aplikacji. Na przykład, jeśli Twój serwer został opracowany w języku Java, możesz użyć wektorów do przechowywania dostępnych połączeń bezczynnych, korzystając z poniższego kodu.
availableConnections = new Vector(connections);
busyConnections = new Vector();
for(int i=0; i<connections; i++) {
availableConnections.addElement(makeNewConnection());
}
Nadzorowanie dostępnych połączeń
Klasa powinna być w stanie sprawdzić, czy na liście zajętych połączeń nie ma żadnych bezczynnych połączeń, i zwrócić je. Odbywa się to zasadniczo w celu ponownego użycia połączenia lub zamknięcia połączeń, które nie są używane. Czasami połączenia wygasają, dlatego podczas zwracania połączenia ważne jest, aby sprawdzić, czy nadal jest otwarte. Jeśli nie, musisz odrzucić to połączenie i powtórzyć proces. Gdy połączenie jest odrzucane, otwierany jest slot, który może być użyty do przetworzenia nowego połączenia po osiągnięciu limitu. Można to osiągnąć za pomocą
public synchronized Connection getConnection() throws SQLException {
if (!availableConnections.isEmpty()) { Connection existingConnection =
(Connection)availableConnections.lastElement(); int lastIndex = availableConnections.size() - 1; availableConnections.removeElementAt(lastIndex); if (existingConnection.isClosed()) {
notifyAll(); // Freed up a spot for anybody waiting.
return(getConnection()); // Repeat process. } else {
busyConnections.addElement(existingConnection);
return(existingConnection); }
} }
Przypisywanie nowego połączenia
Powinieneś być w stanie uruchomić wątek w tle, aby przypisać nowe połączenie, jeśli nie ma bezczynności i jeśli limit połączenia jest prawie osiągnięty.
if ((totalConnections() < maxConnections) && !connectionPending) { // Pending = connecting in bg
makeBackgroundConnection(); }
try {
wait(); // Give up lock and suspend self.
} catch(InterruptedException ie) {} return(getConnection()); // Try again.
Czekam na nowe połączenie
Gdy nie ma bezczynnego połączenia i osiągnięto limit połączeń, konfiguracja powinna mieć możliwość oczekiwania na nowe połączenie bez ciągłego buforowania. Możemy to zrobić za pomocą metody wait, która zapewnia blokadę synchronizacji wątku i zawiesza wątek do momentu otrzymania powiadomienia.
try {
wait();
} catch(InterruptedException ie) {}
return(getConnection());
Aby zapewnić dobrą etykę aplikacji, klienci nie powinni czekać w czasie rzeczywistym na połączenie, zamiast tego wyrzucą wyjątek w przypadku braku połączeń z poniższym kodem:
throw new SQLException("Connection limit reached");
Zamykanie połączenia
Gdy połączenia są zbierane bezużytecznie, należy je zamknąć, zamiast robić to wprost. Jeśli jednak chcesz mieć jednoznaczne podejście do zamknięcia połączenia, możesz użyć:
public synchronized void closeAllConnections() {
// The closeConnections method loops down Vector, calling // close and ignoring any exceptions thrown. closeConnections(availableConnections); availableConnections = new Vector(); closeConnections(busyConnections);
busyConnections = new Vector();
}
Wniosek
Pule połączeń dla PostgreSQL pomaga nam zmniejszyć liczbę zasobów wymaganych do połączenia z bazą danych i poprawia szybkość połączenia z bazą danych. Osiąga się to poprzez łączenie połączeń z bazą danych, utrzymywanie tych połączeń i w konsekwencji zmniejszenie liczby połączeń, które muszą zostać otwarte.