netty优化

  为了提高单机netty服务器的并发处理能力,可以从以下几个方面对netty进行优化。

      首先,在服务器初始化的时候需要设置两个线程池: 一个是用来接收客户端的连接,可以命名为eventLoopGroupBoss, 由于接收连接是非常快速的事情,所以这个线程池的线程数通常设置为1,设置为多个线程时可能会在多个线程之间产生对资源的竞争,反而不利于服务器处理能力的提高;另一个是用来处理客户端的读写操作,命名为eventLoopGroupWorker,由于处理网络IO操作是非常耗时的,为了能够尽可能多的处理客户端的连接,所以这个线程池的线程数通常设为多个,具体需要设置的数量可以根据业务量和CPU核数进行调整,一般设置成CPU核数的两倍到三倍是一个好的选择。在这个过程中,为了方便系统BUG 的解决,可以为每个线程池设置一个自定义的ThreadFactory,这个factory的作用是根据线程池的类型为创建的线程设置一个特殊的名称,如boss线程的名称是NettyBoss_,工作线程的名称是NettyServerSelector_%d_%d,这样可以在以后的问题排除过程中利用这个名称识别不同的线程,分析这些线程的资源占用率,便于找到问题的根源。

     此外,如果部署服务器的操作系统是Linux, 在选中worker线程池的时候可以考虑用epoll代替传统的select,前者对应neitty提供的EpollEventLoopGroup,后者对应的是NioEventLoopGroup。之所以要这样做,是因为传统的select基于轮询的方式来进行事件处理,随着fd数量的增加,导致轮询的处理开销增大,实际的事件处理效率就会下降。而epoll是基于通知的方式,每当事件准备就绪的时候,epoll就会通知线程进行处理,这样可以保证及时性的同时也不会随着fd的数量的增加而降低效率。

   

    其次,给netty的运行参数设置一个合理的值,对netty的运行性能会有很大的影响。首先,需要设置的参数是SO_BACKLOG,这个值通常设置1024,意味着当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。这样就可以服务器处理能力达到饱和的情况下用队列来暂存用户取得请求,等服务器空闲的时候再从队列中取出请求来处理,而不是马上拒接掉。其次,需要设置数据发送和接收缓冲区大小的参数,分别是SO_SNDBUF和SO_RCVBUF,两者可以设置成1048576,即是128K,在性能优化是通常都设置成这样。再次,给netty的内存分配设置一个内存池,需要内存时就从内存池中分配,使用完了再归还给内存池以便可以重复利用。这样就可以避免每次使用内存所带来的内存创建和销毁开销,而且还不容易形成内存碎片。基于这样的目的可以使用netty提供的PooledByteBufAllocator分配器,需要注意的是使用这个分配器分配的内存使用完后必须手动进行释放,否则会造成内存泄漏。最后,还有一个参数在业务量比较大的时候需要进行设置,这就是WRITE_BUFFER_WATER_MARK,他的作用是限制netty向channel写数据时使用的缓冲区边界。当netty需要往channel中写入数据时会先把数据写入一个Buffer缓冲区,这个缓冲区是各个channel独占的,不共享。等到channel空闲的时候就从缓冲区中读取数据进行发送。这样做可以提高网络的吞吐量。但也带来了一个缺点,就是在碰到对端非常慢(对端慢指的是对端处理TCP包的速度变慢,比如对端负载特别高的时候就有可能

 是这个情况)的时候就出问题了,这个时候如果还是不断地写数据,这个buffer就会不断地增长,最后就会由于占用大量的内存引起服务器处理缓慢,进而可能引起崩溃。这时WRITE_BUFFER_WATER_MARK就派上用场了,它规定了高低水位线。当Buffer的数据超过高水位线时就停止写入数据,设置channel的isWritable为false。等到buffer中的数据由于被消费而低于低水位线时设置channel的isWritable为true,又可以重新接受写入的数据。所以设置了这个参数之后,对应用的要求是,每次写数据时先判断channel的isWritable,在 true时才进行写入。 
 

       最后,在构造netty处理链的时候可以在链的最前边加上一个线程池,它的作用是把之后的handler放在单独的线程中进行处理,如编解码,加解密,具体的业务逻辑处理等。这样可以达到不阻塞I/0线程的目的,让I/0线程可以及时返回来处理新的请求,借此可以极大的提高服务的并发处理能力,相关的代码如下:

ch.pipeline().addLast(
        defaultEventExecutorGroup, //单独的线程池,让之后的handler在这里处理
        new NettyEncoder(), //解码
        new NettyDecoder(), //编码
        new IdleStateHandler(0, 0, nettyServerConfig.getServerChannelMaxIdleTimeSeconds()),
        new NettyConnetManageHandler(),
        new NettyServerHandler());//业务处理Handler
}

 此外,考虑到具体的业务逻辑处理可能涉及到耗时的数据库操作或远程RPC调用,因而在具体的业务处理Handler中启动一个新的线程来处理相关逻辑,如上图的NettyServerHandler。这样可以让这些耗时的操作不阻塞处理Handler的线程,让线程可以及时返回来处理新的操作,同样可以起到提高服务器并发处理能力的目的。相关的代码如下:

class NettyServerHandler extends SimpleChannelInboundHandler<RemotingCommand> {
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception {
            Runnable run = new Runnable() {
                @Override
                public void run() {
                    //具体的业务逻辑处理
                }
            }
            submit(run);//在线程池中提交处理任务
        }
 }

   

猜你喜欢

转载自laolinshi.iteye.com/blog/2341729