Spaze · 分布式Minecraft服务器初设想

一次创建高性能、分布式、跨平台的Minecraft服务器的尝试

开工之前,在这里简要分析一下完成一个mc服务器所需的功能

MC服务器所需实现的功能

基础功能

  • Gate

    • 响应客户端的连接
    • 聊天消息的广播
    • 命令的解析
    • 日夜交替和天气改变
  • Master

    • 管理Gate和Node之间的连接
  • Node

    • Unit
      • 每tick对各个维度中的方块进行更新(包括但不限于红石、水流、农作物生长)
      • 玩家的动作对周围玩家可见(局部广播玩家状态)
      • 动物和怪物的AI
      • 物品合成、附魔等处理
    • 玩家可以在各个维度之间转移
    • 管理每个维度中的方块和实体
  • Database

    • 将世界(所有维度的集合)储存到文件或数据库
    • 读取配置文件,设置端口、online-mode、难度、服务器简介等
  • Generater

    • 玩家进入未生成的区块附近时生成区块

拓展功能

  • 提供用于编写插件的API(是否有必要兼容Bukkit API呢?)

服务器结构

概念 中文 解释
Gate 进程。接受客户端连接,将玩家托管到Node
Master 调度器 管理多个Gate和多个Node之间的连接
Node 计算节点 是一个进程,运行着多个Unit
Unit 工作单元 在独立的线/协程上负责对一定区块进行更新
Database 存档 用于储存和交换诸如地形和实体等数据
Generater 生成器 用于生成地形

碎碎念(帮助我理清思路)

玩家加入Gate之后,Gate完成登录验证,然后请求Master安排一个Unit来处理。
Master收到请求之后根据玩家的在游戏中地理位置找到一个合适的Unit,并将UnitAddr(包括两个信息:Node地址和Unit编号)返回给Gate。
当Gate收到分配给该玩家的UnitAddr之后,发起对该Unit的连接(先连接对应的Node,再确认是进入Node中哪个Unit)。
此时Unit要加载该玩家所在位置附近的区块,并加载该玩家的数据。同时Gate会代理玩家和Unit之间的通讯。

有时,由于玩家之间距离的改变,可能需要将两个Unit的玩家合并到一个Unit处理,这就需要Master介入管理。玩家游玩时,Unit要向Master报告玩家的位置。
Unit的合并及拆分本质上是玩家在Unit之间的转移。当Master发现有Unit需要合并或拆分时。会命令Unit进行玩家的转移操作。
玩家在Unit之间的切换需要Gate、Node和Master之间通力合作。收到Master的通知后,原Unit首先将玩家的数据保存到Database,之后通知Gate连接新的Unit,然后卸载附近的区块,也把区块数据保存到数据库。
Gate收到需要切换代理目标的通知后,断开与原Unit的连接,并连接新的Unit,继续转发数据包。
新Unit不需要特殊的操作,当玩家从Gate过来之后,照常加载附近的区块就好了(可能需要等待原Unit释放) 容易看出,Unit之间的数据交换是通过Database,而不是直接通讯实现的。

在Unit交接的时候会出现EID(实体ID)不一致的问题,即原Unit告诉玩家一个实体的EID是A,而转移到新Unit之后可能发现A已被另一个实体使用了。这个问题我想到了几个解决方案:

  • 使用唯一的全局EID,每次创建实体时Unit需要从某个服务申请EID。会导致服务端运行速度变慢
  • 保持客户端和服务端的EID不一致性,服务端建立EID映射的map,会增加Unit复杂度。
  • 在切换Unit的时候旧Unit先删除玩家客户的上原有的实体,新Unit重新给玩家加载所有实体。或是旧Unit将EID顺带传递给新Unit,新Unit只修正冲突的EID。可将顺序EID生成改为随机EID生成,降低EID冲突的几率,会增加服务端和数据库的复杂度。

当玩家退出游戏时,Gate要通知Master和Unit,Unit完成最后的数据保存工作。

sequenceDiagram
    note right of Player: Player Join
    Player->>Gate: Connect
    activate Player
    opt Online-mode
        Gate->Player: Login Check
    end
    Gate->>Master: Player Join
    participant Unit
    Database->>Master: Player data
    Master->>Gate: UnitAddr
    Gate->>Unit: Player Join
    activate Unit
    Unit->>Database: Request
    Database->>Unit: Player data
    Database->>Unit: Chunk data
    loop Playing
        Gate->Unit: Proxy Playing Packet
        Database-->Unit: Chunk data
        Unit-->>Master: Report player position
    end
    note right of Player: Player Quit
    Player->>Gate: Disconnect
    deactivate Player
    Gate->>Master: Player disconnect
    Gate->>Unit: Player disconnect
    Unit->>Database: Player data
    deactivate Unit
sequenceDiagram
    participant Gate
    participant Master
    participant UnitS as 原Unit
    participant UnitD as 新Unit
    loop Playing
        Gate-->UnitS: Proxy Playing Packet
        activate UnitS
        UnitS-->>Master: Report player position
    end
    note left of Master: Player transfer
    Master->>UnitS: Quit Player
    UnitS->>Gate: Change Unit
    UnitS->>Database: Player data
    deactivate UnitS
    Gate->>UnitD: Player Join
    activate UnitD
    UnitD->>Database: Request
    Database->>UnitD: Player data
    Database->>UnitD: Chunk data
    loop Playing
        Gate-->UnitD: Proxy Playing Packet
        UnitD-->>Master: Report player position
        deactivate UnitD
    end