使遺留軟件現代化:使用 Erlang 和 CloudI 進行 MUD 編程
已發表: 2022-03-11什麼是傳統現代化?
遺留代碼無處不在。 隨著代碼激增的速度繼續呈指數增長,越來越多的代碼被降級為遺留狀態。 在許多大型組織中,遺留系統的維護消耗了 90% 以上的信息系統資源。
對遺留代碼和系統進行現代化改造以滿足當前性能和處理需求的需求非常普遍。 這篇文章提供了一個使用 Erlang 編程語言和基於 Erlang 的 CloudI 面向服務架構 (SOA) 的案例研究,以使遺留代碼——特別是幾十年前的 C 源代碼集合——適應 21 世紀.
殺死源代碼龍
多年前,我非常喜歡基於文本的多人在線遊戲,即多用戶地下城 (MUD)。 但他們總是充斥著性能問題。 我決定深入研究一堆幾十年前的 C 源代碼,看看我們如何對這些遺留代碼進行現代化改造,並將這些早期的在線遊戲推向極限。 在高層次上,這個項目是使用 Erlang 調整遺留軟件以滿足 21 世紀需求的一個很好的例子。
一個簡短的總結:
- 目標:拿一個 50 人限制的老式 MUD 視頻遊戲,並推送其源代碼以支持成千上萬的同時連接。
- 問題:遺留的單線程C源代碼。
- 解決方案:CloudI,一種基於 Erlang 的服務,提供容錯性和可擴展性。
什麼是基於文本的 MUD?
所有大型多人在線角色扮演遊戲 (MMORPG) - 如魔獸世界和 EverQuest - 都開發了一些功能,其早期起源可以追溯到被稱為多用戶地下城 (MUD) 的較早的基於文本的多人在線遊戲。
第一個 MUD 是 Roy Trubshaw 的 Essex MUD(或 MUD1),它最初是在 1978 年在 DEC PDP-10 上使用 MARO-10 彙編語言開發的,但後來被轉換為 C 編程語言的前身 BCPL(並且一直運行到1987)。 (如您所見,這些東西比大多數程序員都要老。)
MUD 在 1980 年代末和 1990 年代初逐漸流行起來,各種 MUD 代碼庫都是用 C 編寫的。例如,DikuMUD 代碼庫被稱為派生 MUD 源代碼的最大樹之一的根,其中至少有 51 個獨特的變體。基於相同的 DikuMUD 源代碼。 (順便說一下,在這段時間裡,MUD 也被稱為“多本科生毀滅者”,因為有很多大學本科生因為對它們的痴迷而輟學。)
遺留 MUD 的問題
歷史上的 C MUD 源代碼(包括 DikuMUD 及其變體)由於其創建時存在的限製而充滿了性能問題。
缺乏線程
那時,還沒有易於訪問的線程庫。 此外,線程會使源代碼更難維護和修改。 結果,這些 MUD 都是單線程的。
在單個“滴答聲”(跟踪所有遊戲事件進程的內部時鐘的增量)期間,MUD 源代碼必須為每個連接的套接字處理每個遊戲事件。 換句話說:每段代碼都會減慢單個滴答的處理速度。 如果任何計算迫使處理時間超過單個刻度,MUD 就會滯後,影響每個連接的玩家。
由於這種滯後,遊戲立即變得不那麼吸引人了。 玩家無助地看著他們的角色死亡,他們自己的命令仍未處理。
介紹 SillyMUD
出於這個遺留應用程序現代化實驗的目的,我選擇了 SillyMUD,它是 DikuMUD 的歷史衍生產品,它影響了現代 MMORPG 及其共有的性能問題。 在 1990 年代,我玩了一個源自 SillyMUD 代碼庫的 MUD,所以我知道源代碼將是一個有趣且有點熟悉的起點。
我繼承了什麼?
SillyMUD 源代碼與其他歷史 C MUD 的源代碼相似,因為它被限制為大約 50 個並發玩家(根據源代碼,準確地說是 64 個)。
然而,我注意到源代碼出於性能原因(即,推動其並發播放器限制)已被修改。 具體來說:
- 源代碼缺少對連接 IP 地址的域名查找,這是由於域名查找強制執行的延遲(通常,較舊的 MUD 需要域名查找以更容易禁止惡意用戶)。
- 源代碼禁用了“捐贈”命令(有點不尋常),因為可能會創建捐贈項目的長鍊錶,然後需要處理密集型列表遍歷。 這些反過來又會損害所有其他玩家的遊戲性能(單線程,記得嗎?)。
介紹 CloudI
由於 CloudI 提供的容錯性和可擴展性,CloudI 之前被討論為多語言開發的解決方案。
CloudI 在 Erlang、C/C++、Java、Python 和 Ruby 中提供服務抽象(以提供面向服務的架構 (SOA)),同時在 CloudI 框架內隔離軟件故障。 容錯是通過 CloudI 的 Erlang 實現提供的,依賴於 Erlang 的容錯特性及其對 Actor 模型的實現。 這種容錯性是 CloudI 的 Erlang 實現的一個關鍵特性,因為所有軟件都包含錯誤。
CloudI 還提供了一個應用服務器來控制服務執行的生命週期和服務進程的創建(作為非 Erlang 編程語言的操作系統進程或在 Erlang 中實現的服務的 Erlang 進程),以便在沒有外部狀態影響的情況下執行服務可靠性。 更多信息,請參閱我之前的帖子。

