Java 大型多人在线游戏服务器的可扩展性

10
我已经为Android创建了一个名为The Infinite Black的大型多人在线游戏: https://market.android.com/details?id=theinfiniteblack.client 出乎意料,这个游戏迎来了爆炸式的增长,一周内新增了超过40,000名用户,平均同时在线连接数约为300个,并呈指数级增长。
服务器架构包括每个连接2个线程(阻塞读/写),一个ServerSocket线程用于生成新的客户端,以及一个控制器线程,轮询每个客户端以获取新操作,将其应用于游戏世界,然后在完成后刷新数据。
服务器是用Java构建的,我对Java不太熟悉,尤其在这种高压力情况下。在内存和线程管理方面,C#真的让我被宠坏了。
现在我刚刚订购了两个非常强大的系统作为专用游戏服务器,并希望最大限度地利用资源。关于Java资源配置的很多信息都被证明是误导性的、错误的或过时的。
我目前使用-Xss512k作为启动参数,并理解它规定了每个线程的堆栈大小分配,但并不完全理解它可能涉及到的一切。有哪些工具或方法可用于告诉我是否超过了标准并可以缩小规模?还应考虑哪些其他命令行参数?
新服务器具有16GB的RAM和i7-2600K Sandy Bridge 3.4GHz处理器:有哪些配置选项可以充分利用这一点?我的目标是每台服务器同时在线1,200个客户端(2,400个线程)。
我应该担心哪些意想不到的问题和问题?
我阅读了关于最大线程数的截然不同的故事:如果我试图推动2,400个活跃线程,会发生什么事情?
Java似乎不是为这种任务而设计的。我应该考虑将服务器迁移到另一种语言吗?
我目前在开发中使用Eclipse调试模式运行服务器。这是我的Eclipse.ini配置:--launcher.XXMaxPermSize 256M -Xms256m -Xmx1024m

7
实际上,Java 就是为这个任务而设计的。 :-) - corsiKa
1
是啊,谁曾想过要用Java来处理大型服务器呢?但遗憾的是,整个解决方案都有问题——每个用户只能使用2个线程,这种情况下任何编程语言都无济于事。 - Voo
如何将现代TCP服务器扩展到数万个TCP连接。 http://www.kegel.com/c10k.html - vz0
3个回答

8
您没有明确表明您的疑惑来源。 Plurk Comet: Handling 100,000+ Concurrent Connections with Netty (2009)
1999年,我部署了一个Java Web服务器,每小时处理40,000个黄页搜索查询(服务器配备了400 MHz CPU)。2004年,我开发了一个Java应用程序,每个服务器可处理8000个并发连接(在双1.2 GHz Sparc服务器上)。有六个网关服务器和一个主服务器来控制它们并集中事件。
您的配置可能不同,但我可以说,在C#发布之前,Java已经支持高容量Web服务器。
个人而言,我不会让单个服务器拥有超过10,000个并发连接,但这只是一个经验法则,可能不再适用。您可以在单个JVM中拥有32,000个线程。在Linux上,这个数字不会太高。但是,我会在单个服务器上使用多个网关JVM,以最小化全GC时间(最小化全GC时间的最佳方法是减少垃圾数量,但这可能需要更多的努力)。
新服务器配备16GB的RAM和i7-2600K Sandy Bridge 3.4GHz处理器:在配置方面有哪些选项可尽可能地利用这些资源?我的目标是每台服务器同时在线1,200个客户端(2,400个线程)。

我想不出为什么会有问题。

应该关注哪些意想不到的陷阱和问题?

认为需要打开每个可能的命令行参数,当实际上可以去掉所有命令。如果你有4个网关JVM,每个连接300个,这可能耗尽所有内存,甚至不需要指定-Xmx设置。

Java似乎并不适用于这种类型的任务。我应该考虑将服务器迁移到另一种语言吗?

最好问问自己为什么这样认为。你有一个简单解决的问题或可能是没有根据的疑虑。

这是我的Eclipse .ini配置:

你如何配置eclipse对从eclipse运行的任何程序设置没有影响。

BufferedOutputStream适用于大多数应用程序,并且在JVM中最多可以处理1000个连接。但是Java 1.4(2002年)添加了NIO,这是一种更轻量级的方法,可将系统扩展到10,000个及以上的连接。

顺便说一下:我在2003年开发的服务器基于NIO调度程序,但除非使用Netty等标准库,否则它相当复杂。

从那时起,我成功地使用了每个连接模型的单个线程来阻塞NIO。我认为这比使用调度程序更容易管理,并且可能具有较低的延迟特性。我有一个监视器线程,定期检查连接是否在写操作上阻塞,并在需要时关闭它们。我不认为您需要每个连接两个线程,但我不认为这会对您的情况产生影响,因为您的服务器上没有足够的连接。

正如glowcoder所建议的那样,您是否考虑使用UDP来广播不太关键的信息?


