Shinoyuki-BetterAutoSave
Forge 1.20.1 服务器异步保存世界——区块、实体和存档数据的序列化移至主线程之外。告别自动保存时的卡顿尖峰。
介绍
BetterAutoSave
简体中文 | 英文

让服务器自动保存不再卡顿~
此模组的部分代码由 Claude Opus 4.8 / Claude Fable 5 生成。如果遇到任何问题,请提交 issue
下载:Modrinth · GitHub Releases
项目状态:正在积极开发,处于快速迭代的 1.0 前阶段,更新频繁(包括与 BetterBackup 协调发布的版本)。核心的异步保存功能已在生产环境中经过长期验证,默认设置下安全可靠;异步区块加载是较新的可选功能,默认关闭。请关注 Releases / Modrinth 获取更新
此模组解决什么问题
原版 Minecraft 服务器每 5 分钟自动保存一次。保存期间,主线程必须序列化所有已修改的区块并将其写入磁盘——在此期间整个服务器都会卡住。在空服务器上你可能感觉不到,但在有许多模组和玩家的服务器上,这种停顿通常会持续 200 毫秒到几秒,所有玩家都会同时卡顿。
除了定期自动保存,还有其他一些情况也会造成类似的卡顿:玩家传送或大量区块卸载时(区块离开内存前必须先保存)、保存期间实体密集的区域(大型农场 / 刷怪塔),以及村庄和袭击等全局数据(原版 SavedData)需要写入单个大文件时。
BAS 让主线程只做必须在原地完成的一件事——为待保存的数据创建独立快照。序列化和磁盘 I/O 则交由后台线程处理。由于后台线程操作的是副本,因此不会干扰主线程;快照完成后,主线程便能立即继续运行。区块、实体和已保存数据都会经过这套流程。当服务器负载吃紧时,BAS 会自动降低处理速度;但随着下一轮自动保存周期临近,它会强制全速处理,避免积压不断增加。
要求与安装
两个加载器版本均由同一份源码维护。两者都仅需安装在服务端;客户端无需安装。
- Forge 1.20.1:Forge 47.3.22 或更高版本(47.3 / 47.4 系列均可),Java 17 或更高版本
- NeoForge 1.21.1:NeoForge 21.1 系列,Java 21 或更高版本
从 Modrinth 或 Releases 下载与你的加载器匹配的 jar,并放入服务器的 mods/ 文件夹:
- Forge 请使用
-alljar(文件名类似shinoyuki_betterautosave-<version>-all.jar;其中包含 MixinExtras 和其他依赖项)。普通的精简版 jar 会因缺少依赖项而在加载时崩溃。 - NeoForge 请使用
shinoyuki_betterautosave-neoforge-<version>.jar。
首次启动后,配置文件会生成在 config/Shinoyuki-Optimize/shinoyuki_betterautosave/common.toml。默认设置开箱即用;大多数服务器无需更改任何设置。
会丢失地图数据吗?
不会。BAS 的设计基于一个原则:安全性绝不能低于原版。
- 关闭服务器时,会等待所有待处理的保存操作写入磁盘后才允许服务器退出,最后一次保存则通过原版同步路径完成。
- BAS 绝不会“把保存操作留到以后再处理”——区块一到应保存的时机,就会立即进入后台处理。不会出现“几分钟都没保存,崩溃后全部丢失”的情况(一些类似模组存在这个问题)。
- 如果后台写入失败,会自动重试,绝不会假装成功:重试次数耗尽后,区块和已保存数据会回退到原版同步写入;实体没有坐标恢复队列,而且已被原版从内存中移除,因此重试耗尽时会记录一条 ERROR 日志,并丢弃该区块最新一批实体增量数据——这与原版在此情况下的结果相同(原版实体保存同样没有重试和同步回退;BAS 其实会多重试几次)。
除此之外,Forge 版本还修复了三个原版中会静默丢失数据的路径(玩家数据读取失败、进度和统计数据写入时遭到截断,以及 level.dat 只有一个备份)。这些修复默认启用——请参阅配置参考。
常用配置
| 键 | 默认值 | 说明 |
|—|—|—|
| general.enabled | true | 总开关;关闭后行为与原版相同,就像未安装一样 |
| throttle.chunksPerTickBase | 4 | 每个游戏刻由主线程快照的区块数量上限 |
| throttle.adaptiveEnabled | true | 服务器负载吃紧时自动减速;建议保持开启 |
| workers.chunkWorkerThreads | 2 | 处理区块的后台线程数 |
| workers.entityWorkerThreads | 2 | 处理实体的后台线程数 |
| workers.savedDataWorkerThreads | 1 | 处理已保存数据的后台线程数;如果有大量写入原版 SavedData 的模组,可调至 2 |
| compat.eventCompatMode | PARTIAL | 事件兼容级别;除非确定有需要,否则请保持默认值 |
Forge 版本有 43 项设置,NeoForge 版本有 26 项。每项设置、各默认值的原因以及推荐的部署步骤,均在 CONFIGURATION.en.md 中说明,涵盖玩家数据保护、level.dat 完整性、异步区块加载、Prometheus 监控以及与备份工具的配合使用。
两个版本的功能对照表
两个版本并不完全相同。有些功能缺失是因为 NeoForge 已在上游修复了相应问题,因此不需要对应选项;另一些功能则是 Forge 优先实现,目前尚未对称移植。
| 功能 | Forge 1.20.1 | NeoForge 1.21.1 |
|—|—|—|
| 异步保存(区块 / 实体 / 已保存数据) | 是 | 是 |
| 异步区块加载([load] 部分) | 是 | 否(该部分不存在;NeoForge 版本完全没有加载端的 mixin) |
| level.dat 注册表缓存 | 是 | 不需要(上游已将该表移出 level.dat) |
| level.dat 启动检查 | 是 | 不需要(1.21 已会在读取失败时回退并隔离文件) |
| level.dat 启动备份 / 写入后验证 | 是 | 否(1.21 也没有对应功能;等待移植) |
| 玩家数据读取回退 | 是 | 不需要(已在 1.21 上游修复) |
| 进度 / 统计数据原子写入 | 是 | 否(1.21 写入时仍会截断;等待移植) |
| 跳过未更改的进度数据、错峰保存玩家数据 | 是 | 否(仅影响性能;等待移植) |
| 同步区块加载 / 游戏刻间隔诊断 | 是 | 是 |
游戏内命令
需要 OP(权限等级 2)。
| 命令 | 效果 |
|—|—|
| /betterautosave status | 单行显示当前状态 |
| /betterautosave metrics | 单行显示指标摘要 |
| /betterautosave debug | 完整诊断:队列深度、各阶段耗时、计数器 |
| /betterautosave flush | 将所有待处理的保存全部写入磁盘。命令会立即返回,并在后台轮询直到完成或超时(safety.shutdownTimeoutSeconds) |
| /betterautosave drain-unload | 等待所有待处理的区块落地;同样在后台轮询并立即返回 |
| /betterautosave hottest-chunks [count] | 列出保存最慢的区块(默认 10,接受 1-50)以定位热点 |
| /betterautosave force-async | 对当前维度的所有区块强制执行一次后台保存(诊断用) |
| /betterautosave diagnose [count] | 列出主线程同步加载区块的来源以及 tick 间隔统计(默认 10,接受 1-50) |
| /betterautosave diagnose reset | 清空上述两张表(累计计数器保留;见下文) |
高开销区块通常位于方块实体密集的地方——大型自动化农场、模组商店面板、复杂的红石。
普通监控看不到的卡顿
有两类卡顿永远不会出现在普通仪表盘上。自 0.20.0 起,BAS 会自行记录这两类情况——仅观察,从不干预,默认开启。
主线程同步加载区块。 当一个区块不在内存中,而主线程上的某个东西直接请求它时,主线程会原地等待,直到磁盘读取——以及必要时的地形生成——完成,在此期间整个服务器都会冻结。一次 74 名玩家的生产压力测试测得单次等待长达 5.2 秒。BAS 会记录每一次超过 50 毫秒的等待(阈值可配置):阻塞了多久、区块坐标、维度,以及调用栈上第一个非原版类。
tick 之间的长暂停。 MSPT 只衡量游戏 tick 内部花费的时间;两个 tick 之间的等待不会被计入。同一次压力测试中包含了 17.1 秒和 14.8 秒的暂停,这些在 TPS 图表和仪表盘上都不可见——一切看起来都很健康,而玩家却在超时。BAS 在每个 tick 开始时取一个时间戳、结束时取一个时间戳,并记录任何超过 1 秒的间隔(可配置);一个可选的深度模式(默认关闭)会将间隔归因到导致它的具体任务。
开销:同步加载探针位于原版四槽区块缓存未命中之后所走的分支上,因此缓存命中时根本不会到达;未命中时它只增加两次纳秒级读取,并且只有在等待超过阈值时才捕获堆栈——正常运行时从不捕获。tick 间隔检查每个 tick 只需两次纳秒级读取。
关于归因:检测报告的是阻塞发生在哪条调用链上,而不是谁有 bug。此功能报告的卡顿通常反映的是另一个模组的调用模式——在许多情况下同步获取区块是完全合理的,其开销只是随服务器规模、视距和磁盘速度而增长——本身并不能作为该模组存在缺陷的证据。请将其视为调查的起点,而非结论。
使用 /betterautosave diagnose 查看,或抓取四个新的 Prometheus 指标(bas_sync_load_stalls_total、bas_tick_gap_max_seconds 以及另外两个)。八项设置和命令输出的完整示例见 CONFIGURATION.en.md 第 8 节。
设计边界
BAS 已经在其能力范围内做到极限:在它所坚持的严苛兼容性约束下,已经没有值得继续进行的异步区块优化空间。即使还有优化余地,实施这些优化也会破坏兼容性,导致数据安全问题和模组冲突。
在一次有 74 名玩家在线、持续采样 550 秒的生产环境压力测试中,NbtIo 写入仅占主线程时间的 0.03%,ChunkSerializer 序列化占 0.67%,而 BAS 自身的全部操作合计占 1.42%。这并不等于“已经没有优化空间”——同一份采样数据仍显示约有 0.3 个百分点可以优化(copySections 无条件调用两次 PalettedContainer.copy,即使区块分段为空也一样,约占 0.1 个百分点;批量处理 POI 重放,约占 0.1 到 0.2 个百分点)。但这些优化已经淹没在噪声中,而且在不改变兼容性前提的情况下,可实现的优化加起来不到半个百分点。
这也不等于“BAS 让区块加载不再是瓶颈”。事实恰恰相反:瓶颈在区块系统中 BAS 无法触及的那一半。在同一份采样数据中,DistanceManager 距离场传播占用了区块系统主线程预算的 82%,而实际推动加载进度的任务仅占 18%。
继续采取每一步会破坏什么,都有具体依据。将 ForgeCaps 移至工作线程,属于与 issue #8 数据丢失相同的问题:添加了区块能力的模组会静默丢失数据。将 ChunkDataEvent.Load 移至工作线程,会让所有监听器都在主线程之外运行;它不会抛出任何异常,只会随着时间推移逐渐出错。将 POI / SectionStorage 移至工作线程,会遇到 SectionStorage 非线程安全的问题,最终导致村民 AI 数据静默损坏。接管 DistanceManager,意味着要处理一个非线程安全的状态机,还会与 C2ME 正面冲突。要求两端都安装,或强制所有操作异步,则会失去单端安装、按需启用和即时回滚的能力——而这正是 BAS 最大的差异化优势。
性能已达到兼容性所允许的极限,因此本次版本转变了方向:BAS 不再追逐最后那零点几个百分点的性能提升,而是开始告诉服务器所有者,卡顿究竟来自哪里。完整说明见 ROADMAP.md(中文)。
模组冲突
- 不能同时安装(都会接管同一套存档路径):Fast Async World Save(
fastasyncworldsave,BAS 检测到该模组时会记录一条 WARN)、Smooth Chunk Save,以及其他异步 / 每刻存档模组。 - C2ME / C2ME-Forge:按功能区分。存档功能只能二选一;在 BAS 默认配置下,并行加载可以互补,但启用 BAS 异步加载后只能二选一;世界生成始终可以互补。
- 兼容:Starlight、Radium / Canary、Modernfix、FerriteCore 等类似模组。
相关说明、完整的注入点列表,以及各兼容级别的数据完整性约定,见 COMPATIBILITY.en.md。
出问题时快速恢复
以下三种选项都能保持世界数据完整:
- 临时禁用:将
general.enabled设为false,然后重启或执行/reload。模组仍会安装,但会跳过所有逻辑——相当于纯原版。 - 彻底卸载:将 jar 文件移出
mods/,然后重启。世界数据仍受原版保存机制保护;卸载不会造成任何损失。 - 调整设置,而非移除:如果怀疑是性能设置导致问题,可调整
chunksPerTickBase(1-64),或先将eventCompatMode切换为FULL——无需卸载。
构建 / 开发
./gradlew build # compile + run all tests (common / forge / neoforge)
./gradlew :forge:runServer # start a 1.20.1 Forge dev server
./gradlew :neoforge:runServer # start a 1.21.1 NeoForge dev server
模块布局:common/(不依赖 Minecraft 的纯算法核心,源码会合并到两个加载器中——核心存档状态机只在此处实现一次,绝不分叉)+ forge/(1.20.1)+ neoforge/(1.21.1)。
版本路线图和功能概览见 ROADMAP.md(中文);双版本移植详情见 archive/MULTIVERSION_PLAN.md(中文)。
许可证
AGPL-3.0-or-later,并附带两项第 7 条附加权限(LICENSE-EXCEPTION.md):整合包分发例外——可在整合包和服务器包中原样附带未修改的官方发布 jar,无需履行其他义务,但须保留项目名称和仓库链接;以及 Minecraft 链接例外,明确允许与 Minecraft 本身及采用 LGPL 许可的模组加载器组合使用。本模组的修改版本仍适用完整的 AGPL,包括其第 13 条网络服务条款。
常见问题
Shinoyuki-BetterAutoSave 支持哪些 Minecraft 版本?
Shinoyuki-BetterAutoSave 支持 Minecraft Java 版 1.20.1–1.21.1,共 2 个版本,可用的加载器:Forge / NeoForge。下载时注意选择和你游戏版本一致的文件。
Shinoyuki-BetterAutoSave 怎么安装?
先用 PCL2 或 HMCL 启动器安装对应版本的 Forge / NeoForge,再把下载的 .jar 文件放进游戏目录的 mods 文件夹,重新启动游戏即可。开启了版本隔离的,mods 文件夹在 versions/版本名 下面。运行环境:客户端和服务端都要装。
评论
登录后评论和评分