nginx 概念原理及优化 - 高超博客

技术分享 Story 82浏览 0评论

一、nginx高效的原理:

   1、概念:

       NGINX采用了异步、事件驱动的方法来处理连接。这种处理方式无需(像使用传统架构的服务器一样)为每个请求创建额外的专用进程或者线程,而是在一个工作进程中处理多个连接和请求。为此,NGINX工作在非阻塞的socket模式下,并使用了epoll 和 kqueue这样有效的方法。

   2、什么是epoll

       epoll的最大好处在于他不会随着被监控描述符的数目的增长而导致效率极致下降。epoll监控的描述符的数目很大,并且epoll对描述符的响应是触发的,即当有描述符有时间发生会有触发。

   3、什么是文件描述符

      文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。

   4、epoll的工作

          检测四类事件:

             # 处理新事件

             # 定时处理事件

             # 处理普通读写

             # 处理从磁盘读事件

          

   (1)nginx的处理连接事件和处理其他事件都是在同一个I/O复用下,那么它是如何保证连接事件对响应的要求的呢?niginx是通过将获取的事件先不调用其回调,而是把他们先放入俩个post队列,这俩个队列分别为

.ngx_posted_accept_events
.ngx_posted_events

第一个队列用来保存连接事件,而第二个队列用来保存普通读写事件,之后在执行时我们可以先保证ngx_posted_accept_events中的事件先处理,就可以保证连接对响应速度的敏感性

         (2)如何防止串话 
    串话问题可以说是服务器程序中都需要处理的一个问题。串话问题是指刚刚关闭了一个套接字,又来了一个新连接,而新连接刚好系统给分配的就是刚关闭的那个套接字,那么如果方才哪个套接字还有事件未处理完成,接下来它给对应的套接字发送数据很有可能就会发到新建立的用户那。那么nginx如何来解决这个问题呢?很简单,nginx在每次获得新连接后都会将连接中的一个标志为置反,这样本个连接和上个连接的instance就会不同,而每个事件都包含了连接,所以每次处理事件时只需要比较事件中的instance是否相同就OK了

        (3)如何处理”惊群问题” 
    所谓惊群问题就是说多个进程在同时监听同一个端口,当有连接到来时,系统会把多个进程都唤醒,但是当然任然只有一个进程能处理到新连接,所以本来其他进程是不需要被唤醒的,但是被唤醒了,这就是注明的惊群问题。nginx解决它的方法也很简单,只需要保证同一时间点只有一个进程在监听端口就可避免惊群问题了。但是问题的关键是如何能保证同一时间点只有一个进程来监听端口。nginx采用了尝试加锁,根据加锁的返回值确定本进程是否要接下来处理新连接事件,从而解决了”惊群问题”

      (4)如何解决负载均衡

    nginx解决个进程间的负载均衡问题并没有均衡分配,而是当每个进程处理的额连接数超过了规定其处理的最大连接数的7/8时,就会本次不处理连接,而是将其处理的连接数-1,这样相当于就把机会让给了其他线程,从而实现了负载均衡了。

   5、i/o复用     

    关于I/O多路复用(又被称为“事件驱动”),首先要理解的是,操作系统为你提供了一个功能,当你的某个socket可读或者可写的时候,它可以给你一个通知。这样当配合非阻塞的socket使用时,只有当系统通知我哪个描述符可读了,我才去执行read操作,可以保证每次read都能读到有效数据而不做纯返回-1和EAGAIN的无用功。写操作类似。
二、nginx 进程模型

  1、master进程

   主要用来管理worker进程,包含:接收来自外界的信号,向各worker进程发送信号,监控worker进程的运行状态,当worker进程退出后(异常情况下),会自动重新启动新的worker进程。master进程充当整个进程组与用户的交互接口,同时对进程进行监护。它不需要处理网络事件,不负责业务的执行,只会通过管理worker进程来实现重启服务、平滑升级、更换日志文件、配置文件实时生效等功能

     2、worker进程之间是平等的,每个进程,处理请求的机会也是一样的。当我们提供80端口的http服务时,一个连接请求过来,每个进程都有可能处理这个连接,怎么做到的呢?首先,每个worker进程都是从master进程fork过来,在master进程里面,先建立好需要listen的socket(listenfd)之后,然后再fork出多个worker进程。所有worker进程的listenfd会在新连接到来时变得可读,为保证只有一个进程处理该连接,所有worker进程在注册listenfd读事件前抢accept_mutex,抢到互斥锁的那个进程注册listenfd读事件,在读事件里调用accept接受该连接。当一个worker进程在accept这个连接之后,就开始读取请求,解析请求,处理请求,产生数据后,再返回给客户端,最后才断开连接,这样一个完整的请求就是这样的了。  
 
三、对于以上概念,提供以下的优化方案
 
nginx.conf文件调优:
 
  1、高层配置:
worker_processes 8;

 nginx进程数,建议按照cpu数目来指定,一般为它的倍数。

worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000;

 为每个进程分配cpu,上例中将8个进程分配到8个cpu,当然可以写多个,或者将一个进程分配到多个cpu。