我很想知道.NET如何使这个问题变得更容易。 ;) - Peter Lawrey
让我换一种方式来表述。1996年Sun发布Java时,已经发布了Solaris 2.5(64位操作系统的第5个小更新版本)。微软则早在一年前才发布了其第一个32位操作系统,旗舰产品MS Office还不支持32位。.NET推出晚了6年,直到4年前才实现了64位。Sun和Oracle长期以来一直针对更大型的服务器系统(而微软则主导桌面市场)。 - Peter Lawrey
你在C#方面的生产力经验并不让我感到惊讶。Visual Studio是一款出色的商业产品。我并不是很喜欢Eclipse,因为似乎需要大量不适合的插件才能使其功能完整。这就是为什么我使用IntelliJ,它是另一款商业产品,而且在我看来更加高效。它也有一个免费版本。 - Peter Lawrey
顺便问一下,你为什么认为NIO很难?在几千行代码中,一个人可以编写一个不错的服务器。确保每个会话的公平处理可能有点棘手。但是,每个客户端会话+每个发送线程的队列是一种简单明了的设计(并且可以完全无锁,除了很少需要的Selector.wakeup)。 - bestsss
NIO并不难,但也不像阻塞IO那样简单。我建议不要把事情搞得比必要的更复杂,如果最简单的解决方案能够胜任工作(在许多情况下都是如此),我建议采取这种方法。 - Peter Lawrey
显示剩余7条评论

4
在Java中,每个线程在堆栈上占用的内存量相同。这意味着你的主线程,假设它有32k的保留大小(我认为这是默认值),将与你的通信线程(如果你考虑一下,可能只需要1k!)具有相同的保留大小。这就是为什么Java推出了nio-这样你就不需要为每个连接创建一个线程。
让我们举个1g RAM的例子。每个线程有32k的情况下,假设我们有一半的内存用于堆栈,一半用于堆,那么我们最终得到512可用于堆栈的大小。这给了我们16,384个线程的余地。这也意味着我们的线程调度程序必须管理16,384个线程。这大大增加了其中一个线程饿死的机会。现在,如果一个人被饿死了,那么他很遗憾;如果main被饿死了,那么...大家都很遗憾!
使用nio,你只需要两个线程。主线程和通信线程。(实际上你甚至可以不用通信线程...)。现在实际上你可能会有更多的线程,比如游戏循环之类的。但是10个线程比16k个线程更容易被适当地安排!
nio并不一定易于学习,但它非常值得。
如果你不使用nio,有一件事我会考虑:每个连接只使用1个线程而不是两个线程。你不需要第二个线程进行写入:你可以有一个带队列的线程,并让它为所有客户端执行所有写入操作。这将至少使你的吞吐量翻倍。

1
作为一名独立游戏开发者,我可以告诉你,转换到 nio 并不像听起来那么痛苦。这可能需要几天时间,但您将能够为服务器上的许多客户端提供服务,只要它具有处理其游戏所需的 CPU 和 RAM(而不是受线程通信限制)。当我考虑一个 MMO 时,我的估计是使用传统套接字最多可处理1k玩家,而使用NIO则可处理15k玩家。对于我来说,购买少15倍的服务器是值得的! - corsiKa
@glowcoder,你能解释一下为什么要有两个通道吗?你可能需要同步它们,这很糟糕。如果我选择UDP,只需传输增量状态(包括背景),TCP/UDP之间唯一的区别就是服务提供商更喜欢TCP。UDP大多数情况下可以通过NAK存活,大大减少了带宽和负载。 - bestsss
@bestsss,你仍然希望玩家请求(如移动、行动等)通过TCP进行。否则,你将不得不实现自己的SYN/ACK/NAK协议,这很愚蠢。 - corsiKa
@glowcoder,是的,关于客户端请求,我主要指的是服务器->客户端,而客户端->服务器应该是负载较少的通道。 - bestsss
@bestsss,UDP 上不会有任何客户端到服务器的通信。客户端需要告诉服务器的唯一事情就是它的请求。因此,您只需要一个 UDP 通道(广播游戏状态增量),一个 TCP 通道(向服务器发送客户端请求,从服务器发送周期性游戏状态刷新以克服 UDP 数据包丢失和客户端请求结果)。 - corsiKa
显示剩余3条评论

-3

你不能考虑节点负载或线程数量。

1) 如果你的游戏需要扩展到数百万用户,你需要一个由注册表负载平衡的服务器集群。

2) 每个节点必须具有低延迟,这意味着每个传入的玩家消息和世界更新计算(1个tick)必须在毫秒内完成。这是完全可行的。不需要每个节点都有怪物配置。

3) 每秒调用30次

=> 你可以在单个节点上拥有数千个同时在线的玩家,并通过前端基础设施中的注册表将十万级别的玩家分段扩展到数百万级别,以确保玩家连接到最佳ping延迟游戏服务器。

使用此模式,每个实时游戏节点可加载10,000个同时在线玩家,每个回合制游戏节点可加载+300,000个玩家。

瓶颈通常是IO。使用SSD进行数据库存储。

Java不是问题。2400个线程是问题。您可以通过循环时间来解决问题,并且必须考虑毫秒级滴答循环。

希望对你有所帮助。

Nuggeta管理员 - 100%免费的高负载多人游戏Java服务器


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接