运营 23 年、240 万行 Python 代码,EVE Online 为什么现在才开始迁移 Python 3?

如果要找一个“技术债并不等于烂代码”的案例,EVE Online 很合适。这个大型多人在线游戏从 2003 年上线起就重度使用 Stackless Python,2010 年升级到 Python 2.7,此后整整 16 年没有再切换 Python 主版本。Python 2.7 在 2020 年已经结束官方支持,EVE 却继续稳定运行;直到 2026 年 8 月,开发团队才正式宣布启动 Python 3 迁移。

乍看之下,这像一个“终于还技术债”的故事。实际更值得讨论的问题是:为什么一套全球在线、运行 23 年的系统可以合理地留在旧语言这么久,又为什么今天迁移的收益终于超过风险。EVE 团队面对的不是一个停机后批量升级的企业后台,而是一套 240 万行 Python、约 2 万个源文件、承载 23 年玩家资产和经济数据、每天需要保持 23.75 小时在线的系统。迁移成功的定义也很特别:玩家最好什么都感觉不到。

Stackless Python 曾经是架构选择,不是偶然遗留

EVE 早期采用 Stackless Python 有明确工程原因。Stackless 的 tasklet 提供轻量级协作式并发,让一个服务器节点可以管理大量玩家和游戏实体。在 2003 年的硬件和 Python 生态条件下,这种模型非常适合 MMO 服务器的海量并发状态机。CCP 不只是使用者,还曾长期参与 Stackless 生态。

这说明“老技术”不能简单用当前流行度判断。当年它解决了真实约束,而且持续支撑了一个复杂商业系统二十多年。企业软件里类似情况很多:某个自研 RPC 框架、老数据库、旧版 Java、COBOL、Fortran 或专用实时系统,今天看起来陈旧,当年可能正是最合理方案。只要系统稳定、人才和工具还能维持,主动升级反而可能引入更大风险。

EVE 团队自己也承认,长期不迁移的原因之一就是 Python 2.7 “足够可靠”。对一款实时运营游戏而言,升级语言版本不能直接创造玩家内容,却可能破坏交易、战斗、坐标、库存和经济系统,因此多年里风险收益比一直不划算。

迁移动力来自生态和工具,而不只是“Python 2 已停止维护”

2026 年再看,环境已经不同。现代调试器、Profiler、库和工具链都围绕 Python 3 演进,Python 2 生态越来越难获得新能力;团队每多留一年,就要自己维护更多原本可以由社区解决的基础设施。Python 3 本身在近年也经历了明显性能提升,对长期 CPU 密集和事件密集的服务端代码具有潜在收益。

更重要的是人才成本。新工程师进入一套 Python 2.7 + 旧语义 + 私有工具链的系统,需要先学习大量现代 Python 社区已经不再使用的习惯。技术债最终往往不是“旧代码跑得慢”,而是维护系统所需的人力成本不断上升,能使用的外部工具不断减少,升级任何依赖都要付额外适配成本。

EVE Frontier 已经在现代 Python 3 上运行 Carbon engine,也给主游戏迁移提供了经验。这里仍有一个边界需要说清:EVE Online 本次官方公告并没有详细说明最终如何替换 Stackless;Simon Willison 提到,EVE Frontier 曾展示使用开源 carbonengine/scheduler 离开 Stackless 的方案,这可以作为技术方向参考,但不能直接视为 EVE Online 已经确认采用的最终实现。

240 万行代码,第一步反而是把问题“机械化”

EVE 团队没有一上来就手工改代码,而是先建立可度量的迁移过程。约 2 万个 Python 文件同时用真实 Python 2.7 和 Python 3 解释器编译,编译器成为语法兼容性的“ground truth”。第一次扫描结果比预想好:95.9% 的文件已经可以被两个版本解析,在 240 万行代码中,真正阻止 Python 3 解析的行大约只有 3300 行。

这些阻塞项非常具有年代感:约 1500 个旧式 print 语句、800 个带 L 后缀的 long 整数字面量、600 个古老异常捕获写法,还有 50 处 <> 不等号。对于这种机械语法差异,最合理方法不是让高级工程师逐行判断,而是使用 Python-Future/Futurize 之类自动 fixer 批量改写,先把代码变成同时能在 Python 2.7 和 Python 3 下运行的中间状态

“双运行时兼容阶段”是整个迁移路线最值得企业借鉴的设计。它避免了 Big Bang:代码在正式切换 Python 3 之前,仍然可以持续部署到原来的 Python 2.7 生产环境。迁移被拆成大量小修改,每一批都可以测试、回滚和观察,而不是积累半年后一次性换掉整个运行时。

