nio框架采用多个Selector

来源:互联网 发布:英文域名 编辑:程序博客网 时间:2024/04/29 05:46

随着并发数量的提高,传统nio框架采用一个Selector来支撑大量连接事件的管理和触发已经遇到瓶颈,因此现在各种nio框架的新版本都采用多个 Selector并存的结构,由多个Selector均衡地去管理大量连接。这里以Mina和Grizzly的实现为例。

   在Mina 2.0中,Selector的管理是由org.apache.mina.transport.socket.nio.NioProcessor来处理,每个NioProcessor对象保存一个Selector,负责具体的select、wakeup、channel的注册和取消、读写事件的注册和判断、实际的IO读写操作等等,核心代码如下:

Java代码  收藏代码
  1. public NioProcessor(Executor executor) {  
  2.         super(executor);  
  3.         try {  
  4.             // Open a new selector  
  5.             selector = Selector.open();  
  6.         } catch (IOException e) {  
  7.             throw new RuntimeIoException("Failed to open a selector.", e);  
  8.         }  
  9.     }  
  10.   
  11.   
  12.     protected int select(long timeout) throws Exception {  
  13.         return selector.select(timeout);  
  14.     }  
  15.   
  16.    
  17.     protected boolean isInterestedInRead(NioSession session) {  
  18.         SelectionKey key = session.getSelectionKey();  
  19.         return key.isValid() && (key.interestOps() & SelectionKey.OP_READ) != 0;  
  20.     }  
  21.   
  22.   
  23.     protected boolean isInterestedInWrite(NioSession session) {  
  24.         SelectionKey key = session.getSelectionKey();  
  25.         return key.isValid() && (key.interestOps() & SelectionKey.OP_WRITE) != 0;  
  26.     }  
  27.   
  28.     protected int read(NioSession session, IoBuffer buf) throws Exception {  
  29.         return session.getChannel().read(buf.buf());  
  30.     }  
  31.   
  32.   
  33.     protected int write(NioSession session, IoBuffer buf, int length) throws Exception {  
  34.         if (buf.remaining() <= length) {  
  35.             return session.getChannel().write(buf.buf());  
  36.         } else {  
  37.             int oldLimit = buf.limit();  
  38.             buf.limit(buf.position() + length);  
  39.             try {  
  40.                 return session.getChannel().write(buf.buf());  
  41.             } finally {  
  42.                 buf.limit(oldLimit);  
  43.             }  
  44.         }  
  45.     }  
 

   这些方法的调用都是通过AbstractPollingIoProcessor来处理,这个类里可以看到一个nio框架的核心逻辑,注册、select、派发,具体因为与本文主题不合,不再展开。NioProcessor的初始化是在NioSocketAcceptor的构造方法中调用的:

Java代码  收藏代码
  1. public NioSocketAcceptor() {  
  2.        super(new DefaultSocketSessionConfig(), NioProcessor.class);  
  3.        ((DefaultSocketSessionConfig) getSessionConfig()).init(this);  
  4.    }  
 



   直接调用了父类AbstractPollingIoAcceptor的构造函数,在其中我们可以看到,默认是启动了一个SimpleIoProcessorPool来包装NioProcessor:

Java代码  收藏代码
  1. protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig,  
  2.             Class<? extends IoProcessor<T>> processorClass) {  
  3.         this(sessionConfig, nullnew SimpleIoProcessorPool<T>(processorClass),  
  4.                 true);  
  5.     }  
 

   这里其实是一个组合模式,SimpleIoProcessorPool和NioProcessor都实现了Processor接口,一个是组合形成的Processor池,而另一个是单独的类。调用的SimpleIoProcessorPool的构造函数是这样:

Java代码  收藏代码
  1. private static final int DEFAULT_SIZE = Runtime.getRuntime().availableProcessors() + 1;  
  2. public SimpleIoProcessorPool(Class<? extends IoProcessor<T>> processorType) {  
  3.     this(processorType, null, DEFAULT_SIZE);  
  4. }  

 
    可以看到,默认的池大小是cpu个数+1,也就是创建了cpu+1个的Selector对象。它的重载构造函数里是创建了一个数组,启动一个 CachedThreadPool来运行NioProcessor,通过反射创建具体的Processor对象,这里就不再列出了。

    Mina当有一个新连接建立的时候,就创建一个NioSocketSession,并且传入上面的SimpleIoProcessorPool,当连接初始化的时候将Session加入SimpleIoProcessorPool:

Java代码  收藏代码
  1. protected NioSession accept(IoProcessor<NioSession> processor,  
  2.             ServerSocketChannel handle) throws Exception {  
  3.   
  4.         SelectionKey key = handle.keyFor(selector);  
  5.          
  6.         if ((key == null) || (!key.isValid()) || (!key.isAcceptable()) ) {  
  7.             return null;  
  8.         }  
  9.   
  10.         // accept the connection from the client  
  11.         SocketChannel ch = handle.accept();  
  12.          
  13.         if (ch == null) {  
  14.             return null;  
  15.         }  
  16.   
  17.         return new NioSocketSession(this, processor, ch);  
  18.     }  
  19.   
  20.         
  21.         private void processHandles(Iterator<H> handles) throws Exception {  
  22.             while (handles.hasNext()) {  
  23.                 H handle = handles.next();  
  24.                 handles.remove();  
  25.   
  26.                 // Associates a new created connection to a processor,  
  27.                 // and get back a session  
  28.                 T session = accept(processor, handle);  
  29.                  
  30.                 if (session == null) {  
  31.                     break;  
  32.                 }  
  33.   
  34.                 initSession(session, nullnull);  
  35.   
  36.                 // add the session to the SocketIoProcessor  
  37.                 session.getProcessor().add(session);  
  38.             }  
  39.         }  
 


    加入的操作是递增一个整型变量并且对数组大小取模后对应的NioProcessor注册到session里:

Java代码  收藏代码
  1. private IoProcessor<T> nextProcessor() {  
  2.     checkDisposal();  
  3.     return pool[Math.abs(processorDistributor.getAndIncrement()) % pool.length];  
  4. }  
  5.   
  6. if (p == null) {  
  7.         p = nextProcessor();  
  8.         IoProcessor<T> oldp =  
  9.             (IoProcessor<T>) session.setAttributeIfAbsent(PROCESSOR, p);  
  10.         if (oldp != null) {  
  11.             p = oldp;  
  12.         }  
  13. }  

 
    这样一来,每个连接都关联一个NioProcessor,也就是关联一个Selector对象,避免了所有连接共用一个Selector负载过高导致 server响应变慢的后果。但是注意到NioSocketAcceptor也有一个Selector,这个Selector用来干什么的呢?那就是集中处理OP_ACCEPT事件的Selector,主要用于连接的接入,不跟处理读写事件的Selector混在一起,因此Mina的默认open的 Selector是cpu+2个

    看完mina2.0之后,我们来看看Grizzly2.0是怎么处理的,Grizzly还是比较保守,它默认就是启动两个Selector,其中一个专门负责accept,另一个负责连接的IO读写事件的管理。Grizzly 2.0中Selector的管理是通过SelectorRunner类,这个类封装了Selector对象以及核心的分发注册逻辑,你可以将他理解成 Mina中的NioProcessor,核心的代码如下:

Java代码  收藏代码
  1. protected boolean doSelect() {  
  2.         selectorHandler = transport.getSelectorHandler();  
  3.         selectionKeyHandler = transport.getSelectionKeyHandler();  
  4.         strategy = transport.getStrategy();  
  5.          
  6.         try {  
  7.   
  8.             if (isResume) {  
  9.                 // If resume SelectorRunner - finish postponed keys  
  10.                 isResume = false;  
  11.                 if (keyReadyOps != 0) {  
  12.                     if (!iterateKeyEvents()) return false;  
  13.                 }  
  14.                  
  15.                 if (!iterateKeys()) return false;  
  16.             }  
  17.   
  18.             lastSelectedKeysCount = 0;  
  19.              
  20.             selectorHandler.preSelect(this);  
  21.              
  22.             readyKeys = selectorHandler.select(this);  
  23.   
  24.             if (stateHolder.getState(false) == State.STOPPING) return false;  
  25.              
  26.             lastSelectedKeysCount = readyKeys.size();  
  27.              
  28.             if (lastSelectedKeysCount != 0) {  
  29.                 iterator = readyKeys.iterator();  
  30.                 if (!iterateKeys()) return false;  
  31.             }  
  32.   
  33.             selectorHandler.postSelect(this);  
  34.         } catch (ClosedSelectorException e) {  
  35.             notifyConnectionException(key,  
  36.                     "Selector was unexpectedly closed", e,  
  37.                     Severity.TRANSPORT, Level.SEVERE, Level.FINE);  
  38.         } catch (Exception e) {  
  39.             notifyConnectionException(key,  
  40.                     "doSelect exception", e,  
  41.                     Severity.UNKNOWN, Level.SEVERE, Level.FINE);  
  42.         } catch (Throwable t) {  
  43.             logger.log(Level.SEVERE,"doSelect exception", t);  
  44.             transport.notifyException(Severity.FATAL, t);  
  45.         }  
  46.   
  47.         return true;  
  48.     }  
 


    基本上是一个reactor实现的样子,在AbstractNIOTransport类维护了一个SelectorRunner的数组,而Grizzly 用于创建tcp server的类TCPNIOTransport正是继承于AbstractNIOTransport类,在它的start方法中调用了 startSelectorRunners来创建并启动SelectorRunner数组:

