要在基于 JVM 的环境里运营 MMORPG??
开场
最近确实有些懈怠了。我得重新把精神绷紧一点。ㅠㅠ 这也是一篇很久没写的博客文章了。
在上一篇 游戏制作计划 里,我写过 MMORPG 会是最后一个挑战。之后最常出现的反应,大概就是这个。Java?你真的要在 JVM 上实现并运营一个实时 MMORPG?这不是只在很小规模下才勉强成立的事情吗?
这篇文章我想讲的核心只有一句话。如今 JVM 上的高 Hz 服务器 tick 之所以变成现实可选项,最大的原因就是 GC 的进步。Netty 很重要,Kotlin 很重要,分区、AOI 这类 MMO 设计也很重要。但在讨论那些之前,首先必须看的仍然是 pause time。
在这个领域里,真正的问题不是平均性能,而是 GC
如果你还记得过去的 Java,那么在实时网络游戏里,首先想到的顾虑往往就是 GC。更准确地说,不是 throughput,而是 tail latency。平均值再漂亮,只要来一次足够长的 stop-the-world,tick 就会立刻被拖慢,输入响应和战斗手感也会一起塌掉。
这一点在游戏服务器上尤其致命。
20Hz服务器的 tick 预算是50ms。60Hz服务器的 tick 预算是16.6ms。128Hz服务器的 tick 预算是7.8ms。
如果 GC pause 会突然跳到几十毫秒,那么即使平均 TPS 看起来正常,玩家也会立刻感觉到不对。rubber banding、输入延迟、战斗判定错位、packet flush 延后,很多时候都从这种 tail 问题开始。所以,所谓 JVM 能不能做实时游戏,本质上比起 JVM 的平均性能够不够,更接近于 GC 现在到底短到了多可预测的程度。
现在的 JVM GC 已经不是过去那个 JVM GC 了
这里的变化其实非常明显,OpenJDK 的路线本身就已经说明问题了。
- ZGC 在 JDK 15 成为了 production feature。
- Generational ZGC 在 JDK 21 引入。
- 到了 JDK 23,Generational ZGC 成为了默认模式。
- 在 JDK 24,非分代 ZGC 被移除。
- 也就是说,Java 25 可以看作 generational-only ZGC 时代的第一个 LTS。
- Shenandoah 本来就已经是 production GC,而在 Java 25 里,它的 generational mode 也升级成了 product feature。
这里重要的并不只是 JVM 多了一个新选项。按照 OpenJDK 的说明,Generational ZGC 的目标是在更频繁回收 young object 的同时,降低 allocation stall 风险、降低所需的 heap overhead、并减少 GC CPU overhead。而最关键的 pause 特性并没有丢掉。JEP 439 明确说明,application pause 通常会被控制在 1ms 以下。
这意味着,过去那个让很多人一看到 Java 游戏服务器就先摇头的前提,也就是 GC 会偶尔把世界停住,已经不再是默认前提了。GC 当然没有消失,但它已经不再是那个足以让 JVM 一开始就出局的决定性理由。
真实生产环境的案例也在支持这个方向
其中一个特别值得看的例子是 Netflix。Netflix 在 JDK 21 之后把默认 GC 策略转向了 Generational ZGC,并且按照他们自己的说法,目前关键流媒体视频服务里,已经有一半以上运行在 JDK 21 + Generational ZGC 上。
更重要的是结果。Netflix 最初预期 ZGC 会是一种用一点 throughput 换低延迟的取舍。但实际结果比预期更好。平均延迟和 P99 延迟都改善了,CPU 使用率则持平甚至更优。在他们评估过的最差案例里,非分代 ZGC 在相同工作负载下会比 G1 多消耗 36% 的 CPU,而到了 Generational ZGC,这个结果反而变成了接近 10% 的 CPU 改善。
当然,Netflix 不是游戏公司。但这里真正值得看的不是业务类型,而是系统对 pause 和 tail latency 的控制能力。实时游戏服务器说到底也是在更严苛的预算下解决同一类问题。
那么,JVM 到底能不能做实时游戏
到这里,再用一句很笼统的话去问 JVM 能不能做实时游戏 已经没什么意义了。应该把场景拆开来看。
足够现实的范围 其实已经很大。
- 基于 tick 的 MMORPG
- 沙盒型世界服务器
- 大厅、匹配、状态同步后端
- 需要
20~60Hz左右服务器 tick 的多人游戏
一个非常大众的例子就是 Minecraft Java Edition 服务器。仅仅这一点,就已经很难再继续坚持 Java 根本跑不了实时多人世界 这种说法了。
当然,也依然存在需要更谨慎处理的区域。
- tick 预算极端紧张的
128Hz竞技型 FPS - 需要极端 deterministic latency 的工作负载
- 内存余量非常有限的环境
- GC 线程和 application thread 会在饱和 CPU 上互相抢资源的系统
所以,重点不是 JVM 什么都能做。重点是 在大多数游戏服务器场景里,过去那个让 JVM 直接失去资格的最大理由,也就是 GC pause 问题,已经被大幅缓解了。
然后,MMO 特有的设计才真正开始发挥作用
这里一定要把顺序说清楚。我一直在说 zone tick、系统级 cadence、AOI 驱动的同步很重要。现在正在实现的 mmorpg-service 也确实遵循这个结构。
- 默认循环是
10Hz - World 和 Zone 设置可以在
1~15Hz范围内调整 - Monster AI 会根据状态使用不同 cadence
- 掉落物消失检查和自动恢复重试会以更慢的频率执行
- Netty 的 I/O 线程只负责收包,真正的游戏规则由 zone loop 处理
AOIManager会根据距离和事件改变同步频率
但这些终究还是第二层。要是 pause time 仍然会动不动跳到几十毫秒,那么你在上面再怎么拆 zone tick、再怎么细化 AOI,也只是建立在会晃动的地板上。高 Hz 之所以变得现实,最大的原因是 GC 的进步。MMO 设计,是建立在这层基础之上的第二层结构。
归根结底,这篇文章只想说明一句话
现在 JVM 能处理更高 Hz 的最大背景,就是 GC 的进步。
过去 Java = stop-the-world 很可怕 这个联想太强了。现在 low-pause GC 已经成熟得多,再加上 Generational ZGC 和最新 Shenandoah 的演进,JVM 已经不再是那种在实时服务器讨论里会被自然排除的平台。
当你在这个基础上再叠加 Kotlin、Netty、zone loop、cadence 和 AOI 这些 MMO 设计之后,整个故事就完全不一样了。接下来该问的问题,不应该再是 JVM 天生就不行吧?,而应该是 在我们的 latency budget 里,GC 和 tick 应该怎么设计?
也正因为如此,我认为基于 JVM 运营 MMORPG,已经完全是一个非常现实的选择了。