真正困难的是那 2 万处“都能运行,但含义变了”的代码

语法只占小头。EVE 扫描出了大约 2 万行在 Python 2 和 Python 3 下都能成功编译,却可能产生不同结果的代码。最经典的例子是除法:Python 2 中 1 / 2 得到整数 0,Python 3 中得到浮点数 0.5。

对普通脚本,这也许只是测试失败;在 EVE 里,它可能出现在伤害计算、坐标、价格、市场逻辑或资源数量中。一段代码究竟应该用整数除法 //,还是应该接受浮点结果,没有自动迁移工具能替工程师决定。机器可以找到疑点,人必须理解业务语义。

这正是大型遗留系统升级的核心规律:可自动识别的问题通常并不最危险。真正高风险的是“程序照样跑,结果悄悄变了”。字符编码、字典迭代、排序、整数边界、序列化格式、时间处理、异常行为,都可能造成这种语义漂移。编译成功率只是迁移进度的一部分,行为一致性才是最终验收标准。

EVE 团队先清除 3300 个机械阻塞行,再把人的注意力集中到约 2 万个语义差异点,是很典型的“机器做筛选,人做判断”。这种方法同样适合工业软件、银行核心系统和长期运行的科研代码。

数据兼容比代码兼容更难

官方公告有一句很重要的话:每个角色、每个技能点、每件仓库资产、每一笔 ISK 都是由 Python 2 时代的代码写下的,它们必须在 Python 3 下读出来时保持原样。这实际上把迁移问题从源码层扩展到数据语义层。

长期系统里最危险的往往是隐含序列化约定。旧程序可能把字符串、整数、对象类型甚至某些异常行为写进数据库或缓存,十几年后没人再清楚为什么当初这样编码。新运行时即使逻辑正确,只要读取老数据时有一次类型转换差异,也可能造成无法恢复的经济或资产错误。

因此 EVE 的迁移需要在真实玩家数据和接近真实负载下反复验证。团队使用 Singularity 测试服务器邀请玩家参与 playtest,再逐步把 Stage 1 修改部署到正式 Tranquility。对于游戏来说,玩家本身就是规模最大的回归测试流量;对工业软件而言,对应的做法可能是影子流量、双写双读、历史项目批量回放和数字孪生环境验证。

“用户什么都没感觉到”是最高级的迁移结果

现代软件行业喜欢用新版本和新界面证明项目成果,基础设施迁移往往恰好相反。EVE 团队把目标写得很明确:如果迁移顺利,玩家应该几乎完全察觉不到,最多感觉某些地方更流畅。

这是一种很成熟的工程价值观。大型底层迁移的收益不是发布当天多了多少功能,而是未来十年的变化速度提高。进入 Python 3 后,团队可以使用现代库、Profiler、调试器、类型工具和运行时优化;新开发者上手成本下降;安全与依赖维护重新回到社区主流;未来性能升级也不必背着 2010 年的语言约束。

企业内部很多技术改造之所以难拿预算,也因为这种收益不容易展示。数据库升级、编译器迁移、工业软件内核重构、协议替换,在业务侧看起来“什么都没变”。但它们决定系统还能不能继续活十年、还能不能吸引工程师、还能不能接入新技术。

对工业软件和长期运行系统的几个启示

EVE 的迁移路线给长期工业系统很强的参考价值。第一,先建立量化扫描,不要靠“感觉代码很老”制定计划。把语法阻塞、语义差异、数据兼容、第三方依赖分别计数,项目才可管理。第二,建立双版本中间态,让迁移代码继续在旧生产环境运行,可以显著降低 Big Bang 风险。第三,把自动化用在机械改写和疑点发现,把最稀缺的人类专家留给业务语义判断。

第四,升级语言只是表面,真正要保护的是历史行为与历史数据。工业控制、仿真、金融和大型 SaaS 系统一样拥有大量“只有老数据才能触发”的边界条件,必须用真实历史数据做回放。第五,迁移计划应该为长期迭代服务,而不是为了追版本号。Python 3 的价值在于重新接入现代生态,而不是把“2”改成“3”。

EVE Online 这次迁移最大的看点,可能要几年以后才能看出来。如果玩家几乎没注意到切换发生,而开发团队的工具、性能和迭代能力持续改善,这个项目才算真正成功。对于一套 23 年历史、240 万行 Python 的在线系统,“平静地进入下一个十年”本身就是非常高的工程成就。

参考资料

分享到