Modernizzazione del software legacy: programmazione MUD utilizzando Erlang e CloudI

Pubblicato: 2022-03-11

Che cos'è la modernizzazione dell'eredità?

Il codice legacy è ovunque. E poiché la velocità con cui il codice prolifera continua ad aumentare in modo esponenziale, sempre più codice viene relegato allo stato legacy. In molte grandi organizzazioni, la manutenzione dei sistemi legacy consuma oltre il 90% delle risorse dei sistemi informativi.

La necessità di modernizzare il codice e i sistemi legacy per soddisfare le attuali esigenze di prestazioni ed elaborazione è diffusa. Questo post fornisce un caso di studio sull'uso del linguaggio di programmazione Erlang e dell'architettura SOA (Service Oriented Architecture) CloudI basata su Erlang, per adattare il codice legacy - in particolare, una raccolta di codice sorgente C vecchia di decenni - al 21° secolo .

In molte grandi organizzazioni, la manutenzione dei sistemi legacy consuma oltre il 90% delle risorse dei sistemi informativi.

Uccidere il drago del codice sorgente

Anni fa, ero un grande fan dei giochi online multiplayer basati su testo noti come Multi-User Dungeons (MUD). Ma erano sempre pieni di problemi di prestazioni. Ho deciso di tornare indietro in una pila di codice sorgente C vecchia di decenni e vedere come potremmo modernizzare questo codice legacy e spingere questi primi giochi online ai loro limiti. Ad alto livello, questo progetto è stato un ottimo esempio di utilizzo di Erlang per adattare il software legacy per soddisfare i requisiti del 21° secolo.

Un breve riassunto:

  • L'obiettivo : prendere un vecchio videogioco MUD limitato a 50 giocatori e spingere il suo codice sorgente per supportare migliaia e migliaia di connessioni simultanee.
  • Il problema : codice sorgente C legacy a thread singolo.
  • La soluzione : CloudI, un servizio basato su Erlang che fornisce tolleranza ai guasti e scalabilità.

Modernizzazione del software legacy: programmazione MUD utilizzando Erlang e CloudI

Che cos'è un MUD basato su testo?

Tutti i giochi di ruolo online multigiocatore di massa (MMORPG) – come World of Warcraft ed EverQuest – hanno sviluppato funzionalità le cui prime origini possono essere ricondotte a vecchi giochi multiplayer online basati su testo noti come Multi-User Dungeons (MUD).

Il primo MUD è stato l'Essex MUD (o MUD1) di Roy Trubshaw, originariamente sviluppato nel 1978 utilizzando il linguaggio assembler MARO-10 su un DEC PDP-10, ma convertito in BCPL, un predecessore del linguaggio di programmazione C (ed era in esecuzione fino a quando 1987). (Come puoi vedere, queste cose sono più vecchie della maggior parte dei programmatori.)

I MUD hanno gradualmente guadagnato popolarità durante la fine degli anni '80 e l'inizio degli anni '90 con varie basi di codice MUD scritte in C. La base di codice DikuMUD, ad esempio, è conosciuta come la radice di uno dei più grandi alberi di codice sorgente MUD derivato, con almeno 51 varianti uniche tutte basato sullo stesso codice sorgente DikuMUD. (Durante questo lasso di tempo, per inciso, i MUD sono diventati alternativamente noti come "Multi-Undergraduate Destroyer" a causa del numero di studenti universitari che hanno bocciato la scuola a causa della loro ossessione per loro.)

Il problema con i MUD legacy

Il codice sorgente C MUD storico (incluso DikuMUD e le sue varianti) è pieno di problemi di prestazioni dovuti alle limitazioni esistenti al momento della creazione.

Mancanza di filettatura

All'epoca non esisteva una libreria di threading facilmente accessibile. Inoltre, il threading avrebbe reso più difficile la manutenzione e la modifica del codice sorgente. Di conseguenza, questi MUD erano tutti a thread singolo.

Ogni pezzo di codice rallenta l'elaborazione di un singolo tick. E se qualsiasi calcolo costringe l'elaborazione a durare più a lungo di un singolo tick, il MUD è in ritardo, con un impatto su ogni giocatore connesso.

