传统服务器模型
传统的服务器模型如Apache 为每一个请求生成一个子进程。当用户连接到服务器时 一个子进程就产生,并做连接处理。每个连接获得一个单独的线程和子进程。当用户请求数据返回时,子进程开始等待数据库操作返回。此时另外一个用户也发起请求的时候。这时候就产品了阻塞。
这种模式在小的工作负荷时表现良好,当请求的数量变得很大时候服务器的压力会过于巨大。比如达到Apache最大进程数时候,所有的进程会变得很缓慢。
fork操作延时
新建进程的性能很大依赖于操作系统的对fork的实现,不同的操作系统处理并非都很理想,都会有不同的延时。
进程调度
Linux每10ms中断一次在运行态的进程,查看是否需要切换别的进程执行。进程调度的任务就是决定下一个应该执行的进程,难度就在于如何公平的分配CPU资源
内存占用和线程
创建多个进程会带来另外一个问题:内存消耗,每一个创建的进程都会占用内存。
可靠性
该模型具有可靠性的问题,一个配置不当的服务器,很容易受到拒绝服务攻击(Dos)。当大量并发请求的服务资源时,负载均衡配置不当时,服务器很快会耗尽资源而崩溃
同步阻塞IO
应用程序执行一个系统调用,这会导致应用程序阻塞。这意味着应用程序会一直阻塞,直到系统调用完成为止(数据传输完成或发生错误)。调用应用程序处于一种不再占用CPU,而只是简单等待响应的状态,但是该进程依然占用着资源。当大量并发I/O请求到达时,则会产生I/O阻塞,造成服务器瓶颈。
事件驱动模型
操作系统并不是设计来处理服务器工作的负载。操作系统的设计是让用户执行的多线程程序,使后台文件写入和UI操作同时进程,而并不是设计处理大量并发请求的连接
Fork和多线程是相当耗费资源的操作,创建线程需要分配一个权限的内存堆栈。此外,上下文的切换也是一项开销,CPU调度模型并不适合一个传统的Web服务器。因此传统的服务器模型面临多进程多线程延迟以及内存消耗的问题。要解决C10K(就是单机1万个并发连接问题)问题显得十分复杂。
由于网络负载工作包含大量的等待,比如Apache服务器:产生大量的子进程,需要消耗大量的内存。但是大多数的子进程占用大量内存资源却只是等待一个阻塞任务的结束。所以新的模型抛弃了对每个请求生成子进程的想法。所有的请求和事物操作只使用一个单独的线程管理,此线程称为事件循环
。事件循环将一部的管理所有用户连接和文件读取或者数据库服务器。当请求到达时,使用poll或者select唤醒操作系统对其请求做相应处理。这样一来处理的并发请求不再是紧紧围绕在阻塞资源。
当然,这样也有一定的开销,如保持一个始终打开的TCP连接的列表,但内存并不会由于大量并发请求而急速上升,因为这个列表只占内存堆上很小的一部分。
网友评论