Nginx从入门到精通阅读笔记1

来源:互联网 发布:淘宝美工和客服哪个好 编辑:程序博客网 时间:2024/05/24 07:21
Nginx架构初窥

Nginx的进程模型:

以前版本的Nginx重启的时候是发送信号来实现,比如 kill -HUP pid。现在Nginx改成了一系列的命令行参数,比如./nginx -s reload,这样我们就启动了一个新的nginx进程,新的进程在解析到reload参数后,就知道我们是重新加载配置文件,它向master发送信号,然后master会先重新加载配置文件,然后启动新的进程,并向所有的老进程发送信号,不再接收新的请求,老进程处理完当前的连接后退出。master启动的新进程就开始接收新的请求了。

每个worker进程都是从master进程fork来的,在fork之前master是先建立好listen的socket了,这样fork出来的多个work进程都可以去accept请求(注意每个进程当然是不同的socket,但是监听的ip地址和端口是同一个,这个在网络协议里面是允许的),当一个请求进来后,所有的socket都会得到通知,但是只有一个进程可以成功accept,这就是所谓的惊群现象。Nginx提供了一个accept_mutex来避免惊群现象,Nginx默认是打开的,我们可以通过配置项来修改关闭。

Nginx的网络事件模型:

有人认为Nginx采用多个worker来处理请求,每个worker只有一个主线程,因为处理并发数有限啊,因为一般worker的数目和cpu的数目一致。其实不是这样,nginx采用了异步非阻塞的方式来处理请求。这里拿apache来对比,apache常用的工作方式是每个请求独占一个工作线程,当并发数上到几千时,同时有几千的线程处理请求。因为要线程要切换,这对操作系统来说是个不小的挑战,并且带来的内存占用会非常大,线程的上下文切换带来的CPU开销很大,性能自然就上不去了。

分析一下nginx采用的异步非阻塞方式,先看一个请求的完整过程:首先,请求过来,建立连接,接收数据,发送数据。在操作系统层面就是读写事件,当读写事件没有准备好时,必然不可操作,不用非阻塞方式来调用,那事件没有准备好就只能阻塞等待,这样cpu或者等待或者会切换给别人用,如果等待的话肯定不能高并发,浪费了cpu资源,如果是切换给别人用,那就成了类似apache的模式。所以在Nginx里面最忌讳的就是阻塞的系统调用了。所以,如果事件没有准备好,就返回EAGAIN,等一会再来检查,知道好了为止。但这样的轮询也是很浪费资源。于是在操作系统提供这样一种机制,同时监控多个事件,仍然是阻塞的调用,我们可以设置超时时间,超时时间之内,有事件准备好了就返回,只有所有事件都没有准备好时,才会阻塞。这样,我们就可以并发处理大量的并发了,当然,这里的并发请求,是指未处理完的请求,线程只有一个,所以同时能处理的请求当然只有一个了,只是在请求间进行不断的切换而已,切换也是因为异步事件未准备好,而主动让出的。这里的切换是没有任何代价的,可以理解为循环处理多个准备好的事件。与多线程相比,这种事件处理方式有很大优势,不需要创建线程,每个请求占用的内存很少,没有上下文切换,事件处理非常轻量级。并发数再多也不会导致无谓的资源浪费(上下文切换)。这里我们应该很容易理解为什么要把worker的个数设置为CPU的核数。并且,Nginx提供了更好的多核特性,我们可以将某个进程绑定到某一个核上,这样就不会因为进程的切换带来cache的失效。(Nginx类似的优化很多,比如nginx在比较4个字节的字符串时,转换成int比较,这样可以减少CPU指令数)


Nginx的信号事件模型:

对Nginx来说,有一些特定的信号,代表着特定的意义。信号会中断程序当前的运行,改变状态后继续执行。如果是系统调用,则可能会导致系统调用的失败,需要重入。(感觉介绍的不详细,个人理解就是中断当前程序,调用信号处理函数)

Nginx的定时器模型:

epoll_wait等函数在调用的时候可以设置一个超时时间,Nginx借助这个超时时间来实现定时器。nginx里面的定时器事件放在一个最小堆里面,每次进入epoll_wait前,先从最小堆里面拿到所有定时器的最小时间,在计算出epoll_wait的超时时间后进入epoll_wait。所以,当没有事件产生,也没有中断信号时,epoll_wait会超时,也就是说,定时器事件到了。这时,nginx会检查所有的超时事件,将他们的状态设置为超时,然后再去处理网络事件,因为在写Nginx代码时,处理网络事件的回调函数时,第一件事就是判断超时,然后再去处理网络事件。

Nginx connection:

某一个子进程accept成功后,会创建与nginx对连接的封装,即ngx_connection_t结构体。接着,设置读写事件处理函数并添加读写事件来与客户端进行数据的交换。最后,nginx或客户端来主动关掉连接。

nginx也可以作为客户端来请求其它server的数据(如upstream),此时,与其它server创建的连接也是封装在ngx_connection_t中。作为客户端,ngxin先获取一个ngx_connection_t结构体,然后创建socket,并设置socket的属性。然后通过添加读写事件,调用connect/read/write来调用连接,最后关掉连接,释放ngx_connection_t。

在ngxin中,每个进程有一个最大连接上限,这个和操作系统的上限(fd值,通过ulimit -n获取)没关系,各定义各的。ngxin通过设置worker_connections来设置每个进程可使用的连接最大值。nginx在实现时,是通过一个连接池来管理的,每个worker进程都有一个独立的连接池,连接池的大小是worker_connections。它里面并不是真实的连接,只是一个worker_connections大小的ngx_connection_t结构数组。并且,nginx会通过一个链表free_connections来保存所有的空闲ngx_connection_t,每次获取一个连接时,就从空闲连接链中取一个,用完后,再放回空闲连接链表里面。

因为worker_connections,表示的是每个进程的连接数,因为nginx所能建立连接的最大值就是worker_connections*worker_processes。这是作为http server的连接数,如果是作为反向代理,则除以二worker_connections*worker_processes/2,因为反向代理每个请求会占用两个连接。


负载均衡:

如果某个进程一直得到accept,但是他的连接数有限,可能导致某些请求得不到服务。这时需要用到accept_mutex选项,只有获得了accept_mutex的进程才会去添加accept事件。Ngxin通过一个变量ngx_accept_disabled来控制是否去竞争accept_mutex锁。这个值是空闲连接数跟总连接数的八分之一比较,当小于八分之一时,就不会去尝试获取锁,这样就给了其它进程获取锁的机会。


0 0
原创粉丝点击