读书笔记:《Netty进阶之路》——Netty服务端、Netty客户端、优雅关闭

1 Netty服务端

  第一章提出“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中,不阻塞调用线程。

2 优雅关闭

  Netty优雅关闭作业shutdownGracefully(),该方法位于EventExecutorGroup接口中,在netty-4.1.36.Final,EventLoop(单线程线程池SingleThreadEventExecutor)和EventLoopGroup(多线程线程池MultithreadEventExecutorGroup)都有实现。关闭过程中的工作包括:(1)设置线程状态码(2)发送尚未发送或正在发送的消息(3)完成到期的定时任务(4)执行NIO线程的Hook(5)Channel释放,剩余定时任务释放,多路复用器注销,退出EventLoop线程。

3 Netty客户端

  第二章提出“客户端连接池资源泄露,出现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的优雅关闭。

你可能感兴趣的:(Java,Netty)