Netty比Tomcat慢

26
我们刚刚构建了一个用于将数据存储到磁盘并使用Netty作为前端的服务器。在负载测试期间,我们发现Netty每秒可以扩展到大约8,000个消息。考虑到我们的系统,这看起来非常低。为了进行基准测试,我们编写了一个Tomcat前端并运行相同的负载测试。通过这些测试,我们获得了大约25,000个消息每秒。
以下是我们负载测试机器的规格:
- Macbook Pro四核心 - 16GB RAM - Java 1.6
以下是Netty的负载测试设置:
- 10个线程 - 每个线程100,000个消息 - Netty服务器代码(相当标准) - 我们的Netty管道在服务器上有两个处理程序:FrameDecoder和SimpleChannelHandler,用于处理请求和响应。 - 客户端JIO使用Commons Pool来池化和重复使用连接(池的大小与线程数相同)
以下是Tomcat的负载测试设置:
- 10个线程 - 每个线程100,000个消息 - 使用Servlet调用服务器代码的默认配置下的Tomcat 7.0.16 - 客户端使用URLConnection而没有任何池化
我的主要问题是为什么性能存在如此巨大的差异?有关于Netty显著提高性能的明显事项吗?
编辑:以下是主要的Netty服务器代码:
NioServerSocketChannelFactory factory = new NioServerSocketChannelFactory();
ServerBootstrap server = new ServerBootstrap(factory);
server.setPipelineFactory(new ChannelPipelineFactory() {
  public ChannelPipeline getPipeline() {
    RequestDecoder decoder = injector.getInstance(RequestDecoder.class);
    ContentStoreChannelHandler handler = injector.getInstance(ContentStoreChannelHandler.class);
    return Channels.pipeline(decoder, handler);
  }
});

server.setOption("child.tcpNoDelay", true);
server.setOption("child.keepAlive", true);
Channel channel = server.bind(new InetSocketAddress(port));
allChannels.add(channel);
我们的处理函数看起来像这样:
public class RequestDecoder extends FrameDecoder {
  @Override
  protected ChannelBuffer decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) {
    if (buffer.readableBytes() < 4) {
      return null;
    }

    buffer.markReaderIndex();
    int length = buffer.readInt();
    if (buffer.readableBytes() < length) {
      buffer.resetReaderIndex();
      return null;
    }

    return buffer;
  }
}

public class ContentStoreChannelHandler extends SimpleChannelHandler {
  private final RequestHandler handler;

  @Inject
  public ContentStoreChannelHandler(RequestHandler handler) {
    this.handler = handler;
  }

  @Override
  public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
    ChannelBuffer in = (ChannelBuffer) e.getMessage();
    in.readerIndex(4);

    ChannelBuffer out = ChannelBuffers.dynamicBuffer(512);
    out.writerIndex(8); // Skip the length and status code

    boolean success = handler.handle(new ChannelBufferInputStream(in), new ChannelBufferOutputStream(out), new NettyErrorStream(out));
    if (success) {
      out.setInt(0, out.writerIndex() - 8); // length
      out.setInt(4, 0); // Status
    }

    Channels.write(e.getChannel(), out, e.getRemoteAddress());
  }

  @Override
  public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
    Throwable throwable = e.getCause();
    ChannelBuffer out = ChannelBuffers.dynamicBuffer(8);
    out.writeInt(0); // Length
    out.writeInt(Errors.generalException.getCode()); // status

    Channels.write(ctx, e.getFuture(), out);
  }

  @Override
  public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) {
    NettyContentStoreServer.allChannels.add(e.getChannel());
  }
}

更新:

我已经成功将我的Netty方案优化到每秒4000次的水平。几周前,我在连接池中测试客户端PONG作为一种安全保护措施以防止空闲套接字,但是我在开始负载测试之前忘记移除该代码。这段代码实际上会在每次从池中检出Socket(使用Commons Pool)时PING服务器。我注释掉了这段代码,现在使用Netty可以达到每秒21000次,使用Tomcat可以达到每秒25000次。

虽然对于Netty来说这是一个好消息,但与Tomcat相比,我仍然每秒少4000次。如果有人感兴趣看到我的客户端代码(我认为已经排除故障,但显然不是),我可以发布它。


1
如果加载器设置不同,您如何将结果差异归因于服务器? - forty-two
好奇心大发 :) 这个问题解决了吗?Netty 是否击败了 Tomcat? - Yuriy Nakonechnyy
@Yura - 我们从未回到Netty做更多的测试。事实上,那个应用从未进入生产。我们可能会在今年某个时候重新启动它。我很可能会尝试使用最新版本的Netty并看看是否可以使其工作。 - voidmain
1
@BrianPontarelli 好的,谢谢回复 - 很想听听结果 :) 我可能还会测试Netty与Tomcat的性能以比较两者,因为奇怪的是,这个Q/A是我通过Google找到的唯一一个足够充分的两者之间的基准测试。 - Yuriy Nakonechnyy
你可以使用jvisualvm或top来检查它们的资源消耗。如果Netty使用的资源比Tomcat少,那么你可以想象你可以运行几个该服务器的副本并分担负载。或者只测试一小部分负载。基本上,Netty或非阻塞的想法是从一开始就便宜并且具有弹性,我认为这是Java从一开始就缺少的东西(大而强大的JVM具有大量线程/工作程序以实现高性能)。 - HoaPhan
显示剩余27条评论
1个回答

2

方法messageReceived会使用工作线程执行,这个线程可能被RequestHandler#handle阻塞,因为后者正在忙于进行一些I/O操作。

你可以尝试在通道管道中添加一个OrderdMemoryAwareThreadPoolExecutor推荐)来执行处理程序,或者尝试将处理程序工作分派到新的ThreadPoolExecutor中,并传递一个对套接字通道的引用以便稍后将响应写回客户端。例如:

@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {   

    executor.submit(new Runnable() {
        processHandlerAndRespond(e);        
    });
}

private void processHandlerAndRespond(MessageEvent e) {

    ChannelBuffer in = (ChannelBuffer) e.getMessage();
    in.readerIndex(4);
    ChannelBuffer out = ChannelBuffers.dynamicBuffer(512);
    out.writerIndex(8); // Skip the length and status code
    boolean success = handler.handle(new ChannelBufferInputStream(in), new ChannelBufferOutputStream(out), new NettyErrorStream(out));
    if (success) {
        out.setInt(0, out.writerIndex() - 8); // length
        out.setInt(4, 0); // Status
    }
    Channels.write(e.getChannel(), out, e.getRemoteAddress());
} 

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