约 32 分钟技术学习游戏引擎

Games104学习笔记(25)

GAMES104:网络游戏的进阶架构(下)

课程:GAMES104《现代游戏引擎:从入门到实践》
第 19 讲(下):网络游戏的进阶架构
本地视频时长:约 1 小时 24 分钟

一、本讲概览

上一部分讨论的是一局实时游戏内部的问题:如何平滑复制角色移动,以及如何在延迟存在时完成相对公平的命中判定。本部分把视角提升到大型多人在线游戏(MMO)的整体后台。

MMO 不只是“同时在线人数更多”。它通常还意味着:

  • 世界长期存在,玩家离线后数据仍需保存。
  • 登录、聊天、邮件、好友、公会、交易和排行榜等服务长期运行。
  • 玩家和数据规模超过单台服务器的承载能力。
  • 服务会扩容、缩容、更新、故障和迁移。
  • 服务器必须限制每个客户端接收的数据量。
  • 客户端运行在不可信设备上,需要系统化反作弊。
  • 大世界需要跨机器分区,同时尽量保持移动和交互连续。

因此,本讲的主线可以概括为:

  1. 把 MMO 后台拆成边缘接入、玩法、社交和数据服务。
  2. 用分布式系统技术管理服务实例与数据分片。
  3. 通过压缩、更新策略和 AOI 控制网络带宽。
  4. 从程序、协议、行为和数据多个层面治理作弊。
  5. 通过分区、边界复制与多实例组合扩展游戏世界。

二、学习目标

  • 理解 MMO 与普通房间制多人游戏在生命周期和系统规模上的差异。
  • 掌握登录服务、网关、玩法服务、社交服务和数据层的基本职责。
  • 理解关系型数据库与内存键值数据库在游戏后台中的不同用途。
  • 认识分布式系统中的互斥、幂等、部分故障、一致性和事务问题。
  • 掌握一致性哈希、虚拟节点、服务发现和健康检查的基本思想。
  • 理解同步数据的量化、差量、频率和优先级优化。
  • 掌握 AOI 的目的,以及空间网格和正交链表两类实现思路。
  • 建立“客户端不可信、服务器做最终裁决”的反作弊边界。
  • 理解程序加固、通信加密、服务端验证、行为检测各自解决的问题。
  • 理解世界分区、无缝边界、活动实体与影子实体如何协同。

三、MMO 的核心特征

MMO 是 Massively Multiplayer Online Game,即大型多人在线游戏。它并不局限于传统 MMORPG;只要一个游戏需要长期在线服务、海量账号数据和大规模玩家交互,就会遇到类似的后台问题。

1. 持久世界

房间制游戏的一场对局结束后,战斗进程通常可以销毁;MMO 的角色、物品、货币、任务、公会和市场却需要长期保存。

持久化意味着服务端要处理:

  • 玩家登录前后的状态恢复。
  • 多个服务同时访问同一份玩家数据。
  • 更新失败后的重试与回滚。
  • 版本升级时的数据兼容和迁移。
  • 物品与货币操作的原子性。

2. 复杂社会关系

玩家不只与战斗场景交互,还会形成好友、公会、队伍、邮件、聊天、排行榜和交易关系。它们的生命周期和扩展方式并不相同,不适合全部塞进一个游戏进程。

3. 大规模并发

单个场景可能只容纳有限玩家,但整个产品可能同时运行大量场景、副本和后台服务。系统的可扩展性因此来自“多个可组合服务和实例”,而不是追求一台无限强的服务器。

4. 经济系统

持久世界中的货币与物品会形成经济循环:

  • Source:任务、掉落、奖励等资源来源。
  • Sink:维修、手续费、消耗品等资源回收口。

若资源只产生不回收,会导致通货膨胀;若回收过强,则会压制玩家成长。交易系统不仅是功能模块,也关系到整个虚拟经济的稳定性和安全性。

四、MMO 后台的分层思路

大型后台通常不会由一个巨型进程包办全部职责,而是按变化频率、数据归属、负载模式和故障边界拆分。

层次 典型组件 主要职责
接入层 登录服务、网关 身份校验、连接维护、协议入口、内外网隔离
玩法层 世界服、场景服、副本服、匹配服 运行权威玩法逻辑和实时状态
社交层 聊天、好友、邮件、公会、排行榜 维护跨场景和长期社会关系
数据层 关系型数据库、内存数据库、缓存 持久化、查询、会话与热点数据
基础设施层 服务发现、配置、监控、日志 管理实例、健康状态和运行环境

