我们刚刚构建了一个用于将数据存储到磁盘并使用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服务器代码:
以下是我们负载测试机器的规格:
- 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次。如果有人感兴趣看到我的客户端代码(我认为已经排除故障,但显然不是),我可以发布它。