Modernizacja starszego oprogramowania: programowanie MUD przy użyciu Erlang i CloudI
Opublikowany: 2022-03-11Co to jest modernizacja starszej wersji?
Starszy kod jest wszędzie. A ponieważ tempo, w jakim kod się rozmnaża, nadal rośnie wykładniczo, coraz więcej tego kodu jest degradowanych do statusu dziedzictwa. W wielu dużych organizacjach utrzymanie starszych systemów pochłania ponad 90% zasobów systemów informatycznych.
Powszechna jest potrzeba modernizacji starszego kodu i systemów w celu spełnienia obecnych wymagań dotyczących wydajności i przetwarzania. Ten post zawiera studium przypadku użycia języka programowania Erlang i opartej na Erlangu architekturze zorientowanej na usługi CloudI (SOA) w celu dostosowania starszego kodu – w szczególności kilkudziesięcioletniego zbioru kodu źródłowego C – do XXI wieku .
Zabicie smoka kodu źródłowego
Lata temu byłem wielkim fanem tekstowych gier online dla wielu graczy, znanych jako Multi-User Dungeons (MUDs). Ale zawsze mieli problemy z wydajnością. Postanowiłem wrócić do stosu kodu źródłowego C sprzed dziesięcioleci i zobaczyć, jak możemy zmodernizować ten starszy kod i wykorzystać te wczesne gry online do granic możliwości. Na wysokim poziomie projekt ten był doskonałym przykładem wykorzystania Erlanga do dostosowania starszego oprogramowania do wymagań XXI wieku.
Krótkie podsumowanie:
- Cel : Weź starą grę wideo MUD dla 50 graczy i wypchnij jej kod źródłowy, aby obsługiwać tysiące jednoczesnych połączeń.
- Problem : Starszy, jednowątkowy kod źródłowy C.
- Rozwiązanie : CloudI, usługa oparta na Erlangu, która zapewnia odporność na błędy i skalowalność.
Co to jest MUD oparty na tekście?
Wszystkie gry typu Massively Multiplayer Online Role Playing Games (MMORPG) – takie jak World of Warcraft i EverQuest – posiadają funkcje, których wczesne początki sięgają starszych tekstowych gier online dla wielu graczy, znanych jako Lochy dla wielu użytkowników (MUD).
Pierwszym MUD był Essex MUD (lub MUD1) Roya Trubshawa, który został pierwotnie opracowany w 1978 roku przy użyciu języka asemblera MARO-10 na DEC PDP-10, ale został przekonwertowany na BCPL, poprzednik języka programowania C (i działał do 1987). (Jak widać, te rzeczy są starsze niż większość programistów.)
MUDy stopniowo zyskiwały popularność pod koniec lat 80. i na początku lat 90. dzięki różnym bazom kodu MUD napisanym w języku C. Baza kodu DikuMUD, na przykład, jest znana jako korzeń jednego z największych drzew kodu źródłowego MUD, z co najmniej 51 unikalnymi wariantami. oparty na tym samym kodzie źródłowym DikuMUD. (W tym czasie, nawiasem mówiąc, MUD stał się alternatywnie znany jako „Niszczyciel wielu studiów licencjackich” ze względu na liczbę studentów, którzy opuścili szkołę z powodu ich obsesji.)
Problem ze starszymi MUDami
Historyczny kod źródłowy C MUD (w tym DikuMUD i jego warianty) jest pełen problemów z wydajnością z powodu istniejących ograniczeń w momencie jego tworzenia.
Brak nawlekania
W tamtych czasach nie było łatwo dostępnej biblioteki wątków. Co więcej, wątki utrudniłyby utrzymanie i modyfikację kodu źródłowego. W rezultacie wszystkie te MUDy były jednowątkowe.
Podczas pojedynczego „tyknięcia” (przyrostu wewnętrznego zegara, który śledzi postęp wszystkich zdarzeń gry), kod źródłowy MUD musi przetworzyć każde zdarzenie gry dla każdego podłączonego gniazda. Innymi słowy: każdy fragment kodu spowalnia przetwarzanie pojedynczego tiku. A jeśli jakiekolwiek obliczenia wymuszają, aby przetwarzanie trwało dłużej niż jeden tik, MUD opóźnia się, wpływając na każdego podłączonego gracza.
Z tym opóźnieniem gra natychmiast staje się mniej wciągająca. Gracze bezradnie patrzą, jak ich postacie umierają, a ich własne polecenia pozostają nieprzetworzone.
Przedstawiamy SillyMUD
Na potrzeby tego eksperymentu modernizacji starszych aplikacji wybrałem SillyMUD, historyczną pochodną DikuMUD, która wpłynęła na współczesne gry MMORPG i problemy z wydajnością, które dzielą. W latach 90. grałem w MUD, który wywodził się z bazy kodu SillyMUD, więc wiedziałem, że kod źródłowy będzie interesującym i nieco znajomym punktem wyjścia.
Co dziedziczyłem?
Kod źródłowy SillyMUD jest podobny do innych historycznych MUD-ów w języku C, ponieważ ogranicza się do około 50 równoczesnych odtwarzaczy (a dokładniej 64, w oparciu o kod źródłowy).
Zauważyłem jednak, że kod źródłowy został zmodyfikowany ze względu na wydajność (tj. w celu przekroczenia ograniczenia równoczesnego odtwarzacza). Konkretnie:
- W kodzie źródłowym brakowało wyszukiwania nazwy domeny w adresie IP połączenia, nieobecne z powodu opóźnienia wymuszanego przez wyszukiwanie nazwy domeny (zwykle starszy MUD wymaga wyszukiwania nazwy domeny, aby ułatwić blokowanie złośliwych użytkowników).
- Kod źródłowy miał wyłączone polecenie „donate” (trochę nietypowe) ze względu na możliwość tworzenia długich połączonych list darowanych przedmiotów, które następnie wymagały intensywnego przetwarzania list. To z kolei obniża wydajność gry dla wszystkich innych graczy (jednowątkowych, pamiętasz?).
Przedstawiamy CloudI
CloudI był wcześniej omawiany jako rozwiązanie do tworzenia poliglotów ze względu na odporność na błędy i skalowalność, jakie zapewnia.
CloudI zapewnia abstrakcję usług (aby zapewnić architekturę zorientowaną na usługi (SOA)) w Erlang, C/C++, Java, Python i Ruby, jednocześnie utrzymując izolację błędów oprogramowania w ramach CloudI. Odporność na awarie jest zapewniana przez implementację Erlang CloudI, opierając się na funkcjach Erlanga w zakresie odporności na awarie i implementacji modelu aktora. Ta odporność na błędy jest kluczową cechą implementacji Erlang CloudI, ponieważ całe oprogramowanie zawiera błędy.
CloudI zapewnia również serwer aplikacji do kontrolowania czasu życia wykonania usługi i tworzenia procesów usług (jako procesów systemu operacyjnego dla języków programowania innych niż Erlang lub jako procesów Erlang dla usług zaimplementowanych w Erlang), tak aby wykonanie usługi odbywało się bez wpływu stanu zewnętrznego niezawodność. Więcej w moim poprzednim poście.

