Schnelles Failover mit Amazon Aurora PostgreSQL - Amazon Aurora

Die vorliegende Übersetzung wurde maschinell erstellt. Im Falle eines Konflikts oder eines Widerspruchs zwischen dieser übersetzten Fassung und der englischen Fassung (einschließlich infolge von Verzögerungen bei der Übersetzung) ist die englische Fassung maßgeblich.

Schnelles Failover mit Amazon Aurora PostgreSQL

Im Folgenden erfahren Sie, wie Sie sicherstellen können, dass ein Failover so schnell wie möglich erfolgt. Zur schnellen Wiederherstellung nach dem Failover können Sie die Cluster-Cache-Verwaltung für Ihren Aurora-PostgreSQL-DB-Cluster verwenden. Weitere Informationen finden Sie unter Schnelle Wiederherstellung nach Failover mit der Cluster-Cacheverwaltung für Aurora PostgreSQL.

Zu den Schritten, die Sie ausführen können, um ein schnelles Failover durchzuführen, gehören die folgenden:

  • Setzen Sie Transmission Control Protocol (TCP)-Keepalives mit kurzen Zeitrahmen, um länger laufende Abfragen zu stoppen, bevor das Lese-Timeout abläuft, wenn ein Fehler auftritt.

  • Legen Sie strikte Timeouts für das DNS (Java Domain Name System) fest. Auf diese Weise wird sichergestellt, dass der schreibgeschützte Aurora-Endpunkt bei späteren Verbindungsversuchen ordnungsgemäß die schreibgeschützten Knoten durchlaufen kann.

  • Legen Sie die Timeout-Variablen, die in der JDBC-Verbindungszeichenfolge verwendet werden, auf einen möglichst niedrigen Wert fest. Setzen Sie für kurze und lange dauernde Abfragen unterschiedliche Verbindungsobjekte ein.

  • Verwenden Sie die bereitgestellten Aurora-Endpunkte für Lese- und Schreibvorgänge, um eine Verbindung zum Cluster herzustellen.

  • Verwenden Sie RDS-API-Operationen, um die Anwendungsreaktion bei serverseitigen Ausfällen zu testen. Testen Sie die Anwendungsreaktionen auf clientseitige Ausfälle mithilfe eines Packet-Dropping-Tool.

  • Verwenden Sie den AWS JDBC-Treiber, um die Failover-Funktionen von Aurora PostgreSQL in vollem Umfang zu nutzen. Weitere Informationen zum AWS JDBC-Treiber und vollständige Anweisungen zu seiner Verwendung finden Sie im Amazon Web Services (AWS) JDBC-Treiber-Repository. GitHub

Diese werden im Folgenden ausführlicher erläutert.

Einstellen von TCP-Keepalive-Parametern

Wenn Sie eine TCP-Verbindung einrichten, wird ein Satz von Timern mit der Verbindung verknüpft. Wenn der Keepalive-Timer den Wert null erreicht, wird ein Keepalive-Prüfpaket an den Endpunkt der Verknüpfung gesendet. Wenn das Prüfpaket eine Antwort erhält, können Sie davon ausgehen, dass die Verbindung nach wie vor aktiv ist.

Indem Sie TCP-Keepalive-Parameter aktivieren und auf strikte Werte festlegen, können Sie sicherstellen, dass im Falle, dass der Client keine Verbindung zur Datenbank herstellen kann, alle aktiven Verbindungen schnell beendet werden. Die Anwendung kann dann eine Verbindung zu einem neuen Endpunkt herstellen.

Die folgenden TCP-Keepalive-Parameter müssen festgelegt werden:

  • tcp_keepalive_time gibt die Zeitspanne in Sekunden an, nach der ein Keepalive-Paket gesendet wird, wenn keine Daten vom Socket gesendet wurden. ACKs werden nicht als Daten betrachtet. Wir empfehlen die folgende Einstellung:

    tcp_keepalive_time = 1

  • tcp_keepalive_intvl gibt die Zeitspanne in Sekunden an, die zwischen dem ersten gesendeten Paket und dem Senden von nachfolgenden Keepalive-Paketen verstreicht. Stellen Sie diese Zeit ein, indem Sie die tcp_keepalive_time-Parameter verwenden. Wir empfehlen die folgende Einstellung:

    tcp_keepalive_intvl = 1

  • tcp_keepalive_probes gibt die Anzahl von unbeantworteten Keepalive-Prüfpaketen an, nach denen die Anwendung benachrichtigt wird. Wir empfehlen die folgende Einstellung:

    tcp_keepalive_probes = 5

