幻兽帕鲁服务器重启导致坏档与修复

发表于 更新于

前言

最近我在设计开发一款 Palworld 的服务端管理工具,在中途我碰巧遇到并修复了一个恶劣的坏档问题。具体表现为重启后造成坏档,主要是玩家的存档损坏,进入服务器需要重新创建人物。如果你遇到的不是此现象,本文可能帮不到你。

重启的必要性

凡是自建幻兽帕鲁服务器的人,恐怕目前是离不开频繁重启的。

因为此游戏的服务端程序疑似存在严重的内存泄漏,或是非常不合理的利用内存(例如从不释放不再加载的区域)。要判断是内存泄漏还是不合理使用内存的简单方法可以从“内存是无上限的持续上涨”,还是“上涨到一定程度就稳定”来分析。前者就是内存泄漏无疑。因为我没有超大内存的服务器,我无法验证这一点。

考虑到此游戏的开发水平,我认为这两者极有可能都存在。即同时包含内存泄漏的 BUG 和毫无优化的内存使用,从而导致了夸张的内存问题。

除非此游戏彻底解决内存泄漏并优化内存使用,否则靠「重启」来解决问题这一原始粗暴的方式将长期存在。

自动重启设计

我的工具会不断的收集系统的内存占用率,若占用达到设置的上限,就会触发重启。重启是通过 RCON 协议连接游戏控制台,发送 DoExitShutdown 命令完成的。为了避免丢失进度,在发送这两个命令前,还会发送 Save 主动触发存档保存。

重启导致坏档

在我完成自动重启这一功能后,我迫不及待的上游戏测试效果。为了更容易更频繁的测试,我将内存使用率设置为 25% 时重启。在一台 16GB 的服务器上骑着云海鹿跑跑图,很快就会自动重启了。

第一次重启后进入游戏,就发生了玩家存档丢失。我进入自己的服务器发现要新建人物。我意识到存档肯定是某方面出了的问题,但我还不确定是否跟重启有关。因为一直以来都有玩家说自己的存档无故损坏,我有所预料,所以我的工具在每次重启前都会打包备份整个世界存档。于是我恢复了存档,再次测试重启功能,结果玩家存档再次丢失。

我有点懵了,重启居然导致坏档?还是主动发送 Save 命令的前提下?我重新恢复存档,进游戏后手动发送 /Save/DoExit 命令,存档再次损坏。

我初步意识到,是重启前使用的命令导致了存档的损坏。

进一步测试

为了验证我的猜想,我进行了近 20 次的测试(触发我的工具来重启服务端)。每重启一次,我都会确定人物是否要重建,如果要我就恢复存档。结果存档损坏率达到了 100%。是的,我的存档只要使用 SaveDoExit/Shutdown 命令就会损坏。

为什么我要特意说明是触发工具的重启功能导致 100% 的坏档呢?因为我又反复做了手动测试。在 SaveDoExit/Shutdown 命令发送的间隔达到 3 秒以上时,有机率不会坏档。所以坏档和这两种命令同时发送有必然的关系。因为程序的执行速度太快,它造成了 100% 的坏档。

你自以为通过官方的关闭命令(DoExitShutdown)并在之前发送保存命令(Save)一定是安全的,实际上它们具有极大的风险。它们是坏档的罪魁祸首。

反而杀死服务端进程后再启动,或直接重启服务器,才是最安全的。太过讽刺。

不会坏档的情况

我的玩家存档大小为 8.5KB 左右,当我使用这个存档时触发上述条件 100% 会坏档。但是我新建一个人物,玩家存档为 2.5KB 左右,通过这个小存档反复触发上述条件,则变成了有一定概率坏档,不再是 100%。

所以我暂且认为,玩家存档越大,上述条件造成的坏档风险越高。

找出损坏点

为了找到问题根源,首先得放弃自己的存档,因为真实存档“不够纯净”。我的过程如下:

  1. 新建一个人物,在确保数据被写入到硬盘后,备份玩家存档文件(命名为 palyer-good.sav)。
  2. 故意触发坏档条件,使存档损坏。如果没有损坏就多试几次,直到造成坏档。
  3. 确保存档已损坏,即进入服务器后要新建人物。但你不能新建,因为会覆盖掉坏档。
  4. 将损坏的玩家存档备份出来(命名为 palyer-bad.sav)。

如果你没有相关经验,本文暂时只能简单的告诉你玩家存档在 steamapps/common/PalServer/Pal/Saved/SaveGames/0/<世界存档>/Palyers/ 目录下,以 .sav 后缀的文件就是。

得到了两种纯净的存档文件,就可以准备比对差异了。

使用 palworld-save-tools 将两个备份出来的 .sav 文件转换为 JSON 文件。JSON 是一种文本形式的人类可读的结构化数据格式,我们比对两个存档 JSON 文件的差异,就能找出问题所在。

此处我直接公布结果:在两个基本崭新的存档 JSON 内容的 295 行,save_game_class_name 字段的值有所不同。我估计多数人都能一眼就看出问题就在于此。

坏档的 save_game_class_name:

None.PalWorldPlayerSaveGame

好档的 save_game_class_name:

/Script/Pal.PalWorldPlayerSaveGame

疑似是存档在保存时,没有获取到某个资源(或模块)的引用,所以是 None。但服务端程序未处理这种情况,并将 None 作为一部分继续拼凑字符串。结果后续载入存档时,又发现此字段的值是无效的,服务端程序再次上演「简单且暴力」的戏码直接弃用存档。

这很符合低水平开发者或人手不足仓促完成功能的特征,此游戏在其它很多方面也有印证。

修复坏档

将坏档的 save_game_class_name 字段的值修改为 /Script/Pal.PalWorldPlayerSaveGame 就可修复。当然,修复一个刻意制造的崭新坏档没有意义,你应该直接操作你的真实玩家存档。完成后,复制回存档目录,重进游戏即可发现一切正常。

如果你对存档的修改也毫无经验,参考 palworld-save-tools 的项目的说明内容,1. 安装方法 2. 执行格式转换命令。学会这两步足矣。

如果你的玩家存档已经被新建人物覆盖掉了,那就无法恢复了。切记,在新建人物前甚至是新建后的数秒内,都有机会保存坏的旧档,但一旦耽误了,就无法挽回了(被覆盖)。

重启的姿势

经过了这一遭,我不再信任所谓的保存命令和退出命令。考虑到此游戏开发人员的水平,也许我本就不应该抱有太大信任:)

现在我的工具完全通过管理进程的销毁和创建来实现重启,而不是使用关闭命令。直接操作进程的重启,反而没有造成过一次的坏档。缺点可能就是会丢失重启前数秒到几十秒的进度,但我的工具在重启前会不断的广播倒计时和通知,玩家们意识到将要重启,便会放下重要的事情等待重启完成。所以我想这不算问题。

结语

我的工具还未开放,该工具主要是由自主运作的 agent 程序和集中式远程管理的服务端组成。agent 程序我会完全开源,但目前还未到时候。此文也会作为功能设计说明的一部分被引用。

作者头像 一点点入门知识 打赏作者
本文由作者按照 CC BY 4.0 进行授权
分享:

相关文章