Modernizando o software legado: programação MUD usando Erlang e CloudI
Publicados: 2022-03-11O que é Modernização Legada?
O código legado está em toda parte. E à medida que a taxa de proliferação do código continua a aumentar exponencialmente, cada vez mais esse código está sendo relegado ao status de legado. Em muitas grandes organizações, a manutenção de sistemas legados consome mais de 90% dos recursos de sistemas de informação.
A necessidade de modernizar o código e os sistemas legados para atender às demandas atuais de desempenho e processamento é generalizada. Este post fornece um estudo de caso do uso da linguagem de programação Erlang e da Arquitetura Orientada a Serviços CloudI (SOA) baseada em Erlang, para adaptar o código legado – em particular, uma coleção de décadas de código-fonte C – para o século XXI .
Matando o Dragão do Código Fonte
Anos atrás, eu era um grande fã de jogos online multiplayer baseados em texto conhecidos como Multi-User Dungeons (MUDs). Mas eles estavam sempre cheios de problemas de desempenho. Decidi mergulhar de volta em uma pilha de código-fonte C de décadas e ver como poderíamos modernizar esse código legado e levar esses primeiros jogos online ao limite. Em alto nível, este projeto foi um ótimo exemplo de uso do Erlang para adaptar software legado para atender aos requisitos do século XXI.
Um breve resumo:
- O objetivo : Pegue um antigo videogame MUD com limite de 50 jogadores e envie seu código-fonte para suportar milhares e milhares de conexões simultâneas.
- O problema : código-fonte C legado de thread único.
- A solução : CloudI, um serviço baseado em Erlang que oferece tolerância a falhas e escalabilidade.
O que é um MUD baseado em texto?
Todos os Massively Multiplayer Online Role Playing Games (MMORPGs) – como World of Warcraft e EverQuest – desenvolveram recursos cujas origens iniciais podem ser rastreadas até antigos jogos multiplayer online baseados em texto conhecidos como Multi-User Dungeons (MUDs).
O primeiro MUD foi o Essex MUD de Roy Trubshaw (ou MUD1) que foi originalmente desenvolvido em 1978 usando a linguagem assembler MARO-10 em um DEC PDP-10, mas foi convertido para BCPL, um predecessor da linguagem de programação C (e estava funcionando até 1987). (Como você pode ver, essas coisas são mais antigas do que a maioria dos programadores.)
Os MUDs gradualmente ganharam popularidade durante o final dos anos 1980 e início dos anos 1990 com várias bases de código MUD escritas em C. A base de código DikuMUD, por exemplo, é conhecida como a raiz de uma das maiores árvores de código fonte MUD derivado, com pelo menos 51 variantes únicas todas baseado no mesmo código fonte do DikuMUD. (Durante esse período, aliás, os MUDs tornaram-se alternativamente conhecidos como o “Multi-Undergraduate Destroyer” devido ao número de estudantes universitários que falharam na escola devido à sua obsessão por eles.)
O problema com os MUDs legados
O código-fonte histórico do C MUD (incluindo o DikuMUD e suas variantes) está repleto de problemas de desempenho devido às limitações existentes no momento da criação.
Falta de rosqueamento
Naquela época, não havia biblioteca de encadeamento facilmente acessível. Além disso, o encadeamento tornaria o código-fonte mais difícil de manter e modificar. Como resultado, esses MUDs eram todos de thread único.
Durante um único “tick” (um incremento do relógio interno que rastreia a progressão de todos os eventos do jogo), o código-fonte do MUD precisa processar todos os eventos do jogo para cada soquete conectado. Em outras palavras: cada pedaço de código retarda o processamento de um único tick. E se qualquer computação forçar o processamento a durar mais do que um único tick, o MUD fica atrasado, impactando todos os jogadores conectados.
Com esse atraso, o jogo imediatamente se torna menos envolvente. Os jogadores assistem impotentes enquanto seus personagens morrem, com seus próprios comandos permanecendo não processados.
Apresentando SillyMUD
Para os propósitos deste experimento de modernização de aplicativos legados, escolhi o SillyMUD, um derivado histórico do DikuMUD que influenciou os MMORPGs modernos e os problemas de desempenho que eles compartilham. Durante a década de 1990, eu joguei um MUD que foi derivado da base de código SillyMUD, então eu sabia que o código-fonte seria um ponto de partida interessante e um tanto familiar.
O que eu estava herdando?
O código-fonte do SillyMUD é semelhante ao de outros C MUDs históricos, pois é limitado a aproximadamente 50 players simultâneos (64, para ser preciso, com base no código-fonte).
No entanto, notei que o código-fonte foi modificado por motivos de desempenho (ou seja, para aumentar sua limitação de player simultâneo). Especificamente:
- O código-fonte estava faltando uma pesquisa de nome de domínio no endereço IP de conexão, ausente devido à latência imposta por uma pesquisa de nome de domínio (normalmente, um MUD mais antigo deseja uma pesquisa de nome de domínio para facilitar a proibição de usuários mal-intencionados).
- O código-fonte teve seu comando “donate” desabilitado (um pouco incomum) devido à possível criação de longas listas vinculadas de itens doados que, então, exigiam travessias de lista de processamento intensivo. Estes, por sua vez, prejudicam o desempenho do jogo para todos os outros jogadores (single-thread, lembra?).
Apresentando o CloudI
O CloudI foi discutido anteriormente como uma solução para o desenvolvimento poliglota devido à tolerância a falhas e à escalabilidade que ele oferece.
CloudI fornece uma abstração de serviço (para fornecer uma Arquitetura Orientada a Serviços (SOA)) em Erlang, C/C++, Java, Python e Ruby, mantendo as falhas de software isoladas dentro da estrutura CloudI. A tolerância a falhas é fornecida por meio da implementação Erlang do CloudI, contando com os recursos de tolerância a falhas do Erlang e sua implementação do Actor Model. Essa tolerância a falhas é um recurso fundamental da implementação Erlang da CloudI, pois todos os softwares contêm bugs.
O CloudI também fornece um servidor de aplicativos para controlar o tempo de vida da execução do serviço e a criação de processos de serviço (como processos do sistema operacional para linguagens de programação não Erlang ou como processos Erlang para serviços implementados em Erlang) para que a execução do serviço ocorra sem afetar o estado externo confiabilidade. Para saber mais, veja meu post anterior.