Mit diesen Einstellungen sollte die Anwendung innerhalb von fünf Sekunden benachrichtigt werden, wenn die Datenbank nicht mehr antwortet. Falls die Keepalive-Pakete innerhalb des Anwendungsnetzwerks häufig verworfen werden, können Sie einen höheren tcp_keepalive_probes-Wert angegeben. Dies ermöglicht mehr Puffer in weniger zuverlässigen Netzwerken, erhöht jedoch die Zeit, die benötigt wird, um einen tatsächlichen Ausfall zu erkennen.

Einrichten von TCP-Keepalive-Parametern in Linux
  1. Testen Sie, wie Sie Ihre TCP-Keealive-Parameter konfigurieren.

    Wir empfehlen, dies über die Befehlszeile mit den folgenden Befehlen zu tun. Diese vorgeschlagene Konfiguration ist systemweit. Mit anderen Worten, es wirkt sich auch auf alle anderen Anwendungen aus, die Sockets mit der SO_KEEPALIVE-Option erstellen.

    sudo sysctl net.ipv4.tcp_keepalive_time=1 sudo sysctl net.ipv4.tcp_keepalive_intvl=1 sudo sysctl net.ipv4.tcp_keepalive_probes=5
  2. Nachdem Sie eine geeignete Konfiguration für Ihre Anwendung gefunden haben, schreiben Sie diese Einstellungen fest, indem Sie die folgenden Zeilen zu /etc/sysctl.conf hinzufügen, einschließlich aller vorgenommenen Änderungen:

    tcp_keepalive_time = 1 tcp_keepalive_intvl = 1 tcp_keepalive_probes = 5

Konfigurieren Ihrer Anwendung für schnelles Failover

Im Folgenden finden Sie eine Beschreibung verschiedener Konfigurationsänderungen für Aurora PostgreSQL, die Sie für ein schnelles Failover vornehmen können. Weitere Informationen zur Einrichtung und Konfiguration des PostgreSQL-JDBC-Treibers finden Sie in der Dokumentation zum PostgreSQL-JDBC-Treiber.

Reduzieren von DNS-Cache-Timeouts

Wenn Ihre Anwendung versucht, eine Verbindung nach einem Failover aufzubauen, wird der neue Aurora-PostgreSQL-Writer ein früherer Reader sein. Sie können ihn finden, indem Sie den schreibgeschützten Aurora-Endpunkt verwenden, bevor DNS-Updates vollständig verbreitet wurden. Den Java DNS-TTL–(time to live)-Wert niedrig festzulegen, z. B. unter 30 Sekunden, sorgt dafür, dass bei späteren Verbindungsversuchen die Reader-Knoten durchlaufen werden können.

// Sets internal TTL to match the Aurora RO Endpoint TTL java.security.Security.setProperty("networkaddress.cache.ttl" , "1"); // If the lookup fails, default to something like small to retry java.security.Security.setProperty("networkaddress.cache.negative.ttl" , "3");

Einstellen einer Aurora PostgreSQL-Verbindungszeichenfolge für ein schnelles Failover

Für die Verwendung eines schnellen Failover in Aurora PostgreSQL stellen Sie sicher, dass die Verbindungszeichenfolge Ihrer Anwendung eine Liste mit Hosts statt einen einzelnen Host enthält. Im Folgenden eine beispielhafte Verbindungszeichenfolge, die Sie verwenden können, um sich mit einem Aurora-PostgreSQL-Cluster zu verbinden. In diesem Beispiel sind die Hosts fett dargestellt.

