Wie können Sie Fehlerbedingungen reproduzieren und anzeigen, was während der Ausführung der Anwendung geschieht?
Wie visualisieren Sie die Interaktionen zwischen den verschiedenen gleichzeitigen Teilen der Anwendung?
Nach meiner Erfahrung lautet die Antwort auf diese beiden Aspekte wie folgt:
Verteilte Ablaufverfolgung
Bei der verteilten Ablaufverfolgung handelt es sich um eine Technologie, mit der Zeitdaten für jede einzelne gleichzeitige Komponente Ihres Systems erfasst und grafisch dargestellt werden. Darstellungen von gleichzeitigen Ausführungen sind immer verschachtelt, sodass Sie sehen können, was parallel ausgeführt wird und was nicht.
Das verteilte Tracing hat seinen Ursprung (natürlich) in verteilten Systemen, die per Definition asynchron sind und eine hohe Gleichzeitigkeit aufweisen. Ein verteiltes System mit verteiltem Tracing ermöglicht Folgendes:
a) wichtige Engpässe identifizieren, b) eine visuelle Darstellung der idealen "Läufe" Ihrer Anwendung erhalten und c) Einblick in das ausgeführte gleichzeitige Verhalten gewähren, d) Zeitdaten erhalten, die zur Beurteilung von Unterschieden zwischen Ihren Änderungen verwendet werden können System (extrem wichtig, wenn Sie starke SLAs haben).
Die Folgen der verteilten Ablaufverfolgung sind jedoch:
Es erhöht den Aufwand für alle gleichzeitig ablaufenden Prozesse, da mehr Code ausgeführt und möglicherweise über ein Netzwerk gesendet werden kann. In einigen Fällen ist dieser Aufwand sehr hoch. Selbst Google verwendet das Tracing-System Dapper nur für eine kleine Teilmenge aller Anforderungen, um die Benutzerfreundlichkeit nicht zu beeinträchtigen.
Es gibt viele verschiedene Tools, die nicht alle miteinander kompatibel sind. Dies wird durch Standards wie OpenTracing etwas verbessert, aber nicht vollständig gelöst.
Hier erfahren Sie nichts über freigegebene Ressourcen und deren aktuellen Status. Möglicherweise können Sie anhand des Anwendungscodes und des angezeigten Diagramms eine Vermutung anstellen, dies ist jedoch in dieser Hinsicht kein nützliches Werkzeug.
Aktuelle Tools setzen voraus, dass Sie über genügend Arbeitsspeicher und Speicherplatz verfügen. Das Hosten eines Zeitreihen-Servers ist möglicherweise nicht billig, abhängig von Ihren Einschränkungen.
Fehlerverfolgungssoftware
Ich verweise oben auf Sentry, vor allem, weil es das am häufigsten verwendete Tool ist und aus gutem Grund - eine Fehlerverfolgungssoftware wie Sentry, die die Laufzeitausführung übernimmt, um eine Stapelverfolgung der aufgetretenen Fehler gleichzeitig an einen zentralen Server weiterzuleiten.
Der Nettovorteil einer solchen dedizierten Software in gleichzeitigem Code:
- Doppelte Fehler werden nicht dupliziert . Mit anderen Worten, wenn auf einem oder mehreren gleichzeitigen Systemen dieselbe Ausnahme auftritt, erhöht Sentry einen Vorfallbericht , sendet jedoch nicht zwei Kopien des Vorfalls.
Dies bedeutet, dass Sie herausfinden können, bei welchem gleichzeitigen System welche Art von Fehler auftritt, ohne unzählige gleichzeitige Fehlerberichte durchlaufen zu müssen. Wenn Sie jemals E-Mail-Spam von einem verteilten System erhalten haben, wissen Sie, wie sich die Hölle anfühlt.
Sie können sogar verschiedene Aspekte Ihres gleichzeitigen Systems mit Tags versehen (dies setzt jedoch voraus, dass Sie nicht über genau einen Thread verschachtelt arbeiten, was technisch ohnehin nicht gleichzeitig ist, da der Thread einfach effizient zwischen Tasks wechselt, aber dennoch Ereignishandler verarbeiten muss bis zum Abschluss) und sehen Sie eine Aufschlüsselung der Fehler nach Tag.
- Sie können diese Fehlerbehandlungssoftware ändern, um zusätzliche Details zu Ihren Laufzeitausnahmen bereitzustellen. Welche offenen Ressourcen hatte der Prozess? Gibt es eine gemeinsam genutzte Ressource, die dieser Prozess gehalten hat? Welcher Benutzer hat dieses Problem festgestellt?
Zusätzlich zu sorgfältigen Stack-Traces (und Quellzuordnungen, wenn Sie eine verkleinerte Version Ihrer Dateien bereitstellen müssen) können Sie auf einfache Weise feststellen, was die meiste Zeit falsch läuft.
- (Sentry-spezifisch) Sie können ein separates Sentry-Berichts-Dashboard für Testläufe des Systems einrichten, mit dem Sie Fehler beim Testen feststellen können.
Zu den Nachteilen einer solchen Software gehören:
Wie alles fügen sie Masse hinzu. Möglicherweise möchten Sie ein solches System nicht für eingebettete Hardware verwenden. Ich empfehle dringend, einen Testlauf einer solchen Software durchzuführen und eine einfache Ausführung mit und ohne diese zu vergleichen, die über ein paar hundert Läufe auf einem inaktiven Computer ausgeführt wurde.
Nicht alle Sprachen werden gleichermaßen unterstützt, da viele dieser Systeme implizit auf das Abfangen einer Ausnahme angewiesen sind und nicht alle Sprachen robuste Ausnahmen aufweisen. Davon abgesehen gibt es Kunden für eine Vielzahl von Systemen.
Sie stellen möglicherweise ein Sicherheitsrisiko dar, da viele dieser Systeme im Wesentlichen als Closed-Source-Systeme ausgeführt werden. In solchen Fällen sollten Sie Ihre Recherchen sorgfältig durchführen oder, falls gewünscht, Ihre eigenen würfeln.
Sie geben Ihnen möglicherweise nicht immer die Informationen, die Sie benötigen. Dies ist ein Risiko bei allen Versuchen, die Sichtbarkeit zu erhöhen.
Die meisten dieser Dienste wurden für hochkonkurrierende Webanwendungen entwickelt, sodass möglicherweise nicht jedes Tool für Ihren Anwendungsfall perfekt ist.
In der Summe : Sichtbarkeit ist der wichtigste Teil eines jeden gleichzeitigen Systems. Die beiden Methoden, die ich oben in Verbindung mit speziellen Dashboards zu Hardware und Daten beschreibe, um ein umfassendes Bild des Systems zu einem bestimmten Zeitpunkt zu erhalten, sind in der Branche weit verbreitet, um genau diesen Aspekt zu berücksichtigen.
Einige zusätzliche Vorschläge
Ich habe mehr Zeit damit verbracht, Code von Leuten zu reparieren, die versucht haben, gleichzeitig auftretende Probleme auf schreckliche Weise zu lösen. Jedes Mal habe ich Fälle gefunden, in denen die folgenden Dinge die Entwicklererfahrung erheblich verbessern können (was genauso wichtig ist wie die Benutzererfahrung):
Verlassen Sie sich auf Typen . Die Eingabe dient zur Validierung Ihres Codes und kann zur Laufzeit als zusätzlicher Schutz verwendet werden. Wenn keine Eingabe vorhanden ist, verlassen Sie sich auf Behauptungen und einen geeigneten Fehlerbehandler, um Fehler abzufangen. Gleichzeitiger Code erfordert defensiven Code, und Typen dienen als beste verfügbare Validierungsmethode.
- Testen Sie Verknüpfungen zwischen Codekomponenten , nicht nur der Komponente selbst. Verwechseln Sie dies nicht mit einem vollständigen Integrationstest, der jede Verbindung zwischen jeder Komponente testet und selbst dann nur nach einer globalen Validierung des Endzustands sucht. Dies ist ein schrecklicher Weg, um Fehler abzufangen.
Ein guter Verbindungstest überprüft, ob die empfangene und die gesendete Nachricht erwartungsgemäß übereinstimmen , wenn eine Komponente isoliert mit einer anderen Komponente kommuniziert. Wenn zwei oder mehr Komponenten für die Kommunikation auf einen gemeinsam genutzten Dienst angewiesen sind, drehen Sie sie alle auf, lassen Sie sie über den zentralen Dienst Nachrichten austauschen und prüfen Sie, ob sie am Ende alle das bekommen, was Sie erwarten.
Wenn Sie Tests, an denen viele Komponenten beteiligt sind, in einen Test der Komponenten selbst und in einen Test der Kommunikation der einzelnen Komponenten aufteilen, gewinnen Sie mehr Vertrauen in die Gültigkeit Ihres Codes. Mit einer solch strengen Anzahl von Tests können Sie Verträge zwischen Diensten erzwingen und unerwartete Fehler abfangen, die auftreten, wenn sie gleichzeitig ausgeführt werden.
- Verwenden Sie die richtigen Algorithmen, um Ihren Anwendungsstatus zu überprüfen. Ich spreche von einfachen Dingen, z. B. wenn ein Master-Prozess darauf wartet, dass alle seine Mitarbeiter eine Aufgabe erledigen, und nur dann zum nächsten Schritt übergehen möchte, wenn alle Mitarbeiter vollständig erledigt sind - dies ist ein Beispiel für die globale Erkennung Terminierung, für die bekannte Methoden wie Safras Algorithmus existieren.
Einige dieser Tools werden mit Sprachen gebündelt geliefert. Rust beispielsweise garantiert, dass Ihr Code zur Kompilierungszeit keine Race-Bedingungen aufweist, während Go über einen integrierten Deadlock-Detektor verfügt, der auch zur Kompilierungszeit ausgeführt wird. Wenn Sie Probleme erkennen können, bevor sie die Produktion erreichen, ist dies immer ein Gewinn.
Eine allgemeine Faustregel: Entwurf für Fehler in gleichzeitigen Systemen . Stellen Sie sich vor, dass allgemeine Dienste abstürzen oder ausfallen. Dies gilt auch für Code, der nicht über mehrere Computer verteilt ist - gleichzeitiger Code auf einem einzelnen Computer kann auf externen Abhängigkeiten beruhen (z. B. einer freigegebenen Protokolldatei, einem Redis-Server, einem verdammten MySQL-Server), die jederzeit verschwinden oder entfernt werden können .
Der beste Weg, dies zu tun, besteht darin, den Anwendungsstatus von Zeit zu Zeit zu validieren. Lassen Sie für jeden Dienst Integritätsprüfungen durchführen und stellen Sie sicher, dass die Verbraucher dieses Dienstes über den schlechten Zustand informiert werden. Moderne Container-Tools wie Docker machen das ganz gut und sollten dazu genutzt werden, Dinge zu sandboxen.
Wie finden Sie heraus, was gleichzeitig und was sequentiell gemacht werden kann?
Eine der größten Lektionen, die ich bei der Arbeit mit einem System gelernt habe, das gleichzeitig sehr häufig ausgeführt wird, ist folgende: Es kann nie genug Metriken geben . Metriken sollten absolut alles in Ihrer Anwendung steuern - Sie sind kein Ingenieur, wenn Sie nicht alles messen.
Ohne Metriken können Sie nicht ein paar sehr wichtige Dinge tun:
Beurteilen Sie den Unterschied, den Änderungen am System bewirken. Wenn Sie nicht wissen, ob der Abstimmknopf A die Metrik B erhöht und die Metrik C verringert hat, wissen Sie nicht, wie Sie Ihr System reparieren können, wenn Personen unerwartet bösartigen Code auf Ihr System übertragen (und diesen an Ihr System senden). .
Verstehe, was du als nächstes tun musst, um Dinge zu verbessern. Solange Sie nicht wissen, dass in Anwendungen nur noch wenig Arbeitsspeicher zur Verfügung steht, können Sie nicht erkennen, ob Sie mehr Arbeitsspeicher benötigen oder mehr Festplatten für Ihre Server kaufen sollten.
Metriken sind so wichtig und essentiell, dass ich mich bewusst bemüht habe, zu planen, was ich messen möchte, bevor ich überhaupt darüber nachdenke, was ein System benötigt. Tatsächlich sind Metriken so wichtig, dass ich glaube, dass sie die richtige Antwort auf diese Frage sind: Sie wissen nur, was sequentiell oder gleichzeitig gemacht werden kann, wenn Sie messen, was die Bits in Ihrem Programm tun. Das richtige Design verwendet Zahlen, keine Vermutungen.
Davon abgesehen gibt es sicherlich ein paar Faustregeln:
Sequentielle impliziert Abhängigkeit. Zwei Prozesse sollten sequentiell ablaufen, wenn einer in irgendeiner Weise von dem anderen abhängig ist. Prozesse ohne Abhängigkeiten sollten gleichzeitig ablaufen. Planen Sie jedoch eine Möglichkeit, Fehler im Upstream zu behandeln, die nicht verhindert, dass Prozesse im Downstream auf unbestimmte Zeit warten.
Mischen Sie niemals eine E / A-gebundene Aufgabe mit einer CPU-gebundenen Aufgabe auf demselben Kern. Schreiben Sie (zum Beispiel) keinen Webcrawler, der zehn gleichzeitige Anforderungen im selben Thread startet, sie kratzt, sobald sie eingehen, und erwarten Sie eine Skalierung auf fünfhundert - E / A-Anforderungen werden parallel in eine Warteschlange gestellt, aber Die CPU durchläuft sie weiterhin seriell. (Dieses ereignisgesteuerte Single-Thread-Modell ist sehr beliebt, aber es ist aufgrund dieses Aspekts begrenzt. Statt dies zu verstehen, wringen die Leute einfach ihre Hände und sagen, dass Node nicht skaliert, um Ihnen ein Beispiel zu geben.)
Ein einzelner Thread kann viel E / A-Arbeit leisten. Verwenden Sie jedoch Threadpools, die zusammen alle Kerne belegen, um die Parallelität Ihrer Hardware voll auszunutzen. Im obigen Beispiel wird das Starten von fünf Python-Prozessen (von denen jeder einen Kern auf einem Sechs-Kern-Rechner verwenden kann) nur für die CPU-Arbeit und eines sechsten Python-Threads nur für die E / A-Arbeit viel schneller skaliert, als Sie denken.
Die CPU-Parallelität kann nur über einen dedizierten Threadpool genutzt werden. Ein einzelner Thread ist oft genug für viele E / A-gebundene Arbeiten. Aus diesem Grund lassen sich ereignisgesteuerte Webserver wie Nginx besser skalieren (sie leisten nur E / A-gebundene Arbeit) als Apache (bei dem E / A-gebundene Arbeit mit etwas verbunden wird, das CPU erfordert und einen Prozess pro Anforderung startet) Zehntausende von parallel empfangenen GPU-Berechnungen sind eine schreckliche Idee.