Java中几种使用NIO的模式

 

 

NIO的通用模型是使用一个Selector管理所有的SocketChannel,包括ServerSocketChannel。但是,诸多开源工程中,使用模式会有些不同,下面描述了几种开源软件在某些组件中如何使用Selector的。

 

1. 多个Selector管理客户端的读写

 

Voldemort使用NIO有些不同。ServerSocketChannel是阻塞模式,但是,与客户端通信的SocketChannel却是非阻塞模式。ServerSocketChannel并没有被Selector管理,而是在一个单独的线程中不断监听客户端的请求,建立SocketChannel。

 

一个重要的不同是,Voldemort使用多个Selector来管理SocketChannel,Selector的数量可配置。Voldemort使用round-robin模式来分配某个Selector处理SocketChannel。

 

Voldemort的SelectorManager文档这样写道:

 

First, the thread processing the event calls interestOps() on the SelectionKey to update what types of events it's interested in. In fact, it does this twice - first before any processing occurs it disables all events (so that the same channel isn't selected concurrently (similar to disabling interrupts)) and secondly after processing is completed to re-enable interest in events. Understandably, interestOps() has some internal state that it needs to update, and so the thread must grab a lock on the Selector to do internal interest state modifications. With hundreds/thousands of threads, this lock is very heavily contended as backed up by profiling and empirical testing. 

The second reason the thread pool approach was slow was that after calling interestOps() to re-enable events, the threads in the thread pool had to invoke the Selector API's wakeup() method or else the state change would go unnoticed (it's similar to notifyAll for basic thread synchronization). This causes the select() method to return immediately and process whatever requests are immediately available. However, with so many threads in play, this lead to a near constant spinning of the select()/wakeup() cycling. 

Astonishingly it was found to be about 25% faster to simply execute all IO synchronously/serially as it eliminated the context switching, lock contention, etc. However, we actually have N simultaneous SelectorManager instances in play, which are round-robin-ed by the caller (NioSocketService). 
 

这样做的主要目的是为性能考虑。作者认为,在高频率地调用interestOps, wakeup的情况下,会涉及到Selector加锁,解锁的操作,这些操作会严重影响性能。作者还指出,使用多Selector可以提升大概25%的性能。

 

2. 读写操作由不同的Selector管理

 

Hadoop的RPC组件中的Server使用了这个模式。Listener管理的Selector负责接收链接,数据的读操作。下面是Listener的代码片段:

 

          selector.select();
          Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
          while (iter.hasNext()) {
            key = iter.next();
            iter.remove();
            try {
              if (key.isValid()) {
                if (key.isAcceptable())
                  doAccept(key);
                else if (key.isReadable())
                  doRead(key);
              }
            } catch (IOException e) {
            }
            key = null;
          }
 

一个名为Responder的独立类负责数据的输出操作,该类管理了一个Selector,各个客户端的SocketChannel的写事件会注册到该Selector上。下面是Responder的骨架代码:

 

          waitPending();     // If a channel is being registered, wait.
          writeSelector.select(PURGE_INTERVAL);
          Iterator<SelectionKey> iter = writeSelector.selectedKeys().iterator();
          while (iter.hasNext()) {
            SelectionKey key = iter.next();
            iter.remove();
            try {
              if (key.isValid() && key.isWritable()) {
                  doAsyncWrite(key);
              }
            } catch (IOException e) {
              LOG.info(getName() + ": doAsyncWrite threw exception " + e);
            }
          }

 

这样做的好处很明显,首先降低的了代码复杂度。其次,读写分离,减轻一个Selector带来的锁争用,可以提高性能。 

 

3. Selector只管理ServerSocketChannel打开客户端链接

 

Jetty的NIO Connector使用了这种模式,一个Selector只管理ServerSocketChannel的Accept事件,一旦一个链接被打开,会构造一个SelectableEndPoint,SocketChannel被SelectableEndPoint管理,包括从SocketChannel读数据,以及将数据写到SocketChannel。

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值