jdbc:postgresql://myauroracluster.cluster-c9bfei4hjlrd.us-east-1-beta.rds.amazonaws.com:5432, myauroracluster.cluster-ro-c9bfei4hjlrd.us-east-1-beta.rds.amazonaws.com:5432 /postgres?user=<primaryuser>&password=<primarypw>&loginTimeout=2 &connectTimeout=2&cancelSignalTimeout=2&socketTimeout=60 &tcpKeepAlive=true&targetServerType=primary

Für die bestmögliche Verfügbarkeit und um eine Abhängigkeit von der RDS-API zu vermeiden, empfehlen wir, eine Datei zu pflegen, mit der Sie eine Verbindung herstellen können. Diese Datei enthält eine Hostzeichenfolge, aus der die Anwendung Daten lesen kann, wenn Sie eine Verbindung zur Datenbank herstellen. Die Hostzeichenfolge enthält alle für das Cluster verfügbaren Aurora-Endpunkte. Weitere Informationen zu Aurora-Endpunkten finden Sie unter Amazon Aurora-Verbindungsverwaltung.

Beispielsweise können Sie Ihre Endpunkte wie folgt in einer lokalen Datei speichern.

myauroracluster.cluster-c9bfei4hjlrd.us-east-1-beta.rds.amazonaws.com:5432, myauroracluster.cluster-ro-c9bfei4hjlrd.us-east-1-beta.rds.amazonaws.com:5432

Ihre Anwendung liest aus dieser Datei. um den Hostdatenabschnitt der JDBC-Verbindungzeichenfolge mit Daten füllen. Das Umbenennen des DB-Clusters bewirkt eine Änderung dieser Endpunkte. Stellen Sie sicher, dass Ihre Anwendung dieses Ereignis behandelt, falls es auftritt.

Eine weitere Option ist die Verwendung einer Liste mit DB-Instance-Knoten wie folgt.

my-node1.cksc6xlmwcyw.us-east-1-beta.rds.amazonaws.com:5432, my-node2.cksc6xlmwcyw.us-east-1-beta.rds.amazonaws.com:5432, my-node3.cksc6xlmwcyw.us-east-1-beta.rds.amazonaws.com:5432, my-node4.cksc6xlmwcyw.us-east-1-beta.rds.amazonaws.com:5432

Der Vorteil dieses Ansatzes besteht darin, dass der PostgreSQL-JDBC-Verbindungstreiber alle Knoten dieser Liste in einer Schleife durchläuft, um eine gültige Verbindung zu finden. Im Gegensatz dazu werden bei Verwendung der Aurora-Endpunkte bei jedem Verbindungsversuch nur zwei Knoten ausprobiert. Die Verwendung von DB-Instance-Knoten hat jedoch einen Nachteil. Wenn Sie Knoten zum Cluster hinzufügen oder daraus entfernen und dadurch die Liste der Instance-Endpunkte nicht mehr aktuell ist, findet der Verbindungstreiber möglicherweise keinen geeigneten Host mehr für einen Verbindungsaufbau.

Um sicherzustellen, dass die Anwendung mit dem Verbindungsaufbau zu einem Host nicht zu lange wartet, legen Sie für die folgenden Parameter strikte Werte fest:

  • targetServerType – Steuert, ob der Treiber eine Verbindung zu einem Schreib- oder Leseknoten herstellt. Um sicherzustellen, dass Ihre Anwendungen nur eine Verbindung zu einem Schreibknoten herstellen, legen Sie den targetServerType auf primary fest.

    Werte für den Parameter targetServerType sind primary, secondary, any und preferSecondary. Der preferSecondary-Wert versucht, zuerst die Verbindung zu einem Lese-Knoten herzustellen. Er stellt eine Verbindung zum Schreiber her, wenn keine Leser-Verbindung hergestellt werden kann.

  • loginTimeout – Steuert, wie lange Ihre Anwendung auf die Anmeldung bei der Datenbank wartet, nachdem eine Socket-Verbindung hergestellt wurde.

  • connectTimeout – Steuert, wie lange der Socket wartet, um eine Verbindung zur Datenbank herzustellen.

