使遗留软件现代化:使用 Erlang 和 CloudI 进行 MUD 编程

已发表: 2022-03-11

什么是传统现代化?

遗留代码无处不在。 随着代码激增的速度继续呈指数增长,越来越多的代码被降级为遗留状态。 在许多大型组织中,遗留系统的维护消耗了 90% 以上的信息系统资源。

对遗留代码和系统进行现代化改造以满足当前性能和处理需求的需求非常普遍。 这篇文章提供了一个使用 Erlang 编程语言和基于 Erlang 的 CloudI 面向服务架构 (SOA) 的案例研究,以使遗留代码——特别是几十​​年前的 C 源代码集合——适应 21 世纪.

在许多大型组织中,遗留系统的维护消耗了 90% 以上的信息系统资源。

杀死源代码龙

多年前,我非常喜欢基于文本的多人在线游戏,即多用户地下城 (MUD)。 但他们总是充斥着性能问题。 我决定深入研究一堆几十年前的 C 源代码,看看我们如何对这些遗留代码进行现代化改造,并将这些早期的在线游戏推向极限。 在高层次上,这个项目是使用 Erlang 调整遗留软件以满足 21 世纪需求的一个很好的例子。

一个简短的总结:

  • 目标:拿一个 50 人限制的老式 MUD 视频游戏,并推送其源代码以支持成千上万的同时连接。
  • 问题:遗留的单线程C源代码。
  • 解决方案:CloudI,一种基于 Erlang 的服务,提供容错性和可扩展性。

使遗留软件现代化:使用 Erlang 和 CloudI 进行 MUD 编程

什么是基于文本的 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 源代码必须为每个连接的套接字处理每个游戏事件。 换句话说:每段代码都会减慢单个滴答的处理速度。 如果任何计算迫使处理时间超过单个刻度,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 改进了多少?

通过 CloudI 集成,连接数量增加了三个数量级,同时提供了容错能力并提高了相同传统游戏玩法的效率。

有以下三个主要改进领域:

  1. 容错。 通过现代化的 SillyMUD CloudI 服务集成,套接字错误和延迟与 SillyMUD 源代码的隔离确实提供了一定程度的容错能力。
  2. 连接可扩展性。 通过使用内部 CloudI 服务,SillyMUD 并发用户的限制可以轻松地从 64(历史上)增加到 16,384 用户(没有延迟问题!)
  3. 效率和性能。 由于在 CloudI 中而不是单线程 SillyMUD 源代码中完成连接处理,SillyMUD 游戏源代码的效率自然得到提高,并且可以处理更高的负载。

因此,通过简单的 CloudI 集成,连接数量增加了三个数量级,同时提供了容错能力并提高了相同传统游戏玩法的效率。

更大的图景

Erlang 为生产系统提供了 99.9999999% 的正常运行时间(每年少于 31.536 毫秒的停机时间)。 借助 CloudI,我们为其他编程语言和系统带来了同样的可靠性。

除了证明这种改进停滞的遗留游戏服务器源代码的方法的可行性(SillyMUD 的最后一次修改是在 20 多年前的 1993 年!)之外,该项目还展示了如何利用 Erlang 和 CloudI 对遗留应用程序进行现代化改造并提供故障- 容错性、改进的性能和总体上的高可用性。 这些结果具有使遗留代码适应 21 世纪的巨大潜力,而无需对软件进行大修。