Modernisation des logiciels hérités : programmation MUD à l'aide d'Erlang et de Cloud I

Publié: 2022-03-11

Qu'est-ce que la modernisation héritée ?

Le code hérité est partout. Et comme la vitesse à laquelle le code prolifère continue d'augmenter de façon exponentielle, de plus en plus de ce code est relégué au statut d'héritage. Dans de nombreuses grandes organisations, la maintenance des systèmes hérités consomme plus de 90 % des ressources des systèmes d'information.

La nécessité de moderniser le code et les systèmes hérités pour répondre aux exigences actuelles en matière de performances et de traitement est généralisée. Cet article fournit une étude de cas sur l'utilisation du langage de programmation Erlang et de l'architecture orientée service (SOA) CloudI basée sur Erlang, pour adapter le code hérité - en particulier, une collection de code source C vieille de plusieurs décennies - au 21e siècle .

Dans de nombreuses grandes organisations, la maintenance des systèmes hérités consomme plus de 90 % des ressources des systèmes d'information.

Tuer le dragon du code source

Il y a des années, j'étais un grand fan des jeux en ligne multijoueurs textuels connus sous le nom de donjons multi-utilisateurs (MUD). Mais ils étaient toujours criblés de problèmes de performances. J'ai décidé de me replonger dans une pile de code source C vieux de plusieurs décennies et de voir comment nous pourrions moderniser ce code hérité et pousser ces premiers jeux en ligne à leurs limites. À un niveau élevé, ce projet était un excellent exemple d'utilisation d'Erlang pour adapter des logiciels hérités afin de répondre aux exigences du 21e siècle.

Un bref résumé:

  • Le but : Prenez un ancien jeu vidéo MUD limité à 50 joueurs et poussez son code source pour prendre en charge des milliers et des milliers de connexions simultanées.
  • Le problème : Legacy, code source C mono-thread.
  • La solution : CloudI, un service basé sur Erlang qui offre une tolérance aux pannes et une évolutivité.

Modernisation des logiciels hérités : programmation MUD à l'aide d'Erlang et de Cloud I

Qu'est-ce qu'un MUD textuel ?

Tous les jeux de rôle en ligne massivement multijoueurs (MMORPG) – comme World of Warcraft et EverQuest – ont développé des fonctionnalités dont les premières origines remontent aux anciens jeux en ligne multijoueurs textuels connus sous le nom de Donjons multi-utilisateurs (MUD).

Le premier MUD était Essex MUD (ou MUD1) de Roy Trubshaw qui a été développé à l'origine en 1978 en utilisant le langage assembleur MARO-10 sur un DEC PDP-10, mais a été converti en BCPL, un prédécesseur du langage de programmation C (et fonctionnait jusqu'à 1987). (Comme vous pouvez le voir, ces choses sont plus anciennes que la plupart des programmeurs.)

Les MUD ont progressivement gagné en popularité à la fin des années 1980 et au début des années 1990 avec diverses bases de code MUD écrites en C. La base de code DikuMUD, par exemple, est connue comme la racine de l'un des plus grands arbres de code source MUD dérivé, avec au moins 51 variantes uniques toutes basé sur le même code source DikuMUD. (Au cours de cette période, incidemment, les MUD sont devenus alternativement connus sous le nom de "Multi-Undergraduate Destroyer" en raison du nombre d'étudiants de premier cycle qui ont échoué à l'école en raison de leur obsession pour eux.)

Le problème avec les anciens MUD

Le code source historique C MUD (y compris DikuMUD et ses variantes) est truffé de problèmes de performances en raison des limitations existantes au moment de sa création.

Manque de filetage

À l'époque, il n'y avait pas de bibliothèque de threads facilement accessible. De plus, le threading aurait rendu le code source plus difficile à maintenir et à modifier. En conséquence, ces MUD étaient tous monothread.

Chaque morceau de code ralentit le traitement d'un seul tick. Et si un calcul force le traitement à s'étendre sur plus d'un tick, le MUD est en retard, ce qui a un impact sur chaque joueur connecté.