拆分的价值不是“服务越多越先进”,而是让不同负载可以独立伸缩,并限制故障影响范围。过度拆分也会把本地函数调用变成不可靠的网络调用,增加部署、监控和一致性成本。

五、接入层:登录服务与网关

1. 登录服务

登录服务负责验证客户端身份。典型流程是:

  1. 客户端提交账号凭据或平台票据。
  2. 登录服务访问账号数据或第三方认证服务。
  3. 验证成功后签发短期会话令牌。
  4. 客户端携带令牌连接游戏网关。
  5. 网关验证令牌并建立玩家会话。

令牌应具有身份、有效期、签发方和防篡改信息。它不能永久有效,也不应直接暴露长期凭据。

2. 网关

网关是公网客户端与内部服务之间的重要边界,常见职责包括:

  • 维持客户端长连接。
  • 协议解析、路由和转发。
  • 限流、封禁和异常流量过滤。
  • 隐藏内部服务地址。
  • 在后端实例切换时保持统一入口。
  • 收集连接层指标和审计信息。

网关不宜承载大量核心玩法逻辑。否则它会从稳定的接入层变成难以扩展的业务瓶颈。

3. 为什么要隔离内外网

内部服务不应全部直接暴露给玩家。统一经过网关可以缩小攻击面,并让认证、限流和协议升级集中处理。即便客户端发现了内部消息格式,也不应具备直接访问数据库或任意服务实例的路径。

六、玩法与社会服务

1. 场景和副本服务

场景服务维护开放区域中的权威状态;副本服务运行生命周期较短、参与者较少的独立玩法实例。

两者的共同特点是:

  • 对延迟敏感。
  • 大量状态保存在内存中。
  • 按固定逻辑帧推进。
  • 玩家离开后只需持久化关键结果。

副本天然适合横向扩展:新对局创建新实例,结束后释放实例。开放世界则需要进一步解决空间分区和跨区交互。

2. 匹配服务

匹配服务根据段位、延迟、队伍规模、等待时间和玩法偏好组合玩家。它更关注队列、排序和约束,而不是实时世界模拟。

匹配条件越严格,质量可能越高,但等待时间也越长。因此实际系统通常随等待时间逐步放宽搜索范围。

3. 聊天、好友、邮件与公会

这些服务跨越场景和服务器实例:

  • 聊天需要频道、屏蔽、审核和消息扇出。
  • 好友需要关系图、在线状态和隐私控制。
  • 邮件需要可靠存储、附件领取和防重复。
  • 公会需要成员、权限、事件和共享资产管理。

把它们从场景进程中拆出后,即使玩家切换地图或副本,社会关系也能保持连续。

4. 交易系统

交易可能包括:

  • 市场挂单与购买。
  • 玩家间赠送物品或货币。
  • 邮件附件。
  • 真实货币购买虚拟物品。

交易操作不能只追求低延迟,更要保证不重复、不丢失和可审计。以购买为例,扣款和发货必须形成一个可恢复的业务过程;任何一步失败,都要能判断应该重试、补偿还是人工处理。

七、游戏数据存储

1. 关系型数据库

关系型数据库要求预先定义结构,并通过表、键和约束组织数据。它适合:

  • 玩家账号和角色数据。
  • 物品、背包和订单。
  • 商店与交易记录。
  • 需要复杂条件组合的查询。
  • 强调一致性和事务的数据。

优点是结构明确、查询灵活、事务能力成熟;代价是磁盘访问和复杂事务可能成为高并发路径上的瓶颈。

2. 内存键值数据库

以 Redis 为代表的内存存储具有低延迟、键值访问和高效范围查询等特点,常用于:

  • 会话管理。
  • 匹配队列。
  • 排行榜。
  • 在线状态。
  • 热点缓存。
  • 为持久数据库削峰。

内存数据库很快,但“快”不等于“天然可靠”。仍需考虑持久化模式、副本、故障切换、缓存过期和数据源一致性。

3. 数据分级

可以按价值和时效划分数据:

数据类型 示例 主要要求
权威持久数据 付费订单、货币、稀有物品 强一致、审计、可恢复
运行时状态 角色位置、技能冷却、怪物状态 低延迟、频繁更新
会话数据 登录令牌、连接映射 短生命周期、快速访问
派生数据 排行榜缓存、统计结果 可重建、允许短暂延迟