Durante un singolo "tick" (un incremento dell'orologio interno che tiene traccia della progressione di tutti gli eventi di gioco), il codice sorgente MUD deve elaborare ogni evento di gioco per ogni presa collegata. In altre parole: ogni pezzo di codice rallenta l'elaborazione di un singolo tick. E se qualsiasi calcolo costringe l'elaborazione a durare più a lungo di un singolo tick, il MUD è in ritardo, con un impatto su ogni giocatore connesso.

Con questo ritardo, il gioco diventa subito meno coinvolgente. I giocatori guardano impotenti mentre i loro personaggi muoiono, con i propri comandi che rimangono non elaborati.

Presentazione di SillyMUD

Ai fini di questo esperimento di modernizzazione delle applicazioni legacy, ho scelto SillyMUD, un derivato storico di DikuMUD che ha influenzato i moderni MMORPG e i problemi di prestazioni che condividono. Durante gli anni '90, ho suonato un MUD che era derivato dal codice di SillyMUD, quindi sapevo che il codice sorgente sarebbe stato un punto di partenza interessante e in qualche modo familiare.

Cosa stavo ereditando?

Il codice sorgente SillyMUD è simile a quello di altri C MUD storici in quanto è limitato a circa 50 giocatori simultanei (64, per la precisione, in base al codice sorgente).

Tuttavia, ho notato che il codice sorgente era stato modificato per motivi di prestazioni (ad esempio, per aumentare la limitazione del lettore simultaneo). Nello specifico:

  • Nel codice sorgente mancava una ricerca del nome di dominio sull'indirizzo IP della connessione, assente a causa della latenza imposta da una ricerca del nome di dominio (normalmente, un vecchio MUD vuole una ricerca del nome di dominio per rendere più facile il ban degli utenti malintenzionati).
  • Il codice sorgente aveva il comando "donate" disabilitato (un po' insolito) a causa della possibile creazione di lunghi elenchi collegati di articoli donati che richiedevano poi attraversamenti di elenchi ad alta intensità di elaborazione. Questi, a loro volta, danneggiano le prestazioni di gioco per tutti gli altri giocatori (a thread singolo, ricordi?).

Presentazione di CloudI

CloudI è stato precedentemente discusso come una soluzione per lo sviluppo poliglotta grazie alla tolleranza agli errori e alla scalabilità che fornisce.

CloudI fornisce un'astrazione del servizio (per fornire una Service Oriented-Architecture (SOA)) in Erlang, C/C++, Java, Python e Ruby, mantenendo isolati gli errori software all'interno del framework CloudI. La tolleranza ai guasti viene fornita tramite l'implementazione Erlang di CloudI, basandosi sulle funzionalità di tolleranza ai guasti di Erlang e sulla sua implementazione dell'Actor Model. Questa tolleranza agli errori è una caratteristica chiave dell'implementazione Erlang di CloudI, poiché tutto il software contiene bug.

CloudI fornisce anche un server delle applicazioni per controllare la durata dell'esecuzione del servizio e la creazione di processi di servizio (come processi del sistema operativo per linguaggi di programmazione non Erlang o come processi Erlang per servizi implementati in Erlang) in modo che l'esecuzione del servizio avvenga senza impatto sullo stato esterno affidabilità. Per ulteriori informazioni, vedere il mio post precedente.

In che modo CloudI può modernizzare un MUD legacy basato su testo?

Il codice sorgente storico C MUD offre un'interessante opportunità per l'integrazione di CloudI visti i suoi problemi di affidabilità:

  • La stabilità del server di gioco influisce direttamente sul fascino di qualsiasi meccanica di gioco.
  • Concentrare lo sviluppo del software sulla correzione dei bug di stabilità del server limita le dimensioni e la portata del gioco risultante.

Con l'integrazione di CloudI, i bug di stabilità del server possono ancora essere risolti normalmente, ma il loro impatto è limitato in modo che il funzionamento del server di gioco non sia sempre influenzato quando un bug non scoperto in precedenza causa il malfunzionamento di un sistema di gioco interno. Ciò fornisce un ottimo esempio dell'uso di Erlang per imporre la tolleranza agli errori in una base di codice legacy.

Quali modifiche erano necessarie?

La base di codice originale è stata scritta per essere sia a thread singolo che fortemente dipendente dalle variabili globali. Il mio obiettivo era preservare la funzionalità del codice sorgente legacy mentre lo modernizzavo per l'uso attuale.

Con CloudI, sono stato in grado di mantenere il codice sorgente a thread singolo pur fornendo scalabilità della connessione socket.

Il mio obiettivo era preservare la funzionalità del codice sorgente legacy adattandolo all'uso moderno.

Esaminiamo le modifiche necessarie:

Uscita console

Il buffering dell'output della console SillyMUD (un display del terminale, spesso connesso a Telnet) era già in atto, ma l'utilizzo diretto del descrittore di file richiedeva il buffering (in modo che l'output della console potesse diventare la risposta a una richiesta di servizio CloudI).

Gestione delle prese

La gestione del socket nel codice sorgente originale si basava su una chiamata alla funzione select() per rilevare input, errori e possibilità di output, nonché per mettere in pausa un tick di gioco di 250 millisecondi prima di gestire eventi di gioco in sospeso.

L'integrazione CloudI SillyMUD si basa sulle richieste di servizio in entrata per l'input durante la pausa con la funzione cloudi_poll dell'API C CloudI (per i 250 millisecondi prima della gestione degli stessi eventi di gioco in sospeso). Il codice sorgente SillyMUD è stato eseguito facilmente all'interno di CloudI come servizio CloudI dopo essere stato integrato con l'API C CloudI (sebbene CloudI fornisca API C e C++, utilizzando l'API C una migliore integrazione facilitata con il codice sorgente C di SillyMUD).

Abbonamenti

L'integrazione CloudI si iscrive a tre modelli di nomi di servizio principali per gestire gli eventi di connessione, disconnessione e di gioco. Questi modelli di nomi provengono dall'API C CloudI chiamando la sottoscrizione nel codice sorgente dell'integrazione. Di conseguenza, le connessioni WebSocket o le connessioni Telnet hanno destinazioni dei nomi di servizio per l'invio di richieste di servizio quando vengono stabilite le connessioni.

Il supporto WebSocket e Telnet in CloudI è fornito dai servizi CloudI interni ( cloudi_service_http_cowboy per il supporto WebSocket e cloudi_service_tcp per il supporto Telnet). Poiché i servizi CloudI interni sono scritti in Erlang, sono in grado di sfruttare l'estrema scalabilità di Erlang, mentre allo stesso tempo utilizzano l'astrazione del servizio CloudI che fornisce le funzioni dell'API CloudI.

Andando avanti

Evitando la gestione del socket, si è verificata una minore elaborazione in caso di errori del socket o situazioni come la morte del collegamento (in cui gli utenti sono disconnessi dal server). Pertanto, la rimozione della gestione dei socket di basso livello ha risolto il problema principale della scalabilità.

La rimozione della gestione dei socket di basso livello ha risolto il problema principale della scalabilità.

Ma i problemi di scalabilità rimangono. Ad esempio, il MUD usa il filesystem come database locale sia per gli elementi di gioco statici che dinamici (cioè i giocatori ei loro progressi, insieme alle zone del mondo, agli oggetti e ai mostri). Il refactoring del codice legacy del MUD per affidarsi invece a un servizio CloudI per un database fornirebbe ulteriore tolleranza agli errori. Se utilizzassimo un database anziché un filesystem, più processi di servizio SillyMUD CloudI potrebbero essere utilizzati contemporaneamente come server di gioco separati, mantenendo gli utenti isolati dagli errori di runtime e riducendo i tempi di inattività.

Quanto è migliorato il MUD?

Con l'integrazione CloudI, il numero di connessioni è stato scalato di tre ordini di grandezza, fornendo al contempo tolleranza agli errori e aumentando l'efficienza dello stesso gameplay legacy.

Tre sono state le principali aree di miglioramento:

  1. Tolleranza ai guasti. Con l'integrazione del servizio SillyMUD CloudI modernizzata, l'isolamento degli errori del socket e della latenza dal codice sorgente SillyMUD fornisce un certo grado di tolleranza agli errori.
  2. Scalabilità della connessione. Con l'utilizzo dei servizi CloudI interni, la limitazione sugli utenti simultanei SillyMUD può facilmente passare da 64 (storicamente) a 16.384 utenti (senza problemi di latenza!) .
  3. Efficienza e prestazioni. Con la gestione della connessione eseguita all'interno di CloudI invece del codice sorgente SillyMUD a thread singolo, l'efficienza del codice sorgente del gameplay di SillyMUD è naturalmente migliorata e può gestire un carico maggiore.

Quindi, con la semplice integrazione di CloudI, il numero di connessioni è stato scalato di tre ordini di grandezza fornendo al contempo la tolleranza agli errori e aumentando l'efficienza dello stesso gameplay legacy.

L'immagine più grande

Erlang ha fornito un tempo di attività del 99,9999999% (meno di 31,536 millisecondi di inattività all'anno) per i sistemi di produzione. Con CloudI portiamo questa stessa affidabilità ad altri linguaggi e sistemi di programmazione.

Oltre a dimostrare la fattibilità di questo approccio per migliorare il codice sorgente stagnante del server di gioco legacy (SillyMUD è stato modificato l'ultima volta oltre 20 anni fa nel 1993!), questo progetto dimostra a un livello più ampio come Erlang e CloudI possono essere sfruttati per modernizzare le applicazioni legacy e fornire errori -tolleranza, prestazioni migliorate e disponibilità elevata in generale. Questi risultati hanno un potenziale promettente per adattare il codice legacy al 21° secolo senza richiedere un'importante revisione del software.