第一章提出“Netty作为服务端启动时偶尔会有服务端意外退出的现象”的问题。原因:Netty作为服务端启动,为了防止服务端意外退出,应该防止EventLoopGroup意外关闭,因为EventLoopGroup是非守护线程,只要它没有退出,则JVM不会关闭。
若启动线程是main线程,且优雅关闭作业也在main线程,则启动完成后需要阻塞线程,否则finally块会关闭EventLoopGroup:
正确的用法示例:
EventLoopGroup executors = new NioEventLoopGroup(3);
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(executors)
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.channel(NioServerSocketChannel.class)
.localAddress(serverPort)
.childHandler(new NATChannelInitializer(true));
try {
ChannelFuture future = bootstrap.bind().sync();
future.channel().closeFuture().sync(); // 阻塞main线程
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
executors.shutdownGracefully();
}
错误的用法示例:
EventLoopGroup executors = new NioEventLoopGroup(3);
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(executors)
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.channel(NioServerSocketChannel.class)
.localAddress(serverPort)
.childHandler(new NATChannelInitializer(true));
try {
ChannelFuture future = bootstrap.bind().sync();
// 没有阻塞main线程,程序继续执行到finally块,导致EventLoopGroup关闭,服务端意外退出
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
executors.shutdownGracefully();
}
若启动线程不是main线程,即启动线程由tomcat或spring boot某个方法调用启动,则这时不应该阻塞线程,应该让调用立即返回,优雅关闭作业可以放在Channel的CloseListener中。
错误的用法示例(同样的代码,在main线程中是正确的,在方法调用中是错误的):
EventLoopGroup executors = new NioEventLoopGroup(3);
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(executors)
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.channel(NioServerSocketChannel.class)
.localAddress(serverPort)
.childHandler(new NATChannelInitializer(true));
try {
ChannelFuture future = bootstrap.bind().sync();
future.channel().closeFuture().sync(); // 阻塞了调用线程,调用不能立即返回
} catch (InterruptedException e) {
e.printStackTrace();
}
正确的示例:
EventLoopGroup executors = new NioEventLoopGroup(3);
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(executors)
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.channel(NioServerSocketChannel.class)
.localAddress(serverPort)
.childHandler(new NATChannelInitializer(true));
try {
ChannelFuture future = bootstrap.bind().sync();
// 添加监听器执行优雅关闭作业
future.channel().closeFuture().addListener((future)->{
executors.shutdownGracefully();
})
// 不阻塞调用线程,调用立即返回
} catch (InterruptedException e) {
e.printStackTrace();
}
所以最好的办法是,不论什么线程启动Netty,优雅关闭作业放在closeFuture中,不阻塞调用线程。
Netty优雅关闭作业shutdownGracefully(),该方法位于EventExecutorGroup接口中,在netty-4.1.36.Final,EventLoop(单线程线程池SingleThreadEventExecutor)和EventLoopGroup(多线程线程池MultithreadEventExecutorGroup)都有实现。关闭过程中的工作包括:(1)设置线程状态码(2)发送尚未发送或正在发送的消息(3)完成到期的定时任务(4)执行NIO线程的Hook(5)Channel释放,剩余定时任务释放,多路复用器注销,退出EventLoop线程。
第二章提出“客户端连接池资源泄露,出现OOM错误”。原因:错误的用法是每初始化一个TCP连接时初始化了一个EventLoopGroup,EventLoopGroup资源占用比EventLoop大,然而一个EventLoopGroup可以管理不止一个TCP连接(EventLoop)。Netty作为客户端实际上是把Netty作为TCP连接池使用,Redis的Java客户端Redisson就使用了Netty作为连接池,可以参考其用法。
正确的用法示例:
// 客户端TCP连接集合
private List<ChannelFuture> channelFutures = new ArrayList<ChannelFuture>();
public void start() {
EventLoopGroup executors = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
bootstrap.option(ChannelOption.SO_REUSEADDR, true);
bootstrap.group(executors)
.channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(serverHost, serverPort))
.handler(new NATChannelInitializer(false));
//初始化多个TCP连接
for (int i = 0; i < 10; i++) {
this.channelFutures.add(bootstrap.connect().sync());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
错误的用法示例:
// 客户端TCP连接集合
private List<ChannelFuture> channelFutures = new ArrayList<ChannelFuture>();
public void start() {
//初始化多个TCP连接
for (int i = 0; i < 10; i++) {
EventLoopGroup executors = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
bootstrap.option(ChannelOption.SO_REUSEADDR, true);
bootstrap.group(executors)
.channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(serverHost, serverPort))
.handler(new NATChannelInitializer(false));
this.channelFutures.add(bootstrap.connect().sync());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
第一、二章介绍了Netty作为服务端和客户端的正确用法,顺带介绍了Netty的优雅关闭。