Sie können weitere Anwendungsparameter ändern, um den Verbindungsvorgang zu beschleunigen, anhängig davon, wie strikt die Anwendung sein soll:

  • cancelSignalTimeout – In einigen Anwendungen soll möglicherweise ein Best-Effort-Abbruchsignal für eine abgelaufene Abfrage gesendet werden. Falls dieses Abbruchsignal in Ihrem Failover-Pfad ist, ziehen Sie dafür eine strikte Einstellung in Erwägung, damit dieses Signal nicht an inaktiven Host gesendet wird.

  • socketTimeout – Dieser Parameter steuert, wie lange vom Socket auf Lesevorgänge gewartet wird. Er kann als globales Abfrage-Timeout genutzt werden, um sicherzustellen, dass eine Abfrage nicht länger als für diesen Wert wartet. Eine gute Praxis ist es, zwei Verbindungs-Handler zu haben. Ein Verbindungs-Handler führt kurzlebige Abfragen aus und legt diesen Wert niedriger fest. Bei einem anderen Verbindungs-Handler für Abfragen mit langer Ausführungsdauer ist dieser Wert viel höher festgelegt. Auf diese Weise können Sie sich sicher sein, dass TCP-Keepalive-Parameter im Falle eines Serverausfalls Abfragen beenden, die über eine lange Zeit ausgeführt werden.

  • tcpKeepAlive – Aktivieren Sie diesen Parameter, um sicherzustellen, dass die festgelegten TCP-Keepalive-Parameter angewendet werden.

  • loadBalanceHosts – Ist der Wert dieses Parameters auf true gesetzt, stellt die Anwendung die Verbindung zu einem zufällig aus der Liste der Host-Kandidaten ausgewählten Host her.

Weitere Optionen zum Abrufen der Hostzeichenfolge

Sie können die Hostzeichenfolge aus mehreren Quellen erhalten, einschließlich der aurora_replica_status-Funktion und durch Verwenden der Amazon RDS-API.

In vielen Fällen müssen Sie bestimmen, wer Schreiber im Cluster ist oder um weitere Leseknoten im Cluster zu finden. Dazu kann sich Ihre Anwendung mit einer beliebigen DB-Instance im DB-Cluster verbinden und die aurora_replica_status-Funktion abfragen. Sie können diese Funktion verwenden, um die Dauer für das Auffinden eines Host für eine Verbindung zu reduzieren. In bestimmten Netzwerkausfallszenarien zeigt out-of-date die aurora_replica_status Funktion jedoch möglicherweise Informationen an oder ist unvollständig.

Eine gute Möglichkeit, um sicherzustellen, dass Ihre Anwendung einen Knoten findet, zu dem eine Verbindung hergestellt werden kann, besteht darin, zu versuchen, eine Verbindung zum Cluster-Schreiber-Endpunkt und dann zum Cluster-Reader-Endpunkt herzustellen. Sie tun dies, bis Sie eine lesbare Verbindung herstellen können. Diese Endpunkte ändern sich nur, wenn Sie Ihren DB-Cluster umbenennen. Sie können diese daher normalerweise als statische Elemente Ihrer Anwendung beibehalten oder in einer Ressourcendatei speichern, aus der Ihre Anwendung liest.

Nachdem Sie eine Verbindung zu einem dieser Endpunkte hergestellt haben, können Sie Informationen über den Rest des Clusters erhalten. Dazu rufen Sie die aurora_replica_status-Funktion auf. Der folgende Befehl ruft z. B. Informationen mit aurora_replica_status ab.

postgres=> SELECT server_id, session_id, highest_lsn_rcvd, cur_replay_latency_in_usec, now(), last_update_timestamp FROM aurora_replica_status(); server_id | session_id | highest_lsn_rcvd | cur_replay_latency_in_usec | now | last_update_timestamp -----------+--------------------------------------+------------------+----------------------------+-------------------------------+------------------------ mynode-1 | 3e3c5044-02e2-11e7-b70d-95172646d6ca | 594221001 | 201421 | 2017-03-07 19:50:24.695322+00 | 2017-03-07 19:50:23+00 mynode-2 | 1efdd188-02e4-11e7-becd-f12d7c88a28a | 594221001 | 201350 | 2017-03-07 19:50:24.695322+00 | 2017-03-07 19:50:23+00 mynode-3 | MASTER_SESSION_ID | | | 2017-03-07 19:50:24.695322+00 | 2017-03-07 19:50:23+00 (3 rows)

