`
wlvfox
  • 浏览: 26463 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

多selector应用

阅读更多
dennis_zane 写道

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

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

public NioProcessor(Executor executor) {
        super(executor);
        try {
            // Open a new selector
            selector = Selector.open();
        } catch (IOException e) {
            throw new RuntimeIoException("Failed to open a selector.", e);
        }
    }


    protected int select(long timeout) throws Exception {
        return selector.select(timeout);
    }

 
    protected boolean isInterestedInRead(NioSession session) {
        SelectionKey key = session.getSelectionKey();
        return key.isValid() && (key.interestOps() & SelectionKey.OP_READ) != 0;
    }


    protected boolean isInterestedInWrite(NioSession session) {
        SelectionKey key = session.getSelectionKey();
        return key.isValid() && (key.interestOps() & SelectionKey.OP_WRITE) != 0;
    }

    protected int read(NioSession session, IoBuffer buf) throws Exception {
        return session.getChannel().read(buf.buf());
    }


    protected int write(NioSession session, IoBuffer buf, int length) throws Exception {
        if (buf.remaining() <= length) {
            return session.getChannel().write(buf.buf());
        } else {
            int oldLimit = buf.limit();
            buf.limit(buf.position() + length);
            try {
                return session.getChannel().write(buf.buf());
            } finally {
                buf.limit(oldLimit);
            }
        }
    }
 

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

 public NioSocketAcceptor() {
        super(new DefaultSocketSessionConfig(), NioProcessor.class);
        ((DefaultSocketSessionConfig) getSessionConfig()).init(this);
    }
 



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

protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig,
            Class<? extends IoProcessor<T>> processorClass) {
        this(sessionConfig, null, new SimpleIoProcessorPool<T>(processorClass),
                true);
    }
 

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

    private static final int DEFAULT_SIZE = Runtime.getRuntime().availableProcessors() + 1;
    public SimpleIoProcessorPool(Class<? extends IoProcessor<T>> processorType) {
        this(processorType, null, DEFAULT_SIZE);
    }

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

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

protected NioSession accept(IoProcessor<NioSession> processor,
            ServerSocketChannel handle) throws Exception {

        SelectionKey key = handle.keyFor(selector);
       
        if ((key == null) || (!key.isValid()) || (!key.isAcceptable()) ) {
            return null;
        }

        // accept the connection from the client
        SocketChannel ch = handle.accept();
       
        if (ch == null) {
            return null;
        }

        return new NioSocketSession(this, processor, ch);
    }

      
        private void processHandles(Iterator<H> handles) throws Exception {
            while (handles.hasNext()) {
                H handle = handles.next();
                handles.remove();

                // Associates a new created connection to a processor,
                // and get back a session
                T session = accept(processor, handle);
               
                if (session == null) {
                    break;
                }

                initSession(session, null, null);

                // add the session to the SocketIoProcessor
                session.getProcessor().add(session);
            }
        }
 


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

    private IoProcessor<T> nextProcessor() {
        checkDisposal();
        return pool[Math.abs(processorDistributor.getAndIncrement()) % pool.length];
    }

    if (p == null) {
            p = nextProcessor();
            IoProcessor<T> oldp =
                (IoProcessor<T>) session.setAttributeIfAbsent(PROCESSOR, p);
            if (oldp != null) {
                p = oldp;
            }
    }

 
    这样一来,每个连接都关联一个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,核心的代码如下:

protected boolean doSelect() {
        selectorHandler = transport.getSelectorHandler();
        selectionKeyHandler = transport.getSelectionKeyHandler();
        strategy = transport.getStrategy();
       
        try {

            if (isResume) {
                // If resume SelectorRunner - finish postponed keys
                isResume = false;
                if (keyReadyOps != 0) {
                    if (!iterateKeyEvents()) return false;
                }
               
                if (!iterateKeys()) return false;
            }

            lastSelectedKeysCount = 0;
           
            selectorHandler.preSelect(this);
           
            readyKeys = selectorHandler.select(this);

            if (stateHolder.getState(false) == State.STOPPING) return false;
           
            lastSelectedKeysCount = readyKeys.size();
           
            if (lastSelectedKeysCount != 0) {
                iterator = readyKeys.iterator();
                if (!iterateKeys()) return false;
            }

            selectorHandler.postSelect(this);
        } catch (ClosedSelectorException e) {
            notifyConnectionException(key,
                    "Selector was unexpectedly closed", e,
                    Severity.TRANSPORT, Level.SEVERE, Level.FINE);
        } catch (Exception e) {
            notifyConnectionException(key,
                    "doSelect exception", e,
                    Severity.UNKNOWN, Level.SEVERE, Level.FINE);
        } catch (Throwable t) {
            logger.log(Level.SEVERE,"doSelect exception", t);
            transport.notifyException(Severity.FATAL, t);
        }

        return true;
    }
 


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

 private static final int DEFAULT_SELECTOR_RUNNERS_COUNT = 2;
 

  public void start() throws IOException {

  if (selectorRunnersCount <= 0) {
                selectorRunnersCount = DEFAULT_SELECTOR_RUNNERS_COUNT;
            }
  startSelectorRunners();

}

 protected void startSelectorRunners() throws IOException {
        selectorRunners = new SelectorRunner[selectorRunnersCount];
       
        synchronized(selectorRunners) {
            for (int i = 0; i < selectorRunnersCount; i++) {
                SelectorRunner runner =
                        new SelectorRunner(this, SelectorFactory.instance().create());
                runner.start();
                selectorRunners[i] = runner;
            }
        }
    }

 

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

 public Future<RegisterChannelResult> registerChannelAsync(
            SelectableChannel channel, int interestOps, Object attachment,
            CompletionHandler completionHandler)
            throws IOException {
        SelectorRunner runner = getSelectorRunner(interestOps);
       
        return transport.getSelectorHandler().registerChannelAsync(
                runner, channel, interestOps, attachment, completionHandler);
    }
   
    private SelectorRunner getSelectorRunner(int interestOps) {
        SelectorRunner[] runners = getTransportSelectorRunners();
        int index;
        if (interestOps == SelectionKey.OP_ACCEPT || runners.length == 1) {
            index = 0;
        } else {
            index = (counter.incrementAndGet() % (runners.length - 1)) + 1;
        }
       
        return runners[index];
    }
 



    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设置这个数值。

  
  

 

分享到:
评论

相关推荐

    ListView Button ImageView 里应用selector选择器切换图片并保持住

    现在,我们将详细探讨如何在ListView、Button和ImageView中应用selector来实现这一功能。 首先,让我们了解什么是`selector`。Selector是Android中的一个资源类型,它可以定义不同状态下的显示样式,如按下、聚焦、...

    flutter file-selector

    Flutter的file_selector插件可以帮助开发者在移动应用中方便地选择文件。 要使用file_selector插件,首先需要将插件的依赖项添加到pubspec.yaml文件中,并运行flutter pub get命令获取插件的最新版本。 在插件使用...

    Selector

    3. "Selector":这个文件名可能是另一个文件,可能是CSS文件,JavaScript文件,或者是一个包含更多关于Selector用法的文本文件。 综合这些信息,我们可以深入学习以下几个知识点: 1. **CSS选择器**:包括类选择器...

    Android selector 完整demo

    接下来,我们将这个Selector应用到按钮(Button)上。在布局XML文件(例如:activity_main.xml)中,我们可以这样设置: ```xml android:id="@+id/my_button" android:layout_width="wrap_content" android:...

    Android设置button背景selector和字体selector

    4. **在Button中应用Selector**: 在布局文件中,我们将上述创建的Selector分别设置给Button的`android:background`和`android:textColor`属性。例如: ```xml android:id="@+id/my_button" android:layout_...

    android selector注入器

    `android selector注入器` 库的目标就是自动化这个过程,它能够自动为你的View生成相应的Selector,并根据View的状态动态应用这些样式。这样,开发者就可以避免编写大量的XML资源文件,减少重复工作,更专注于业务...

    iconFont 实现selector的Demo

    4. **应用selector**:在布局文件中,将创建的selector作为控件的背景,如`android:background="@drawable/icon_selector"`。 最后,这个Demo提供了实际操作的例子,可以帮助开发者理解并实践以上理论知识,提高...

    java selector 测试并发

    这种机制极大地提高了处理并发连接的效率,尤其在服务器端编程中,如构建高并发的网络应用。 Java Selector的核心类包括`Selector`、`SelectionKey`和`SelectableChannel`。`Selector`是实际进行选择操作的对象,它...

    用selector设置button可用和不可用的样式

    本教程将详细介绍如何使用Selector来设置Button在可用和不可用状态下的样式,以提高应用的视觉效果和交互性。 Selector在Android中是一种基于状态的选择器,它可以为不同状态下的View定义不同的样式。它允许开发者...

    Laravel开发-selector

    在Laravel框架中,开发一个名为"selector"的组件,我们可以深入理解其背后的原理和应用场景。选择器模式(Selector Pattern)是一种设计模式,它允许在运行时动态地选择算法或行为,提供了一种灵活的方式来应对多态...

    marvell_Product Selector Guide.pdf

    Marvell Product Selector Guide Marvell Product Selector Guide 是一份详细的产品选择指南,涵盖了 Marvell 公司在以太网(Ethernet)领域的各种解决方案。本指南旨在帮助用户快速选择合适的 Marvell 产品,以...

    SelectorGadget CSS选择器

    3. **兼容性检查**:SelectorGadget还会检查生成的选择器在其他浏览器中的兼容性,确保你在不同平台上的样式应用都能正常工作。 4. **代码复制**:一键复制生成的选择器,方便直接粘贴到你的CSS或JavaScript文件中...

    java基于NIO选择器Selector的多人聊天室

    Java NIO(非阻塞I/O)是一种在Java中处理I/O操作的高效方式,它引入了选择器(Selector)的概念,使得一个单独的线程可以监控多个输入输出通道(Channels),大大提高了并发处理能力。在这个"java基于NIO选择器...

    Android selector

    1. **状态列表**: 这是Selector的核心,它包含了多个状态和对应的Drawable。每个状态都是一个标签,每个标签可以设置一个drawable和一个android:state_属性来表示View的不同状态。 - `android:state_pressed`: 表示...

    带圆角的selector

    例如,对于一个`Button`,可以在布局XML文件中使用`android:background="@drawable/your_selector"`来应用自定义的`Selector`。 6. **代码动态设置**:除了在XML中静态设置,我们也可以在Java或Kotlin代码中动态地...

    java selector类的用法举例

    `Selector`是Java NIO的核心组件之一,它能够同时监控多个注册过的`Channel`的状态,从而允许单个线程处理多个输入流,提高了程序的性能。当`Selector`检测到一个或多个`Channel`已经准备好进行I/O操作时,它会提供...

    SelectorGadget Chrome插件文件信息

    SelectorGadget是一款强大的Chrome浏览器插件,主要用于网页元素选择,特别是在网页抓取和网络爬虫领域发挥着重要作用。它的设计目标是简化CSS选择器的创建和测试,使得开发者和数据挖掘者能更轻松地定位和选取网页...

    java nio Selector的使用-客户端

    Selector是Java NIO中的核心组件之一,它允许单个线程处理多个通道(channels)的读写事件,极大地提高了服务器的并发能力。本篇文章将深入探讨如何在Java NIO中使用Selector处理客户端的I/O请求。 首先,我们需要...

    Java NIO——Selector机制解析三(源码分析)

    通过深入学习和理解Selector机制,开发者可以有效地设计和实现高性能的服务器应用,比如网络服务器、聊天服务器等,充分利用系统资源,提高并发处理能力。结合实际项目需求,合理运用Java NIO的Selector机制,可以...

    selenium css selector 定位详解

    **注意:** 在实际应用中,也可以省略元素类型,只使用ID选择器,如`driver.find_element_by_css_selector("#kw1")`。 #### 六、属性选择器 (Attribute Selector) 属性选择器允许根据元素的属性及其值进行选择。...

Global site tag (gtag.js) - Google Analytics