不是所有数据都需要同一种数据库和一致性等级。正确做法是先确定数据丢失或短暂不一致的后果,再选择存储方案。

八、分布式系统为何困难

服务拆分后,本地程序中的许多隐含保证都会消失。

1. 数据访问互斥

多个实例可能同时修改同一角色、同一物品或同一公会。仅靠单进程锁无法协调跨机器访问,需要使用:

  • 数据库条件更新。
  • 乐观版本号。
  • 分布式锁。
  • 单写者或按键分片。
  • 事件串行化。

锁不是目的;真正目标是定义数据所有权,并减少多处同时写入。

2. 幂等性

网络请求超时并不代表服务端没有执行。客户端或上游服务重试时,可能造成重复扣款、重复发奖或重复创建角色。

幂等操作满足:

$$ f(f(x)) = f(x) $$

业务系统常通过请求唯一标识、去重表、状态机和条件更新实现幂等。每个重试接口都应明确:重复请求会返回原结果,还是会再次产生副作用。

3. 部分故障

分布式调用可能出现:

  • 请求未到达。
  • 请求到达但响应丢失。
  • 下游服务超时。
  • 某个副本失效而其他副本仍正常。
  • 网络分区导致双方都无法确认对方状态。

这意味着“失败”不再是一个简单布尔值。调用方必须设置超时、重试上限、退避、熔断和降级策略。

4. 一致性与共识

数据存在多个副本后,需要决定:

  • 谁可以写。
  • 哪个版本更新。
  • 读请求是否允许看到旧值。
  • 网络分区时优先可用性还是一致性。
  • 故障恢复后如何合并状态。

实时位置可以容忍短暂不一致,但货币、订单和所有权通常需要更强保证。

5. 分布式事务

跨多个服务的操作无法依赖单机事务自动完成。例如市场购买可能同时涉及订单、买方货币、卖方收益、物品所有权和邮件。

常见思路是:

  • 缩小强事务边界。
  • 让关键资源由单一服务拥有。
  • 使用业务状态机记录每一步。
  • 通过消息和补偿动作实现最终一致。
  • 为所有步骤保留可审计的唯一编号。

九、数据分片与一致性哈希

1. 普通取模的迁移问题

最简单的分片方式是:

$$ server(k) = h(k) \bmod N $$

其中 $h(k)$ 是玩家或数据键的哈希,$N$ 是服务器数量。

当 $N$ 改变时,大量键的余数都会变化,造成大规模迁移和缓存失效。服务频繁扩缩容时,这种扰动不可接受。

2. 哈希环

一致性哈希把哈希空间首尾相接成环。假设哈希值范围为:

$$ h(k) \in [0, 2^m - 1] $$

服务器节点和数据键都映射到环上。一个键通常归属于沿顺时针方向遇到的第一个服务器节点。

添加或移除节点时,主要只影响相邻区间,而不是重新映射所有键。在理想均匀分布下,新增一个节点时迁移比例约为:

$$ \frac{1}{N+1} $$

3. 虚拟节点

物理服务器数量有限时,哈希环上的节点可能分布不均,导致负载倾斜。可以让每台物理服务器拥有多个虚拟节点:

$$ v_{i,j} = h(server_i, j) $$

其中 $i$ 表示物理服务器,$j$ 表示该服务器的虚拟节点编号。

虚拟节点的作用包括:

  • 让负载在环上分布得更均匀。
  • 通过虚拟节点数量表达不同机器的权重。
  • 故障时把负载分散给多台其他服务器。
  • 迁移时以较细粒度重新分配数据。

一致性哈希解决的是“键如何稳定映射到节点”,并不自动解决数据复制、强一致、热点键或故障恢复。

十、服务发现与健康检查

服务实例的地址和数量会不断变化,调用方不应把它们永久写死在配置里。

1. 服务注册

实例启动后向服务发现系统注册:

  • 服务名称。
  • 网络地址和端口。
  • 版本、区域和标签。
  • 当前容量或权重。

实例关闭时主动注销;异常退出时则依赖租约或心跳超时清理。

2. 服务发现

网关或其他服务查询“当前有哪些可用实例”,再按轮询、权重、最少连接或一致性哈希选择目标。

发现模式可以是:

  • 客户端发现:调用方获取实例列表并自行负载均衡。
  • 服务端发现:调用方访问统一代理,由代理选择后端实例。

3. 健康检查

