1. HintergrundIn letzter Zeit wurden von Testern viele Probleme gemeldet, darunter besonders beunruhigende Probleme, die bei Systemzuverlässigkeitstests auftraten. Erstens treten solche Probleme manchmal „sporadisch“ auf und lassen sich in der Umgebung nur schwer schnell reproduzieren. Zweitens kann die Lokalisierungskette von Zuverlässigkeitsproblemen manchmal sehr lang sein. In extremen Fällen kann es erforderlich sein, von Dienst A zu Dienst Z oder vom Anwendungscode bis zur Hardwareebene zu verfolgen. Dieses Mal zeige ich Ihnen, wie ich ein MySQL-Hochverfügbarkeitsproblem lokalisiert habe. Der Prozess war voller Wendungen, aber das Problem selbst ist recht repräsentativ, deshalb zeichne ich es als Referenz auf. ArchitekturZunächst einmal verwendet dieses System MySQL als Hauptkomponente zur Datenspeicherung. Das Ganze ist eine typische Microservice-Architektur (SpringBoot + SpringCloud), und die Persistenzschicht verwendet die folgenden Komponenten: Mybatis, realisiert SQL <-> Methodenzuordnung hikaricp, Implementierung eines Datenbankverbindungspools mariadb-java-client, implementiert JDBC-Treiber Im MySQL-Serverteil verwendet das Backend eine Dual-Master-Architektur und das Front-End verwendet Keepalived in Kombination mit Floating IP (VIP), um eine Schicht hoher Verfügbarkeit bereitzustellen. wie folgt: veranschaulichen
Keepalived implementiert eine Routing-Layer-Konvertierung basierend auf dem VRRP-Protokoll. Gleichzeitig verweist VIP nur auf eine virtuelle Maschine (Master). Wenn der Masterknoten ausfällt, erkennen andere Keepalived-Knoten das Problem und wählen einen neuen Master aus. Danach wechselt VIP zu einem anderen verfügbaren MySQL-Instanzknoten. Auf diese Weise verfügt die MySQL-Datenbank über grundlegende Hochverfügbarkeitsfunktionen. Ein weiterer Punkt ist, dass Keepalived auch regelmäßige Integritätsprüfungen der MySQL-Instanz durchführt. Sobald es feststellt, dass die MySQL-Instanz nicht verfügbar ist, beendet es seinen eigenen Prozess, wodurch dann die VIP-Umschaltaktion ausgelöst wird. ProblemphänomenAuch dieser Testfall basiert auf dem Szenario eines Ausfalls einer virtuellen Maschine:
Allerdings wurde nach vielen Tests festgestellt, dass nach einem Neustart des MySQL Masternode Containers mit einer gewissen Wahrscheinlichkeit das Business nicht mehr erreichbar ist! 2. AnalyseprozessNachdem das Problem aufgetreten war, war die erste Reaktion des Entwicklers, dass es ein Problem mit dem Hochverfügbarkeitsmechanismus von MySQL gab. Da es in der Vergangenheit Probleme gab, bei denen der VIP-Wechsel aufgrund einer falschen Keepalived-Konfiguration nicht rechtzeitig erfolgte, sind wir bereits auf der Hut davor. Nach einer gründlichen Untersuchung habe ich keine Konfigurationsprobleme mit Keepalived gefunden. Da ich keine anderen Optionen hatte, habe ich die Tests ein paar Mal wiederholt und das Problem trat erneut auf. Wir haben dann mehrere Fragen gestellt: 1.Keepalived wird eine Beurteilung basierend auf der Erreichbarkeit der MySQL-Instanz vornehmen. Könnte es ein Problem mit der Integritätsprüfung geben? In diesem Testszenario führt die Zerstörung des MySQL-Containers jedoch dazu, dass die Porterkennung von Keepalived fehlschlägt, was wiederum dazu führt, dass Keepalived fehlschlägt. Wenn auch Keepalived beendet wird, sollte VIP automatisch vorzeitig beendet werden. Durch Vergleich der Informationen der beiden virtuellen Maschinenknoten wurde festgestellt, dass der VIP tatsächlich umgeschaltet wurde. 2. Ist der Container, in dem sich der Geschäftsprozess befindet, im Netzwerk nicht erreichbar? Versuchen Sie, den Container zu betreten und führen Sie nach dem Wechsel einen Telnet-Test für die Floating-IP und den Port durch. Sie werden feststellen, dass der Zugriff weiterhin erfolgreich ist. VerbindungspoolNachdem wir die beiden vorherigen verdächtigen Punkte behoben haben, können wir unsere Aufmerksamkeit nur noch dem DB-Client des Business-Dienstes zuwenden. Aus den Protokollen können wir ersehen, dass zum Zeitpunkt des Fehlers auf der Geschäftsseite einige Ausnahmen aufgetreten sind:
Die Meldung hier lautet, dass die Zeit zum Herstellen der Verbindung für den Geschäftsvorgang abgelaufen ist (über 30 Sekunden). Kann es sein, dass die Anzahl der Verbindungen nicht ausreicht? Der Business-Zugang nutzt den hikariCP-Verbindungspool, welcher ebenfalls eine sehr beliebte Komponente auf dem Markt ist. Anschließend haben wir die aktuelle Konfiguration des Verbindungspools wie folgt überprüft: //Mindestanzahl inaktiver Verbindungen spring.datasource.hikari.minimum-idle=10 //Die maximale Größe des Verbindungspools spring.datasource.hikari.maximum-pool-size=50 // Maximale Leerlaufzeit der Verbindung spring.datasource.hikari.idle-timeout=60000 //Lebensdauer der Verbindung spring.datasource.hikari.max-lifetime=1800000 //Länge des Verbindungstimeouts abrufen spring.datasource.hikari.connection-timeout=30000 Es wird darauf hingewiesen, dass der Hikari-Verbindungspool mit einem Mindestleerlauf von 10 konfiguriert ist. Dies bedeutet, dass der Verbindungspool auch bei fehlender Geschäftstätigkeit 10 Verbindungen garantieren sollte. Hinzu kommt, dass das derzeitige Geschäftszugriffsvolumen äußerst gering ist und es nicht zu einer Situation kommen sollte, in der die Anzahl der Verbindungen nicht ausreicht. Eine weitere Möglichkeit besteht darin, dass „Zombie-Verbindungen“ auftreten. Das heißt, während des Neustartvorgangs hat der Verbindungspool diese nicht verfügbaren Verbindungen nicht freigegeben, sodass keine Verbindungen verfügbar sind. Die Entwickler glaubten an die „Zombie-Link“-Theorie und neigten zu der Annahme, dass die Ursache wahrscheinlich ein Fehler in der HikariCP-Komponente war … Also begann ich, den Quellcode von HikariCP zu lesen und fand heraus, dass der Code, in dem die Anwendungsschicht eine Verbindung vom Verbindungspool anfordert, wie folgt lautet: öffentliche Klasse HikariPool { //Holen Sie sich den Verbindungsobjekteintrag public Connection getConnection(final long hardTimeout) throws SQLException { suspendResumeLock.acquire(); endgültige lange Startzeit = aktuelle Zeit (); versuchen { //Verwende das voreingestellte 30-Sekunden-Timeout. long timeout = hardTimeout; Tun { //Betreten Sie die Schleife und holen Sie sich innerhalb der angegebenen Zeit verfügbare Verbindungen. //Holen Sie sich Verbindungen vom ConnectionBag PoolEntry. poolEntry = connectionBag.borrow(timeout, MILLISECONDS); wenn (Pooleintrag == null) { break; // Unsere Zeit ist abgelaufen... break und Ausnahme auslösen } endgültig lang jetzt = aktuelleZeit(); //Wenn das Verbindungsobjekt als gelöscht markiert ist oder die Überlebensbedingungen nicht erfüllt, schließen Sie die Verbindung, wenn (poolEntry.isMarkedEvicted() || (elapsedMillis(poolEntry.lastAccessed, now) > aliveBypassWindowMs && !isConnectionAlive(poolEntry.connection))) { Verbindung schließen(Pooleintrag, Pooleintrag.isMarkedEvicted() ? EVICTED_CONNECTION_MESSAGE : DEAD_CONNECTION_MESSAGE); Timeout = HardTimeout – verstricheneMillis(Startzeit); } //Verbindungsobjekt erfolgreich abrufen, sonst { metricsTracker.recordBorrowStats(poolEntry, startTime); gib poolEntry.createProxyConnection zurück (leakTaskFactory.schedule (poolEntry), jetzt); } } während (Timeout > 0L); //Timeout, Ausnahme auslösen metricsTracker.recordBorrowTimeoutStats(startTime); auslösen: createTimeoutException(startTime); } Fang (UnterbrocheneAusnahme e) { Thread.currentThread().interrupt(); throw new SQLException(poolName + " - Während des Verbindungsaufbaus unterbrochen", e); } Endlich { suspendResumeLock.release(); } } } Die Methode getConnection() zeigt den gesamten Prozess zum Herstellen einer Verbindung, wobei connectionBag ein Containerobjekt zum Speichern von Verbindungsobjekten ist. Wenn die von connectionBag erhaltene Verbindung die Überlebensbedingung nicht mehr erfüllt, wird sie manuell geschlossen. Der Code lautet wie folgt: void closeConnection(finaler PoolEntry poolEntry, finaler String closureReason) { //Entfernen Sie das Verbindungsobjekt, wenn (connectionBag.remove(poolEntry)) { endgültige Verbindung Verbindung = poolEntry.close(); //Verbindung asynchron schließen closeConnectionExecutor.execute(() -> { quietlyCloseConnection(Verbindung, Schließungsgrund); //Wenn die Anzahl der verfügbaren Verbindungen abnimmt, wird die Aufgabe zum Füllen des Verbindungspools ausgelöst, wenn (poolState == POOL_NORMAL) { Füllpool(); } }); } } Beachten Sie, dass die Verbindung nur geschlossen wird, wenn eine der folgenden Bedingungen erfüllt ist:
Da wir sowohl idleTimeout als auch maxLifeTime auf sehr große Werte gesetzt haben, müssen wir uns darauf konzentrieren, die Beurteilung in der isConnectionAlive-Methode wie folgt zu überprüfen: öffentliche Klasse PoolBase { //Beurteilen, ob die Verbindung aktiv ist boolean isConnectionAlive(final Connection connection) { versuchen { versuchen { //Ausführungstimeout der JDBC-Verbindung festlegen setNetworkTimeout(connection, validationTimeout); letzte int ValidierungsSeconds = (int) Math.max(1000L, ValidierungsTimeout) / 1000; //Wenn TestQuery nicht festgelegt ist, verwenden Sie die JDBC4-Validierungsschnittstelle if (isUseJdbc4Validation) { gibt Verbindung zurück.istgültig(Validierungssekunden); } //Verwenden Sie eine TestQuery-Anweisung (z. B. select 1), um die Verbindung zu ermitteln. try (Statement statement = connection.createStatement()) { wenn (isNetworkTimeoutSupported != TRUE) { setQueryTimeout(Anweisung, Validierungssekunden); } Anweisung.ausführen(config.getConnectionTestQuery()); } } Endlich { setNetworkTimeout(Verbindung, Netzwerk-Timeout); wenn (isIsolateInternalQueries && !isAutoCommit) { Verbindung.Rollback(); } } gibt true zurück; } Fang (Ausnahme e) { //Wenn eine Ausnahme auftritt, zeichnen Sie die Fehlerinformationen im Kontext auf lastConnectionFailure.set(e); logger.warn("{} - Verbindung {} ({}) konnte nicht validiert werden. Erwägen Sie ggf. die Verwendung eines kürzeren maxLifetime-Werts.", PoolName, Verbindung, z. B. getMessage()); gibt false zurück; } } } Wir können sehen, dass in der Methode PoolBase.isConnectionAlive eine Reihe von Erkennungen an der Verbindung durchgeführt werden, und wenn eine Ausnahme auftritt, werden die Ausnahmeinformationen im aktuellen Threadkontext aufgezeichnet. Wenn HikariPool dann eine Ausnahme auslöst, wird auch die Ausnahme der letzten fehlgeschlagenen Erkennung wie folgt erfasst: private SQLException createTimeoutException(lange Startzeit) { logPoolState("Timeout-Fehler"); metricsTracker.recordConnectionTimeout(); Zeichenfolge sqlState = null; //Letzte Verbindungsfehlerausnahme abrufen final Throwable originalException = getLastConnectionFailure(); if (originalException-Instanz von SQLException) { sqlState = ((SQLException) originalException).getSQLState(); } //Eine Ausnahme auslösen finale SQLException connectionException = new SQLTransientConnectionException(poolName + " - Verbindung ist nicht verfügbar, Zeitüberschreitung der Anforderung nach " + elapsedMillis(startTime) + "ms.", sqlState, originalException); if (originalException-Instanz von SQLException) { connectionException.setNextException((SQLException) originalException); } Verbindungsausnahme zurückgeben; } Die Ausnahmemeldung hier stimmt im Wesentlichen mit dem Ausnahmeprotokoll überein, das wir im Geschäftsdienst sehen. Zusätzlich zu der durch das Timeout generierten Meldung „Verbindung ist nicht verfügbar, Anforderung nach xxx ms abgelaufen“ gibt das Protokoll auch die Informationen zum Überprüfungsfehler aus:
An diesem Punkt haben wir den Code für die Anwendung zum Herstellen einer Verbindung grob aussortiert. Der gesamte Prozess wird in der folgenden Abbildung dargestellt: Aus Sicht der Ausführungslogik gibt es bei der Verarbeitung des Verbindungspools kein Problem. Im Gegenteil, viele Details wurden berücksichtigt. Wenn eine nicht aktive Verbindung geschlossen wird, wird auch die Aktion „removeFromBag“ aufgerufen, um sie aus dem Verbindungspool zu entfernen. Daher sollte es kein Problem mit Zombie-Verbindungsobjekten geben. Dann müssen unsere bisherigen Spekulationen falsch sein! In Angst verfallenNeben der Codeanalyse ist den Entwicklern auch aufgefallen, dass die aktuell verwendete hikariCP-Version 3.4.5 ist, während der Business-Service mit Problemen in der Umgebung die Version 2.7.9 ist. Das scheint auf etwas hinzuweisen... Nehmen wir erneut an, dass es in hikariCP Version 2.7.9 einen unbekannten BUG gibt, der zu dem Problem führt. Um das Verhalten des Verbindungspools im Umgang mit serverseitigen Fehlern weiter zu analysieren, haben wir versucht, es auf einem lokalen Computer zu simulieren. Dieses Mal haben wir hikariCP 2.7.9 zum Testen verwendet und die hikariCP-Protokollebene auf DEBUG gesetzt. Im Simulationsszenario stellt die lokale Anwendung für den Betrieb eine Verbindung zur lokalen MySQL-Datenbank her. Die Schritte sind wie folgt:
Das resultierende Protokoll sieht wie folgt aus:
Aus den Protokollen ist ersichtlich, dass hikariCP die fehlerhafte Verbindung erfolgreich erkennen und aus dem Verbindungspool entfernen kann. Nach dem Neustart von MySQL kann der Geschäftsbetrieb automatisch erfolgreich wiederhergestellt werden. Aufgrund dieses Ergebnisses schlug die auf dem HikariCP-Versionsproblem basierende Idee erneut fehl und das Forschungs- und Entwicklungsteam geriet erneut in Angst und Schrecken. Vertreibe die Wolken und sieh das LichtNachdem viele Versuche zur Verifizierung des Problems fehlschlugen, versuchten wir schließlich, Pakete im Container zu erfassen, in dem sich der Geschäftsdienst befindet, um zu sehen, ob wir irgendwelche Hinweise finden konnten. Geben Sie den fehlerhaften Container ein, führen Sie „tcpdump -i eth0 tcp port 30052“ aus, um Pakete zu erfassen, und greifen Sie dann auf die Serviceschnittstelle zu. In diesem Moment passierte etwas Seltsames, es wurden keine Netzwerkpakete generiert! Das Geschäftsprotokoll zeigte außerdem eine Ausnahme bezüglich des Fehlschlagens der Verbindungsherstellung nach 30 Sekunden. Wir haben die Netzwerkverbindung mit dem Befehl netstat überprüft und festgestellt, dass nur eine TCP-Verbindung im Status ESTABLISHED vorhanden war. Mit anderen Worten, es besteht eine hergestellte Verbindung zwischen der aktuellen Geschäftsinstanz und dem MySQL-Server, aber warum meldet das Unternehmen immer noch eine verfügbare Verbindung? Dafür gibt es zwei mögliche Gründe:
Grund eins lässt sich schnell widerlegen. Erstens hat der aktuelle Dienst keine Timer-Aufgabe. Zweitens, selbst wenn die Verbindung belegt ist, sollten neue Geschäftsanforderungen den Verbindungspool nach dem Prinzip des Verbindungspools veranlassen, eine neue Verbindung herzustellen, solange die Obergrenze nicht erreicht ist. Daher sollte es, unabhängig davon, ob es sich um die Überprüfung des Netstat-Befehls oder das TCPdump-Ergebnis handelt, nicht immer nur eine Verbindung geben. Dann ist Situation 2 sehr wahrscheinlich. Behalten Sie diesen Gedanken im Hinterkopf und fahren Sie mit der Analyse des Thread-Stapels des Java-Prozesses fort. Nach der Ausführung von kill -3 pid zur Ausgabe und Analyse des Thread-Stacks werden wie erwartet folgende Einträge im aktuellen Thread-Stack gefunden:
Hier zeigt sich, dass sich der HikariPool-1-Verbindungs-Addierer-Thread immer im ausführbaren Zustand von socketRead befindet. Dem Namen nach sollte dieser Thread der Task-Thread sein, der vom HikariCP-Verbindungspool zum Herstellen von Verbindungen verwendet wird. Der Socket-Lesevorgang stammt von der Methode MariaDbConnection.newConnection(), die ein Vorgang der MariaDB-Java-Client-Treiberschicht zum Herstellen einer MySQL-Verbindung ist. Die ReadInitialHandShakePacket-Initialisierung ist ein Link im MySQL-Verbindungsaufbauprotokoll. Kurz gesagt, der obige Thread befindet sich gerade im Prozess des Linkaufbaus. Der Prozess des Linkaufbaus zwischen dem MariaDB-Treiber und MySQL läuft wie folgt ab: Der erste Schritt beim Aufbau einer MySQL-Verbindung besteht darin, eine TCP-Verbindung herzustellen (Drei-Wege-Handshake). Der Client liest ein erstes Handshake-Nachrichtenpaket des MySQL-Protokolls, das Informationen wie die MySQL-Versionsnummer, den Authentifizierungsalgorithmus usw. enthält, und tritt dann in die Phase der Identitätsauthentifizierung ein. Das Problem hierbei besteht darin, dass die Initialisierung von ReadInitialHandShakePacket (Lesen des Handshake-Nachrichtenpakets) in einem Socket-Lesezustand stattgefunden hat. Wenn der MySQL-Remotehost zu diesem Zeitpunkt ausfällt, bleibt der Vorgang hängen. Obwohl die Verbindung zu diesem Zeitpunkt hergestellt wurde (im Status ESTABLISHED), sind der Protokoll-Handshake und der nachfolgende Identitätsauthentifizierungsprozess noch nicht abgeschlossen. Das heißt, die Verbindung kann nur als Halbfertigprodukt betrachtet werden (sie kann nicht in die Liste des hikariCP-Verbindungspools aufgenommen werden). Aus dem DEBUG-Log des fehlerhaften Dienstes können wir außerdem ersehen, dass im Verbindungspool keine verfügbaren Verbindungen vorhanden sind, und zwar wie folgt:
Eine weitere zu klärende Frage ist, ob das Blockieren eines solchen Socket-Lesevorgangs zum Blockieren des gesamten Verbindungspools führt. Nach dem Lesen des Codes haben wir den Prozess zum Herstellen einer Verbindung von hikariCP geklärt, der mehrere Module umfasst:
HouseKeeper wird 100 ms nach der Initialisierung des Verbindungspools ausgeführt. Es ruft die Methode fillPool() auf, um das Füllen des Verbindungspools abzuschließen. Wenn beispielsweise min-idle 10 beträgt, werden bei der Initialisierung 10 Verbindungen erstellt. ConnectionBag verwaltet eine Liste der aktuellen Verbindungsobjekte. Das Modul verwaltet außerdem einen Zähler der Verbindungsanforderer (Waiter), um die aktuelle Anzahl der Verbindungsanforderungen auszuwerten. Die Logik der Borrow-Methode ist wie folgt: öffentliches T-borgen (langes Timeout, letzte TimeUnit TimeUnit) wirft InterruptedException { // Versuchen Sie, die endgültige Liste <Objekt> list = threadList.get(); vom Thread-Local abzurufen. für (int i = Liste.Größe() - 1; i >= 0; i--) { ... } // Berechnen Sie die Aufgaben, die aktuell auf die Anforderung warten. final int waiting = waiters.incrementAndGet(); versuchen { für (T bagEntry : sharedList) { if (bagEntry.compareAndSet(STATUS_NICHT_IN_VERWENDET, STATUS_IN_VERWENDET)) { //Wenn eine verfügbare Verbindung hergestellt wird, wird die Füllaufgabe ausgelöst, wenn (Warten > 1) { listener.addBagItem(wartend - 1); } Beuteleintrag zurückgeben; } } //Keine Verbindung verfügbar, löse zuerst die Füllaufgabe aus listener.addBagItem(waiting); //Warten Sie, bis innerhalb der angegebenen Zeit eine verfügbare Verbindung zustande kommt. Timeout = timeUnit.toNanos(Timeout); Tun { endgültiger langer Start = aktuelleZeit(); endgültiges T bagEntry = handoffQueue.poll(Timeout, NANOSEKUNDEN); if (bagEntry == null || bagEntry.compareAndSet(ZUSTAND_NICHT_IN_VERWENDET, ZUSTAND_IN_VERWENDET)) { Beuteleintrag zurückgeben; } Zeitüberschreitung -= verstricheneNanos(Start); } während (Timeout > 10_000); gibt null zurück; } Endlich { waiters.decrementAndGet(); } } Beachten Sie, dass diese Methode eine listener.addBagItem()-Methode auslöst, unabhängig davon, ob eine Verbindung verfügbar ist. HikariPool implementiert diese Schnittstelle wie folgt: öffentliche void addBagItem(finale int wartend) { final boolean shouldAdd = waiting - addConnectionQueueReadOnlyView.size() >= 0; // Ja, >= ist beabsichtigt. wenn (sollteHinzufügen) { //Rufen Sie AddConnectionExecutor auf, um die Aufgabe zum Erstellen einer Verbindung zu übermitteln. addConnectionExecutor.submit(poolEntryCreator); } anders { logger.debug("{} - Verbindung ausgelassen hinzufügen, wartend {}, Warteschlange {}", Poolname, wartend, addConnectionQueueReadOnlyView.size()); } } PoolEntryCreator implementiert die spezifische Logik zum Erstellen einer Verbindung wie folgt: öffentliche Klasse PoolEntryCreator { @Überschreiben öffentlicher Boolescher Aufruf () { langer SleepBackoff = 250L; //Bestimmen Sie, ob eine Verbindung hergestellt werden muss, während (poolState == POOL_NORMAL && shouldCreateAnotherConnection()) { //MySQL-Verbindung erstellen final PoolEntry poolEntry = createPoolEntry(); wenn (Pooleintrag != null) { //Die Verbindung wurde erfolgreich hergestellt und kehrt direkt zurück. VerbindungsBag.add(poolEntry); logger.debug("{} - Verbindung {} hinzugefügt", poolName, poolEntry.connection); if (loggingPrefix != null) { logPoolState(loggingPrefix); } gibt Boolean.TRUE zurück; } ... } // Der Pool ist angehalten oder heruntergefahren oder hat die maximale Größe erreicht. gibt Boolean.FALSE zurück; } } Es ist ersichtlich, dass AddConnectionExecutor ein Single-Thread-Design annimmt. Wenn eine neue Verbindungsanforderung generiert wird, wird die PoolEntryCreator-Aufgabe asynchron ausgelöst, um sie zu ergänzen. PoolEntryCreator.createPoolEntry() übernimmt die gesamte Arbeit zum Herstellen der MySQL-Treiberverbindung, in unserem Fall ist der MySQL-Verbindungsherstellungsprozess jedoch dauerhaft blockiert. Daher wird die Aufgabe zum Herstellen einer neuen Verbindung immer in die Warteschlange gestellt, unabhängig davon, wie die Verbindung später hergestellt wird. Dies führt dazu, dass für das Unternehmen keine Verbindung verfügbar ist. Die folgende Abbildung veranschaulicht den Linkaufbauprozess von hikariCP: OK, lassen Sie uns das vorherige Szenario zum Zuverlässigkeitstest noch einmal durchgehen: Zuerst fiel die MySQL-Masterinstanz aus, dann erkannte hikariCP eine tote Verbindung und gab sie frei. Beim Freigeben der geschlossenen Verbindung stellte es fest, dass die Anzahl der Verbindungen aufgefüllt werden musste, was sofort eine neue Verbindungsaufbauanforderung auslöste. 3. LösungNachdem wir uns mit der Materie befasst hatten, betrachteten wir die Optimierung hauptsächlich aus zwei Blickwinkeln:
Was Optimierungspunkt 1 betrifft, sind wir uns alle einig, dass er nicht sehr nützlich ist. Wenn die Verbindung hängt, bedeutet dies, dass die Thread-Ressourcen verloren gegangen sind, was sich sehr nachteilig auf den nachfolgenden stabilen Betrieb des Dienstes auswirkt. Darüber hinaus hat hikariCP dies hier bereits geschrieben. Die wichtigste Lösung besteht daher darin, das Blockieren von Anrufen zu vermeiden. Nachdem ich die offizielle Dokumentation von MariaDB-Java-Client konsultiert hatte, stellte ich fest, dass der Parameter für das Netzwerk-E/A-Timeout in der JDBC-URL wie folgt angegeben werden kann: Spezifische Referenz: https://mariadb.com/kb/en/about-mariadb-connector-j/ Wie beschrieben kann socketTimeout das SO_TIMEOUT-Attribut des Sockets festlegen, um die Timeout-Periode zu steuern. Der Standardwert ist 0, was bedeutet, dass kein Timeout erfolgt. Wir haben der MySQL JDBC-URL die relevanten Parameter wie folgt hinzugefügt: spring.datasource.url=jdbc:mysql://10.0.71.13:33052/appdb?socketTimeout=60000&connectTimeout=30000&serverTimezone=UTC Danach haben wir die Zuverlässigkeit von MySQL mehrere Male überprüft und festgestellt, dass das Phänomen des Hängenbleibens der Verbindung nicht mehr auftrat und das Problem gelöst war. IV. ZusammenfassungDieses Mal habe ich meine Erfahrungen bei der Behebung eines MySQL-Verbindungsdeadlock-Problems geteilt. Aufgrund des enormen Arbeitsaufwands beim Einrichten der Umgebung und der Zufälligkeit bei der Reproduktion des Problems war der gesamte Analyseprozess etwas holprig (und ich bin auch auf einige Fallstricke gestoßen). Tatsächlich lassen wir uns durch manche oberflächlichen Phänomene leicht verwirren und wenn wir das Gefühl haben, ein Problem sei schwer zu lösen, neigen wir eher dazu, das Problem mit voreingenommenem Denken anzugehen. Beispielsweise wurde in diesem Fall allgemein angenommen, dass ein Problem mit dem Verbindungspool vorlag, tatsächlich wurde es jedoch durch eine ungenaue Konfiguration des MySQL JDBC-Treibers (MariaDB-Treiber) verursacht. Grundsätzlich sollte jedes Verhalten vermieden werden, das zum Hängenbleiben von Ressourcen führen kann. Wenn wir den Code und die zugehörigen Konfigurationen in den frühen Phasen gründlich untersuchen können, glaube ich, dass 996 weiter von uns entfernt sein wird. Oben finden Sie eine ausführliche Erklärung der Gründe, warum MySQL-Verbindungen hängen bleiben. Weitere Informationen zu den Gründen, warum MySQL-Verbindungen hängen bleiben, finden Sie in den anderen verwandten Artikeln auf 123WORDPRESS.COM! Das könnte Sie auch interessieren:
|
<<: Eine einfache Implementierungsmethode für eine digitale LED-Uhr in CSS3
>>: Schauen wir uns einige leistungsstarke Operatoren in JavaScript an
1. Verwenden Sie Docker-Images, um alle Image-Dat...
1. Pfeilfunktion 1. Nutzen Sie die Tatsache, dass...
Unter Linux wird Bash als Standard übernommen, wa...
Ich bin heute bei der Arbeit auf ein SQL-Problem ...
Inhaltsverzeichnis 1. Projektanforderungen 2. Dok...
Funktionen von MySQL: MySQL ist ein relationales ...
1) Prozess 2) FSImage und Bearbeitungen Nodenode ...
Rendern Häufig verwendete Stile im Blog Garden /*...
Inhaltsverzeichnis Erstellen von OAuth-Apps Holen...
Das Span-Tag wird häufig beim Erstellen von HTML-...
Inhaltsverzeichnis Vorne geschrieben Anforderungs...
1. Ändern Sie die Transparenz, um ein allmähliche...
Wenn wir mit einer SQL-Anweisung konfrontiert wer...
Hier verwende ich Samba (Filesharing-Dienst) v4.9...
1 Anforderungen im Überblick Die Daten mehrerer T...