例子是基于 Netty 4.1.25.Final + Spring Boot 2 + JDK 1.8 + Maven
demo:
https://github.com/emacsist/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.