Beispielsweise könnte der Host-Abschnitt Ihrer Verbindungszeichenfolge sowohl mit den Schreiber- als auch den Reader-Cluster-Endpunkten, wie im Folgenden dargestellt, beginnen.

myauroracluster.cluster-c9bfei4hjlrd.us-east-1-beta.rds.amazonaws.com:5432, myauroracluster.cluster-ro-c9bfei4hjlrd.us-east-1-beta.rds.amazonaws.com:5432

In diesem Szenario versucht die Anwendung, eine Verbindung zu einem beliebigen Knotentyp (primärer oder sekundärer) herzustellen. Wenn Ihre Anwendung verbunden ist, empfiehlt es sich, zuerst den Lese-/Schreibstatus des Knotens zu untersuchen. Fragen Sie dazu das Ergebnis des Befehls SHOW transaction_read_only ab.

Gibt die Abfrage den Wert OFF zurück, haben Sie erfolgreich eine Verbindung zum primären Knoten hergestellt. Angenommen, der Rückgabewert ist jedoch ON und Ihre Anwendung benötigt eine Lese-/Schreibverbindung. In diesem Fall können Sie die aurora_replica_status-Funktion aufrufen, um festzustellen, ob server_id session_id='MASTER_SESSION_ID' enthält. Diese Funktion gibt Ihnen den Namen des primären Knotens zurück. Sie können dies mit dem endpointPostfix verwenden, das wie folgt beschrieben ist.

Stellen Sie sicher, dass Sie wissen, wenn Sie eine Verbindung zu einem Replica herstellen, das veraltete Daten enthält. In diesem Fall zeigt die aurora_replica_status Funktion möglicherweise out-of-date Informationen an. Sie können einen Schwellenwert für Alterung auf Anwendungsebene festlegen. Um dies zu überprüfen, können Sie den Unterschied zwischen der Serverzeit und dem last_update_timestamp-Wert betrachten. Im Allgemeinen sollte Ihre Anwendung es vermeiden, aufgrund widersprüchlicher Informationen, die von der Funktion aurora_replica_status zurückgegeben werden, zwischen zwei Hosts hin- und herzuschalten. Ihre Anwendung sollte zuerst alle bekannten Hosts ausprobieren, anstatt den von aurora_replica_status zurückgegebenen Daten zu folgen.

Java-Beispiel zum Auflisten von Instances, die den Vorgang DescribeDBClusters-API verwenden

Sie können programmgesteuert mit AWS SDK for Java nach der Instance-Liste suchen, genauer gesagt, mit dem Vorgang DescribeDBClusters-API.

Hier ist ein kleines Beispiel, wie Sie dies in Java 8 durchführen können.

AmazonRDS client = AmazonRDSClientBuilder.defaultClient(); DescribeDBClustersRequest request = new DescribeDBClustersRequest() .withDBClusterIdentifier(clusterName); DescribeDBClustersResult result = rdsClient.describeDBClusters(request); DBCluster singleClusterResult = result.getDBClusters().get(0); String pgJDBCEndpointStr = singleClusterResult.getDBClusterMembers().stream() .sorted(Comparator.comparing(DBClusterMember::getIsClusterWriter) .reversed()) // This puts the writer at the front of the list .map(m -> m.getDBInstanceIdentifier() + endpointPostfix + ":" + singleClusterResult.getPort())) .collect(Collectors.joining(","));

Hier enthält pgJDBCEndpointStr eine formatierte Liste mit Endpunkten, wie im Folgenden.

my-node1.cksc6xlmwcyw.us-east-1-beta.rds.amazonaws.com:5432, my-node2.cksc6xlmwcyw.us-east-1-beta.rds.amazonaws.com:5432

