事件驱动服务器, event-driven server https://gist.github.com/jcouyang/9914091
OPPC模型瓶颈
传统服务器模型如Apache为每一个请求生成一个子进程。当用户连接到服务器的一个子进程就产生,并处理连接。每个连接获得一个单独的线程和子进程。当用户请求数据返回时,子进程开始等待数据库操作返回。如果此时另一个用户也请求返回数据,这时就产生了阻塞。
这种模式在非常小的工作负荷是表现良好,当请求的数量变得太大是服务器会压力过于巨大。 当Apache达到进程的最大数量,所有进程都变得缓慢。每个请求都有自己的线程,如果服务代码使用PHP编写时,每个进程所需要的内存量是相当大的[1]。
fork()操作延时
事实上,基于OPPC的网络并不如想象中的高效。首先新建进程的性能很大程度上依赖于操作系统对fork()的实现,然而不同操作系统的处理并非都理想。
操作系统fork操作只是简单的拷贝分页映射。动态链接为共享库和全局偏移表中的ELF (Executable and Linking Format) 部分创建太多的分页映射。虽然静态的链接fork会是的性能大幅度提升,但是延时依然不乐观。
进程调度
Linux每10毫秒 (Alpha是1毫秒,该值为已编译常量) 中断一次在运行态的进程,查看是否要切换别的进程执行。进程调度的任务就是决定下一个应该执行的进程,而其难度就在于如何公平的分配CPU资源。一个好的调度算法应该给每一个进程都分享公平的CPU资源,而且不应该出现饥饿进程。
Unix系统采用多级反馈队列调度算法。使用多个不同优先级的就绪队列,使用Heap保持队列按优先级顺序排序。Linux 2.6版本提供了一个复杂度O(1)的调度算法,将进程调度延时降至最小。但是进程调度的频率是100Hz,意味着10毫秒会中止一个进程而判断是否需要切换到另一个进程。如果切换过多,会让CPU忙于切换,导致降低吞吐量。
内存占用与线程 创建多进程会带来另外一个问题: 内存消耗。
每一个创建的进程都会占用内存,在 Linux 2.6 中的测试结果, 400个左右的连接后 fork() 的性能要超过 pthread_create() 的性能。 IBM 对 Linux 做过优化后, 一个进程可以处理 10 万个连接。 fork () 在每一个连接时都 fork() 一次成本太高,多线程在于需要考虑线程安全(thread-safe)与死锁(deadlock),以及内存泄露问题这些问题。
可靠性
该模型具有可靠性问题。一个配置不当的服务器,很容易遭受拒绝服务攻击 (DoS) 。当大量并发请求的服务器资源时,负载均衡配置不当时服务器会很快耗尽源而奔溃。
同步阻塞 I/O
在这个模型中,应用程序执行一个系统调用,这会导致应用程序阻塞。这意味着应用程序会一直阻塞,直到系统调用完成为止 (数据传输完成或发生错误) 。调用应用程序处于一种不再占用CPU,而只是简单等待响应的状态,但是该进程依然占用着资源。当大量并发I/O请求到达时,则会产生I/O阻塞,造成服务器瓶颈。
事件驱动模型服务器
通过上诉分析与实验说明,事实上,操作系统并不是设计来处理服务器工作负载。传统的线程模型是基于运行应用程序是的一些密集型操作的需要。 操作系统的设计是让用户执行的多线程程序,使后台文件写入和UI操作同时进行,而并不是设计于处理大量并发请求连接。
Fork和多线程是相当费资源的操作,创建线程需要分配一个全新的内存堆栈。此外,上下文切换也是一项开销的,CPU调度模型是并不太适合一个传统的Web服务器。
因此,OPPC模型面临着多进程多线程的延迟已经内存消耗的问题。要用OPPC模型解决C10K问题显得十分复杂。
为解决C10K问题,一些新的服务器呈现出来。下列是解决C10K问题的Web服务器:
nginx: 一个基于事件驱动的处理请求架构反向代理服务器。
Cherokee: Twitter使用的开源Web服务器。
[Tornado][13]: 一个Python语言实现的非阻塞式Web服务器框架。Facebook的FriendFeed模块使用此框架完成。
Node.js: 异步非阻塞Web服务器,运行于Google V8 JavaScript引擎。
...