每个连接模型的 Java 线程与 NIO

Posted

技术标签:

【中文标题】每个连接模型的 Java 线程与 NIO【英文标题】:Java thread per connection model vs NIO 【发布时间】:2011-06-12 17:37:51 【问题描述】:

非阻塞 Java NIO 是否仍然比每个连接异步套接字的标准线程慢?

此外,如果您要为每个连接使用线程,您会创建新线程还是使用非常大的线程池?

我正在用 Java 编写一个 MMORPG 服务器,它应该能够在足够强大的硬件条件下轻松扩展 10000 个客户端,尽管客户端的最大数量是 24000(我认为每个连接模型的线程不可能达到,因为Java 中的 15000 个线程限制)。 从一篇三年前的文章中,我听说每个连接模型使用一个线程阻塞 IO 仍然比 NIO 快 25%(即这篇文档http://www.mailinator.com/tymaPaulMultithreaded.pdf),但是今天还能达到同样的效果吗?从那时起,Java 发生了很大变化,我听说在比较现实生活场景时结果是有问题的,因为使用的 VM 不是 Sun Java。 另外,因为它是一个有许多并发用户相互交互的 MMORPG 服务器,使用同步和线程安全实践是否会降低性能,以至于服务 10000 个客户端的单线程 NIO 选择器会更快? (不需要所有的工作都必须在带有选择器的线程上处理,它可以像 MINA/Netty 的工作方式一样在工作线程上处理)。

谢谢!

【问题讨论】:

10k 线程对任何(commodity)服务器来说都不是赢家 :-) 此外,单个机器上的 10k 个活动客户端非常...不太可能。 @pst:如果你的意思是商品;非量子,尚未被发现的技术,我完全同意。我认为凯文最小的问题是线程数。对于没有就此事提供任何有用的意见,我深表歉意。还要记住 QOTD:测试。 @pst 哦,甜蜜的 JRE 它的弹性!你让我的一天变得有价值。 @Captain Giraffe 我迷路了:-/ 【参考方案1】:

对 NIO 的好处应该持保留态度。

在 HTTP 服务器中,大多数连接都是保持连接,它们大部分时间都是空闲的。为每个线程预先分配一个线程会浪费资源。

对于 MMORPG,情况非常不同。我猜连接一直忙于接收用户的指令并向用户发送最新的系统状态。大多数时间都需要一个线程来进行连接。

如果您使用 NIO,您将不得不不断地为连接重新分配线程。与简单的每连接固定线程解决方案相比,这可能是一个较差的解决方案。

默认线程堆栈大小非常大,(1/4 MB?)这是只能有有限线程的主要原因。尝试减少它,看看您的系统是否可以支持更多。

但是,如果您的游戏确实非常“忙碌”,那么您最需要担心的是您的 CPU。不管 NIO 与否,在一台机器上处理数以千计的超活跃玩家真的很困难。

【讨论】:

For MMORPG things are very different. I guess connections are constantly busy receiving instructions from users and sending latest system state to users. 我想用户会去睡觉,有时,至少他们中的大多数。所以我预计会有数千个繁忙的连接,甚至更多的空闲连接。我想知道如何将它们分开,也许使用线程并让它们在 Socket 超时时死掉? 我做了一个单线程客户端,旧 io 中的单线程服务器和 nio 中的一个(nio 击败旧 io 或捆绑它......很难说......并驾齐驱。) github.com/deanhiller/webpieces/tree/master/core/… Nio 似乎是一个更好的选择,然后拥有 N 大小的线程池与 10k 套接字的比率。【参考方案2】:

其实有3种解决方案:

    多线程 单线程和NIO 同时解决方案 1 和 2 时间

为了提高性能,最好的办法是使用少量有限的线程,并在新消息通过网络进入时使用 NIO 将网络事件多路复用到这些线程上。


在一个线程中使用 NIO 是一个坏主意,原因如下:

如果您有多个 CPU 或内核,您将闲置资源,因为如果您只有一个线程,则一次只能使用一个内核。 如果您出于某种原因必须阻塞(可能是为了访问磁盘),那么当您在等待磁盘时可能正在处理另一个连接时,您的 CPU 就会处于空闲状态。

每个连接一个线程是个坏主意,因为它无法扩展。假设有:

10000 个连接 2 个 CPU,每个 CPU 有 2 个内核 在任何给定时间只会阻塞 100 个线程

然后你可以计算出你只需要 104 个线程。再多的话,您就会浪费资源来管理您不需要的额外线程。管理 10 000 个线程需要大量的簿记工作。这会让你慢下来。


这就是您将这两种解决方案结合起来的原因。此外,请确保您的 VM 正在使用最快的系统调用。每个操作系统都有自己独特的高性能网络 IO 系统调用。确保您的虚拟机使用的是最新最好的。我相信这是 Linux 中的 epoll()。

此外,如果您要使用 每个连接的线程,你会 创建新线程还是使用 非常大的线程池?

这取决于您要花多少时间进行优化。最快的解决方案是在需要时创建线程和字符串等资源。然后让垃圾收集器在您处理完它们后认领它们。您可以通过拥有资源池来提高性能。您无需创建新对象,而是向池中请求一个对象,并在完成后将其返回到池中。这增加了并发控制的复杂性。这可以通过non-blocking algorithms 等高级并发算法进一步优化。 Java API 的新版本为您提供了其中的一些。你可以用你的余生只在一个程序上做这些优化。对于您的特定应用程序而言,最佳解决方案可能是一个值得单独发布的问题。

【讨论】:

你是如何决定 104 个线程的?我看到 10000/100 + 4。但是为什么不只有 100 个线程,另外 4 个是干什么用的? 4 个阅读线程和 100 个工人?会有多个线程调用 selector.select() 吗?我目前正在探索使用个人选择器拥有多个线程的想法。我已经在 C 中完成了它,但是 NIO 语法对我来说有点不同。以循环方式向选择器注册键是我的想法。每个线程调用 select 然后产生一个线程来完成工作,或者使用一些线程池黑盒魔法。 @JustinDanielson,如果有 100 个线程被阻塞,你可以在 CPU 上再增加 4 个线程,因为有 4 个内核。再少一点,当有工作要做时就会有空闲的内核。【参考方案3】:

如果您愿意在足够强大的硬件上花费任何金钱,为什么要将自己限制在一台服务器上。谷歌不使用一台服务器,他们甚至不使用一个数据中心的服务器。

一个常见的误解是 NIO 允许非阻塞 IO,因此它是唯一值得进行基准测试的模型。如果您对阻塞 NIO 进行基准测试,您可以获得比旧 IO 快 30% 的速度。即,如果您使用相同的线程模型并仅比较 IO 模型。

对于复杂的游戏,在达到 10K 连接之前,您很可能会耗尽 CPU。同样,拥有一个水平扩展的解决方案更简单。那么您就不必担心可以获得多少连接。

多少用户可以合理互动? 24?在这种情况下,您有 1000 个独立组进行交互。您不会在一台服务器中拥有这么多内核。

您打算在服务器上为每位用户花费多少钱?您可以以不到 5000 英镑的价格购买具有 64 GB 内存的 12 核服务器。如果您在这台服务器上放置 2500 个用户,则每个用户花费了 2 英镑。

编辑:我有一个参考 http://vanillajava.blogspot.com/2010/07/java-nio-is-faster-than-java-io-for.html 这是我的。 ;) 我让 Java Networking 的一位 GURU 审查了这篇文章,它与他的发现大致一致。

【讨论】:

哇,我不知道NIO也支持阻塞IO!似乎它本身并没有引起太多关注,因此很难找到比较阻塞旧 IO 和阻塞 NIO 的基准,这是一种耻辱。不过我会考虑到这一点。 我怀疑你的业绩数字,你有参考吗?有报道称非阻塞 NIO 比传统 IO 慢 30%。然而,该测试并不现实,因为它对数据没有任何作用。只要流中的每个字节至少被读取一次,NIO/IO 的开销就变得微不足道了。 当你对数据做一些现实的事情时,网络和处理变得更加重要,NIO 或 IO 的优势在很大程度上丧失了。我使用 NIO,因为在我看来,读取延迟大约为 6 us。这对大多数开发人员来说并不是特别重要。 这是一个基准测试,显示 nio 在某些机器上击败旧 io 或将其捆绑。 github.com/deanhiller/webpieces/tree/master/core/… @DeanHiller 结果在哪里,你有没有看答案中提到的阻塞 NIO?【参考方案4】:

如果您有繁忙的连接,这意味着他们不断向您发送数据并且您将它们发回,您可以将 non-Blocking IOAkka 结合使用。

Akka 是一个开源工具包和运行时,简化了在 JVM 上构建并发和分布式应用程序。 Akka 支持多种并发编程模型,但它强调基于 Actor 的并发,灵感来自 Erlang。 Java 和 Scala 都存在语言绑定。

Akka 的逻辑是非阻塞的,因此非常适合异步编程。使用 Akka Actors 您可以删除 Thread overhead。但如果您的套接字流阻塞更频繁,我建议将 Blocking IOQuasar 结合使用

Quasar 是一个用于简单、轻量级 JVM 并发的开源库,它在 JVM 上实现了真正的轻量级线程(AKA 纤程)。 Quasar 纤程的行为与普通 Java 线程一样,只是它们几乎没有内存和任务切换开销,因此您可以在单个 JVM 中轻松生成数十万甚至数百万条纤程。 Quasar 还提供了仿照 Go 语言提供的光纤间通信通道,并配有通道选择器。它还包含 Actor 模型的完整实现,非常接近 Erlang 的模型。

Quasar 的逻辑是阻塞的,所以你可能会产生,比如 24000 根光纤在不同的连接上等待。 Quasar 的优点之一是,纤维可以很容易地与普通线程交互。此外,Quasar 还与流行的库集成,例如 Apache HTTP clientJDBCJersey 等,因此您可以在项目的许多方面使用使用 Fibers 的好处。 您可能会看到这两个框架之间的一个很好的比较here。

【讨论】:

【参考方案5】:

正如你们中的大多数人所说,在达到 10k 并发用户之前,服务器肯定会被锁定在 CPU 使用率上,考虑到以下事实,我想我最好使用线程阻塞 (N)IO 方法对于这个特定的 MMORPG,每个玩家每秒获得几个数据包并不少见,如果要使用一个选择器,可能会使选择器陷入困境。

Peter 提出了一个有趣的观点,即阻塞 NIO 比旧库更快,但不可否认的是,对于繁忙的 MMORPG 服务器,最好使用线程,因为每个玩家接收到多少指令。我不会指望有太多玩家在这个游戏中闲置,所以拥有一堆非运行线程对我来说应该不是问题。我开始意识到即使使用基于 NIO 的框架仍然需要同步,因为它们使用同时​​运行的多个工作线程来处理从客户端接收到的数据包。上下文切换可能被证明是昂贵的,但我会尝试这个解决方案。重构代码相对容易,以便在发现瓶颈时可以使用 NIO 框架。

我相信我的问题已经得到解答。我会再等一会儿,以便从更多人那里获得更多见解。感谢您的所有回答!

编辑:我终于选择了我的行动方针。实际上我犹豫不决,决定使用 JBoss Netty 并允许用户使用类在 oio 或 nio 之间切换

org.jboss.netty.channel.socket.nio.NioserverSocketChannelFactory;
org.jboss.netty.channel.socket.oio.OioServerSocketChannelFactory;

Netty 支持这两者真是太好了!

【讨论】:

【参考方案6】:

您可能会从以前由 Sun 赞助的项目(现在名为 Red Dwarf)中获得一些灵感。 http://www.reddwarfserver.org/ 的旧网站已关闭。 Github 救援:https://github.com/reddwarf-nextgen/reddwarf

【讨论】:

【参考方案7】:

如果您进行客户端网络调用,很可能您只需要普通的套接字 io。

如果您正在创建服务器端技术,那么 NIO 将帮助您将网络 io 部分与履行/处理工作分开。 IO 线程配置为 1 或 2 用于网络 IO。工作线程用于实际处理部分(范围从 1 到 N,取决于机器能力)。

【讨论】:

以上是关于每个连接模型的 Java 线程与 NIO的主要内容,如果未能解决你的问题,请参考以下文章

Java中IO与NIO的区别和使用场景

java中的NIO

java中的NIO

java nio

NIO与传统IO的区别(形象比喻)[转]

Java NIO的三种Reactor线程模型分析