Die Variable endpointPostfix kann eine Konstante sein, die Ihre Anwendung festlegt. Oder Ihre Anwendung kann es erhalten, indem sie den DescribeDBInstances-API-Vorgang für eine einzelne Instance in Ihrem Cluster abfragt. Dieser Wert bleibt innerhalb eines AWS-Region und für einen einzelnen Kunden konstant. Es wird also ein API-Aufruf gespeichert, um diese Konstante in einer Ressourcendatei zu speichern, aus der Ihre Anwendung liest. Im vorherigen Beispiel ist es auf den folgenden festgelegt.

.cksc6xlmwcyw.us-east-1-beta.rds.amazonaws.com

Eine gute Methode zur Gewährleistung der Verfügbarkeit ist die standardmäßige Nutzung der Aurora-Endpunkte Ihres DB-Clusters für den Fall, dass die API nicht oder zu langsam reagiert. Diese Endpunkte sind garantiert auf dem aktuellen Stand – im Zeitraum, der für das Update eines DNS-Datensatzes benötigt wird. Das Aktualisieren des DNS-Eintrags mit einem Endpunkt dauert in der Regel weniger als 30 Sekunden. Sie können den Endpunkt in einer Ressourcendatei speichern, die von Ihrer Anwendung verwendet wird.

Testen eines Failovers

In allen Fällen müssen Sie einen DB-Cluster mit zwei oder mehr DB-Instances haben.

Serverseitig können bestimmte API-Vorgänge einen Ausfall verursachen, anhand dessen Sie testen können, wie Ihre Anwendungen reagieren:

  • FailoverDBCluster – Dieser Vorgang versucht, eine neue DB-Instance in Ihrem DB-Cluster an den Scrhreiber weiterzuleiten.

    Das folgende Codebeispiel zeigt, wie Sie mit failoverDBCluster einen Ausfall verursachen können. Weitere Informationen zur Einrichtung eines Amazon RDS-Clients finden Sie unter Verwenden des AWS SDK for Java.

    public void causeFailover() { final AmazonRDS rdsClient = AmazonRDSClientBuilder.defaultClient(); FailoverDBClusterRequest request = new FailoverDBClusterRequest(); request.setDBClusterIdentifier("cluster-identifier"); rdsClient.failoverDBCluster(request); }
  • RebootDBInstance – Failover ist nicht in diesem API-Vorgang garantiert. Es fährt jedoch die Datenbank auf dem Schreiber herunter. Sie können es verwenden, um zu testen, wie Ihre Anwendung auf das Abbrechen von Verbindungen reagiert. Der ForceFailover-Parameter gilt nicht für Aurora-Engines. Verwenden Sie stattdessen den FailoverDBCluster-API-Vorgang.

  • ModifyDBCluster  – Das Modifizieren des Port-Parameters wird einen Nutzungsausfall verursachen, wenn die Knoten im Cluster über einen neuen Port kommunizieren. Im Allgemeinen kann Ihre Anwendung zuerst auf diesen Fehler reagieren, indem sie sicherstellt, dass nur Ihre Anwendung die Portänderungen kontrolliert. Stellen Sie außerdem sicher, dass die Endpunkte, von denen es abhängig ist, ordnungsgemäß aktualisiert werden können. Sie können dies tun, indem Sie jemanden beauftragen, den Port manuell zu aktualisieren, wenn er Änderungen auf API-Ebene vornimmt. Oder Sie können dies tun, indem Sie die RDS-API in Ihrer Anwendung verwenden, um festzustellen, ob sich der Port geändert hat.

  • ModifyDBInstance – Ändern des DBInstanceClass-Parameters verursacht einen Ausfall.

  • DeleteDBInstance – Das Löschen des primären (Schreibers) wird eine Weiterleitung einer neuen DB-Instance an den Schreiber in Ihrem DB-Cluster verursachen.

Wenn Sie Linux verwenden, können Sie auf Anwendungs- oder Clientseite testen, wie die Anwendung auf plötzliche Paketverluste reagiert. Sie können dies basierend darauf tun, ob Port, Host oder ob TCP-Keepalive-Pakete gesendet oder empfangen werden, indem Sie den Befehl „iptables“ verwenden.

Beispiel für schnelles Java-Failover

Der folgende Beispiel-Code zeigt, wie eine Anwendung einen Aurora-PostgreSQL-Treibermanager einrichten kann.

