MCDR 镜像服增量同步插件:只传真正变了的文件,.mca 再细到 chunk。
- 文件级增量:跨轮索引记下每个文件的
size / mtime / 哈希,没变的文件不读、不传。 - chunk 级增量:region 文件只看脏 chunk(时间戳过滤 + 哈希确认),未变的 chunk 直接复用。
- 镜像纠偏:镜像上被玩家改过的文件,下一轮会被发现并恢复。
- 原子写入:先写临时文件再替换,镜像上不会出现半个文件;写入失败下一轮自动重试。
- 原地写 region:
.mca默认 append-and-commit + 8KB 头 journal,一个 chunk 变了就只写它;碎片攒多了自动整理。 - 外置 chunk 也增量:
.mcc外置容器的 region 只搬迁占位记录,内容变化按普通文件增量同步;不再被任何 region 引用的.mcc会被清理(sync.clean_mcc_orphans)。 - 跨机同步:主服用
exporter模式只读提供数据(HTTP + Bearer token,可上 TLS),镜像侧不用挂载主服目录。 - 自动停服:支持
rcon、MCSManager(mcsm)与自定义命令(external)停/启镜像;停不下来就拒绝写入。 - 多镜像目标:一份源、多个镜像目录,共享一个索引(按目标分区),每个目标可以有自己的停服方式。
- 校验与历史:
!!mu verify核对源/镜像/索引三方是否一致(--deep比哈希),!!mu history查看每轮结果。 - 定时同步:间隔 60 秒 ~ 7 天,可随时开关。
需要 MCDR ≥ 2.12.0、Python ≥ 3.9。
- 从 Releases 下载
MirrorUP-v0.6.3.mcdr放进 MCDR 的plugins/;或者把仓库里的MirrorUP/目录放进去(目录名建议MirrorUP)。 - 安装依赖:
pip install xxhash。MCDR 只校验依赖、不会自动安装,缺依赖会在日志里报RequirementCheckFailure。 - 重启 MCDR,首次加载会生成
config/mirror_up/config.json。
- 编辑配置里的
source.root(主服目录)和target.root(镜像服目录),相对路径以 MCDR 根目录 为基准。 - 停掉镜像服,在主服执行
!!mu diff查看本轮计划。 - 确认无误后执行
!!mu sync。 - 看到"目标已更新完毕"后手动启动镜像服。
- 需要自动同步:
!!mu schedule set 30m,再!!mu schedule on。
首次启动生成 config/mirror_up/config.json,改完执行 !!mu reload 生效。
| 键 | 默认 | 说明 |
|---|---|---|
source.root |
./server |
主服目录(含 world/、mods/) |
source.transport |
local |
local 直接读盘;agent 从主服的 exporter 拉取 |
source.agent.url |
http://127.0.0.1:25580 |
exporter 地址(transport=agent 时用) |
source.agent.token |
空 | 与 exporter 的 agent_server.token 一致 |
source.agent.tls_pin_sha256 |
空 | 可选,固定自签证书指纹 |
role |
driver |
driver 执行同步;exporter 只对外提供数据 |
target.root |
./Mirror/server |
镜像服目录(单目标时的简写;等价于 targets[0]) |
targets |
一个 default |
多镜像目标:[{"name","root","enabled","mirror_control"?}],每个目标可覆盖停服配置 |
sets |
world、mods |
同步哪些目录;config/、server.properties 默认不同步 |
exclude |
session.lock、*.tmp、logs/** 等 |
glob 排除,命中目录整棵跳过 |
sync.concurrency |
4 |
并行拷贝数 |
sync.verify_target |
true |
每轮检查镜像是否被改动 |
sync.region_delta |
true |
.mca 走 chunk 级增量,false 退回整文件 |
sync.region_write_mode |
inplace |
inplace 原地追加,rebuild 重建后替换 |
sync.journal |
true |
inplace 的 region 头快照;false 会降级为 rebuild |
sync.clean_mcc_orphans |
true |
删除不再被任何 region 引用的 .mcc 外置 chunk |
log.history_keep |
20 |
!!mu history 保留多少轮 |
source_flush.enabled |
true |
扫描前 save-off + save-all flush,扫描后 save-on;transport=agent 或目标用 adapter: "self" 时自动关闭 |
mirror_control.adapter |
manual |
manual 只检查;rcon / external / mcsm 自动停启;self 控制本 MCDR 自己的服(就地同步) |
mirror_control.rcon |
127.0.0.1:25575 |
RCON 停服参数 |
mirror_control.external |
空 | stop_cmd / start_cmd / probe_cmd 自定义命令 |
mirror_control.mcsm |
空 | MCSManager 面板:base_url、apikey(v9)或 username/password(v10)、uuid、remote_uuid |
mirror_control.start_after_sync |
true |
同步后自动启动镜像并等端口就绪 |
agent_server |
127.0.0.1:25580 |
exporter 的监听地址、token 与 TLS 证书 |
schedule.interval_seconds |
3600 |
自动同步间隔,60 ~ 604800 秒 |
想把插件装到每一台服上、玩家站在哪台服敲 !!mu sync 就同步哪台服,用 self 适配器:
- 主服:
role: "exporter",只读地对外提供世界(agent_server+ token)。 - 镜像服:
role: "driver",source.transport: "agent"指向主服,targets只留一个指向自己:
"targets": [
{ "name": "self", "root": "./server",
"mirror_control": { "adapter": "self", "notify_players_sec": 10 } }
]adapter: "self" 的意思就是“目标是我这个 MCDR 管的服务器”。有变更时插件会:
- 在服里广播停服提示(
notify_players_sec秒,0 表示不提示); - 调
server.stop()停掉本服——MCDR 不会退出; - 等
session.lock释放,再写入差异; server.start()把本服拉起来,并等它重新可连。
停的是服务端而不是 MCDR,所以同步线程不会被一起停掉。没变更时这四步一步都不做,只是 stat 一遍。
主服与镜像不在同一台机器、或者不想让镜像侧挂载主服目录时,用 exporter + agent:
- 主服(MCDR 里装同一个插件):
role改成"exporter";agent_server.host改成0.0.0.0(或内网 IP),port默认25580;agent_server.token填一个至少 16 字符的随机串;- 执行
!!mu reload(或重启 MCDR),日志出现「exporter 已监听」。
- 镜像侧(driver):
source.transport改成"agent";source.agent.url填http://<主服IP>:25580;source.agent.token填同一个 token。
- 公网部署请开 TLS(
agent_server.tls.enabled+ 证书),并在驱动端填tls_pin_sha256固定指纹。
exporter 只读:它不写任何东西,也不执行同步命令。!!mu status 在 exporter 上显示监听地址与对外提供的 set。
跨机时 source_flush 会被自动关闭(driver 不在源服那台机器上,save-off 只会发给本机镜像服);目标用 adapter: "self" 时同理(本机正是要写入的那台)。请保证 exporter 侧的世界已经落盘:要么主服定期保存,要么同步前在主服执行一次 save-all flush。
| 命令 | 权限 | 说明 |
|---|---|---|
!!mu sync |
2 | 同步一次 |
!!mu sync --dry-run |
2 | 只出计划,不动镜像 |
!!mu sync --full |
2 | 忽略索引,全量重算哈希 |
!!mu sync --force |
2 | 跳过安全检查(空源保护、版本门禁) |
!!mu diff |
2 | 同 --dry-run |
!!mu status |
0 | 索引规模、各目标上次同步、待重试项 |
!!mu verify [--deep] |
2 | 校验源/镜像/索引一致性;--deep 逐个比哈希(会读全部内容) |
!!mu history [N] |
0 | 最近 N 轮同步结果(默认 10,最多 50) |
!!mu cancel |
2 | 取消进行中的同步 |
!!mu schedule on|off |
3 | 开关自动同步 |
!!mu schedule set 30m |
3 | 设置间隔(s/m/h/d) |
!!mu index rebuild |
3 | 丢弃索引,下一轮全量 |
!!mu reload |
3 | 重读配置 |
- 镜像必须完全停止才能同步。默认
manual适配器用session.lock判断;rcon/external可以自动停服;self(就地同步)会自己停本服再拉起——但停不下来时会中止同步,绝不在镜像还活着时写盘。 - 不做备份。镜像上被覆盖的区块和模组没有历史版本。
- 写入量不等于读取量。
.mca默认inplace,改动一个 chunk 时目标端只写这个 chunk 加 16KB(新头 + journal);rebuild模式才会重写整个 region。 - 不覆盖配置文件。
config/、server.properties不在默认同步范围内,手动加进去会覆盖镜像的配置。 - 空源保护。源目录扫出 0 个文件但索引里有记录(路径写错、磁盘没挂载)时会中止,不会清空镜像;确认要清空请加
--force。 - 版本门禁。源与镜像
level.dat的 MC 版本不一致时拒绝写入。 - 跨机要保证 token 一致。driver 与 exporter 的 token 不同会直接连不上;exporter 是只读的,不提供任何写入接口。
- "扫描到 0 个文件,但索引中有 N 条记录":源路径写错或磁盘未挂载。修好后重跑;确实要清空镜像再加
--force。 - "版本不一致":镜像世界来自旧版本。先用同一版本启动一次镜像,或确认要覆盖后
--force。 - "待重试 N 项":文件在拷贝途中一直被改写或被占用(常见于正在写盘的 region),下一轮会自动重试。
- 换了世界或改了源根:
world_id对不上会拒绝旧索引,执行!!mu index rebuild。 - "源端不可用":跨机时连不上 exporter、token 不对,或主服没开
role=exporter。检查地址、端口、token 和主服日志里的「exporter 已监听」。 - "自动停服失败":
rcon连不上或external脚本返回非零。镜像没停下来就不会写盘,修好适配器再同步。 self报"本服没有退出":本服不响应 MCDR 发出的stop(被包装脚本接管、卡在保存,或根本不是能被 MCDR 停止的服务端)。手动关掉本服后再执行!!mu sync;插件在确认退出前不会写入任何文件。
当前 0.6.3:文件级 + chunk 级增量、原地写 region(含头 journal 与碎片整理)、镜像纠偏、定时同步、跨机 agent 传输(含 TLS)、.mcc 外置 chunk 增量与孤儿清理、多镜像目标(共享索引)、校验与同步历史、MCSM/rcon/external 自动停服、就地同步(adapter: "self":命令在哪台服敲就停/写/启哪台服)、Python API。
0.6.2 修复:!!MCDR plugin install mirror_up 时,依赖列表里的 xxhash>=3.0 会被 MCDR 直接拼成 PyPI 链接,> 在 URL 里非法,服务端解析聊天组件时报 Invalid chat component: Illegal character in path。插件库的依赖列表读的是仓库里的 requirements.txt(不是 mcdreforged.plugin.json 的 requirements 字段),所以 0.6.1 只改后者没有生效;0.6.2 把 requirements.txt 也改成不带版本约束的 xxhash。
0.6.3 修复:以 role: "exporter" 加载插件时,agent 绑定端口会走 HTTPServer.server_bind(),其中 socket.getfqdn() 的反向 DNS 查询在 macOS 上可能阻塞十几秒,触发 MCDR 的 TaskExecutor 线程已经 10 秒没有响应 警告。现在改为直接绑定,不再做反向解析。
GPL-3.0 © MiYui34。分发修改版需要同样以 GPL-3.0 开源,并保留版权声明。