Modernisierung älterer Software: MUD-Programmierung mit Erlang und CloudI
Veröffentlicht: 2022-03-11Was ist Legacy-Modernisierung?
Legacy-Code ist überall. Und da die Geschwindigkeit, mit der sich Code vermehrt, weiterhin exponentiell zunimmt, wird immer mehr von diesem Code in den Legacy-Status verbannt. In vielen großen Organisationen verbraucht die Wartung von Legacy-Systemen mehr als 90 % der Informationssystemressourcen.
Die Notwendigkeit, Legacy-Code und -Systeme zu modernisieren, um die aktuellen Leistungs- und Verarbeitungsanforderungen zu erfüllen, ist weit verbreitet. Dieser Beitrag bietet eine Fallstudie über die Verwendung der Programmiersprache Erlang und der auf Erlang basierenden CloudI Service Oriented Architecture (SOA), um Legacy-Code – insbesondere eine jahrzehntealte Sammlung von C-Quellcode – an das 21. Jahrhundert anzupassen .
Den Quellcode-Drachen töten
Vor Jahren war ich ein großer Fan von textbasierten Multiplayer-Online-Spielen, die als Multi-User Dungeons (MUDs) bekannt sind. Aber sie waren immer mit Leistungsproblemen gespickt. Ich beschloss, in einen jahrzehntealten Haufen C-Quellcode zurückzutauchen und zu sehen, wie wir diesen Legacy-Code modernisieren und diese frühen Online-Spiele an ihre Grenzen bringen können. Auf hohem Niveau war dieses Projekt ein großartiges Beispiel für die Verwendung von Erlang zur Anpassung von Legacy-Software an die Anforderungen des 21. Jahrhunderts.
Eine kurze Zusammenfassung:
- Das Ziel : Nehmen Sie ein altes, auf 50 Spieler beschränktes MUD-Videospiel und pushen Sie seinen Quellcode, um Tausende und Abertausende von gleichzeitigen Verbindungen zu unterstützen.
- Das Problem : Legacy, Singlethread-C-Quellcode.
- Die Lösung : CloudI, ein Erlang-basierter Dienst, der Fehlertoleranz und Skalierbarkeit bietet.
Was ist ein textbasierter MUD?
Alle Massively Multiplayer Online Role Playing Games (MMORPGs) – wie World of Warcraft und EverQuest – haben Funktionen entwickelt, deren frühe Ursprünge auf ältere textbasierte Multiplayer-Online-Spiele zurückgeführt werden können, die als Multi-User Dungeons (MUDs) bekannt sind.
Der erste MUD war Roy Trubshaws Essex MUD (oder MUD1), der ursprünglich 1978 unter Verwendung der MARO-10-Assemblersprache auf einem DEC PDP-10 entwickelt wurde, aber in BCPL konvertiert wurde, einen Vorgänger der C-Programmiersprache (und lief bis 1987). (Wie Sie sehen können, sind diese Dinger älter als die meisten Programmierer.)
MUDs gewannen in den späten 1980er und frühen 1990er Jahren mit verschiedenen in C geschriebenen MUD-Codebasen allmählich an Popularität. Die DikuMUD-Codebasis ist beispielsweise als Wurzel eines der größten Bäume abgeleiteten MUD-Quellcodes mit mindestens 51 einzigartigen Varianten bekannt basierend auf dem gleichen DikuMUD-Quellcode. (Während dieser Zeit wurden MUDs übrigens auch als „Multi-Undergraduate Destroyer“ bezeichnet, da viele College-Studenten aufgrund ihrer Besessenheit von ihnen die Schule nicht bestanden.)
Das Problem mit alten MUDs
Der historische C MUD-Quellcode (einschließlich DikuMUD und seiner Varianten) ist aufgrund bestehender Einschränkungen zum Zeitpunkt der Erstellung mit Leistungsproblemen gespickt.
Fehlendes Gewinde
Damals gab es noch keine leicht zugängliche Threading-Bibliothek. Darüber hinaus hätte Threading die Wartung und Änderung des Quellcodes erschwert. Infolgedessen waren diese MUDs alle Single-Threaded.
Während eines einzigen „Ticks“ (ein Inkrement der internen Uhr, die den Fortschritt aller Spielereignisse verfolgt) muss der MUD-Quellcode jedes Spielereignis für jeden verbundenen Socket verarbeiten. Mit anderen Worten: Jedes Stück Code verlangsamt die Verarbeitung eines einzelnen Ticks. Und wenn irgendeine Berechnung die Verarbeitung dazu zwingt, länger als einen einzelnen Tick zu dauern, verzögert sich der MUD und wirkt sich auf jeden verbundenen Spieler aus.
Mit dieser Verzögerung wird das Spiel sofort weniger fesselnd. Die Spieler sehen hilflos zu, wenn ihre Charaktere sterben und ihre eigenen Befehle unverarbeitet bleiben.
Vorstellung von SillyMUD
Für dieses Experiment zur Modernisierung von Legacy-Anwendungen habe ich mich für SillyMUD entschieden, ein historisches Derivat von DikuMUD, das moderne MMORPGs und die ihnen gemeinsamen Leistungsprobleme beeinflusst hat. In den 1990er Jahren spielte ich MUD, das von der SillyMUD-Codebasis abgeleitet war, also wusste ich, dass der Quellcode ein interessanter und etwas vertrauter Ausgangspunkt sein würde.
Was habe ich geerbt?
Der SillyMUD-Quellcode ähnelt dem anderer historischer C-MUDs, da er auf ungefähr 50 gleichzeitige Spieler beschränkt ist (64, um genau zu sein, basierend auf dem Quellcode).
Mir ist jedoch aufgefallen, dass der Quellcode aus Leistungsgründen modifiziert worden war (dh um die Begrenzung der gleichzeitigen Spieler zu verschieben). Speziell:
- Dem Quellcode fehlte eine Domänennamensuche auf der Verbindungs-IP-Adresse, die aufgrund der durch eine Domänennamensuche erzwungenen Latenz fehlte (normalerweise möchte ein älteres MUD eine Domänennamensuche, um das Sperren böswilliger Benutzer zu erleichtern).
- Im Quellcode war der „Spenden“-Befehl deaktiviert (etwas ungewöhnlich), da möglicherweise lange verknüpfte Listen von gespendeten Artikeln erstellt wurden, die dann verarbeitungsintensive Listendurchläufe erforderten. Diese wiederum beeinträchtigen die Spielleistung aller anderen Spieler (single-threaded, erinnerst du dich?).
Einführung von CloudI
CloudI wurde zuvor aufgrund der Fehlertoleranz und Skalierbarkeit, die es bietet, als Lösung für die polyglotte Entwicklung diskutiert.
CloudI bietet eine Dienstabstraktion (um eine serviceorientierte Architektur (SOA) bereitzustellen) in Erlang, C/C++, Java, Python und Ruby, während Softwarefehler innerhalb des CloudI-Frameworks isoliert bleiben. Fehlertoleranz wird durch die Erlang-Implementierung von CloudI bereitgestellt, die sich auf die fehlertoleranten Funktionen von Erlang und die Implementierung des Actor-Modells stützt. Diese Fehlertoleranz ist ein Schlüsselmerkmal der Erlang-Implementierung von CloudI, da jede Software Fehler enthält.
CloudI stellt auch einen Anwendungsserver bereit, um die Lebensdauer der Dienstausführung und die Erstellung von Dienstprozessen (entweder als Betriebssystemprozesse für Nicht-Erlang-Programmiersprachen oder als Erlang-Prozesse für in Erlang implementierte Dienste) zu steuern, sodass die Dienstausführung ohne Auswirkung auf den externen Zustand erfolgt Verlässlichkeit. Für mehr siehe meinen vorherigen Beitrag.