只注册不检查会把流量继续发给已经失效的实例。健康检查可分为:

  • 存活检查:进程是否还在运行。
  • 就绪检查:实例是否已经加载完成并能接收新请求。
  • 深度检查:关键依赖是否正常。

若实例 B 的心跳超时,服务发现系统应把它标记为不健康,并让网关停止路由新请求。但检查过于敏感会造成实例频繁上下线;过于宽松又会延长故障时间。

十一、带宽预算

网络优化首先要有预算,而不是事后随意压缩。

若每次更新平均包含 $n$ 个实体,每个实体状态大小为 $s$ 字节,更新频率为 $f$,则单个客户端的下行带宽近似为:

$$ B \approx fns $$

在玩家数量和世界对象数量增长时,不能简单提高带宽。可控制的三个量分别是:

  • $f$:降低或动态调整更新频率。
  • $n$:通过 AOI 只发送相关实体。
  • $s$:通过量化、差量和编码减少单个状态大小。

带宽优化本质上是在“精度、时效和相关性”之间分配预算。

十二、数据压缩与量化

1. 选择合适精度

游戏同步数据中包含大量位置、旋转和速度。并非所有数据都需要 32 位或 64 位浮点精度。例如人的奔跑速度通常不需要双精度表示。

若一个数值已知范围为 $[x_{\min},x_{\max}]$,使用 $b$ 位无符号整数编码,可量化为:

$$ q = \operatorname{round} \left( \frac{x-x_{\min}}{x_{\max}-x_{\min}} \cdot (2^b-1) \right) $$

解码近似为:

$$ \hat{x}

x_{\min} + \frac{q}{2^b-1} \cdot (x_{\max}-x_{\min}) $$

这里的关键不是盲目降低位数,而是根据实际值域和可接受误差选择精度。

2. 差量更新

完整快照中许多字段没有变化。可以只发送相对基准状态的变化:

$$ \Delta S_t = S_t - S_{base} $$

接收端需要知道差量所依赖的基准。若基准包丢失,则后续差量可能无法恢复,因此协议还需提供确认、基准切换或周期性完整状态。

3. 其他常见手段

  • 布尔值按位打包。
  • 枚举使用尽可能少的位。
  • 小整数使用变长编码。
  • 旋转使用四元数压缩。
  • 静止对象不重复发送位置。
  • 低优先级对象降低更新频率。
  • 多条小消息合并,减少协议头开销。

压缩会消耗 CPU,并提高编解码和版本兼容复杂度,应以测量结果为依据。

十三、更新频率和优先级

所有对象都按相同频率同步会浪费带宽。优先级可以综合考虑:

  • 与玩家的距离。
  • 是否处于视野内。
  • 对象是否正在移动。
  • 是否参与战斗。
  • 是否即将影响玩家。
  • 状态距离上次发送变化了多少。
  • 已经多久没有发送更新。

可把实体 $i$ 的发送评分抽象为:

$$ P_i

w_d D_i + w_v V_i + w_g G_i + w_t T_i $$

其中各项分别表示距离、可见性、Gameplay 重要性和等待时间等因素。网络系统在每帧预算内优先发送得分高的更新。

等待时间项很重要,否则低优先级对象可能长期饥饿,永远得不到刷新。

十四、AOI:只同步玩家关心的世界

AOI 是 Area of Interest,即兴趣区域。它回答的问题是:

对于当前玩家,哪些实体值得进入同步集合?

如果世界中有 $N$ 个玩家,每个人都接收其他所有人的状态,消息扇出接近 $O(N^2)$。大型世界必须根据空间或 Gameplay 关系过滤。

AOI 不一定等同于摄像机视锥。服务器可能还需要同步:

  • 位于身后但会产生声音的敌人。
  • 即将进入视野的高速物体。
  • 与任务或队伍相关的远处角色。
  • 具有全局影响的事件。

因此 AOI 是“相关性集合”,空间距离只是最常用的第一层过滤。

十五、AOI 的空间网格

1. 实体映射

将二维世界划分为规则网格。若网格尺寸为 $w \times h$,世界原点为 $(x_0,y_0)$,实体位置为 $(x,y)$,其格子坐标可写为:

$$ g_x = \left\lfloor \frac{x-x_0}{w} \right\rfloor $$

$$ g_y = \left\lfloor \frac{y-y_0}{h} \right\rfloor $$

每个格子维护其中的实体集合。玩家只需查询自己所在格子及周围格子,再做精确距离判断。

2. 候选格子