Java代码  收藏代码
  1.  private static final int DEFAULT_SELECTOR_RUNNERS_COUNT = 2;  
  2.    
  3.   
  4.   public void start() throws IOException {  
  5.   
  6.   if (selectorRunnersCount <= 0) {  
  7.                 selectorRunnersCount = DEFAULT_SELECTOR_RUNNERS_COUNT;  
  8.             }  
  9.   startSelectorRunners();  
  10.   
  11. }  
  12.   
  13.  protected void startSelectorRunners() throws IOException {  
  14.         selectorRunners = new SelectorRunner[selectorRunnersCount];  
  15.          
  16.         synchronized(selectorRunners) {  
  17.             for (int i = 0; i < selectorRunnersCount; i++) {  
  18.                 SelectorRunner runner =  
  19.                         new SelectorRunner(this, SelectorFactory.instance().create());  
  20.                 runner.start();  
  21.                 selectorRunners[i] = runner;  
  22.             }  
  23.         }  
  24.     }  

 

  可见Grizzly并没有采用一个单独的池对象来管理SelectorRunner,而是直接采用数组管理,默认数组大小是2。 SelectorRunner实现了Runnable接口,它的start方法调用了一个线程池来运行自身。刚才我提到了说Grizzly的Accept 是单独一个Selector来管理的,那么是如何表现的呢?答案在RoundRobinConnectionDistributor类,这个类是用于派发注册事件到相应的SelectorRunner上,它的派发方式是这样:

Java代码  收藏代码
  1. public Future<RegisterChannelResult> registerChannelAsync(  
  2.            SelectableChannel channel, int interestOps, Object attachment,  
  3.            CompletionHandler completionHandler)  
  4.            throws IOException {  
  5.        SelectorRunner runner = getSelectorRunner(interestOps);  
  6.         
  7.        return transport.getSelectorHandler().registerChannelAsync(  
  8.                runner, channel, interestOps, attachment, completionHandler);  
  9.    }  
  10.     
  11.    private SelectorRunner getSelectorRunner(int interestOps) {  
  12.        SelectorRunner[] runners = getTransportSelectorRunners();  
  13.        int index;  
  14.        if (interestOps == SelectionKey.OP_ACCEPT || runners.length == 1) {  
  15.            index = 0;  
  16.        } else {  
  17.            index = (counter.incrementAndGet() % (runners.length - 1)) + 1;  
  18.        }  
  19.         
  20.        return runners[index];  
  21.    }  
 



    getSelectorRunner这个方法道出了秘密,如果是OP_ACCEPT,那么都使用数组中的第一个SelectorRunner,如果不是,那么就通过取模运算的结果+1从后面的SelectorRunner中取一个来注册。

    分析完mina2.0和grizzly2.0对Selector的管理后我们可以得到几个启示:

1、在处理大量连接的情况下,多个Selector比单个Selector好
2、多个Selector的情况下,处理OP_READ和OP_WRITE的Selector要与处理OP_ACCEPT的Selector分离,也就是说处理接入应该要一个单独的Selector对象来处理,避免IO读写事件影响接入速度。
3、Selector的数目问题,mina默认是cpu+2,而grizzly总共就2个,我更倾向于mina的策略,但是我认为应该对cpu个数做一个判断,如果CPU个数超过8个,那么更多的Selector线程可能带来比较大的线程切换的开销,mina默认的策略并非合适,幸好可以通过API设置这个数值。

0 0
原创粉丝点击