Die Anwendung ruft die getConnection-Funktion auf, wenn eine Verbindung hergestellt werden soll. Ein Anruf bei getConnection kann möglicherweise keinen gültigen Host finden. Ein Beispiel ist, wenn kein Schreiber gefunden wird, aber der targetServerType-Parameter auf primary festgelegt ist. In diesem Fall sollte die aufrufende Anwendung einfach erneut versuchen, die Funktion aufzurufen.

Um zu vermeiden, dass das Wiederholungsverhalten auf die Anwendung geschoben wird, können Sie diesen Wiederholungsaufruf in einen Verbindungspooler verpacken. Bei den meisten Verbindungspoolern können Sie eine JDBC-Verbindungszeichenfolge angeben. Ihre Anwendung ewerbung kann sich also bei getJdbcConnectionString einwählen und das an den Verbindungspooler weitergeben. Dadurch können Sie ein schnelleres Failover mit Aurora PostgreSQL verwenden.

import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; import org.joda.time.Duration; public class FastFailoverDriverManager { private static Duration LOGIN_TIMEOUT = Duration.standardSeconds(2); private static Duration CONNECT_TIMEOUT = Duration.standardSeconds(2); private static Duration CANCEL_SIGNAL_TIMEOUT = Duration.standardSeconds(1); private static Duration DEFAULT_SOCKET_TIMEOUT = Duration.standardSeconds(5); public FastFailoverDriverManager() { try { Class.forName("org.postgresql.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } /* * RO endpoint has a TTL of 1s, we should honor that here. Setting this aggressively makes sure that when * the PG JDBC driver creates a new connection, it will resolve a new different RO endpoint on subsequent attempts * (assuming there is > 1 read node in your cluster) */ java.security.Security.setProperty("networkaddress.cache.ttl" , "1"); // If the lookup fails, default to something like small to retry java.security.Security.setProperty("networkaddress.cache.negative.ttl" , "3"); } public Connection getConnection(String targetServerType) throws SQLException { return getConnection(targetServerType, DEFAULT_SOCKET_TIMEOUT); } public Connection getConnection(String targetServerType, Duration queryTimeout) throws SQLException { Connection conn = DriverManager.getConnection(getJdbcConnectionString(targetServerType, queryTimeout)); /* * A good practice is to set socket and statement timeout to be the same thing since both * the client AND server will stop the query at the same time, leaving no running queries * on the backend */ Statement st = conn.createStatement(); st.execute("set statement_timeout to " + queryTimeout.getMillis()); st.close(); return conn; } private static String urlFormat = "jdbc:postgresql://%s" + "/postgres" + "?user=%s" + "&password=%s" + "&loginTimeout=%d" + "&connectTimeout=%d" + "&cancelSignalTimeout=%d" + "&socketTimeout=%d" + "&targetServerType=%s" + "&tcpKeepAlive=true" + "&ssl=true" + "&loadBalanceHosts=true"; public String getJdbcConnectionString(String targetServerType, Duration queryTimeout) { return String.format(urlFormat, getFormattedEndpointList(getLocalEndpointList()), CredentialManager.getUsername(), CredentialManager.getPassword(), LOGIN_TIMEOUT.getStandardSeconds(), CONNECT_TIMEOUT.getStandardSeconds(), CANCEL_SIGNAL_TIMEOUT.getStandardSeconds(), queryTimeout.getStandardSeconds(), targetServerType ); } private List<String> getLocalEndpointList() { /* * As mentioned in the best practices doc, a good idea is to read a local resource file and parse the cluster endpoints. * For illustration purposes, the endpoint list is hardcoded here */ List<String> newEndpointList = new ArrayList<>(); newEndpointList.add("myauroracluster.cluster-c9bfei4hjlrd.us-east-1-beta.rds.amazonaws.com:5432"); newEndpointList.add("myauroracluster.cluster-ro-c9bfei4hjlrd.us-east-1-beta.rds.amazonaws.com:5432"); return newEndpointList; } private static String getFormattedEndpointList(List<String> endpoints) { return IntStream.range(0, endpoints.size()) .mapToObj(i -> endpoints.get(i).toString()) .collect(Collectors.joining(",")); } }