Netty实践笔记

实践过程中遇到的一些问题分享

问题1

避免客户端每次连接服务端都创建一个NioEventLoopGroup,本身NioEventLoopGroup就是一个线程组,如果每次连接都要新建,就会出现1个链路对应1个线程组的情况,原本应该是单个NioEventLoop线程处理多个channel上的事件。因此这种形式会占用资源,并且在节点规模扩大时会出现栈溢出、句柄耗尽无法新建线程等情况。

解决方案:

不要简单的在Netty官方提供的demo外套一层for循环来完成与多个服务端的连接。NioEventLoopGroup的创建与配置独立于连接操作即可,共用一个NIO线程组。以下代码依然存在问题,bootstrap是非线程安全的,多线程下bootstrap还是应该每次连接都建立一个,反正是一个引导,不存在占用过多资源的情况。

Netty实践笔记_第1张图片 Netty实践笔记_第2张图片
问题2

客户端与服务端建立连接后,希望可以保持连接不断开,并且下次有请求需要发送时可以直接使用已经建立好的连接,避免再次花费时间和资源去构建新的连接。

解决方案:

构建一个hashmap,key是服务器host,value是连接建立后返回的channel,在每次处理发送请求时判断是否已经与要发往的服务器之间建立了连接,如果还没建立,则新建连接,否则直接获取对应channel进行读写。这是我在刚接触netty的时候下意识想到的一个临时方法。在连接数不断增加或者并发量增加的时候,效率会降低,而且当连接失效或者channel数量过多时的处理也需要仔细斟酌一下。
后续:改成了caffeine,性能上提升了一些,并且可以设置过期时间。

问题3

当channelhandler中需要处理复杂的业务逻辑时,会长时间占用NioEventLoop线程,性能低下。

解决方案:

新增业务线程池,将复杂的业务逻辑处理独立到I/O线程外部。

问题4

服务端closeFuture().sync()会使main线程阻塞,但是不用sync()方法却出现无法监听任何host的情况,出问题的代码:

1.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) {
                // server端发送的是httpResponse,所以要使用HttpResponseEncoder进行编码
                ch.pipeline().addLast(new HttpResponseEncoder());
                // server端接收到的是httpRequest,所以要使用HttpRequestDecoder进行解码
                ch.pipeline().addLast(new HttpRequestDecoder());
                ch.pipeline().addLast(new ServerMessageHandler());
            }
        })
        .option(ChannelOption.SO_BACKLOG, 128)
        .childOption(ChannelOption.SO_KEEPALIVE, true);

    ChannelFuture f = b.bind(address, PORT).addListener(future -> {
        if (future.isSuccess()) {
            log.info("Server start listening address: {}, port: {}", address, PORT);
        } else {
            log.error("Fail to bind the port: {}", PORT);
        }
    }).sync();
    f.channel().closeFuture().addListener((ChannelFutureListener) future -> {
        if (future.isSuccess()) {
            log.info("Server has closed the connection");
        } else {
            log.error("Server fail to close the connection");
        }
    });
} finally {
    workerGroup.shutdownGracefully();
    bossGroup.shutdownGracefully();
}
解决方案:

在finally语句块前,由于没有调用sync()方法进行阻塞,两个group直接关闭了,导致原先建立起来的host监听被关闭了。所以只需要将关闭group的语句放到异步的listener中即可。

问题5

客户端重连过程中想要设置重连次数,当次数耗尽时切换连接的host。但是变量涉及到线程安全问题。

解决方案:

一开始就暴力的对connect方法进行了同步控制,结果就是被批了。。。。后来想到用ThreadLocal对变量进行线程私有化处理。看到网上有的做法是在connect时记录一个retry次数,然后异步监听channel是否连接成功,并使用定时任务进行失败重试。

问题持续更新中。。。

你可能感兴趣的:(Java网络编程学习,java,netty)