Modernización del software heredado: Programación MUD usando Erlang y CloudI

Publicado: 2022-03-11

¿Qué es la modernización heredada?

El código heredado está en todas partes. Y a medida que la velocidad a la que prolifera el código continúa aumentando exponencialmente, cada vez más ese código queda relegado al estado heredado. En muchas organizaciones grandes, el mantenimiento de los sistemas heredados consume más del 90 % de los recursos de los sistemas de información.

La necesidad de modernizar el código y los sistemas heredados para satisfacer las demandas actuales de rendimiento y procesamiento está muy extendida. Esta publicación proporciona un estudio de caso del uso del lenguaje de programación Erlang y la arquitectura orientada a servicios (SOA) CloudI basada en Erlang, para adaptar el código heredado, en particular, una colección de código fuente C de décadas de antigüedad, al siglo XXI. .

En muchas organizaciones grandes, el mantenimiento de los sistemas heredados consume más del 90 % de los recursos de los sistemas de información.

Matando al dragón del código fuente

Hace años, era un gran fanático de los juegos en línea multijugador basados ​​en texto conocidos como Multi-User Dungeons (MUD). Pero siempre estaban plagados de problemas de rendimiento. Decidí volver a sumergirme en una pila de código fuente C de décadas de antigüedad y ver cómo podíamos modernizar este código heredado y llevar estos primeros juegos en línea a sus límites. En un alto nivel, este proyecto fue un gran ejemplo del uso de Erlang para adaptar el software heredado para cumplir con los requisitos del siglo XXI.

Un breve resumen:

  • El objetivo : tomar un viejo videojuego MUD limitado a 50 jugadores y empujar su código fuente para admitir miles y miles de conexiones simultáneas.
  • El problema : código fuente C heredado de un solo subproceso.
  • La solución : CloudI, un servicio basado en Erlang que brinda tolerancia a fallas y escalabilidad.

Modernización del software heredado: Programación MUD usando Erlang y CloudI

¿Qué es un MUD basado en texto?

Todos los juegos de rol multijugador masivo en línea (MMORPG), como World of Warcraft y EverQuest, han desarrollado funciones cuyos primeros orígenes se remontan a juegos en línea multijugador basados ​​en texto más antiguos conocidos como mazmorras multiusuario (MUD).

El primer MUD fue Essex MUD (o MUD1) de Roy Trubshaw, que se desarrolló originalmente en 1978 usando el lenguaje ensamblador MARO-10 en un DEC PDP-10, pero se convirtió a BCPL, un predecesor del lenguaje de programación C (y estuvo funcionando hasta 1987). (Como puede ver, estas cosas son más antiguas que la mayoría de los programadores).

Los MUD ganaron popularidad gradualmente a fines de la década de 1980 y principios de la de 1990 con varias bases de código de MUD escritas en C. La base de código de DikuMUD, por ejemplo, se conoce como la raíz de uno de los árboles más grandes de código fuente de MUD derivado, con al menos 51 variantes únicas, todas basado en el mismo código fuente de DikuMUD. (Durante este período de tiempo, por cierto, los MUD se conocieron alternativamente como el "Destructor de múltiples estudiantes universitarios" debido a la cantidad de estudiantes universitarios que fracasaron en la escuela debido a su obsesión con ellos).

El problema con los MUD heredados

El código fuente histórico de C MUD (incluido DikuMUD y sus variantes) está plagado de problemas de rendimiento debido a las limitaciones existentes en el momento de su creación.

falta de enhebrado

En aquel entonces, no había una biblioteca de subprocesos de fácil acceso. Además, la creación de subprocesos habría hecho que el código fuente fuera más difícil de mantener y modificar. Como resultado, estos MUD eran todos de un solo subproceso.

Cada pieza de código ralentiza el procesamiento de un solo tick. Y si algún cálculo obliga al procesamiento a abarcar más de un solo tic, el MUD se retrasa, lo que afecta a todos los jugadores conectados.

Durante un solo “tick” (un incremento del reloj interno que rastrea la progresión de todos los eventos del juego), el código fuente de MUD tiene que procesar cada evento del juego para cada enchufe conectado. En otras palabras: cada pieza de código ralentiza el procesamiento de un solo tick. Y si algún cálculo obliga al procesamiento a abarcar más de un solo tic, el MUD se retrasa, lo que afecta a todos los jugadores conectados.

Con este retraso, el juego se vuelve inmediatamente menos atractivo. Los jugadores miran impotentes cómo mueren sus personajes, y sus propios comandos quedan sin procesar.

