C10K 问题的本质上是 OS(operating system)关于 network I/O 的优化问题,即:如何能够更优雅、高效地处理 network I/O。


    0、one thread per request,任何一台单机都是吃不消的。毕竟,file descriptor 的数量是有限制的,你不可能无限制地分配 thread,且创建和销毁 thread 的 overhead 都还挺大的。


    1、那么,就需要“复用”,即:一根 thread 可以处理多个 request。如何做到?

    • 最初步的优化是 select,这根 thread 每次「批量查看」资源是否 available,相当于做了全局优化。这种查看多个资源(即:多个 I/O 路径)是否 available 的方式,就是「多路复用」。
    • 进一步,select 使用的数据结构是「数组」,容易达到上限。那么 poll 就使用「链表」来存放 a batch of requests,来做一个进一步的小优化。

    2、但无论是 select 还是 poll,本质来讲还是做的「polling 轮询」,只不过是批量的轮询。

    一个显然的优化当然是基于 event driven 来做,也可以叫做「信号驱动/signal driven」。(这个理念,对熟悉 node 的 libuv 的 developer 来讲是再显然不过的了。)这个基于 event driven 做的本质上的优化,就是 epollkqueue 了。


    3、理论上讲,都使用了 event driven 了,那这个问题应该是优化到尽头了。但这里其实有一个小坑,就是对于 OS 来讲,当 I/O 的「数据准备好」时,还分为“告知数据准备好了”与“拷贝数据”(从 kernel mode 拷贝到 user mode)两步。

    很遗憾,epoll/kqueue 只是对前一个阶段(告知数据准备好了)做了 event driven,而后一个阶段(copy data)其实依旧是无脑阻塞等待。

    所以,能够“在两个过程都结束后”再发出 event 的 I/O,就是真正的异步 I/O 了,即 AIO。而真正实现了这个 asynchronous I/O 的是 Windows 的 IOCPI/O Completion Port),从名字就可以看出来,是在 I/O(包括数据从 kernel mode 到 user mode 的拷贝)完成之后,发出 event。


    Notes:至于 libevent 和 libuv,那都是 event driven 的框架,按照需要去使用上述不同类型的 system call:select/poll、epoll/kequeue、IOCP。