Pendant un seul « tic » (un incrément de l'horloge interne qui suit la progression de tous les événements du jeu), le code source du MUD doit traiter chaque événement du jeu pour chaque prise connectée. En d'autres termes : chaque morceau de code ralentit le traitement d'un seul tick. Et si un calcul force le traitement à s'étendre sur plus d'un tick, le MUD est en retard, ce qui a un impact sur chaque joueur connecté.

Avec ce décalage, le jeu devient immédiatement moins engageant. Les joueurs regardent, impuissants, leurs personnages mourir, leurs propres commandes restant non traitées.

Présentation de SillyMUD

Pour les besoins de cette expérience de modernisation d'applications héritées, j'ai choisi SillyMUD, un dérivé historique de DikuMUD qui a influencé les MMORPG modernes et les problèmes de performances qu'ils partagent. Au cours des années 1990, j'ai joué à un MUD dérivé de la base de code SillyMUD, donc je savais que le code source serait un point de départ intéressant et quelque peu familier.

De quoi ai-je hérité ?

Le code source de SillyMUD est similaire à celui des autres MUD C historiques en ce sens qu'il est limité à environ 50 joueurs simultanés (64, pour être précis, sur la base du code source).

Cependant, j'ai remarqué que le code source avait été modifié pour des raisons de performances (c'est-à-dire pour repousser sa limitation de lecteur simultané). Spécifiquement:

  • Il manquait au code source une recherche de nom de domaine sur l'adresse IP de connexion, absente en raison de la latence imposée par une recherche de nom de domaine (normalement, un MUD plus ancien souhaite une recherche de nom de domaine pour faciliter l'interdiction des utilisateurs malveillants).
  • Le code source avait sa commande "donate" désactivée (un peu inhabituelle) en raison de la création possible de longues listes liées d'éléments donnés qui nécessitaient alors des parcours de liste intensifs en traitement. Ceux-ci, à leur tour, nuisent aux performances de jeu de tous les autres joueurs (monothread, vous vous souvenez ?).

Présentation de Cloud I

CloudI a déjà été présenté comme une solution pour le développement polyglotte en raison de la tolérance aux pannes et de l'évolutivité qu'il offre.

CloudI fournit une abstraction de service (pour fournir une architecture orientée service (SOA)) en Erlang, C/C++, Java, Python et Ruby, tout en maintenant les erreurs logicielles isolées dans le cadre CloudI. La tolérance aux pannes est fournie par l'implémentation d'Erlang de CloudI, en s'appuyant sur les fonctionnalités de tolérance aux pannes d'Erlang et sur son implémentation du modèle d'acteur. Cette tolérance aux pannes est une caractéristique clé de l'implémentation Erlang de CloudI, car tous les logiciels contiennent des bogues.

