データをディスクに保存するサーバーの構築を完了し、Netty を前面に配置しました。負荷テスト中に、Netty が 1 秒あたり約 8,000 メッセージにスケーリングしていることがわかりました。私たちのシステムを考えると、これは非常に低く見えました。ベンチマークのために、Tomcat フロントエンドを作成し、同じ負荷テストを実行しました。これらのテストでは、1 秒あたり約 25,000 のメッセージを取得していました。
負荷テスト マシンの仕様は次のとおりです。
- Macbook Pro クアッドコア
- 16GBのRAM
- Java 1.6
Netty の負荷テストのセットアップは次のとおりです。
- 10スレッド
- スレッドあたり 100,000 メッセージ
- Netty サーバー コード (かなり標準的) - サーバー上の Netty パイプラインは、FrameDecoder と、要求と応答を処理する SimpleChannelHandler の 2 つのハンドラーです。
- Commons Pool を使用して接続をプールおよび再利用するクライアント側 JIO (プールのサイズはスレッド数と同じ)
Tomcat の負荷テストのセットアップは次のとおりです。
- 10スレッド
- スレッドあたり 100,000 メッセージ
- サーブレットを使用してサーバーコードを呼び出すデフォルト構成のTomcat 7.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());
}
}
更新:
Netty ソリューションを 4,000/秒以内にすることができました。数週間前、アイドル状態のソケットに対する安全策として接続プールでクライアント側の PING をテストしていましたが、負荷テストを開始する前にそのコードを削除するのを忘れていました。このコードは、(Commons Pool を使用して) Socket がプールからチェックアウトされるたびにサーバーを効果的に PING します。そのコードをコメントアウトしたところ、Netty で 21,000/秒、Tomcat で 25,000/秒になりました。
これは Netty 側にとっては朗報ですが、Tomcat よりも Netty の方が毎秒 4,000 少ない結果が得られます。誰かがそれを見ることに興味がある場合は、クライアント側を投稿できます(除外したと思っていましたが、明らかにそうではありません)。