防复制设计
背包类插件常见的复制物品方式有三种:两个子服同时打开同一个背包、存档前一刻当机、点击太快造成前后两次操作重叠。 星月行囊针对每一种都有对应的设计,而且默认就开着,不需要设置。
一个行囊只在一个地方使用
打开行囊时,子服在数据库里拿下这个行囊的 租约(谁在用、用到什么时候),读取时用 SELECT … FOR UPDATE 锁住那一行,两个子服不可能同时拿到。
开着的子服每隔几秒续约一次;关闭行囊几秒后(或玩家下线时)立刻还回去。另一个子服在租约有效期间打开,会看到「你的行囊正在子服 xxx 使用中」。
子服当机时租约不会还:最多等 server.lease-seconds(默认 30 秒)就自动失效;那个子服重新启动时也会立刻释放自己留下的租约。
每一步都是一笔交易
行囊菜单里的每一次点击都会被拦下,由星月行囊自己算出结果,然后:
- 先拿走 要离开玩家身上的东西(例如要存进行囊的那一组),要给玩家的东西先不给;
- 把改到的行囊格子写进数据库,条件是
revision(版本号)没有变、租约还是自己的; - 数据库收下了,才把东西给玩家;数据库拒绝了,就把第 1 步拿走的东西原样还回去。
整个过程行囊处于「存档中」,任何其他点击都会被忽略,所以快速点击、连点外挂都无法让两次操作重叠。 每一步只写改到的那几格,几百格的大行囊也一样快。
答复遗失时不猜
如果数据库在提交的那一瞬间断线,插件不知道这一步到底存进去了没有。星月行囊 不会猜:每一步都带一个随机编号, 断线后用这个编号去问数据库,确认之前行囊保持 锁住、东西既不给也不退。玩家这时下线,确认结果后物品会放回行囊(或下次进服发还)。
当机后自动比对
最危险的情况:玩家把钻石存进行囊(数据库已收下),服务器在存玩家资料之前当机。重开后玩家资料回到较早的存档 —— 钻石还在身上,行囊里也有一份。
星月行囊在每一步存档时,同时在数据库记下「玩家这边多了 / 少了什么」并给一个序号,玩家资料里也记下「我已经包含到第几步」。 玩家下次进服时比对两边:数据库里有、玩家资料却没包含的那几步,而且是 由当机的那次运行做的,就自动补做 ——
- 当时存进行囊的:从玩家身上收回(身上没有了就从行囊收回);
- 当时从行囊取出的:再给玩家一次(放不下就放回行囊)。
玩家会收到一句提示,后台也会写一行记录。正常关服、正常下线的情况不会补做任何东西。详细的判断方式与同步插件的设置见 当机保护。
实测
发布前的自动化测试(Paper 1.21.11 与 26.3):
| 测试 | 结果 |
|---|---|
| 存入后直接结束服务器进程(玩家资料没存),重开进服 | 身上没有多出来,行囊里有一份 |
| 取出后直接结束服务器进程,重开进服 | 物品补回身上,行囊是空的,只有一份 |