CloudI fournit également un serveur d'application pour contrôler la durée de vie de l'exécution du service et la création des processus de service (soit en tant que processus du système d'exploitation pour les langages de programmation non-Erlang, soit en tant que processus Erlang pour les services implémentés en Erlang) afin que l'exécution du service se produise sans impact sur l'état externe. fiabilité. Pour en savoir plus, voir mon post précédent.

Comment CloudI peut-il moderniser un ancien MUD textuel ?

Le code source historique C MUD offre une opportunité intéressante pour l'intégration CloudI compte tenu de ses problèmes de fiabilité :

  • La stabilité du serveur de jeu a un impact direct sur l'attrait de toute mécanique de jeu.
  • Concentrer le développement logiciel sur la correction des bogues de stabilité du serveur limite la taille et la portée du jeu résultant.

Avec l'intégration CloudI, les bugs de stabilité du serveur peuvent toujours être corrigés normalement, mais leur impact est limité afin que le fonctionnement du serveur de jeu ne soit pas toujours impacté lorsqu'un bug précédemment non découvert provoque l'échec d'un système de jeu interne. Cela fournit un excellent exemple de l'utilisation d'Erlang pour appliquer la tolérance aux pannes dans une base de code héritée.

Quels changements étaient nécessaires ?

La base de code originale a été écrite pour être à la fois monothread et fortement dépendante des variables globales. Mon objectif était de préserver la fonctionnalité du code source hérité tout en le modernisant pour une utilisation actuelle.

Avec CloudI, j'ai pu conserver le code source à un seul thread tout en offrant une évolutivité de la connexion par socket.

Mon objectif était de préserver la fonctionnalité du code source hérité tout en l'adaptant à un usage moderne.

Passons en revue les modifications nécessaires :

Sortie console

La mise en mémoire tampon de la sortie de la console SillyMUD (un écran de terminal, souvent connecté à Telnet) était déjà en place, mais certaines utilisations directes de descripteurs de fichiers nécessitaient une mise en mémoire tampon (afin que la sortie de la console puisse devenir la réponse à une demande de service CloudI).

Manipulation des douilles

La gestion des sockets dans le code source d'origine reposait sur un appel de fonction select() pour détecter les entrées, les erreurs et les chances de sortie, ainsi que pour faire une pause pendant un tick de jeu de 250 millisecondes avant de gérer les événements de jeu en attente.

L'intégration CloudI SillyMUD s'appuie sur les demandes de service entrantes pour l'entrée lors de la pause avec la fonction cloudi_poll de l'API C cloudi_poll (pendant les 250 millisecondes avant de gérer les mêmes événements de jeu en attente). Le code source de SillyMUD s'exécutait facilement dans CloudI en tant que service CloudI après avoir été intégré à l'API C CloudI (bien que CloudI fournisse à la fois des API C et C++, l'utilisation de l'API C a facilité l'intégration avec le code source C de SillyMUD).

Abonnements

L'intégration CloudI souscrit à trois modèles de nom de service principaux pour gérer les événements de connexion, de déconnexion et de jeu. Ces modèles de nom proviennent de l'API C CloudI appelant subscribe dans le code source de l'intégration. Par conséquent, les connexions WebSocket ou Telnet ont des destinations de nom de service pour envoyer des demandes de service lorsque les connexions sont établies.

La prise en charge de WebSocket et de Telnet dans CloudI est assurée par les services CloudI internes ( cloudi_service_http_cowboy pour la prise en charge de WebSocket et cloudi_service_tcp pour la prise en charge de Telnet). Étant donné que les services CloudI internes sont écrits en Erlang, ils sont capables de tirer parti de l'extrême évolutivité d'Erlang, tout en utilisant l'abstraction de service CloudI qui fournit les fonctions de l'API CloudI.

Aller de l'avant

En évitant la gestion des sockets, moins de traitement s'est produit sur des erreurs de socket ou des situations telles que la mort de lien (dans laquelle les utilisateurs sont déconnectés du serveur). Ainsi, la suppression de la gestion des sockets de bas niveau a résolu le problème d'évolutivité principal.

La suppression de la gestion des sockets de bas niveau a résolu le problème d'évolutivité principal.

Mais des problèmes d'évolutivité subsistent. Par exemple, le MUD utilise le système de fichiers comme base de données locale pour les éléments de jeu statiques et dynamiques (c'est-à-dire les joueurs et leur progression, ainsi que les zones du monde, les objets et les monstres). La refactorisation du code hérité du MUD pour s'appuyer à la place sur un service CloudI pour une base de données fournirait une tolérance aux pannes supplémentaire. Si nous utilisions une base de données plutôt qu'un système de fichiers, plusieurs processus de service SillyMUD CloudI pourraient être utilisés simultanément en tant que serveurs de jeu distincts, protégeant les utilisateurs des erreurs d'exécution et réduisant les temps d'arrêt.

Dans quelle mesure le MUD s'est-il amélioré ?

Avec l'intégration CloudI, le nombre de connexions a été mis à l'échelle de trois ordres de grandeur tout en offrant une tolérance aux pannes et en augmentant l'efficacité du même gameplay hérité.

Il y avait trois principaux domaines d'amélioration :

  1. Tolérance aux pannes. Avec l'intégration du service SillyMUD CloudI modernisé, l'isolation des erreurs de socket et de la latence du code source SillyMUD offre un certain degré de tolérance aux pannes.
  2. Évolutivité de la connexion. Avec l'utilisation des services CloudI internes, la limitation des utilisateurs simultanés de SillyMUD peut facilement passer de 64 (historiquement) à 16 384 utilisateurs (sans problème de latence !) .
  3. Efficacité et performances. La gestion de la connexion étant effectuée dans CloudI au lieu du code source SillyMUD à thread unique, l'efficacité du code source du jeu SillyMUD est naturellement améliorée et peut gérer une charge plus élevée.

Ainsi, avec une simple intégration CloudI, le nombre de connexions a été mis à l'échelle de trois ordres de grandeur tout en offrant une tolérance aux pannes et en augmentant l'efficacité du même gameplay hérité.

La vue d'ensemble

Erlang a fourni une disponibilité de 99,9999999 % (moins de 31,536 millisecondes d'indisponibilité par an) pour les systèmes de production. Avec CloudI, nous apportons cette même fiabilité à d'autres langages de programmation et systèmes.

En plus de prouver la viabilité de cette approche pour améliorer le code source du serveur de jeu hérité stagnant (SillyMUD a été modifié pour la dernière fois il y a plus de 20 ans en 1993 !), ce projet démontre à un niveau plus large comment Erlang et CloudI peuvent être exploités pour moderniser les applications héritées et fournir des erreurs -tolérance, performances améliorées et haute disponibilité en général. Ces résultats offrent un potentiel prometteur pour adapter le code hérité au 21e siècle sans nécessiter une refonte majeure du logiciel.