muduo网络库:并发服务器设计

常见并发服务器方案

  1. 循环式/迭代式服务器
    整个服务器的流程是
  • 创建套接字,绑定监听。
  • 在一个循环中执行:
    • 接受客户端连接
    • 解码 处理 编码
    • 关闭连接
  • 关闭连接

这种连接方式实际上是短链接,而循环式服务器只能是短连接。整个程序是一个单线程的应用程序,不能是长连接的原因是因为如果需要进行长链接持续读,那么需要在写后在进行读,此时如果来一个新的连接请求就得不到响应。
另外整个连接处理过程不能太长,如果太长就会影响用户的响应时间。
总的来说这种方式具有下面几个缺点:

  • 不能进行长连接
  • 不能处理复杂的业务请求
  • 不能利用cpu多核优势
  1. cocurrent服务器

这个服务器的流程如上图所示,通过多进程的方式来处理多个客户端请求,并且可以处理长连接。

  1. pre-fork or pre threaded

    这种称为预先创建线程或者进程的方式,每个进程或者线程负责一个客户端的请求。这种方式的一个缺点是会出现“惊群”现象。
    所谓惊群现象,就是父进程创建socket,bind、listen后,通过fork创建多个子进程,每个子进程继承了父进程的socket,调用accpet开始监听等待网络连接。这个时候有多个进程同时等待网络的连接事件,当这个事件发生时,这些进程被同时唤醒,就是“惊群”。这样会导致什么问题呢?我们知道进程被唤醒,需要进行内核重新调度,这样每个进程同时去响应这一个事件,而最终只有一个进程能处理事件成功,其他的进程在处理该事件失败后重新休眠或其他。

  2. reactor模式

  • reactor可以使用select/poll/epoll来实现。
  • 我们首先注册我们关注的监听套接字;
  • 用客户端发来请求,acceptor来接受连接,然后将对应的文件描述符加入reactor中关注可读事件;
  • 如果发生可读事件,开始dispatch(分派);
  • 然后进行业务逻辑的处理。

以上所有的操作都是在单线程上完成的,因此不能处理较复杂的业务逻辑,也不能利用多核cpu的优势。

  1. 过渡方案
  • reactor + thread per request
    每来一个请求,创建一个线程,在请求较多的情况下系统的负载显著上升。
  • reactor + worker thread
    每个连接在一个工作者线程中完成,这种方式不如cocurrent方式,因为多了reactor。
  1. reactor + threadpool

    与reactor不同的地方是,将业务代码通过线程池来实现。
    这种模式适用于计算密集型的业务。

  2. mutiple reactors

    如上图所示,拥有一个mainReactor,当acceptor返回已连接套接字,就将活跃的套接字分配给subReactor。如果分配的顺序是按顺序,那么就叫做“轮叫”(round robin),这种方式能够保证每个reactor都有均匀的任务数量。
    每个进程或者线程拥有一个事件循环。(reactors in threads or reactors in process)。
    这种方式能够应对更大的突发IO。
    一个reactor能够应对一个千兆网卡,我们可以根据网卡数量来设计线程池或者进程池的容量。

  3. mutiple reactors + thread pool

    这种方式也叫做one loop per thread + thread pool。
    实际上在这种模式下,一共有两个线程池,一个是reactor的线程池,一个是业务线程的线程池。

  4. proactor服务器(基于异步IO)
    这种异步方式linux支持不够,暂不考虑。