Presentamos SillyMUD

A los efectos de este experimento de modernización de aplicaciones heredadas, elegí SillyMUD, un derivado histórico de DikuMUD que ha influido en los MMORPG modernos y los problemas de rendimiento que comparten. Durante la década de 1990, jugué un MUD derivado del código base de SillyMUD, así que sabía que el código fuente sería un punto de partida interesante y algo familiar.

¿Qué estaba heredando?

El código fuente de SillyMUD es similar al de otros C MUD históricos en el sentido de que está limitado a aproximadamente 50 jugadores simultáneos (64, para ser precisos, según el código fuente).

Sin embargo, me di cuenta de que el código fuente había sido modificado por razones de rendimiento (es decir, para impulsar su limitación de jugadores concurrentes). Específicamente:

  • Al código fuente le faltaba una búsqueda de nombre de dominio en la dirección IP de la conexión, ausente debido a la latencia impuesta por una búsqueda de nombre de dominio (normalmente, un MUD más antiguo quiere una búsqueda de nombre de dominio para facilitar la prohibición de usuarios malintencionados).
  • El código fuente tenía su comando "donar" deshabilitado (un poco inusual) debido a la posible creación de largas listas enlazadas de elementos donados que luego requerían recorridos de lista intensivos en procesamiento. Estos, a su vez, perjudican el rendimiento del juego para todos los demás jugadores (un solo subproceso, ¿recuerdas?).

Presentación de CloudI

CloudI se analizó anteriormente como una solución para el desarrollo políglota debido a la tolerancia a fallas y la escalabilidad que brinda.

CloudI proporciona una abstracción de servicios (para proporcionar una arquitectura orientada a servicios (SOA)) en Erlang, C/C++, Java, Python y Ruby, mientras mantiene las fallas de software aisladas dentro del marco de CloudI. La tolerancia a fallas se proporciona a través de la implementación de Erlang de CloudI, que se basa en las funciones de tolerancia a fallas de Erlang y su implementación del modelo de actor. Esta tolerancia a fallas es una característica clave de la implementación de Erlang de CloudI, ya que todo el software contiene errores.

CloudI también proporciona un servidor de aplicaciones para controlar la duración de la ejecución del servicio y la creación de procesos de servicio (ya sea como procesos de sistema operativo para lenguajes de programación que no sean Erlang o como procesos de Erlang para servicios implementados en Erlang) para que la ejecución del servicio se produzca sin que el estado externo afecte fiabilidad. Para más, vea mi publicación anterior.

¿Cómo puede CloudI modernizar un MUD heredado basado en texto?

El código fuente histórico de C MUD brinda una oportunidad interesante para la integración de CloudI debido a sus problemas de confiabilidad:

  • La estabilidad del servidor de juegos afecta directamente el atractivo de cualquier mecánica de juego.
  • Centrar el desarrollo de software en corregir errores de estabilidad del servidor limita el tamaño y el alcance del juego resultante.

Con la integración de CloudI, los errores de estabilidad del servidor aún se pueden corregir con normalidad, pero su impacto es limitado, por lo que el funcionamiento del servidor del juego no siempre se ve afectado cuando un error no descubierto anteriormente hace que falle un sistema de juego interno. Esto proporciona un gran ejemplo del uso de Erlang para hacer cumplir la tolerancia a fallas en una base de código heredada.

¿Qué cambios fueron necesarios?

El código base original fue escrito para ser de un solo subproceso y altamente dependiente de variables globales. Mi objetivo era preservar la funcionalidad del código fuente heredado mientras lo modernizaba para el uso actual.

Con CloudI, pude mantener el código fuente de un solo subproceso y al mismo tiempo proporcionar escalabilidad de conexión de socket.

Mi objetivo era preservar la funcionalidad del código fuente heredado mientras lo adaptaba para el uso moderno.

Repasemos los cambios necesarios:

Salida de consola

El almacenamiento en búfer de la salida de la consola SillyMUD (una pantalla de terminal, a menudo conectada con Telnet) ya estaba en su lugar, pero algunos usos directos del descriptor de archivos requerían almacenamiento en búfer (para que la salida de la consola pudiera convertirse en la respuesta a una solicitud de servicio de CloudI).

Manejo de zócalos

El manejo de sockets en el código fuente original se basaba en una llamada a la función select() para detectar entradas, errores y la posibilidad de salida, así como para pausar un juego de 250 milisegundos antes de manejar los eventos pendientes del juego.

