例子是基于 Netty 4.1.25.Final + Spring Boot 2 + JDK 1.8 + Maven

github netty http demo

AppInitializer.java

package io.github.emacsist.netty.httpdemo.config;

import io.github.emacsist.netty.httpdemo.handler.AppHandler;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @author emacsist
 */
@Component
@ChannelHandler.Sharable
public class AppInitializer extends ChannelInitializer {
    private static final int MB = 1024 * 1024;
    @Autowired
    private AppHandler appHandler;

    @Override
    protected void initChannel(final Channel channel) {
        final ChannelPipeline p = channel.pipeline();
        p.addLast(new HttpServerCodec());
        p.addLast(new HttpObjectAggregator(1 * MB));
        p.addLast(appHandler);
    }
}

AppHandler.java

package io.github.emacsist.netty.httpdemo.handler;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.AsciiString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * @author emacsist
 */
@Component
@ChannelHandler.Sharable
public class AppHandler extends SimpleChannelInboundHandler<FullHttpRequest> {


    private static final Logger log = LoggerFactory.getLogger(AppHandler.class);

    private static final AsciiString CONTENT_TYPE = AsciiString.cached("Content-Type");
    private static final AsciiString TEXT_PLAIN = AsciiString.cached("text/plain; charset=utf-8");
    private static final AsciiString CONTENT_LENGTH = AsciiString.cached("Content-Length");


    @Override
    protected void channelRead0(final ChannelHandlerContext ctx, final FullHttpRequest fullHttpRequest) {
        final String uri = fullHttpRequest.uri();
        final QueryStringDecoder queryStringDecoder = new QueryStringDecoder(uri);
        final String requestPath = queryStringDecoder.path();


        String body = "";
        switch (requestPath) {
            case "/hello":
                log.info("in hello => {}", queryStringDecoder.parameters());
                //请自行检测参数, 这里假设  /hello 是会带上 ?name=world 类似这参数值的
                body = "Hello " + queryStringDecoder.parameters().get("name").get(0);
                break;
            case "/netty":
                log.info("in netty => {}", queryStringDecoder.parameters());
                body = "Hello Netty.";
                break;
            default:
                break;
        }

        final DefaultFullHttpResponse defaultFullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer(body.getBytes()));
        defaultFullHttpResponse.headers().set(CONTENT_TYPE, TEXT_PLAIN);
        defaultFullHttpResponse.headers().set(CONTENT_LENGTH, defaultFullHttpResponse.content().readableBytes());
        ctx.write(defaultFullHttpResponse);
    }

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

    @Override
    public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) {
        log.error(cause.getMessage(), cause);
        ctx.close();
    }
}

HttpDemoApplication.java

package io.github.emacsist.netty.httpdemo;

import io.github.emacsist.netty.httpdemo.config.AppInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author emacsist
 */
@SpringBootApplication
public class HttpDemoApplication implements CommandLineRunner {

    @Value("${server.port}")
    private int port;
    private static final int KB = 1024;
    @Autowired
    private AppInitializer appInitializer;

    public static void main(final String[] args) {
        SpringApplication.run(HttpDemoApplication.class, args);
    }

    @Override
    public void run(final String... args) {
        final ServerBootstrap serverBootstrap = new ServerBootstrap();
        final EventLoopGroup master = new NioEventLoopGroup();
        final EventLoopGroup worker = new NioEventLoopGroup();
        try {
            serverBootstrap
                    .option(ChannelOption.SO_BACKLOG, 4 * KB)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .group(master, worker)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.DEBUG))
                    .childHandler(appInitializer);
            final Channel ch = serverBootstrap.bind(port).sync().channel();
            System.out.println("start app ok...");
            ch.closeFuture().sync();
        } catch (final InterruptedException e) {
            //ignore
        } finally {
            master.shutdownGracefully();
            worker.shutdownGracefully();
            System.out.println("stop app ok...");
        }
    }
}

注意

编写自己的HTTP业务时, 请记住千万不要阻塞EventLoop线程~, 不然会导致 Netty 的性能急剧下降.

当要处理一些阻塞操作时, 请用其他的线程池来处理.在Netty中一般有两种方式:

ChannelPipeline 指定 executor:

例如这样子:

/**
 * @author emacsist
 */
@Component
@ChannelHandler.Sharable
public class AppInitializer extends ChannelInitializer {
    private static final int MB = 1024 * 1024;
    @Autowired
    private AppHandler appHandler;

    private static final DefaultEventExecutorGroup executorGroup = new DefaultEventExecutorGroup(Runtime.getRuntime().availableProcessors() * 2);

    @Override
    protected void initChannel(final Channel channel) {
        final ChannelPipeline p = channel.pipeline();
        p.addLast(new HttpServerCodec());
        p.addLast(new HttpObjectAggregator(1 * MB));
        p.addLast(executorGroup, appHandler);
    }
}

创建一个全局的 ExecutorGroup , 自己手工创建任务

executorGroup.submit(() -> maxService.dealClick(queryStringDecoder));

这个是公司项目代码的一个片段, 要将HTTP参数 接收-> 解密 -> 进队和处理 Redis, 这些都是阻塞操作来的, 所以这里创建了一个异步任务来处理这些耗时的操作, 以免阻塞 EventLoop 线程.

性能

经过测试, 还是手工这种按需处理异步任务的性能更高点. 差不多高1倍. 当然, 具体问题要具体分析, 总之就是不要阻塞 EventLoop 线程就好, 毕竟Netty高性能的核心, 就在于不要阻塞 EventLoop.