若 AOI 半径为 $r$,横纵格子尺寸分别为 $w$ 和 $h$,最多需要检查的格子数约为:

$$ \left(2\left\lceil\frac{r}{w}\right\rceil+1\right) \left(2\left\lceil\frac{r}{h}\right\rceil+1\right) $$

候选实体仍需通过精确距离测试:

$$ (x_i-x_p)^2 + (y_i-y_p)^2 \leq r^2 $$

比较平方距离可以避免不必要的开方。

3. 优缺点

优点:

  • 实现简单。
  • 查询速度稳定。
  • 玩家 AOI 列表可以缓存。
  • 适合实体分布较均匀的世界。

缺点:

  • 格子太大会产生大量无关候选。
  • 格子太小会增加跨格移动和邻格管理成本。
  • 热点区域仍可能集中大量实体。
  • 不规则地形和多层空间需要额外处理。

十六、AOI 的正交链表

正交链表思路是分别按 $x$ 坐标和 $y$ 坐标维护有序结构。查询时先在一个轴上找到范围,再用另一个轴过滤。

它的特点包括:

  • 内存开销相对可控。
  • AOI 半径可以因玩家而异。
  • 附近对象查询不依赖固定格子大小。

但实体插入或坐标次序变化时需要维护有序关系。若对象经常长距离移动,调整成本会很高;朴素插入最坏可能达到 $O(n)$。

因此:

  • 规则网格适合大量实体持续移动、空间尺度相对稳定的场景。
  • 有序链表适合查询半径变化较多、实体移动相对连续的场景。
  • 实际系统也可以使用四叉树、BVH、空间哈希或多级网格。

选择数据结构时应测量实体密度、移动分布和查询模式,而不是只比较理论复杂度。

十七、AOI 的进入与离开事件

AOI 不只是每帧计算一个集合,还要处理集合变化。

设上一时刻集合为 $A_{t-1}$,当前集合为 $A_t$:

$$ Enter = A_t \setminus A_{t-1} $$

$$ Leave = A_{t-1} \setminus A_t $$

$$ Stay = A_t \cap A_{t-1} $$

对应处理:

  • Enter:发送实体创建和完整初始状态。
  • Stay:发送增量更新。
  • Leave:发送销毁、隐藏或休眠消息。

在 AOI 边缘反复移动会产生频繁进入和离开。可使用不同的进入半径和退出半径形成滞回区:

$$ r_{leave} > r_{enter} $$

这样可减少边界抖动。

十八、反作弊的基本边界

作弊会直接破坏公平性、经济系统和玩家信任。反作弊的首要原则是:

客户端负责输入和表现,服务器负责验证并决定永久结果。

客户端可以被修改、调试、注入、读取内存或模拟输入,因此不能把以下结果完全交给客户端决定:

  • 伤害与命中。
  • 移动是否合法。
  • 物品生成与消耗。
  • 货币变化。
  • 冷却和射速。
  • 随机掉落。
  • 排名和胜负。

但服务器也不能重做客户端的一切。正确边界是让客户端只提交“意图”,服务器根据权威状态验证该意图是否可能。

十九、作弊手段与防护层次

层次 典型作弊 对应防护
客户端程序 修改代码、注入、调试、篡改资源 完整性校验、混淆、加壳、反调试
运行时内存 修改数值、读取隐藏信息 服务端权威、内存保护、最小化敏感数据
网络协议 伪造、重放、篡改或嗅探消息 认证、加密、序列号、时效性检查
输入设备 宏、自动瞄准、外部设备 行为统计、输入模式分析
外部视觉 截屏识别、AI 自动瞄准 服务端行为检测、回放分析
后台业务 刷奖励、复制物品、交易欺诈 幂等、事务、审计和风控

没有单一技术能够彻底消灭作弊。加固提高逆向成本,服务器验证限制作弊收益,检测系统负责发现异常,两者必须组合。

二十、客户端程序加固

1. 可执行文件加壳

加壳会压缩或混淆原始程序,并加入解包代码。运行时先执行解包器,再把原程序恢复到内存。

作用:

  • 增加静态分析难度。
  • 隐藏部分代码结构和字符串。
  • 提高批量修改客户端的门槛。

局限:

  • 程序最终仍要在玩家机器上运行,关键代码会出现在内存中。
  • 加壳可能影响启动速度、崩溃分析和兼容性。
  • 它只能增加成本,不能建立可信执行环境。

2. 混淆与完整性校验