CloudI 如何對傳統的基於文本的 MUD 進行現代化改造?
鑑於其可靠性問題,歷史 C MUD 源代碼為 CloudI 集成提供了一個有趣的機會:
- 遊戲服務器的穩定性直接影響任何遊戲機制的吸引力。
- 專注於修復服務器穩定性錯誤的軟件開發限制了最終遊戲的大小和範圍。
通過 CloudI 集成,仍然可以正常修復服務器穩定性錯誤,但其影響有限,因此當先前未發現的錯誤導致內部遊戲系統失敗時,遊戲服務器的操作並不總是受到影響。 這為使用 Erlang 在遺留代碼庫中強制容錯提供了一個很好的例子。
需要進行哪些更改?
原始代碼庫被編寫為單線程且高度依賴全局變量。 我的目標是保留遺留的源代碼功能,同時對其進行現代化改造以供當前使用。
使用 CloudI,我能夠保持源代碼單線程,同時仍然提供套接字連接的可擴展性。
讓我們回顧一下必要的更改:
控制台輸出
SillyMUD 控制台輸出(終端顯示器,通常與 Telnet 連接)的緩衝已經到位,但某些直接文件描述符使用確實需要緩衝(以便控制台輸出可以成為對 CloudI 服務請求的響應)。
套接字處理
原始源代碼中的套接字處理依賴於select()
函數調用來檢測輸入、錯誤和輸出機會,以及在處理未決遊戲事件之前暫停 250 毫秒的遊戲滴答聲。
CloudI SillyMUD 集成依賴於輸入的傳入服務請求,同時使用 C CloudI API 的cloudi_poll
函數暫停(在處理相同的未決遊戲事件之前的 250 毫秒)。 在與 C CloudI API 集成後,SillyMUD 源代碼很容易在 CloudI 中作為 CloudI 服務運行(雖然 CloudI 提供 C 和 C++ API,但使用 C API 更好地促進了與 SillyMUD 的 C 源代碼的集成)。
訂閱
CloudI 集成訂閱了三種主要的服務名稱模式來處理連接、斷開連接和遊戲事件。 這些名稱模式來自集成源代碼中的 C CloudI API 調用 subscribe。 因此,WebSocket 連接或 Telnet 連接都具有服務名稱目標,用於在建立連接時發送服務請求。
CloudI 中的 WebSocket 和 Telnet 支持由內部 CloudI 服務提供( cloudi_service_http_cowboy
用於 WebSocket 支持, cloudi_service_tcp
用於 Telnet 支持)。 由於內部 CloudI 服務是用 Erlang 編寫的,因此它們能夠利用 Erlang 的極端可擴展性,同時使用提供 CloudI API 功能的 CloudI 服務抽象。
向前走
通過避免套接字處理,套接字錯誤或鏈接死亡(用戶與服務器斷開連接)等情況的處理更少。 因此,刪除低級套接字處理解決了主要的可伸縮性問題。
但可擴展性問題仍然存在。 例如,MUD 使用文件系統作為靜態和動態遊戲元素(即玩家及其進度,以及世界區域、對象和怪物)的本地數據庫。 將 MUD 的遺留代碼重構為依賴於 CloudI 服務的數據庫將提供進一步的容錯能力。 如果我們使用數據庫而不是文件系統,多個 SillyMUD CloudI 服務進程可以同時用作單獨的遊戲服務器,從而使用戶遠離運行時錯誤並減少停機時間。
MUD 改進了多少?
有以下三個主要改進領域:
- 容錯。 通過現代化的 SillyMUD CloudI 服務集成,套接字錯誤和延遲與 SillyMUD 源代碼的隔離確實提供了一定程度的容錯能力。
- 連接可擴展性。 通過使用內部 CloudI 服務,SillyMUD 並髮用戶的限制可以輕鬆地從 64(歷史上)增加到 16,384 用戶(沒有延遲問題!) 。
- 效率和性能。 由於在 CloudI 中而不是單線程 SillyMUD 源代碼中完成連接處理,SillyMUD 遊戲源代碼的效率自然得到提高,並且可以處理更高的負載。
因此,通過簡單的 CloudI 集成,連接數量增加了三個數量級,同時提供了容錯能力並提高了相同傳統遊戲玩法的效率。
更大的圖景
Erlang 為生產系統提供了 99.9999999% 的正常運行時間(每年少於 31.536 毫秒的停機時間)。 借助 CloudI,我們為其他編程語言和系統帶來了同樣的可靠性。
除了證明這種改進停滯的遺留遊戲服務器源代碼的方法的可行性(SillyMUD 的最後一次修改是在 20 多年前的 1993 年!)之外,該項目還展示瞭如何利用 Erlang 和 CloudI 對遺留應用程序進行現代化改造並提供故障- 容錯性、改進的性能和總體上的高可用性。 這些結果具有使遺留代碼適應 21 世紀的巨大潛力,而無需對軟件進行大修。