Netty性能分析

16

我正在编写一个Netty应用程序。该应用程序在一台64位八核Linux服务器上运行。

Netty应用程序是一个简单的路由器,它接受请求(传入管道),从请求中读取一些元数据并将数据转发到远程服务(传出管道)。

此远程服务将向传出管道返回一个或多个响应。 Netty应用程序将把这些响应路由回源客户端(即传入管道)。

将有数千个客户端和数千个远程服务。

我正在进行一些小规模测试(十个客户端,十个远程服务),但在99.9百分位时,我没有看到期望的低于10毫秒的性能。我从客户端和服务器端测量延迟。

我使用了与SPDY类似的完全异步协议。当我们在FrameDecoder中处理第一个字节时,我捕获时间(我只是使用System.nanoTime())。在调用channel.write()之前,我停止计时器。我从传入管道到传出管道以及从传出管道到传入管道测量亚毫秒时间(99.9百分位)。

我还测量了从FrameDecoder中的第一个字节到(以上)message.write()上调用ChannelFutureListener回调的时间。时间是高十毫秒(99.9百分位),但我很难说服自己这是有用的数据。

我的最初想法是我们有一些慢客户端。我观察了channel.isWritable()并记录了当此方法返回false时的情况。在正常情况下,此方法不会返回false。

一些事实:

  • 我们正在使用NIO工厂。我们没有自定义工作程序大小
  • 我们已禁用Nagle(tcpNoDelay = true)
  • 我们启用了保持活动(keepAlive = true)
  • CPU 90%以上的时间处于空闲状态
  • 网络处于空闲状态
  • GC(CMS)每100秒左右被调用一次,时间非常短

是否有一种调试技术可以跟踪以确定为什么我的Netty应用程序运行速度不如我认为它应该?

感觉channel.write()将消息添加到队列中,但是我们(使用Netty的应用程序开发人员)无法透明地了解这个队列。我不知道这个队列是一个Netty队列、操作系统队列、网络卡队列还是其他什么队列。无论如何,我正在审查现有应用程序的示例,我没有看到我遵循的任何反模式。

感谢任何帮助/见解。


1
你的基准测试方法可能没有你想象的那么准确。请查看http://code.google.com/p/caliper/wiki/JavaMicrobenchmarks,了解一些很好的见解。 - sworisbreathing
也许我正在进行微基准测试。在测量延迟的同时,我正在对应用程序进行负载测试。此外,我正在进行大量的测量(每秒1000个请求,在十秒内99.9个百分点)。很明显,当服务处于负载状态时,客户端体验是不可接受的。我如何深入挖掘Netty并弄清楚额外的延迟是我的代码、Netty的代码、操作系统、网络卡、网络等引起的呢?就我所知,我的代码使用的墙钟时间少于一微秒。 - Jake Carr
这是一个开源项目吗? 你找到问题了吗? - matanster
这不是一个开源项目。我从未发现过这个问题。 - Jake Carr
为什么不在您的Netty应用程序上插入JProfiler并使用它来分析性能呢? - Alexander Jardim
3个回答

2

默认情况下,Netty会创建 Runtime.getRuntime().availableProcessors() * 2 个工作线程。在您的情况下是16个。这意味着您可以同时处理最多16个通道,其他通道将等待直到您释放ChannelUpstreamHandler.handleUpstream/SimpleChannelHandler.messageReceived处理程序。因此,请不要在这些(IO)线程中执行大量操作,否则可能会阻塞其他通道。


0
你没有指定你的Netty版本,但听起来像是Netty 3。 Netty 4现在已经稳定,我建议你尽快更新到它。 你已经指定了你想要超低延迟时间,以及数万个客户端和服务。这并不是很兼容。NIO本质上相对于OIO具有合理的延迟。然而,这里的陷阱是OIO可能无法达到你所希望的客户端数量。尽管如此,我会使用一个OIO事件循环/工厂,看看情况如何。
我自己有一个TCP服务器,在本地主机上发送和接收几个TCP数据包需要大约30毫秒(从客户端打开套接字到服务器关闭它的时间)。如果你确实需要如此低的延迟,我建议你切换到TCP之外的协议,因为打开连接需要SYN/ACK垃圾邮件,这将占用你10ms的大部分时间。

0
在多线程环境中测量时间非常困难,如果您使用的是像System.nanoTime()这样的简单工具。想象一下在一个1核心系统上发生以下情况:
  1. 唤醒线程A并开始处理传入请求。
  2. 唤醒线程B并开始处理传入请求。但由于我们正在使用1核心机器,这最终需要将线程A暂停。
  3. 线程B完成并表现得非常快速。
  4. 线程A恢复并完成,但所花费的时间是线程B的两倍。因为您实际上测量了线程A + 线程B完成所需的时间。

在这种情况下,有两种正确测量时间的方法:

  1. 您可以强制使用一个线程来执行操作。
    这样可以测量操作的确切性能,如果操作系统不干扰的话。因为在上面的例子中,线程B也可能在您的程序之外。在这种情况下,常见的方法是对干扰进行中位数处理,这将给出您代码速度的估计值
    但是,您可以假设,在其他空闲的多核系统上,会有另一个核心来处理后台任务,因此您的测量通常不会被中断。将此线程设置为高优先级也有帮助。

  2. 您可以使用更复杂的工具插入JVM来实际测量原子执行和所需时间,这将几乎完全消除外部干扰。其中一个工具是VisualVM,它已经集成在NetBeans中,并作为Eclipse的插件提供。

一般建议:除非您知道这些线程经常会被某些操作阻塞,否则不要使用比核心更多的线程。当使用非阻塞NIO进行IO操作时,情况并非如此,因为没有阻塞。

因此,在您的特殊情况下,正如上面所解释的那样,您实际上会降低客户端的性能,因为在高负载下通信将被暂停多达50%的时间。在最坏的情况下,这可能会导致客户端甚至超时,因为没有保证线程何时实际恢复(除非您明确请求公平调度)。

“使用比核心更多的线程并不是一个好主意(…)但在使用非阻塞NIO时情况并非如此。” 我完全同意你的说法,你有什么想法为什么Netty默认使用核心* 2作为线程数?我也会将其限制为核心数。 - Oliver Hausler

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