Netty 作为高性能 Http 服务器,每秒可处理约 2-3 百万个请求

Sen*_*mar 3 java client-server http netty

我们正在尝试解决处理大量 Http POST 请求的问题,而在使用 Netty Server 时,我只能处理~50K requests/sec太低的请求。

我的问题是如何调整此服务器以确保处理> 1.5 million requests/second

Netty4 服务器

// Configure the server.
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
        ServerBootstrap b = new ServerBootstrap();
        b.option(ChannelOption.SO_BACKLOG, 1024);

        b.group(bossGroup, workerGroup)
         .channel(NioServerSocketChannel.class)
         .handler(new LoggingHandler(LogLevel.INFO))
         .childHandler(new HttpServerInitializer(sslCtx));

        Channel ch = b.bind(PORT).sync().channel();

        System.err.println("Open your web browser and navigate to " +
                (SSL? "https" : "http") + "://127.0.0.1:" + PORT + '/');

        ch.closeFuture().sync();
    } finally {
        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
    }
Run Code Online (Sandbox Code Playgroud)

初始化程序

    public class HttpServerInitializer extends ChannelInitializer<SocketChannel> {

    private final SslContext sslCtx;

    public HttpServerInitializer(SslContext sslCtx) {
        this.sslCtx = sslCtx;
    }

    @Override
    public void initChannel(SocketChannel ch) {
        ChannelPipeline p = ch.pipeline();
        if (sslCtx != null) {
            p.addLast(sslCtx.newHandler(ch.alloc()));
        }
        p.addLast(new HttpServerCodec());
        p.addLast("aggregator", new HttpObjectAggregator(Integer.MAX_VALUE));
        p.addLast(new HttpServerHandler());
    }
}
Run Code Online (Sandbox Code Playgroud)

处理程序

public class HttpServerHandler extends ChannelInboundHandlerAdapter {
private static final String CONTENT = "SUCCESS";

@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
    ctx.flush();
}

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    if (msg instanceof HttpRequest) {
        HttpRequest req = (HttpRequest) msg;
        final FullHttpRequest fReq = (FullHttpRequest) req;
        Charset utf8 = CharsetUtil.UTF_8;
        final ByteBuf buf = fReq.content();
        String in = buf.toString( utf8 );
        System.out.println(" In ==> "+in);
        buf.release();
        if (HttpHeaders.is100ContinueExpected(req)) {
            ctx.write(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE));
        }
        in = null;
        if (HttpHeaders.is100ContinueExpected(req)) {
            ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE));
        }
        boolean keepAlive = HttpHeaders.isKeepAlive(req);
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(CONTENT.getBytes()));
        response.headers().set(CONTENT_TYPE, "text/plain");
        response.headers().set(CONTENT_LENGTH, response.content().readableBytes());

        if (!keepAlive) {
            ctx.write(response).addListener(ChannelFutureListener.CLOSE);
        } else {
            response.headers().set(CONNECTION, Values.KEEP_ALIVE);
            ctx.write(response);
        }
    }
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
{
        cause.printStackTrace();
        ctx.close();
    }

}
Run Code Online (Sandbox Code Playgroud)

Dmi*_*kiy 6

你的问题很笼统。但是,我会尝试为您提供有关 netty 优化和代码改进的答案。

您的代码问题:

  1. System.out.println(" In ==> "+in);- 您不应该在高负载并流处理程序中使用它。为什么?因为println方法内部的代码是同步的,因此会降低您的性能;
  2. 你做 2 个班级演员。到HttpRequest和到FullHttpRequest。你可以只用最后一个;

代码中的 Netty 特定问题:

  1. 您需要添加 epoll 传输(如果您的服务器类似于 Linux)。它将提供 + ~30% 开箱即用;如何
  2. 您需要添加本机 OpenSSL 绑定。它将提供 + ~20%。如何
  3. EventLoopGroup bossGroup = new NioEventLoopGroup();- 您需要正确设置bossGroupworkerGroup组的大小。取决于您的测试场景。你没有提供任何关于你的测试用例的信息,所以我不能在这里给你建议;
  4. new HttpObjectAggregator(Integer.MAX_VALUE)- 你的代码中实际上不需要这个处理程序。因此,为了获得更好的性能,您可以将其删除。
  5. new HttpServerHandler()- 您不需要为每个频道都创建这个处理程序。由于它不持有任何状态,因此可以在所有管道之间共享。@Sharable在 netty 中搜索。
  6. new LoggingHandler(LogLevel.INFO)- 你不需要这个处理程序来进行高负载测试,因为它记录了很多。必要时进行自己的日志记录;
  7. buf.toString( utf8 )——这是非常错误的。您将收入字节转换为字符串。但这没有任何意义,因为所有数据都已经在 netty 中解码了HttpServerCodec。所以你在这里做双重工作;
  8. Unpooled.wrappedBuffer(CONTENT.getBytes())- 您在每个请求上都包装恒定的消息。因此 - 对每个请求做不必要的工作。您可以只创建一次 ByteBuf 并执行retain()duplicate()这取决于您将如何执行此操作;
  9. ctx.write(response)- 您可以考虑使用ctx.write(response, ctx.voidPromise())以减少分配;

这还不是全部。但是,解决上述问题将是一个好的开始。