web-dev-qa-db-ja.com

NettyはTomcatより遅い

データをディスクに保存するサーバーの構築が完了し、Nettyを前面に配置しました。負荷テスト中に、Nettyが1秒あたり約8,000メッセージにスケーリングするのを確認しました。私たちのシステムを考えると、これは本当に低く見えました。ベンチマークとして、Tomcatフロントエンドを作成し、同じ負荷テストを実行しました。これらのテストでは、1秒あたり約25,000件のメッセージを受信して​​いました。

負荷テストマシンの仕様は次のとおりです。

  • MacbookProクアッドコア
  • 16GBのRAM
  • Java 1.6

Nettyの負荷テストのセットアップは次のとおりです。

  • 10スレッド
  • スレッドあたり100,000メッセージ
  • Nettyサーバーコード(かなり標準)-サーバー上のNettyパイプラインは、FrameDecoderと要求と応答を処理するSimpleChannelHandlerの2つのハンドラーです。
  • Commons Poolを使用して接続をプールおよび再利用するクライアント側JIO(プールのサイズはスレッド数と同じでした)

Tomcatの負荷テストのセットアップは次のとおりです。

  • 10スレッド
  • スレッドあたり100,000メッセージ
  • サーブレットを使用してサーバーコードを呼び出すデフォルト構成のTomcat7.0.16
  • プーリングなしでURLConnectionを使用するクライアント側

私の主な質問は、なぜパフォーマンスがこれほど大きく異なるのかということです。 Nettyに関して、Tomcatよりも高速に実行できる明らかなものはありますか?

編集:主な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());
  }
}

[〜#〜] update [〜#〜]

Nettyソリューションを4,000 /秒以内に収めることができました。数週間前、アイドル状態のソケットに対する安全策として、接続プールでクライアント側のPINGをテストしていましたが、負荷テストを開始する前にそのコードを削除するのを忘れていました。このコードは、ソケットがプールからチェックアウトされるたびに(Commons Poolを使用して)サーバーに効果的にPINGを実行しました。そのコードをコメントアウトしたところ、Nettyで21,000 /秒、Tomcatで25,000 /秒になりました。

これはNetty側にとっては素晴らしいニュースですが、NettyではTomcatよりも4,000 /秒少なくなっています。誰かがそれを見たいと思ったら、クライアント側を投稿することができます(私は除外したと思っていましたが、明らかにそうではありませんでした)。

26

メソッドmessageReceivedは、I/O作業の実行でビジー状態になっている可能性のあるRequestHandler#handleによってブロックされている可能性のあるワーカースレッドを使用して実行されます。チャネルパイプラインにハンドラーを実行するためのOrderdMemoryAwareThreadPoolExecutorrecommended)を追加するか、ハンドラーの作業をnew 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());
}