java Netty 比 Tomcat 慢

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12806678/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 10:18:05  来源:igfitidea点击:

Netty slower than Tomcat

javatomcationetty

提问by Brian Pontarelli

We just finished building a server to store data to disk and fronted it with Netty. During load testing we were seeing Netty scaling to about 8,000 messages per second. Given our systems, this looked really low. For a benchmark, we wrote a Tomcat front-end and run the same load tests. With these tests we were getting roughly 25,000 messages per second.

我们刚刚完成了将数据存储到磁盘的服务器的构建,并在其前端使用 Netty。在负载测试期间,我们看到 Netty 扩展到每秒大约 8,000 条消息。鉴于我们的系统,这看起来非常低。对于基准测试,我们编写了一个 Tomcat 前端并运行相同的负载测试。通过这些测试,我们每秒收到大约 25,000 条消息。

Here are the specs for our load testing machine:

以下是我们的负载测试机的规格:

  • Macbook Pro Quad core
  • 16GB of RAM
  • Java 1.6
  • Macbook Pro 四核
  • 16GB 内存
  • 爪哇 1.6

Here is the load test setup for Netty:

这是 Netty 的负载测试设置:

  • 10 threads
  • 100,000 messages per thread
  • Netty server code (pretty standard) - our Netty pipeline on the server is two handlers: a FrameDecoder and a SimpleChannelHandler that handles the request and response.
  • Client side JIO using Commons Pool to pool and reuse connections (the pool was sized the same as the # of threads)
  • 10个线程
  • 每个线程 100,000 条消息
  • Netty 服务器代码(非常标准)——我们在服务器上的 Netty 管道是两个处理程序:一个 FrameDecoder 和一个处理请求和响应的 SimpleChannelHandler。
  • 客户端 JIO 使用 Commons Pool 来池化和重用连接(池的大小与线程数相同)

Here is the load test setup for Tomcat:

这是 Tomcat 的负载测试设置:

  • 10 threads
  • 100,000 messages per thread
  • Tomcat 7.0.16 with default configuration using a Servlet to call the server code
  • Client side using URLConnection without any pooling
  • 10个线程
  • 每个线程 100,000 条消息
  • Tomcat 7.0.16 默认配置使用 Servlet 调用服务器代码
  • 客户端使用 URLConnection 没有任何池

My main question is why such a huge different in performance? Is there something obvious with respect to Netty that can get it to run faster than Tomcat?

我的主要问题是为什么性能会有如此巨大的不同?Netty 有什么明显的东西可以让它比 Tomcat 运行得更快吗?

Edit: Here is the main Netty server code:

编辑:这是主要的 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);

Our handlers look like this:

我们的处理程序看起来像这样:

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());
  }
}

UPDATE:

更新

I've managed to get my Netty solution to within 4,000/second. A few weeks back I was testing a client side PING in my connection pool as a safe guard against idle sockets but I forgot to remove that code before I started load testing. This code effectively PINGed the server every time a Socket was checked out from the pool (using Commons Pool). I commented that code out and I'm now getting 21,000/second with Netty and 25,000/second with Tomcat.

我已经设法让我的 Netty 解决方案达到每秒 4,000 次之内。几周前,我在我的连接池中测试客户端 PING,以防止空闲套接字,但我在开始负载测试之前忘记删除该代码。每次从池中检出 Socket 时,此代码都会有效地 PING 服务器(使用 Commons Pool)。我注释掉了该代码,现在使用 Netty 获得 21,000/秒,使用 Tomcat 获得 25,000/秒。

Although, this is great news on the Netty side, I'm still getting 4,000/second less with Netty than Tomcat. I can post my client side (which I thought I had ruled out but apparently not) if anyone is interested in seeing that.

尽管这对 Netty 来说是个好消息,但我使用 Netty 的速度仍然比使用 Tomcat 少 4,000/秒。如果有人有兴趣看到,我可以发布我的客户端(我认为我已经排除了但显然没有)。

回答by Ben-Hur Langoni Junior

The method messageReceivedis executed using a worker thread that is possibly getting blocked by RequestHandler#handlewhich may be busy doing some I/O work. You could try adding into the channel pipeline an OrderdMemoryAwareThreadPoolExecutor(recommended) for executing the handlers or alternatively, try dispatching your handler work to a new ThreadPoolExecutorand passing a reference to the socket channel for later writing the response back to client. Ex.:

该方法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());
}