
Destiny 2 的网络架构 - 多人游戏深度解析

最小化服务器模拟: Bungie 仅托管会话逻辑严格需要的状态,将云实例大小缩减到约 45 MB,从而使每台服务器可运行近 5,000 个实例。
部署前先声明: 在前期明确声明脚本实际上需要哪些状态(并丢弃其他一切),是一种可迁移到任何多人游戏架构中的优化原则。
Tick 率是一种成本旋钮: 以 10 Hz 运行服务器端会话逻辑,而不是直接匹配客户端模拟速率,可在不影响玩家体验的情况下直接降低 CPU 和带宽开销。
一致性校正必须内置于设计中: 从一开始就将状态一致性构建进架构,避免了一整类会破坏进度的漏洞,而这类问题在上线后排查代价高昂。
审计哪些逻辑运行在哪里: 当关键任务逻辑在没有服务器监督的情况下悄然迁移到客户端时,就会同时产生可利用的漏洞和稳定性风险,因此架构审计应成为持续优化的一部分,而不是一次性工作。
Bungie 现任工作室负责人 Justin Truman 作为《Destiny》的工程负责人之一,在 GDC 2015 上做了这场演讲,讲述了团队构建的联网任务架构,以支持在发售规模下实现无缝、始终在线的共享世界。
间接而言,这凸显了任何游戏工作室,无论大小,都可以应用的最佳实践,以在不牺牲玩家体验的前提下降低云基础设施成本。
核心问题:服务器实际上需要模拟什么?
《Destiny》架构中最具可迁移性的洞见,并不是关于点对点联网或世界设计。它是一个更简单、也更难的问题,而大多数团队问得还不够早:你的服务器实际上需要模拟什么,什么又可以放在别处?
Bungie 的答案是活动主机,一个精简的云进程,只运行活动脚本所依赖的任务关键状态。比如一个小队是否已生成、还剩多少敌人存活、某个目标是否已被触发。不是子弹轨迹。不是动画状态。不是世界空间位置。这些都在别处。
结果是一个大约 45 MB 的活动主机可执行文件——通过拿完整的 Destiny 二进制文件并移除该特定逻辑层不需要的一切实现。以 10 Hz 运行时,一台 40 核服务器几乎可以同时运行近 5,000 个这样的实例。正如 Truman 所解释的,这意味着只需几百台机器就能支撑大约 100 万并发用户,而且还有充足的安全余量。相比之下,采用完整模拟的专用服务器方案,则可能需要接近 50 万个无头进程来运行整个游戏。
当你不再模拟那些不需要模拟的东西时,账目会迅速变化。
传感器:声明所需内容的一种模式
让这一切成为可能的机制,是 Bungie 所称的 sensors。活动脚本在引用某段游戏状态之前,必须显式声明该依赖。一个追踪敌人小队的脚本不会存储单个生命条或世界位置。它追踪的是离散事实:还活着多少,是否已经生成,它们是否正在使用这个射击区域。仅此而已。
这种先声明再使用的方法直接影响了基础设施成本。由于服务器只持久化脚本正式声明需要的内容,状态面保持得很小,单实例占用也保持得很低。传感器状态更新会以原子方式同时发送到所有传感器,确保带宽可预测,并避免出现部分状态不一致。
这种模式远不止适用于《Destiny》的特定架构。在向你的权威服务器添加状态之前,先问一句:这个脚本或会话逻辑真的需要它吗?如果答案是“其实不需要,只是放在那里更方便”,那就是服务器臃肿悄悄开始的地方。对于希望降低 vCPU 和出站流量成本的团队来说,系统性审计哪些状态运行在服务器上以及原因,往往是最有杠杆效应的起点。Edgegap 的 Unreal Engine 服务器剖析指南 是开始这项审计的一个实用入口。
将 Tick 率作为成本杠杆
Bungie 让他们的活动主机以 10 Hz 运行。不是因为 10 Hz 对模拟的每一层都最理想,而是因为这些主机上运行的逻辑并不需要更快。任务脚本状态、小队数量、目标标记、事件触发器,都不需要每秒更新五十次。以更低频率运行是一个有意的成本决策,而不是妥协。
这一点值得内化。Tick 率不是你为整个游戏服务器设置的一个单一旋钮。它是按层做出的决定,而正确答案完全取决于那一层在做什么。战斗和物理模拟可能需要高频更新,才能显得响应迅速。会话逻辑和脚本状态通常并不需要。把两者混为一谈,并让一切都以最高 Tick 率运行,是为服务器容量多花钱的可靠方式。
如果想更深入了解 Tick 率选择如何同时影响游戏精度和基础设施成本,Edgegap 对游戏服务器 tick rate 的解析详细说明了这些权衡。
将一致性恢复作为架构,而非事后补救
《Halo Reach》——《Destiny》的前作——带来的一条代价高昂的教训是:如果你从一开始就不为状态一致性做设计,会发生什么。Reach 中的主机迁移导致黑屏、旗帜重复、进度损坏,以及那些难以复现、对时序敏感、并且在临近发售时修复成本极高的 bug。
Bungie 在《Destiny》中的应对方式,是把 reconciliation 当作核心架构问题,而不是一个需要逐个脚本打补丁的问题。由于活动主机始终是已声明状态的权威来源,任何接收到 reconcile 调用的新物理主机都可以自动进入一致的模拟状态。reconciliation 逻辑复用了与普通状态更新相同的代码路径。它不是特例。它只是系统按设计方式运转。
前期工程投入确实存在。编写传感器比向脚本暴露一个简单函数更费成本。但这笔成本是按每种传感器类型支付一次,而不是每个访问它的脚本都支付一次。在一个包含数百个任务的线上游戏中,这种权衡会显著地向架构化方案倾斜。
从一开始就内建 reconciliation,也意味着你不会在 QA 中——更糟的是在生产环境中——才发现一整类会阻断进度的 bug。
未跟踪客户端逻辑的代价
最能说明架构若不被一致执行会出什么问题的例子,就是 Crota 团队副本漏洞。玩家发现,他们可以在团队副本首领战的特定时刻拔掉网线,从而触发有利的游戏状态。之所以奏效,是因为首领逻辑中一个任务关键部分完全用客户端系统构建,彻底绕过了活动主机的权威状态。
正如 Truman 所说:"我认为这个案例是一次工程失误,因为我们没有足够关注并审计更复杂的团队副本脚本。"
这不仅仅是安全问题,也是优化问题。那些悄悄漂移到客户端、却没有被服务器端跟踪的逻辑,不会出现在你的服务器剖析中。它们不会增加你正在为之付费的实例占用,但也不会给你以为自己拥有的一致性保证。结果就是,你的预期架构与实际架构之间出现了差距。
定期审计哪些内容运行在哪里,并将脚本逻辑与服务器跟踪的状态交叉核对,是保持架构诚实的一部分。游戏越复杂,这种差距就越容易在无人察觉的情况下出现,直到某场实战中出了问题。
本文基于并引用了 Justin Truman 在 GDC 2015 上的原始 GDC 演讲,该演讲发布于 GDC YouTube 频道。原始内容中的所有权利归其各自所有者所有。
书写者
Edgegap团队









