在 Netty 处理程序中重用 HTTP 响应

Reusing HTTP response in Netty handler

我将 Netty 用于服务器,该服务器需要每秒处理数十万个请求,同时在响应延迟方面保持尽可能小的差异。我正在做一些最后的优化,我目前正在研究通过重用我可以重用的任何对象来减少不必要的内存分配。突出显示我的问题的服务器的简化示例如下:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.HttpObjectAggregator;

public class NettyServer {
  public void run() throws Exception {
    EventLoopGroup bossGroup = new NioEventLoopGroup();
    EventLoopGroup workerGroup = new NioEventLoopGroup();

    try {
      ServerBootstrap b = new ServerBootstrap();
      b.group(bossGroup, workerGroup)
          .channel(NioServerSocketChannel.class)
          .childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            public void initChannel(SocketChannel ch) throws Exception {
              ChannelPipeline p = ch.pipeline();
              p.addLast(new HttpServerCodec());
              p.addLast(new HttpObjectAggregator(1048576));
              p.addLast(new NettyHandler());
            }
          });

      ChannelFuture f = b.bind(8080).sync();
      f.channel().closeFuture().sync();
    } finally {
      workerGroup.shutdownGracefully();
      bossGroup.shutdownGracefully();
    }
  }

  public static void main(String[] args) throws Exception {
    new NettyServer().run();
  }
}

处理程序代码如下:

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.util.CharsetUtil;

public class NettyHandler extends SimpleChannelInboundHandler<Object> {

  private static final FullHttpResponse okResponse = OkResponse();
  private static final FullHttpResponse failResponse = FailResponse();

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

  @Override
  protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
    FullHttpRequest request = (FullHttpRequest) msg;
    QueryStringDecoder query = new QueryStringDecoder(request.getUri());
    String path = query.path();

    ChannelFuture f;
    boolean keepAlive = HttpUtil.isKeepAlive(request);

    if ("/ok".equals(path)) {
      f = ctx.write(okResponse);
    } else {
      f = ctx.write(failResponse);
      keepAlive = false;
    }

    if (!keepAlive) {
      f.addListener(ChannelFutureListener.CLOSE);
    }
  }

  private static FullHttpResponse OkResponse() {
    String data = "{ \"status\": ok }";
    FullHttpResponse response = new DefaultFullHttpResponse(
        HttpVersion.HTTP_1_1,
        HttpResponseStatus.OK,
        Unpooled.copiedBuffer(data, CharsetUtil.UTF_8)
    );
    response.headers().set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON);
    response.headers().set(HttpHeaderNames.CACHE_CONTROL, "max-age=0, no-cache, must-revalidate, proxy-revalidate");
    return response;
  }

  private static FullHttpResponse FailResponse() {
    String data = "{ \"status\": fail }";
    FullHttpResponse response = new DefaultFullHttpResponse(
        HttpVersion.HTTP_1_1,
        HttpResponseStatus.OK,
        Unpooled.copiedBuffer(data, CharsetUtil.UTF_8)
    );
    response.headers().set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON);
    response.headers().set(HttpHeaderNames.CACHE_CONTROL, "max-age=0, no-cache, must-revalidate, proxy-revalidate");
    return response;
  }
}

处理程序显示了我要完成的任务。处理程序包含固定 HTTP 响应的静态实例。对于服务器,除错误代码外的所有响应都来自一个小组,并且可以预先构建。使用上面的代码,对处理程序的第二个查询将失败,因为 Netty 的响应引用计数已降至零。我原以为只要在对象上调用 retain() 就足够了,但它看起来不像是这样。

在请求之间重用 HTTP 响应对象的最有效方法是什么?

您应该调用 retainedDuplicate(),否则 readerIndex 等可能会变为“无效”