可组合使用:

  • 符号和控制流混淆。
  • 关键代码或数据加密。
  • 文件和内存区域哈希校验。
  • 检测代码注入和异常模块。
  • 检测调试器、Hook 和篡改。

这些措施需要谨慎控制误报,并避免对正常工具、辅助功能和系统软件造成不必要干扰。

二十一、网络通信安全

1. 混合加密

非对称加密便于安全交换密钥,但计算成本较高;对称加密适合大量数据传输。常见流程是:

  1. 客户端获得服务器公钥或通过可信证书验证服务器。
  2. 双方协商随机会话密钥。
  3. 后续数据使用对称密钥加密和认证。

这类组合同时利用:

  • 非对称密码解决密钥分发和身份问题。
  • 对称密码提供高吞吐的数据保护。

2. 加密不等于可信

加密可以保护传输中的机密性和完整性,但客户端本身仍可能主动发送“格式正确、内容恶意”的请求。因此服务器仍需验证:

  • 请求者是否有权限。
  • 序列号和时间是否合理。
  • 操作是否满足当前状态机。
  • 数值是否超过物理或规则上限。
  • 同一个请求是否已经处理。

3. 防重放

可在消息中加入:

  • 会话标识。
  • 单调递增序列号。
  • 时间戳和有效窗口。
  • 随机数。
  • 消息认证码。

服务器拒绝过期、重复或认证失败的消息。

二十二、作弊程序与行为检测

1. 已知作弊程序检测

客户端反作弊可扫描已知特征,例如:

  • 可执行文件哈希。
  • 窗口标题。
  • 进程名。
  • 已加载模块或驱动。

客户端把检测结果发送到服务器,服务器与特征库比较。此方法对已知作弊有效,但容易被改名、重打包或变种绕过,并存在隐私与误报风险。

2. 行为检测

未知外挂不一定具有已知文件特征,但可能产生异常行为:

  • 反应时间长期接近机器极限。
  • 瞄准轨迹突然吸附。
  • 命中率和爆头率与同层玩家显著偏离。
  • 移动速度、加速度或射速超过规则上限。
  • 资源增长路径无法由合法事件解释。

行为检测不应只依赖单个阈值。应结合玩家水平、武器、地图、延迟和长期历史建立上下文,并保留回放或证据供复核。

3. 外部 AI 作弊

外部程序可以直接读取屏幕画面,识别目标,移动鼠标并射击:

  • 跨平台。
  • 不一定注入游戏进程。
  • 不要求修改游戏代码。
  • 传统文件完整性检测难以发现。

这说明反作弊不能只盯着客户端二进制文件,还要分析服务端可观察到的输入和行为模式。

二十三、服务器权威验证

服务端验证应遵循“便宜检查在前、昂贵检查在后”:

  1. 身份和会话是否合法。
  2. 消息格式、长度和频率是否合法。
  3. 序列号与时间窗口是否合法。
  4. 当前状态是否允许该操作。
  5. 资源、冷却和距离是否满足要求。
  6. 必要时运行碰撞、命中或业务规则检查。

例如移动验证可根据时间差 $\Delta t$ 和允许速度 $v_{\max}$ 限制位移:

$$ |\mathbf{p}t-\mathbf{p}{t-1}| \leq v_{\max}\Delta t + \epsilon $$

其中 $\epsilon$ 用于容纳网络抖动、浮点误差和合法技能位移。服务器不能只凭一次超限立即封禁,还应区分同步误差、Bug 和持续作弊。

二十四、世界分区

单台场景服务器的 CPU、内存和网络能力有限。扩展开放世界的基本办法是把空间划分为多个 Zone,每个 Zone 由一个或多个服务器负责。

1. 硬边界分区

玩家跨区时:

  1. 当前服务器保存或转交玩家状态。
  2. 目标服务器加载状态。
  3. 客户端切换连接或路由。
  4. 新区域开始同步。

硬边界实现简单,但可能出现读取界面、停顿或断开感。它适合地图、频道或副本之间本来就有明显过渡的游戏。

2. 无缝分区

无缝分区要求玩家在边界附近仍能看到并交互另一侧实体。课程给出的关键条件是边界带宽度至少覆盖最大 AOI 半径:

$$ w_{border} \geq r_{AOI}^{max} $$

这样一侧玩家进入边界带时,服务器有空间提前复制另一侧相关实体,而不是等玩家越界后才突然创建。

二十五、活动实体与影子实体