Wie kann CloudI einen veralteten textbasierten MUD modernisieren?
Der historische C MUD-Quellcode bietet angesichts seiner Zuverlässigkeitsprobleme eine interessante Möglichkeit für die CloudI-Integration:
- Die Stabilität des Spielservers wirkt sich direkt auf die Attraktivität jeder Spielmechanik aus.
- Die Fokussierung der Softwareentwicklung auf die Behebung von Serverstabilitätsfehlern begrenzt die Größe und den Umfang des resultierenden Spiels.
Mit der CloudI-Integration können Server-Stabilitätsfehler weiterhin normal behoben werden, aber ihre Auswirkungen sind begrenzt, sodass der Betrieb des Spielservers nicht immer beeinträchtigt wird, wenn ein zuvor unentdeckter Fehler ein internes Spielsystem zum Scheitern bringt. Dies ist ein hervorragendes Beispiel für die Verwendung von Erlang zur Durchsetzung von Fehlertoleranz in einer Legacy-Codebasis.
Welche Änderungen waren erforderlich?
Die ursprüngliche Codebasis wurde so geschrieben, dass sie sowohl Single-Threaded als auch stark von globalen Variablen abhängig ist. Mein Ziel war es, die Funktionalität des Legacy-Quellcodes beizubehalten und ihn gleichzeitig für den heutigen Gebrauch zu modernisieren.
Mit CloudI war ich in der Lage, den Quellcode Single-Threaded zu halten und gleichzeitig die Skalierbarkeit der Socket-Verbindung bereitzustellen.
Sehen wir uns die notwendigen Änderungen an:
Konsolenausgabe
Die Pufferung der SillyMUD-Konsolenausgabe (eine Terminalanzeige, oft mit Telnet verbunden) war bereits vorhanden, aber einige direkte Verwendungen von Dateideskriptoren erforderten eine Pufferung (damit die Konsolenausgabe die Antwort auf eine CloudI-Dienstanforderung werden konnte).
Socket-Handling
Die Socket-Verarbeitung im ursprünglichen Quellcode stützte sich auf einen Aufruf der Funktion select()
, um Eingaben, Fehler und die Möglichkeit der Ausgabe zu erkennen und für einen Spiel-Tick von 250 Millisekunden anzuhalten, bevor anstehende Spielereignisse verarbeitet werden.
Die CloudI SillyMUD-Integration stützt sich auf eingehende Dienstanfragen für Eingaben, während sie mit der cloudi_poll
-Funktion der C CloudI-API pausiert (für die 250 Millisekunden, bevor dieselben ausstehenden Spielereignisse verarbeitet werden). Der SillyMUD-Quellcode lief problemlos in CloudI als CloudI-Dienst, nachdem er in die C-CloudI-API integriert wurde (obwohl CloudI sowohl C- als auch C++-APIs bereitstellt, erleichterte die Verwendung der C-API die Integration mit dem C-Quellcode von SillyMUD).
Abonnements
Die CloudI-Integration abonniert drei Hauptdienstnamenmuster, um Verbindungs-, Trennungs- und Gameplay-Ereignisse zu verarbeiten. Diese Namensmuster stammen von der C CloudI-API, die „subscribe“ im Integrationsquellcode aufruft. Dementsprechend haben entweder WebSocket-Verbindungen oder Telnet-Verbindungen Dienstnamenziele zum Senden von Dienstanforderungen, wenn Verbindungen hergestellt werden.
Die WebSocket- und Telnet-Unterstützung in CloudI wird von internen CloudI-Diensten bereitgestellt ( cloudi_service_http_cowboy
für WebSocket-Unterstützung und cloudi_service_tcp
für Telnet-Unterstützung). Da interne CloudI-Dienste in Erlang geschrieben sind, können sie die extreme Skalierbarkeit von Erlang nutzen und gleichzeitig die CloudI-Dienstabstraktion verwenden, die die CloudI-API-Funktionen bereitstellt.
Vorwärts gehen
Durch das Vermeiden der Socket-Behandlung erfolgte weniger Verarbeitung bei Socket-Fehlern oder Situationen wie Link-Tod (in denen Benutzer vom Server getrennt werden). Daher wurde durch das Entfernen der Low-Level-Socket-Handhabung das primäre Skalierbarkeitsproblem angegangen.
Aber Skalierbarkeitsprobleme bleiben bestehen. Zum Beispiel verwendet das MUD das Dateisystem als lokale Datenbank für sowohl statische als auch dynamische Spielelemente (dh Spieler und ihren Fortschritt zusammen mit den Weltzonen, Objekten und Monstern). Das Refactoring des Legacy-Codes des MUD, um sich stattdessen auf einen CloudI-Dienst für eine Datenbank zu verlassen, würde weitere Fehlertoleranz bieten. Wenn wir anstelle eines Dateisystems eine Datenbank verwenden würden, könnten mehrere SillyMUD CloudI-Dienstprozesse gleichzeitig als separate Spielserver verwendet werden, wodurch die Benutzer von Laufzeitfehlern isoliert bleiben und Ausfallzeiten reduziert werden.
Wie sehr hat sich der MUD verbessert?
Es gab drei primäre Verbesserungsbereiche:
- Fehlertoleranz. Mit der modernisierten SillyMUD CloudI-Dienstintegration bietet die Isolation von Socket-Fehlern und Latenz aus dem SillyMUD-Quellcode ein gewisses Maß an Fehlertoleranz.
- Skalierbarkeit der Verbindung. Mit der Verwendung interner CloudI-Dienste kann die Beschränkung der gleichzeitigen SillyMUD-Benutzer leicht von 64 (historisch) auf 16.384 Benutzer (ohne Latenzprobleme!) gehen.
- Effizienz und Leistung. Da die Verbindungsverwaltung in CloudI statt im Singlethread-Quellcode von SillyMUD erfolgt, wird die Effizienz des SillyMUD-Gameplay-Quellcodes natürlich verbessert und kann eine höhere Last bewältigen.
Mit der einfachen CloudI-Integration wurde die Anzahl der Verbindungen also um drei Größenordnungen skaliert, während Fehlertoleranz bereitgestellt und die Effizienz des gleichen Legacy-Gameplays gesteigert wurde.
Das größere Bild
Erlang hat 99,9999999 % Betriebszeit (weniger als 31,536 Millisekunden Ausfallzeit pro Jahr) für Produktionssysteme bereitgestellt. Mit CloudI bringen wir dieselbe Zuverlässigkeit in andere Programmiersprachen und Systeme.
Dieses Projekt beweist nicht nur die Realisierbarkeit dieses Ansatzes zur Verbesserung des stagnierenden Quellcodes von Legacy-Spielservern (SillyMUD wurde zuletzt vor über 20 Jahren im Jahr 1993 geändert!), sondern demonstriert auf einer breiteren Ebene, wie Erlang und CloudI genutzt werden können, um Legacy-Anwendungen zu modernisieren und Fehler bereitzustellen -Toleranz, verbesserte Leistung und Hochverfügbarkeit im Allgemeinen. Diese Ergebnisse bieten ein vielversprechendes Potenzial für die Anpassung von Legacy-Code an das 21. Jahrhundert, ohne dass eine umfassende Softwareüberholung erforderlich ist.