Como o CloudI pode modernizar um MUD baseado em texto legado?
O código-fonte histórico do C MUD oferece uma oportunidade interessante para a integração do CloudI devido aos seus problemas de confiabilidade:
- A estabilidade do servidor de jogo afeta diretamente o apelo de qualquer mecânica de jogo.
- Concentrar o desenvolvimento de software na correção de bugs de estabilidade do servidor limita o tamanho e o escopo do jogo resultante.
Com a integração CloudI, os bugs de estabilidade do servidor ainda podem ser corrigidos normalmente, mas seu impacto é limitado para que a operação do servidor do jogo nem sempre seja afetada quando um bug não descoberto anteriormente causa uma falha no sistema de jogo interno. Isso fornece um ótimo exemplo do uso de Erlang para impor tolerância a falhas em uma base de código legada.
Que mudanças foram necessárias?
A base de código original foi escrita para ser single-thread e altamente dependente de variáveis globais. Meu objetivo era preservar a funcionalidade do código-fonte legado enquanto o modernizava para uso atual.
Com o CloudI, consegui manter o código-fonte single-thread enquanto ainda oferecia escalabilidade de conexão de soquete.
Vamos rever as alterações necessárias:
Saída do console
O buffer da saída do console SillyMUD (um display de terminal, geralmente conectado ao Telnet) já estava em vigor, mas algum uso direto do descritor de arquivo exigia buffer (para que a saída do console pudesse se tornar a resposta a uma solicitação de serviço CloudI).
Manipulação de soquete
O manuseio de soquete no código-fonte original dependia de uma chamada de função select()
para detectar entrada, erros e a chance de saída, bem como pausar por um tique de jogo de 250 milissegundos antes de lidar com eventos de jogo pendentes.
A integração do CloudI SillyMUD depende de solicitações de serviço recebidas para entrada enquanto pausa com a função cloudi_poll
da API C CloudI (para os 250 milissegundos antes de lidar com os mesmos eventos de jogo pendentes). O código-fonte do SillyMUD foi executado facilmente no CloudI como um serviço do CloudI depois de ser integrado à API C CloudI (embora o CloudI forneça APIs C e C++, usando a API C facilita a integração com o código-fonte C do SillyMUD).
Assinaturas
A integração CloudI assina três padrões de nome de serviço principais para lidar com eventos de conexão, desconexão e jogabilidade. Esses padrões de nome vêm da API C CloudI chamando a assinatura no código-fonte da integração. Da mesma forma, as conexões WebSocket ou Telnet têm destinos de nome de serviço para enviar solicitações de serviço quando as conexões são estabelecidas.
O suporte WebSocket e Telnet no CloudI é fornecido por serviços internos do CloudI ( cloudi_service_http_cowboy
para suporte WebSocket e cloudi_service_tcp
para suporte Telnet). Como os serviços internos do CloudI são escritos em Erlang, eles podem aproveitar a extrema escalabilidade do Erlang e, ao mesmo tempo, usar a abstração do serviço CloudI que fornece as funções da API CloudI.
Daqui para frente
Ao evitar o manuseio de soquete, ocorreu menos processamento em erros de soquete ou situações como morte de link (em que os usuários são desconectados do servidor). Assim, a remoção do manuseio de soquetes de baixo nível abordou o principal problema de escalabilidade.
Mas os problemas de escalabilidade permanecem. Por exemplo, o MUD usa o sistema de arquivos como um banco de dados local para elementos de jogabilidade estáticos e dinâmicos (ou seja, jogadores e seu progresso, juntamente com as zonas do mundo, objetos e monstros). Refatorar o código legado do MUD para, em vez disso, depender de um serviço CloudI para um banco de dados forneceria mais tolerância a falhas. Se usássemos um banco de dados em vez de um sistema de arquivos, vários processos de serviço SillyMUD CloudI poderiam ser usados simultaneamente como servidores de jogos separados, mantendo os usuários isolados de erros de tempo de execução e reduzindo o tempo de inatividade.
Quanto o MUD melhorou?
Havia três áreas principais de melhoria:
- Tolerância ao erro. Com a integração de serviço SillyMUD CloudI modernizada, o isolamento de erros de soquete e latência do código-fonte SillyMUD fornece um grau de tolerância a falhas.
- Escalabilidade da conexão. Com o uso de serviços internos do CloudI, a limitação de usuários simultâneos do SillyMUD pode facilmente passar de 64 (historicamente) para 16.384 usuários (sem problemas de latência!) .
- Eficiência e desempenho. Com o manuseio da conexão sendo feito no CloudI em vez do código-fonte SillyMUD de thread único, a eficiência do código-fonte do jogo SillyMUD é naturalmente melhorada e pode lidar com uma carga maior.
Portanto, com a integração simples do CloudI, o número de conexões foi dimensionado em três ordens de magnitude, proporcionando tolerância a falhas e aumentando a eficiência da mesma jogabilidade herdada.
A foto maior
Erlang forneceu 99,9999999% de tempo de atividade (menos de 31,536 milissegundos de tempo de inatividade por ano) para sistemas de produção. Com o CloudI, trazemos essa mesma confiabilidade para outras linguagens e sistemas de programação.
Além de provar a viabilidade dessa abordagem para melhorar o código-fonte do servidor de jogos legado estagnado (o SillyMUD foi modificado pela última vez há mais de 20 anos em 1993!), este projeto demonstra em um nível mais amplo como Erlang e CloudI podem ser aproveitados para modernizar aplicativos legados e fornecer falhas -tolerância, desempenho aprimorado e alta disponibilidade em geral. Esses resultados têm um potencial promissor para adaptar o código legado ao século 21 sem exigir uma grande revisão de software.