同一个边界实体可能同时出现在两个 Zone 中,但必须明确谁有权修改它。

  • Active Entity:由所属 Zone 权威模拟,可执行 Gameplay 逻辑。
  • Shadow Entity:在邻接 Zone 中的只读或受限副本,用于可见性、查询和过渡。

典型流程:

  1. 实体接近边界。
  2. 权威服务器把状态复制给邻区,创建影子实体。
  3. 邻区玩家能够提前看见并接收其更新。
  4. 实体真正越界时执行所有权移交。
  5. 新区成为 Active,旧区暂时保留 Shadow。
  6. 实体离开边界范围后删除旧副本。

所有权移交必须避免:

  • 两个 Zone 同时模拟,造成重复伤害或重复掉落。
  • 两边都不模拟,造成实体短暂冻结。
  • 消息乱序让旧状态覆盖新状态。
  • 玩家跨界时会话、技能和仇恨丢失。

可使用实体世代号、所有权纪元或迁移事务标识来区分新旧权威。

二十六、跨区交互

仅复制视觉状态不够。边界两侧的玩家还可能:

  • 互相攻击。
  • 产生碰撞。
  • 触发技能范围效果。
  • 交换仇恨与目标。
  • 进入同一队伍或事件。

系统需要规定:

  • 哪一侧执行判定。
  • 如何把请求转发给权威服务器。
  • 跨区结果怎样返回并复制。
  • 所有权迁移期间如何处理新命令。

一种常见原则是:实体所属服务器负责最终状态修改,其他服务器只能提交请求或维护影子。跨区交互越频繁,服务器间通信成本越高,因此 Zone 边界最好放在玩家交互相对稀疏的区域,并支持动态调整。

二十七、复制、实例与组合扩展

世界扩展不只有空间切分,还可以组合多种手段:

  • Zoning:不同服务器负责不同空间区域。
  • Instancing:同一内容创建多个独立副本,分流玩家。
  • Replication:为热点数据或只读世界状态创建副本。
  • Sharding:把玩家分到多个相互独立的世界分片。
  • Layering:同一地图显示不同玩家层,降低局部密度。

课程展示的组合方案中,不同 Zone 可以由不同服务器负责;热点区域可以建立多个实例或复制层;边界使用 Active/Shadow 实体保持连续。

组合设计必须回答三个问题:

  1. 玩家和实体由谁拥有?
  2. 玩家之间何时能够看见和交互?
  3. 负载变化时如何迁移而不破坏状态?

如果这三个问题没有清晰答案,架构图中的“多台服务器”并不会自动带来可扩展性。

二十八、热点与负载均衡

空间分区的隐含风险是玩家分布不均。大型活动、主城或世界 Boss 会让某个 Zone 突然成为热点。

可采用:

  • 将热点内容实例化。
  • 对只读或弱交互内容做复制。
  • 动态改变 Zone 边界。
  • 把非实时服务移出场景进程。
  • 对 NPC、物理和更新频率降级。
  • 限制区域容量并排队。

可定义服务器负载评分:

$$ L

\alpha L_{cpu} + \beta L_{mem} + \gamma L_{net} + \delta L_{entity} $$

负载均衡不能只看玩家数量。不同实体、技能、AI 和网络关系的成本差异很大。

动态迁移也有代价:传输状态、建立影子、暂停写入和切换路由都会消耗资源。触发阈值需要滞回,避免负载在两台服务器间来回震荡。

二十九、可观测性与运维

大型在线服务必须把故障和性能问题变成可观测数据。

1. 接入与服务指标

  • 当前连接数和登录成功率。
  • 网关收发带宽和消息频率。
  • 各 RPC 的延迟、错误率和超时率。
  • 实例心跳、重启和摘除次数。
  • 队列长度与线程占用。

2. 数据指标

  • 数据库查询延迟。
  • 缓存命中率。
  • 热点键与慢查询。
  • 事务失败和补偿次数。
  • 重复请求与幂等命中次数。

3. Gameplay 与网络指标

  • 每个玩家的 AOI 实体数。
  • 每类实体的更新频率和字节数。
  • 完整快照与差量快照比例。
  • Zone 玩家数、实体数和逻辑帧耗时。
  • 跨区消息量、影子实体数和迁移耗时。

4. 安全指标

  • 非法消息、限流和认证失败。
  • 速度、射速与资源规则触发次数。
  • 可疑客户端特征。
  • 行为模型告警与人工复核结果。
  • 封禁后的申诉和误报率。