La integración de CloudI SillyMUD se basa en las solicitudes de entrada de servicios entrantes mientras se detiene con la función cloudi_poll de la API C cloudi_poll (durante los 250 milisegundos antes de manejar los mismos eventos de juego pendientes). El código fuente de SillyMUD se ejecutó fácilmente dentro de CloudI como un servicio de CloudI después de integrarse con la API de C CloudI (aunque CloudI proporciona las API de C y C++, el uso de la API de C facilitó la integración con el código fuente de C de SillyMUD).

Suscripciones

La integración de CloudI se suscribe a tres patrones de nombre de servicio principales para manejar eventos de conexión, desconexión y juego. Estos patrones de nombres provienen de la suscripción de llamada de la API de C CloudI en el código fuente de integración. En consecuencia, las conexiones WebSocket o las conexiones Telnet tienen destinos de nombre de servicio para enviar solicitudes de servicio cuando se establecen conexiones.

La compatibilidad con WebSocket y Telnet en CloudI la proporcionan los servicios internos de CloudI ( cloudi_service_http_cowboy para compatibilidad con WebSocket y cloudi_service_tcp para compatibilidad con Telnet). Dado que los servicios internos de CloudI están escritos en Erlang, pueden aprovechar la escalabilidad extrema de Erlang y, al mismo tiempo, utilizar la abstracción del servicio de CloudI que proporciona las funciones de la API de CloudI.

Avanzando

Al evitar el manejo de sockets, se produjo menos procesamiento en errores de socket o situaciones como la muerte del enlace (en la que los usuarios se desconectan del servidor). Por lo tanto, eliminar el manejo de sockets de bajo nivel solucionó el problema principal de escalabilidad.

La eliminación del manejo de sockets de bajo nivel solucionó el problema principal de escalabilidad.

Pero los problemas de escalabilidad persisten. Por ejemplo, el MUD utiliza el sistema de archivos como una base de datos local para elementos de juego tanto estáticos como dinámicos (es decir, jugadores y su progreso, junto con zonas del mundo, objetos y monstruos). Refactorizar el código heredado del MUD para confiar en un servicio CloudI para una base de datos proporcionaría una mayor tolerancia a fallas. Si usáramos una base de datos en lugar de un sistema de archivos, se podrían usar múltiples procesos de servicio SillyMUD CloudI simultáneamente como servidores de juego separados, manteniendo a los usuarios aislados de errores de tiempo de ejecución y reduciendo el tiempo de inactividad.

¿Cuánto ha mejorado la MUD?

Con la integración de CloudI, la cantidad de conexiones aumentó en tres órdenes de magnitud al mismo tiempo que brinda tolerancia a fallas y aumenta la eficiencia del mismo juego heredado.

Había tres áreas principales de mejora:

  1. Tolerancia a fallos. Con la integración del servicio SillyMUD CloudI modernizado, el aislamiento de los errores de socket y la latencia del código fuente de SillyMUD proporciona un grado de tolerancia a fallas.
  2. Escalabilidad de conexión. Con el uso de los servicios internos de CloudI, la limitación de usuarios concurrentes de SillyMUD puede pasar fácilmente de 64 (históricamente) a 16,384 usuarios (¡sin problemas de latencia!) .
  3. Eficiencia y rendimiento. Dado que el manejo de la conexión se realiza dentro de CloudI en lugar del código fuente de SillyMUD de subproceso único, la eficiencia del código fuente del juego SillyMUD mejora naturalmente y puede manejar una carga mayor.

Entonces, con la integración simple de CloudI, la cantidad de conexiones aumentó en tres órdenes de magnitud al mismo tiempo que brinda tolerancia a fallas y aumenta la eficiencia del mismo juego heredado.

La fotografía más grande

Erlang ha proporcionado un tiempo de actividad del 99,9999999 % (menos de 31,536 milisegundos de tiempo de inactividad por año) para los sistemas de producción. Con CloudI, llevamos esta misma confiabilidad a otros sistemas y lenguajes de programación.

Más allá de probar la viabilidad de este enfoque para mejorar el código fuente del servidor de juegos heredado estancado (¡SillyMUD se modificó por última vez hace más de 20 años en 1993!), Este proyecto demuestra en un nivel más amplio cómo se pueden aprovechar Erlang y CloudI para modernizar aplicaciones heredadas y proporcionar fallas. -tolerancia, rendimiento mejorado y alta disponibilidad en general. Estos resultados tienen un potencial prometedor para adaptar el código heredado al siglo XXI sin necesidad de una revisión importante del software.