Jak CloudI zmodernizować starszy MUD oparty na tekście?
Historyczny kod źródłowy C MUD zapewnia interesującą możliwość integracji CloudI, biorąc pod uwagę jego problemy z niezawodnością:
- Stabilność serwera gier bezpośrednio wpływa na atrakcyjność dowolnej mechaniki gry.
- Skupienie rozwoju oprogramowania na naprawie błędów stabilności serwera ogranicza rozmiar i zakres wynikowej gry.
Dzięki integracji CloudI błędy stabilności serwera można nadal normalnie naprawiać, ale ich wpływ jest ograniczony, tak że nie zawsze ma to wpływ na działanie serwera gry, gdy wcześniej nie wykryty błąd powoduje awarię wewnętrznego systemu gry. Stanowi to doskonały przykład wykorzystania Erlanga do wymuszenia odporności na błędy w starszej bazie kodu.
Jakie zmiany były wymagane?
Oryginalna baza kodu została napisana jako jednowątkowa i wysoce zależna od zmiennych globalnych. Moim celem było zachowanie starszej funkcjonalności kodu źródłowego przy jednoczesnej modernizacji do obecnego użytku.
Dzięki CloudI mogłem zachować jednowątkowy kod źródłowy, jednocześnie zapewniając skalowalność połączenia z gniazdem.
Przyjrzyjmy się niezbędnym zmianom:
Wyjście konsoli
Buforowanie wyjścia konsoli SillyMUD (wyświetlacz terminala, często połączony z Telnetem) już istniało, ale niektóre bezpośrednie użycie deskryptora plików wymagało buforowania (tak, aby wyjście konsoli mogło stać się odpowiedzią na żądanie usługi CloudI).
Obsługa gniazd
Obsługa gniazd w oryginalnym kodzie źródłowym opierała się na wywołaniu funkcji select()
w celu wykrycia danych wejściowych, błędów i szansy na wyjście, a także wstrzymania gry na 250 milisekund przed obsługą oczekujących zdarzeń gry.
Integracja CloudI SillyMUD opiera się na przychodzących żądaniach usług o dane wejściowe podczas wstrzymywania za pomocą funkcji cloudi_poll API C cloudi_poll
(przez 250 milisekund przed obsługą tych samych oczekujących zdarzeń gry). Kod źródłowy SillyMUD łatwo działał w CloudI jako usługa CloudI po zintegrowaniu z C CloudI API (chociaż CloudI zapewnia zarówno C, jak i C++ API, przy użyciu C API lepiej ułatwiono integrację z kodem źródłowym C SillyMUD).
Subskrypcje
Integracja CloudI subskrybuje trzy główne wzorce nazw usług do obsługi zdarzeń łączenia, rozłączania i rozgrywki. Te wzorce nazw pochodzą z wywołania C CloudI API w kodzie źródłowym integracji. W związku z tym połączenia WebSocket lub połączenia Telnet mają miejsca docelowe nazw usług do wysyłania żądań usług po nawiązaniu połączenia.
Obsługa WebSocket i Telnet w CloudI jest zapewniana przez wewnętrzne usługi CloudI ( cloudi_service_http_cowboy
dla obsługi WebSocket i cloudi_service_tcp
dla obsługi Telnet). Ponieważ wewnętrzne usługi CloudI są napisane w języku Erlang, są w stanie wykorzystać ekstremalną skalowalność Erlanga, jednocześnie korzystając z abstrakcji usługi CloudI, która zapewnia funkcje CloudI API.
Iść naprzód
Unikając obsługi gniazda, mniej przetwarzania wystąpiło w przypadku błędów gniazda lub sytuacji, takich jak śmierć łącza (w której użytkownicy są odłączani od serwera). W ten sposób usunięcie obsługi gniazd niskiego poziomu rozwiązało główny problem skalowalności.
Ale problemy ze skalowalnością pozostają. Na przykład MUD wykorzystuje system plików jako lokalną bazę danych zarówno dla statycznych, jak i dynamicznych elementów rozgrywki (tj. graczy i ich postępów, wraz ze strefami świata, obiektami i potworami). Refaktoryzacja starszego kodu MUD, aby zamiast tego polegać na usłudze CloudI dla bazy danych, zapewniłaby dalszą odporność na błędy. Gdybyśmy użyli bazy danych, a nie systemu plików, wiele procesów usługowych SillyMUD CloudI mogłoby być używanych jednocześnie jako oddzielne serwery gier, izolując użytkowników od błędów w czasie wykonywania i skracając przestoje.
Jak bardzo poprawił się MUD?
Były trzy główne obszary poprawy:
- Tolerancja błędów. Dzięki zmodernizowanej integracji usług SillyMUD CloudI izolacja błędów gniazd i opóźnień z kodu źródłowego SillyMUD zapewnia pewien stopień odporności na błędy.
- Skalowalność połączenia. Dzięki wykorzystaniu wewnętrznych usług CloudI, ograniczenie jednoczesnych użytkowników SillyMUD może łatwo wzrosnąć z 64 (historycznie) do 16 384 użytkowników (bez problemów z opóźnieniami!) .
- Wydajność i wydajność. Ponieważ obsługa połączeń odbywa się w CloudI zamiast w jednowątkowym kodzie źródłowym SillyMUD, wydajność kodu źródłowego gry SillyMUD jest naturalnie poprawiona i może obsłużyć większe obciążenie.
Tak więc, dzięki prostej integracji CloudI, liczba połączeń skalowana o trzy rzędy wielkości, przy jednoczesnym zapewnieniu odporności na błędy i zwiększeniu wydajności tej samej starszej rozgrywki.
Większy obraz
Erlang zapewnił 99,9999999% czasu sprawności (mniej niż 31,536 milisekund przestojów rocznie) dla systemów produkcyjnych. Dzięki CloudI zapewniamy tę samą niezawodność innym językom programowania i systemom.
Oprócz udowodnienia wykonalności tego podejścia do poprawy zastałego kodu źródłowego starszego serwera gier (SillyMUD został ostatnio zmodyfikowany ponad 20 lat temu w 1993 roku!), ten projekt pokazuje na szerszym poziomie, w jaki sposób Erlang i CloudI mogą być wykorzystane do modernizacji starszych aplikacji i dostarczania błędów -tolerancja, ulepszona wydajność i ogólnie wysoka dostępność. Wyniki te mają obiecujący potencjał do adaptacji starszego kodu do XXI wieku bez konieczności gruntownego remontu oprogramowania.