日志应携带统一的玩家 ID、会话 ID、请求 ID 和实体 ID,才能把一次跨服务操作串联起来。

三十、常见错误与改进

1. 把所有逻辑放进场景服

结果:场景切换会影响聊天、好友和交易,任何模块故障都可能拖垮整个进程。

改进:按生命周期和负载特征拆分,但保留清晰的数据所有权。

2. 每次失败都立即无限重试

结果:下游故障时形成重试风暴,重复执行有副作用的操作。

改进:先保证幂等,再使用超时、有限重试、退避和熔断。

3. 取模分片后随意增减节点

结果:大量键重新映射,引发缓存失效和数据迁移。

改进:使用一致性哈希和虚拟节点,并补充复制与迁移协议。

4. 给所有玩家广播所有实体

结果:带宽和消息数量随人数快速增长。

改进:AOI 过滤、优先级调度、频率分级和状态压缩配合使用。

5. 只做客户端加固

结果:加固被绕过后,服务端仍接受伪造结果。

改进:客户端只提交意图,服务端验证规则并决定永久状态。

6. 认为加密可以防止作弊

结果:恶意客户端仍可使用合法密钥发送非法操作。

改进:加密保护传输,权威验证保护规则,两者职责不同。

7. 只按玩家数量划分 Zone

结果:大量 AI、技能和跨区交互仍可能让单个 Zone 过载。

改进:结合 CPU、网络、实体复杂度和跨区关系评估负载。

8. 在边界两侧都运行完整逻辑

结果:重复计算、状态冲突,甚至产生重复奖励。

改进:严格区分 Active 与 Shadow,所有权移交需要版本和原子切换语义。

三十一、整体设计原则

  1. MMO 的扩展基础是职责拆分、数据所有权和故障隔离。
  2. 网关是内外网边界,不是核心玩法逻辑容器。
  3. 持久数据、运行时数据和缓存数据应采用不同的一致性策略。
  4. 所有可能重试的写操作都要设计幂等性。
  5. 分布式故障通常是部分故障,超时不能直接等同于未执行。
  6. 一致性哈希减少节点变化时的重映射,但不替代复制和一致性协议。
  7. 服务发现必须与健康检查、负载均衡和摘除机制配套。
  8. 带宽优化同时控制更新频率、相关实体数量和单次状态大小。
  9. AOI 是相关性管理,不只是距离裁剪。
  10. 客户端永远不应成为永久状态的最终裁决者。
  11. 加固、加密、服务端验证和行为检测解决不同层次的问题。
  12. 无缝分区需要边界预复制和明确的实体所有权。
  13. 影子实体用于观察和过渡,活动实体才执行权威逻辑。
  14. 世界扩展通常是分区、实例、复制和分片的组合。
  15. 可扩展架构必须同时具备可观测性、审计和故障恢复能力。

三十二、复习要点

  • MMO 的关键不是单场战斗人数,而是持久世界、长期服务和大规模数据。
  • 登录服务负责认证,网关负责连接、路由和安全边界。
  • 场景与副本服务偏实时;聊天、好友、公会和交易偏长期关系。
  • 关系型数据库适合结构化持久数据,内存数据库适合热点、会话和快速排序。
  • 分布式系统必须面对互斥、幂等、部分故障、一致性和跨服务事务。
  • 一致性哈希通过哈希环减少扩缩容时的数据重映射,虚拟节点改善均衡。
  • 服务发现维护动态实例列表,健康检查及时摘除不可用实例。
  • 带宽近似由更新频率、相关实体数和单状态大小共同决定。
  • 量化与差量减少单次数据大小,AOI 减少需要同步的实体数量。
  • 空间网格简单稳定;正交链表支持变化半径,但不适合频繁长距离移动。
  • AOI 集合应区分 Enter、Stay 和 Leave,并防止边缘反复抖动。
  • 服务器只信任客户端输入意图,不信任客户端计算结果。
  • 加壳和混淆只能提高逆向成本,不能替代服务器权威。
  • 混合加密保护通信,但合法加密消息仍需业务验证。
  • 已知特征检测对变种外挂有限,必须结合行为分析和审计证据。
  • 无缝 Zone 的边界宽度应覆盖最大 AOI 半径。
  • Active/Shadow 模型让跨区可见性与单一权威所有权同时成立。
  • 热点扩展需要分区、实例、复制和动态负载管理的组合。

三十三、参考资料