worker_rlimit_nofile 102400;

  这个指令是指当一个nginx进程打开的最多文件描述符数目,理论值应该是最多打开文件数(ulimit -n)与nginx进程数相除,但是nginx分配请求并不是那么均匀,所以最好与ulimit -n的值保持一致。

 

  2、Events模块

    events模块中包含nginx中所有处理连接的设置。

    events { 

      worker_connections 65536; 

      multi_accept on; 

      use epoll; 

      } 

  

  worker_connections 设置可由一个worker进程同时打开的最大连接数。如果设置了上面提到的worker_rlimit_nofile,我们可以将这个值设得很高。记住,最大客户数也由系统的可用socket连接数限制(~ 64K),所以设置不切实际的高没什么好处。

  multi_accept 告诉nginx收到一个新连接通知后接受尽可能多的连接。

  use 设置用于复用客户端线程的轮询方法。如果你使用Linux 2.6+,你应该使用epoll。如果你使用*BSD,你应该使用kqueue。

 3、http模块   

  HTTP模块控制着nginx http处理的所有核心特性。因为这里只有很少的配置,所以我们只节选配置的一小部分。所有这些设置都应该在http模块中,甚至你不会特别的注意到这段设置。

  http { 

    server_tokens off; 

    sendfile on; 

    tcp_nopush on; 

    tcp_nodelay on; 

  server_tokens  并不会让nginx执行的速度更快,但它可以关闭在错误页面中的nginx版本数字,这样对于安全性是有好处的。

  sendfile 可以让sendfile()发挥作用。sendfile()可以在磁盘和TCP socket之间互相拷贝数据(或任意两个文件描述符)。Pre-sendfile是传送数据之前在用户空间申请数据缓冲区。之后用read()将数据从文件拷贝到这个缓冲区,write()将缓冲区数据写入网络。sendfile()是立即将数据从磁盘读到OS缓存。因为这种拷贝是在内核完成的,    sendfile()要比组合read()和write()以及打开关闭丢弃缓冲更加有效(更多有关于sendfile)。

  tcp_nopush 告诉nginx在一个数据包里发送所有头文件,而不一个接一个的发送。

  tcp_nodelay 告诉nginx不要缓存数据,而是一段一段的发送--当需要及时发送数据时,就应该给应用设置这个属性,这样发送一小块数据信息时就不能立即得到返回值。     

client_header_buffer_size 4k;

  客户端请求头部的缓冲区大小,这个可以根据你的系统分页大小来设置,一般一个请求的头部大小不会超过1k,不过由于一般系统分页都要大于1k,所以这里设置为分页大小。分页大小可以用命令getconf PAGESIZE取得。

open_file_cache max=102400 inactive=20s;

  这个将为打开文件指定缓存,默认是没有启用的,max指定缓存数量,建议和打开文件数一致,inactive是指经过多长时间文件没被请求后删除缓存。

open_file_cache_valid 30s;

  这个是指多长时间检查一次缓存的有效信息。

open_file_cache_min_uses 1;

  open_file_cache指令中的inactive参数时间内文件的最少使用次数,如果超过这个数字,文件描述符一直是在缓存中打开的,如上例,如果有一个文件在inactive时间内一次没被使用,它将被移除。

 

  keepalive_timeout 10; 

  client_header_timeout 10; 

  client_body_timeout 10; 

  reset_timedout_connection on; 

  send_timeout 10; 

  keepalive_timeout  给客户端分配keep-alive链接超时时间。服务器将在这个超时时间过后关闭链接。我们将它设置低些可以让ngnix持续工作的时间更长。

  client_header_timeout 和client_body_timeout 设置请求头和请求体(各自)的超时时间。我们也可以把这个设置低些。

  reset_timeout_connection 告诉nginx关闭不响应的客户端连接。这将会释放那个客户端所占有的内存空间。

  send_timeout 指定客户端的响应超时时间。这个设置不会用于整个转发器,而是在两次客户端读取操作之间。如果在这段时间内,客户端没有读取任何数据,nginx就会关闭连接。

   

open_file_cache max=100000 inactive=20s; 

open_file_cache_valid 30s; 

open_file_cache_min_uses 2; 

open_file_cache_errors on; 

open_file_cache 打开缓存的同时也指定了缓存最大数目,以及缓存的时间。我们可以设置一个相对高的最大时间,这样我们可以在它们不活动超过20秒后清除掉。

open_file_cache_valid 在open_file_cache中指定检测正确信息的间隔时间。

open_file_cache_min_uses 定义了open_file_cache中指令参数不活动时间期间里最小的文件数。

open_file_cache_errors 指定了当搜索一个文件时是否缓存错误信息,也包括再次给配置中添加文件。我们也包括了服务器模块,这些是在不同文件中定义的。如果你的服务器模块不在这些位置,你就得修改这一行来指定正确的位置。

 

client_header_timeout 和client_body_timeout 设置请求头和请求体(各自)的超时时间。我们也可以把这个设置低些。

reset_timeout_connection 告诉nginx关闭不响应的客户端连接。这将会释放那个客户端所占有的内存空间。

send_timeout 指定客户端的响应超时时间。这个设置不会用于整个转发器,而是在两次客户端读取操作之间。如果在这段时间内,客户端没有读取任何数据,nginx就会关闭连接。

  limit_conn_zone $binary_remote_addr zone=addr:5m; 

  limit_conn addr 100; 

limit_conn_zone 设置用于保存各种key(比如当前连接数)的共享内存的参数。5m就是5兆字节,这个值应该被设置的足够大以存储(32K*5)32byte状态或者(16K*5)64byte状态。

limit_conn 为给定的key设置最大连接数。这里key是addr,我们设置的值是100,也就是说我们允许每一个IP地址最多同时打开有100个连接。

  

 
 

转载请注明:成长的对话 » nginx 概念原理及优化 - 高超博客