现在的位置: 首页 > 综合 > 正文

November 3th Tuesday

2013年09月10日 ⁄ 综合 ⁄ 共 3074字 ⁄ 字号 评论关闭

   The function ngx_init_cycle() is too long to feel so tired to study it.   The gaol of this function is used to initialize a new cycle object with the old cycle.  It is hard to look through the codes in it.

 

  I found a boy who also am studying the nginx.  I copied an article from his blog.  Maybe I can use it in the future.

 

  It is folllowing.

 

服务器的并发量取消于两个因素,一个是提供服务的进程数量,另外一个是每个进程可同时处理的并发连接数量。相应的,服务器的并发模型也由两个部分构成:进程模型和连接处理机制。

进程模型可以分为以下几种:

1、单进程模式,这种模式的服务器称为迭代服务器,实现最简单,也没有进程控制的开销,cpu利用率最高,但是所有的客户连接请求排队等待处理,如果有一条连接时长过大,则其他请求就会被阻塞甚至被丢弃,这种模型也很容易被攻击,一般很少使用这种模型;

2、多进程并发模式,这种模式由master进程启动监听并accept连接,然后为每个客户连接请求现场fork一个worker子进程处理客户请求,这种模式也比较简单,但是为每个客户连接请求fork一个子进程比较耗费cpu时间,而且子进程过多的情况下可能会用尽内存,导致开始对换,整体性能急降,这种模型在小并发的情况下比较常用,比如每天处理几千个客户请求的情况;

3、prefork模式,master监听客户连接请求并持续监视可用子进程数量,低于阀值则fork额外的子进程,高于阀值则kill掉一些过剩的子进程。这种模式根据accept的具体情形又可以分为三种变体:

1)master 负责listen,每个worker子进程独自accept,accept无上锁。所有worker阻塞于同一个监听套接字上睡眠,当有新的客户连接请求时,内核会唤醒所有等待该事件的睡眠worker子进程,最先执行的worker将获得连接套接字并处理请求,这种模型会导致惊群问题,尽管只有一个子进程将获得连接,但是所有子进程却都会被唤醒,子进程越多,惊群问题对于性能的影响就越大。另一方面,如果每个worker不是阻塞于accept而是阻塞于select,则很容易造成select冲突问题,这种情况的性能损耗更大,所以这种模型一般都是直接阻塞于accept,不阻塞于select;

2)master 负责listen,每个worker子进程独自accpet,accept有上锁。这种模型解决了惊群问题,只有一个worker阻塞于accpet,其余worker都阻塞于获取锁资源,上锁可以使用文件上锁或者使用共享内存的互斥锁,这种模型的cpu耗时略高于第一种模型。这两种模型都是由内核负责把客户连接请求交由某个worker,客户连接请求的处理比较均匀,因为内核使用了公平的进程切换方式;

3)master负责listen 和accpet,通过某种方式把获得的连接套接字交给一个空闲worker。这种模型下的master必须管理所有worker子进程的状态,并且要使用某种方式的进程间通信方式传递套接字给子进程,比如采用socketpair创建字节流管道用于传递。相对于上面两种模型而言,这种模型复杂度高一些,cpu耗时也更高,并且子进程的分配也由master负责,是否均匀取决于master。

以上的进程模型都假定了两个条件,套接字是阻塞的,并且每个客户连接请求对应一个子进程。这也就意味着如果同时并发量很高的时候,比如超过1万的并发量,就要有1万个worker子进程同时服务,内存耗光后,服务器性能急剧下降。这些模型基本上只能服务于并发量很低的情况,一般在1千以内勉强过得去(还依赖于每个处理的消耗)。

一个自然的解决办法就是把进程与连接的比例从1:1变成m:n。m=1、n>1的情况下,一个worker进程可以处理多个连接请求,这样对于每个客户连接的处理就不能是全程阻塞的了。可以把每个客户连接的处理分为若干过程,每个过程都是一个状态,这样就可以把对一个客户的连接请求处理分解为若干步骤。我们知道,类似于webserver这样的服务器,其客户处于不同的网络环境,并且server所处的网络环境也随着网络状况的改变而改变,所以每个客户连接请求耗费的时长不等,有些可能很长(tcp拥塞控制等),有些可能很短,如果进程阻塞于一条时长大的客户请求上面,对于那些时长短的客户请求是不公平的。如果把每个客户请求的处理分开为不同的阶段,就可以在一个子进程内或者一批子进程间并发的处理更多的连接请求了,并且可以更好的控制资源的分配和管理,将资源的消耗降到一定的低水平,这样也就等于提高了服务器的整体并发能力。

如何把连接请求处理分解为不同的步骤呢?这就要介绍一下并发模型的连接处理机制了,这个机制的关键是IO模型。一般有五种典型的IO模型,下面我们以网络套接口上的输入操作(In)为例,分别介绍五种模型。

对于一个套接口上的输入操作,第一步通常是等待数据从网络中到达(或者等待连接请求建立),当所等待分组到达时(或者等待的连接请求已经建立),它被拷贝到内核中的某个缓冲区(或者listen的监听队列中相应连接请求套接字置位);第二步就是把数据从内核缓冲区拷贝到应用进程的缓冲区(或者accept获得已经建立的连接请求的连接套接字)。

1、阻塞IO模型:当套接口是阻塞的,所有的输入操作(调用connect,accept,read,recvfrom,recv,recvmsg等输入函数)发起后会阻塞到两个步骤完成才会返回;

2、非阻塞IO模型:当套接口是非阻塞的,所有的输入操作在第一个步骤立即返回,这个时候一般需要轮询检查(循环调用输入函数),当数据准备好或者连接已经建立进入第二步的情况下,调用的输入函数将阻塞到第二步完成为止;

以后两种模型都只能处理一个单独的套接口,如果要同时处理多个套接口,就可以使用:

3、 IO复用模型:当在等待多个套接口的输入时,可以调用select、poll等IO复用函数监听这些套接口的输入事件,进程会阻塞在这些调用上,直到有一个或者多个套接口的输入事件发生,也即完成了第一步,IO复用函数会返回这些套接口,接着进程可以调用输入函数完成这些套接口的第二步;

4、信号驱动IO模型:创建套接口的时候,开启套接口的信号驱动IO功能,并安装一个信号处理函数,处理SIGIO信号。当套接口完成了第一步时,会发送SIGIO信号通知进程处理,进程在信号处理函数中完成第二步;

5、异步IO模型:告诉内核启动某个输入操作,并让内核在完成了输入操作之后(两个步骤都完成)通知进程。

前三种模型在所有的操作系统都支持,而后两种模型很少操作系统实现。前四种IO模型都导致进程阻塞,直到IO操作完成,属于同步IO操作;只有异步IO模型不导致进程阻塞,是异步IO操作。

IO 复用模型中,select和poll一般所有的操作系统都会支持,但是每次等待都要设置需要等待的套接口,并且内部的实现不够高效,很难支持监听高并发量的套接口集。不同的操作系统使用了不同的高级轮询技术来支持高性能的监听,一般这些方式都不是可移植的,比如freebsd上实现了 kqueue,solaris实现了/dev/poll,linux实现了epoll等等。

抱歉!评论已关闭.