从网络IO到Thrift网络模型

I/O多路复用

IO多路复用就是经过一种机制,一个进程能够监听多个文件描述符,一个某个描述符就绪(通常是读就绪或写就绪),就可以通知程序进行相应的读写操做。select、poll、epoll本质上都是同步IO,由于他们须要在读写事件就绪后本身负责读写,即这个读写过程是阻塞的,而异步IO则无需本身负责读写,异步IO的实现会把数据从内核拷贝到用户空间。linux

select

基本原理

select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直到有描述符就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,若是当即返回设为null便可),函数返回。当select函数返回后,能够经过遍历fdset,来找到就绪的描述符。segmentfault

select的缺点

  • 单进程所能打开的文件描述符有必定限制,32位机默认1028,64位机默认2048。
  • 对socket进程扫描时是线性扫描,效率很低。
  • 用来存放文件描述符的数据结构,在用户空间和内核空间的复制开销极大。

poll

poll与select相似,略过。网络

epoll

epoll是在linux 2.6内核中提出的,是select和poll的加强版本。数据结构

基本原理

epoll支持水平触发和边缘触发,最大的特色在于边缘触发,它只告诉进程哪些fd刚刚变为就绪态,而且只会通知一次。还有一个特色是,epoll使用“事件”的就绪通知方式,一旦该fd就绪,内核就会采用相似callback的回调机制来激活该fd。并发

epoll的优势

  • 没有最大链接数的限制,1G内存约能监听10W个端口。
  • 不采用轮询的方式,不会随着FD数目的增长效率降低。只有活跃可用的FD才会回调。
  • 内存拷贝,epoll使用mmap减小复制开销。(注:mmap本质就是绕过从网卡、磁盘拷贝数据到内核再拷贝到用户空间的方式,直接从网卡拷贝数据到用户空间,性能爆炸。)

Thrift网络服务模型

thrift提供的网络服务模型有阻塞服务模型、非阻塞服务模型:负载均衡

  • 阻塞服务模型:TSimpleServer、TThreadPoolServer
  • 非阻塞服务模型:TNonblockingServer、THsHaServer和TThreadedSelectorServer

TSimpleServer

该模式采用最简单的阻塞IO,一次只能接收并处理一个socket,处理流程以下:
alt text
此种模式效率低下,生产不会使用,略过。异步

TThreadPoolServer

TThreadPoolServer模式采用阻塞socket方式工做,主线程负责阻塞式(划重点,不是select的方式)监听是否有新socket到来,具体的业务处理交由一个线程池来处理。socket

accept部分的代码以下:函数

protected TSocket acceptImpl() throws TTransportException {
    if (serverSocket_ == null) {
      throw new TTransportException(TTransportException.NOT_OPEN, "No underlying server socket.");
    }
    try {
      // 阻塞式监听新的链接
      Socket result = serverSocket_.accept();
      TSocket result2 = new TSocket(result);
      result2.setTimeout(clientTimeout_);
      return result2;
    } catch (IOException iox) {
      throw new TTransportException(iox);
    }
  }

具体模型以下:
alt text高并发

TThreadPoolServer本质是One Thread Per Connection模型。模型受限于线程池的最大线程数,在链接数很大话,请求只能排队,对于高并发的场景,此模型并不合适。

TNonblockingServer

TNonblockingServer模式也是单线程工做,可是采用NIO的模式,借助Channel/Selector机制, 采用IO事件模型来处理。本质是一种event-loop模型。

具体模型以下:
alt text

event-loop的核心代码以下:

private void select() {
      try {
        // 等待事件,jdk7以前的版本存在问题,会存在会将CPU打满的状况,没有事件,select却返回,从而将CPU打满;Netty中经过threshold,解决了该问题
        selector.select();

        // 获取IO事件
        Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();
        while (!stopped_ && selectedKeys.hasNext()) {
          SelectionKey key = selectedKeys.next();
          selectedKeys.remove();

          // skip if not valid
          if (!key.isValid()) {
            cleanupSelectionKey(key);
            continue;
          }

          // if the key is marked Accept, then it has to be the server
          // transport.
          // 处理链接事件
          if (key.isAcceptable()) {
            handleAccept();
          } else if (key.isReadable()) {
            // deal with reads
            // 处理读事件
            handleRead(key);
          } else if (key.isWritable()) {
            // deal with writes
            // 处理写事件
            handleWrite(key);
          } else {
            LOGGER.warn("Unexpected state in select! " + key.interestOps());
          }
        }
      } catch (IOException e) {
        LOGGER.warn("Got an IOException while selecting!", e);
      }
    }

这个模型通常由一个event dispatcher等待各种事件,待事件发生后原地调用对应的event handler,所有调用完后等待更多事件,故为"loop"。这个模型的实质是把多段逻辑按事件触发顺序交织在一个系统线程中。一个event-loop只能使用一个核,故此类程序要么是IO-bound,要么是每一个handler有肯定的较短的运行时间(好比http server),不然一个耗时漫长的回调就会卡住整个程序,产生高延时。在实践中这类程序不适合多开发者参与,一我的写了阻塞代码可能就会拖慢其余代码的响应。因为event handler不会同时运行,不太会产生复杂的race condition,一些代码不须要锁。此类程序主要靠部署更多进程增长扩展性。

THsHaServer

THsHaServer继承于TNonblockingServer,引入了线程池提升了任务处理的并发能力。THsHaServer是半同步半异步(Half-Sync/Half-Async)的处理模式,Half-Aysnc用于IO事件处理(Accept/Read/Write),Half-Sync用于业务handler对rpc的同步处理上。

具体模型以下:

alt text

THsHaServer与TNonblockingServer模式相比,THsHaServer在完成数据读取以后,将业务处理过程交由一个线程池来完成,主线程直接返回进行下一次循环操做,效率大大提高。

可是,主线程仍然须要处理accpet、read、write时间,当并发量很是大,读取或者发送的数据量比较大时,会将主线程阻塞住,新的链接没法被及时处理。

TThreadedSelectorServer

TThreadedSelectorServer是对THsHaServer的一种改进,它将selector中的read/write事件从主线程中剥离出来。

TThreadedSelectorServer是thrift提供的最高效的网络模型。具体模型以下:
alt text

构成以下:

  • 一个Accpet线程,专门用来处理新的socket
  • n个SelectorThread,用来处理read/write事件,读取、返回数据都是有这些线程完成的,每一个SelectorThread会与若干个socket绑定,每一个SelectorThread会处理与它绑定socket的read/write事件。
  • 一个负载均衡器SelectorThreadLoadBalancer对象,在accpet线程接收到新的socket之后,由SelectorThreadLoadBalancer决定将socket与哪一个SelectorThread绑定(其实就是一个next函数,每分配一个socket,就调用next)。
  • 一个ExecutorService类型的worker线程池,在SelectorThread读取数据以后,将其包装成一个task,分配给worker线程池,处理业务逻辑。

总结:TThreadedSelectorServer模式,其实就是标准的Reactor模式,Tomcat7之后的版本、Cobar、MyCat(分库分表proxy)基本都是这个套路,具体实现略有差别。

原文连接

https://segmentfault.com/a/11...

相关文章
相关标签/搜索