Plötzlich reicht ein Müllsammelsystem allein nicht mehr aus

Hier ist eine kurze Geschichte über mysteriöse Serverausfälle, die ich vor einem Jahr debuggen musste (Artikel vom 05. Dezember 2018, ca. pro Jahr). Die Server funktionierten eine Weile einwandfrei und stürzten dann irgendwann ab. Danach schlugen Versuche, fast jedes Programm auszuführen, das sich auf den Servern befand, mit den Fehlern "Es ist kein Speicherplatz auf dem Gerät vorhanden" fehl, obwohl das Dateisystem nur wenige belegte Gigabyte auf ~ 20-GB-Festplatten meldete.

Es stellte sich heraus, dass das Problem durch das Protokollierungssystem verursacht wurde. Dies war eine Ruby-Anwendung, die Protokolldateien aufnimmt, Daten an einen Remote-Server sendet und alte Dateien löscht. Der Fehler war, dass geöffnete Protokolldateien nicht explizit geschlossen wurden. Stattdessen erlaubte die Anwendung Rubys automatischem Garbage Collector, Dateiobjekte zu bereinigen. Das Problem ist, dass Dateiobjekte nicht viel Speicher belegen, sodass ein Protokollierungssystem theoretisch Millionen von Protokollen offen halten könnte, bevor eine Speicherbereinigung erforderlich ist.

* Nix-Dateisysteme trennen Dateinamen und Daten in Dateien. Auf Daten auf einer Festplatte können mehrere Dateinamen verweisen (d. H. Feste Links), und Daten werden nur gelöscht, wenn der letzte Link gelöscht wird. Ein geöffneter Dateideskriptor wird als Link betrachtet. Wenn die Datei während des Lesens des Programms gelöscht wird, verschwindet der Dateiname aus dem Verzeichnis, die Datendaten bleiben jedoch aktiv, bis das Programm sie schließt. Dies ist, was mit dem Logger passiert ist. Der Befehl du ("Datenträgerverwendung") sucht mithilfe einer Verzeichnisliste nach Dateien, sodass für die Tausenden von Protokolldateien, die noch geöffnet waren, keine Gigabyte an Dateidaten angezeigt wurden. Diese Dateien wurden erst nach dem Ausführen von lsof ("Liste offener Dateien") entdeckt.

Natürlich tritt ein ähnlicher Fehler in anderen ähnlichen Fällen auf. Vor ein paar Monaten musste ich auf eine Java-Anwendung stoßen, die nach einigen Tagen aufgrund eines Netzwerkverbindungslecks ausfiel.

Ich habe den größten Teil meines Codes in C und dann in C ++ geschrieben. Damals dachte ich, dass manuelles Ressourcenmanagement genug ist. Wie kompliziert war das? Jedes malloc () benötigt eine free () - Funktion und jedes open () braucht close (). Einfach. Abgesehen davon, dass nicht alle Programme einfach sind, ist die manuelle Ressourcenverwaltung im Laufe der Zeit zu einer Zwangsjacke geworden. Dann entdeckte ich eines Tages die Linkzählung und die Speicherbereinigung. Ich dachte, dass es alle meine Probleme löst, und hörte ganz auf, mich um das Ressourcenmanagement zu kümmern. Auch für einfache Programme war dies normal, aber nicht alle Programme sind einfach.

Sie können sich nicht auf die Speicherbereinigung verlassen, da dies nur das Problem der Speicherverwaltung löst und komplexe Programme viel mehr als nur Speicher verarbeiten müssen. Es gibt ein beliebtes Mem, das dies mit der Tatsache beantwortet, dass der Speicher 95% der Ressourcenprobleme ausmacht . Sie können sogar sagen, dass alle Ressourcen 0% Ihrer Probleme ausmachen - bis Ihnen eines davon ausgeht. Dann wird diese Ressource zu 100% Ihrer Probleme.

Aber ein solches Denken nimmt Ressourcen immer noch als Sonderfall wahr. Ein tieferes Problem ist, dass mit zunehmender Komplexität von Programmen alles zu einer Ressource wird. Nehmen Sie zum Beispiel ein Kalenderprogramm. Mit einem ausgeklügelten Kalenderprogramm können mehrere Benutzer mehrere freigegebene Kalender und Ereignisse verwalten, die für mehrere Kalender freigegeben werden können. Jeder Teil der Daten wirkt sich letztendlich auf mehrere Teile des Programms aus und sollte relevant und korrekt sein. Daher benötigen Sie für alle dynamischen Daten einen Eigentümer und nicht nur für die Speicherverwaltung. Wenn neue Funktionen hinzugefügt werden, müssen immer mehr Teile des Programms aktualisiert werden. Wenn Sie gesund sind, können Sie jeweils nur Daten von einem Teil des Programms aktualisieren.Damit wird das Recht und die Verantwortung zur Aktualisierung von Daten an sich zu einer begrenzten Ressource. Die Modellierung mutierter Daten unter Verwendung unveränderlicher Strukturen führt nicht zum Verschwinden dieser Probleme, sondern übersetzt sie nur in ein anderes Paradigma.

Die Planung des Eigentums und der Lebensdauer von Ressourcen ist ein unvermeidlicher Bestandteil des Entwurfs komplexer Software. Dies ist einfacher, wenn Sie einige gängige Muster verwenden. Eines der Muster sind austauschbare Ressourcen. Ein Beispiel ist die unveränderliche Zeichenfolge "foo", die semantisch mit jeder anderen unveränderlichen "foo" identisch ist. Diese Art von Ressource benötigt keine vorgegebene Lebensdauer oder Besitz. Um das System so einfach wie möglich zu gestalten, ist es in der Tat besser, keine vorgegebene Lebensdauer oder Eigentümerschaft zu haben (hi Rust, ca. pro). Ein weiteres Muster sind Ressourcen, die nicht austauschbar sind, aber eine bestimmte Lebensdauer haben. Dies umfasst Netzwerkverbindungen sowie abstraktere Konzepte wie das Recht, einen Teil der Daten zu kontrollieren.Am vernünftigsten ist es, die Lebensdauer solcher Dinge beim Codieren explizit sicherzustellen.

Beachten Sie, dass die automatische Speicherbereinigung für die Implementierung des ersten Musters sehr gut ist, nicht jedoch für das zweite, während manuelle Ressourcenverwaltungstechniken (wie RAII) für die Implementierung des zweiten Musters hervorragend geeignet sind, für das erste jedoch schrecklich. Diese beiden Ansätze ergänzen sich in komplexen Programmen.

Source: https://habr.com/ru/post/undefined/


All Articles