Nginx工作原理和优化、漏洞

Nginx (engine x) (外交部)

  • 高性能的HTTP和反向代理服务器

  • IMAP/POP3/SMTP服务器

  • 负载均衡服务器

  • 轻量级的Web 服务器/反向代理服务器

  • 电子邮件(IMAP/POP3)代理服务器

占有内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页服务器中表现较好

结构与扩展

一个主进程和多个工作进程。工作进程是单线程的,且不需要特殊授权即可运行;

HTTP基础功能

处理静态文件,索引文件以及自动索引;

反向代理加速(无缓存),简单的负载均衡和容错;

FastCGI,简单的负载均衡和容错;

模块化的结构。过滤器包括gzipping,byte ranges,chunked responses,以及 SSI-filter。在SSI过滤器中,到同一个 proxy 或者 FastCGI 的多个子请求并发处理;

SSL 和 TLS SNI 支持;

IMAP/POP3代理服务功能:
使用外部 HTTP 认证服务器重定向用户到 IMAP/POP3 后端;
使用外部 HTTP 认证服务器认证用户后连接重定向到内部的 SMTP 后端;

其他HTTP功能
基于名称和基于IP的虚拟服务器;

安装

模块依赖性

gzip模块需要 zlib 库
rewrite模块需要 pcre 库
ssl 功能需要openssl库

Nginx的模块与工作原理

Nginx由 内核和模块 组成
内核的设计非常微小和简洁,完成的工作也非常简单,仅仅通过查找配置文件将客户端请求映射到一个 location block(location是Nginx配置中的一个指令,用于URL匹配),而在这个location中所配置的每个指令将会启动不同的模块去完成相应的工作。

外部请求 → nginx内核处理(调用配置文件) → 基于配置文件选择某个处理模块 → 处理后的内容交给 过滤模块 →响应外部请求

Nginx的模块从结构上分为核心模块、基础模块和第三方模块:

  • 核心模块:HTTP模块、EVENT模块和MAIL模块

  • 基础模块:HTTP Access模块、HTTP FastCGI模块、HTTP Proxy模块和HTTP Rewrite模块,

  • 第三方模块:HTTP Upstream Request Hash模块、Notice模块和HTTP Access Key模块。

用户根据自己的需要开发的模块都属于第三方模块

Nginx的模块从功能上分为如下三类:

  • Handlers(处理器模块)。
    此类模块直接处理请求,并进行输出内容和修改headers信息等操作。Handlers处理器模块一般只能有一个。

  • Filters (过滤器模块)。
    此类模块主要对其他处理器模块输出的内容进行修改操作,最后由Nginx输出。

  • Proxies (代理类模块)。
    此类模块是Nginx的HTTP Upstream之类的模块,这些模块主要与后端一些服务比如FastCGI等进行交互,实现服务代理和负载均衡等功能。

模块可以看做Nginx真正的劳动工作者。

handler模块负责处理请求,完成响应内容的生成,而filter模块对响应内容进行处理。

Nginx的模块直接被编译进Nginx,因此属于静态编译方式。启动Nginx后,Nginx的模块被自动加载,不像Apache,首先将模块编译为一个so文件,然后在配置文件中指定是否进行加载。
在解析配置文件时,Nginx的每个模块都有可能去处理某个请求,但是同一个处理请求只能由一个模块来完成。

Nginx的进程模型

单工作进程
多工作进程

在单工作进程模式下,除主进程外,还有一个工作进程,工作进程是单线程的;
在多工作进程模式下,每个工作进程包含多个线程。
Nginx默认为单工作进程模式。

一个master进程和多个worker进程。

master进程 (nginx管家)

主要用来管理worker进程,包含:接收来自外界的信号,向各worker进程发送信号,监控worker进程的运行状态,当worker进程退出后(异常情况下),会自动重新启动新的worker进程。

kill -HUP pid 重启某个程序

轮休换班

./nginx -s reload,重启nginx
./nginx -s stop,停止nginx的运行

worker进程:

而基本的网络事件,则是放在worker进程中来处理了。多个worker进程之间是对等的,他们同等竞争来自客户端的请求,各进程互相之间是独立的。

worker进程的个数一般我们会设置与机器cpu核数一致

master进程里面,先建立好需要listen的socket(listenfd)之后,然后再fork出多个worker进程。

一个worker进程在accept这个连接之后,就开始读取请求,解析请求,处理请求,产生数据后,再返回给客户端,最后才断开连接,这样一个完整的请求就是这样的了

nginx的进程模型:

Nginx+FastCGI运行原理

FastCGI是一个可伸缩地、高速地在HTTP server和动态脚本语言间通信的接口。CGI 通用网关接口

多数流行的HTTP server都支持FastCGI,包括Apache、Nginx和lighttpd等。同时,FastCGI也被许多脚本语言支持,其中就有PHP。

FastCGI接口方式采用C/S结构

Nginx+FastCGI运行原理

Nginx不支持对外部程序的直接调用或者解析,所有的外部程序(包括php)必须通过FastCGI接口来调用。

FastCGI接口在linux下是socket(这个socket可以是文件socket,也可以是ip socket)

wrapper:为了调用CGI程序,还需要一个FastCGI的wrapper(wrapper可以理解为用于启动另一个程序的程序)

首先需要一个wrapper,这个wrapper需要完成的工作:

通过调用fastcgi(库)的函数通过socket 与 ningx通信(读写socket是fastcgi内部实现的功能,对wrapper是非透明的)
调度thread,进行fork和kill
和application(php)进行通信

spawn-fcgi与PHP-FPM
FastCGI接口方式在脚本解析服务器上启动一个或者多个守护进程对动态脚本进行解析,这些进程就是FastCGI进程管理器,或者称为FastCGI引擎。 spawn-fcgi与PHP-FPM就是支持PHP的两个FastCGI进程管理器。因此HTTPServer完全解放出来,可以更好地进行响应和并发处理。
php-fpm比spawn-fcgi更稳定更优秀

使用Nginx+PHP/PHP-FPM这个组合对PHP进行解析。

FastCGI 的主要优点是把动态语言和HTTP Server分离开来
Nginx与PHP/PHP-FPM经常被部署在不同的服务器上,以分担前端Nginx服务器的压力,使Nginx专一处理静态请求和转发动态请求,而PHP/PHP-FPM服务器专一解析PHP动态请求。

Nginx+PHP-FPM

PHP5.3.3已经集成php-fpm
PHP-FPM提供了更好的PHP进程管理方式
./configure的时候带 –enable-fpm参数即可开启PHP-FPM。

安装Nginx和PHP-FPM完后,配置信息:

PHP-FPM的默认配置php-fpm.conf:
listen_address 127.0.0.1:9000 #这个表示php的fastcgi进程监听的ip地址以及端口
start_servers
min_spare_servers
max_spare_servers

Nginx配置运行php: 编辑nginx.conf加入如下语句:
location ~ .php$ {
root html;
fastcgi_pass 127.0.0.1:9000; #指定了fastcgi进程侦听的端口,nginx就是通过这里与php交互的
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/local/nginx/html$fastcgi_script_name;
}

Nginx通过location指令,将所有以php为后缀的文件都交给127.0.0.1:9000来处理,而这里的IP地址和端口就是FastCGI进程监听的IP地址和端口。

其整体工作流程:
1)、FastCGI进程管理器php-fpm自身初始化,启动主进程php-fpm和启动start_servers个CGI 子进程。
主进程php-fpm主要是管理fastcgi子进程,监听9000端口。
fastcgi子进程等待来自Web Server的连接。
2)、当客户端请求到达Web Server Nginx是时,Nginx通过location指令,将所有以php为后缀的文件都交给127.0.0.1:9000来处理,即Nginx通过location指令,将所有以php为后缀的文件都交给127.0.0.1:9000来处理。
3)FastCGI进程管理器PHP-FPM选择并连接到一个子进程CGI解释器。Web server将CGI环境变量和标准输入发送到FastCGI子进程。
4)、FastCGI子进程完成处理后将标准输出和错误信息从同一连接返回Web Server。当FastCGI子进程关闭连接时,请求便告处理完成。
5)、FastCGI子进程接着等待并处理来自FastCGI进程管理器(运行在 WebServer中)的下一个连接。

Nginx+PHP正确配置

一般web都做统一入口:把PHP请求都发送到同一个文件上,然后在此文件里通过解析「REQUEST_URI」实现路由。

Nginx配置文件分为好多块,常见的从外到内依次是「http」、「server」、「location」等等,缺省的继承关系是从外到内,也就是说内层块会自动获取外层块的值作为缺省值。

server {
    listen 80;
    server_name foo.com;
    root /path;
    location / {
        index index.html index.htm index.php;
        if (!-e $request_filename) {
            rewrite . /index.php last;
        }
    }
    location ~ .php$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME /path$fastcgi_script_name;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
    }
} 
  1. 不应该在location
    模块定义index
    一旦未来需要加入新的「location」,必然会出现重复定义的「index」指令,这是因为多个「location」是平级的关系,不存在继承,此时应该在「server」里定义「index」,借助继承关系,「index」指令在所有的「location」中都能生效。

很多人喜欢用「if」指令做一系列的检查,不过这实际上是「try_files」指令的职责:

try_files $uri $uri/ /index.php;

3)fastcgi_params 配置文件:

include fastcgi_params;

Nginx有两份fastcgi配置文件,分别是「fastcgi_params」和「fastcgi.conf」,它们没有太大的差异,唯一的区别是后者比前者多了一行「SCRIPT_FILENAME」的定义:

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

注意:$document_root 和 $fastcgi_script_name 之间没有 /。

改良后的版本,是不是比开始的版本清爽了很多:

server {  
    listen 80;  
    server_name foo.com;  
    root /path;  
    index index.html index.htm index.php;  
    location / {  
        try_files $uri $uri/ /index.php;  
    }  
    location ~ .php$ {  
       try_files $uri =404;  
       include fastcgi.conf;  
       fastcgi_pass 127.0.0.1:9000;  
   }  
}  

Nginx为啥性能高-多进程IO模型

nginx采用多进程模型好处

nginx多进程事件模型:异步非阻塞

进程带来的内存占用非常大,进程的上下文切换带来的cpu开销很大,自然性能就上不去了,而这些开销完全是没有意义的。

nginx(master进程)-----> 多个nginx work 进程 ------> 处理 http 请求

非阻塞就是,事件没有准备好,马上返回EAGAIN,告诉你,事件还没准备好呢,你慌什么,过会再来吧。好吧,你过一会,再来检查一下事件,直到事件准备好了为止,在这期间,你就可以先去做其它事情,然后再来看看事件好了没。虽然不阻塞了,但你得不时地过来检查一下事件的状态,你可以做更多的事情了,但带来的开销也是不小的。

Nginx优化

1. 编译安装过程优化

1).减小Nginx编译后的文件大小

在编译Nginx时,默认以debug模式进行,而在debug模式下会插入很多跟踪和ASSERT之类的信息,编译完成后,一个Nginx要有好几兆字节。而在编译前取消Nginx的debug模式,编译完成后Nginx只有几百千字节。因此可以在编译之前,修改相关源码,取消debug模式。具体方法如下:

在Nginx源码文件被解压后,找到源码目录下的auto/cc/gcc文件,在其中找到如下几行:

# debug
CFLAGS=”$CFLAGS -g”
注释掉或删掉这两行,即可取消debug模式。

2.为特定的CPU指定CPU类型编译优化

在编译Nginx时,默认的GCC编译参数是“-O”,要优化GCC编译,可以使用以下两个参数:

--with-cc-opt='-O3'
--with-cpu-opt=CPU #为特定的 CPU 编译,有效的值包括:
pentium, pentiumpro, pentium3, # pentium4, athlon, opteron, amd64, sparc32, sparc64, ppc64

要确定CPU类型,可以通过如下命令:

cat /proc/cpuinfo | grep "model name"

  1. 利用TCMalloc优化Nginx的性能

与标准的glibc库的Malloc相比,TCMalloc库在内存分配效率和速度上要高很多

要安装TCMalloc库,需要安装libunwind(32位操作系统不需要安装)和google-perftools两个软件包,libunwind库为基于64位CPU和操作系统的程序提供了基本函数调用链和函数调用寄存器功能。下面介绍利用TCMalloc优化Nginx的具体操作过程。

1).安装libunwind库

可以从http://download.savannah.gnu.org/releases/libunwind下载相应的libunwind版本,这里下载的是libunwind-0.99-alpha.tar.gz。安装过程如下:

tar zxvf libunwind-0.99-alpha.tar.gz  
cd libunwind-0.99-alpha/  
CFLAGS=-fPIC ./configure  
make CFLAGS=-fPIC  
make CFLAGS=-fPIC install 

2).安装google-perftools

可以从http://google-perftools.googlecode.com下载相应的google-perftools版本,这里下载的是google-perftools-1.8.tar.gz。安装过程如下:

tar zxvf google-perftools-1.8.tar.gz  
cd google-perftools-1.8/  
 ./configure  
make && make install  
echo "/usr/local/lib" > /etc/ld.so.conf.d/usr_local_lib.conf  
ldconfig

3).重新编译Nginx

为了使Nginx支持google-perftools,需要在安装过程中添加“–with-google_perftools_module”选项重新编译Nginx。安装代码如下:

./configure   
--with-google_perftools_module 
--with-http_stub_status_module  
--prefix=/opt/nginx  

make  
make install 

4).为google-perftools添加线程目录

创建一个线程目录,这里将文件放在/tmp/tcmalloc下。操作如下:

mkdir /tmp/tcmalloc  
chmod 0777 /tmp/tcmalloc 

5).修改Nginx主配置文件

修改nginx.conf文件,在pid这行的下面添加如下代码:

#pid        logs/nginx.pid;  
google_perftools_profiles /tmp/tcmalloc; 

lsof -n | grep tcmalloc  

Nginx配置文件中设置worker_processes的值为4,因此开启了4个Nginx线程,每个线程会有一行记录。每个线程文件后面的数字值就是启动的Nginx的pid值。

3.Nginx内核参数优化

内核参数的优化,主要是在Linux系统中针对Nginx应用而进行的系统内核参数优化。

下面给出一个优化实例以供参考。

net.ipv4.tcp_max_tw_buckets = 6000 
net.ipv4.ip_local_port_range = 1024 65000  
net.ipv4.tcp_tw_recycle = 1 
net.ipv4.tcp_tw_reuse = 1 
net.ipv4.tcp_syncookies = 1 
net.core.somaxconn = 262144 
net.core.netdev_max_backlog = 262144 
net.ipv4.tcp_max_orphans = 262144 
net.ipv4.tcp_max_syn_backlog = 262144 
net.ipv4.tcp_synack_retries = 1 
net.ipv4.tcp_syn_retries = 1 
net.ipv4.tcp_fin_timeout = 1 
net.ipv4.tcp_keepalive_time = 30 

将上面的内核参数值加入/etc/sysctl.conf文件中,然后执行如下命令使之生效:

/sbin/sysctl -p

TCP参数设置:

net.ipv4.tcp_max_tw_buckets :选项用来设定timewait的数量,默认是180 000,这里设为6000。

net.ipv4.ip_local_port_range:选项用来设定允许系统打开的端口范围。在高并发情况否则端口号会不够用。当NGINX充当代理时,每个到上游服务器的连接都使用一个短暂或临时端口。

net.ipv4.tcp_tw_recycle:选项用于设置启用timewait快速回收.

net.ipv4.tcp_tw_reuse:选项用于设置开启重用,允许将TIME-WAIT sockets重新用于新的TCP连接。

net.ipv4.tcp_syncookies:选项用于设置开启SYN Cookies,当出现SYN等待队列溢出时,启用cookies进行处理。

net.ipv4.tcp_max_orphans:选项用于设定系统中最多有多少个TCP套接字不被关联到任何一个用户文件句柄上。如果超过这个数字,孤立连接将立即被复位并打印出警告信息。这个限制只是为了防止简单的DoS攻击。不能过分依靠这个限制甚至人为减小这个值,更多的情况下应该增加这个值。

net.ipv4.tcp_max_syn_backlog:选项用于记录那些尚未收到客户端确认信息的连接请求的最大值。对于有128MB内存的系统而言,此参数的默认值是1024,对小内存的系统则是128。

net.ipv4.tcp_synack_retries参数的值决定了内核放弃连接之前发送SYN+ACK包的数量。

net.ipv4.tcp_syn_retries选项表示在内核放弃建立连接之前发送SYN包的数量。

net.ipv4.tcp_fin_timeout选项决定了套接字保持在FIN-WAIT-2状态的时间。默认值是60秒。正确设置这个值非常重要,有时即使一个负载很小的Web服务器,也会出现大量的死套接字而产生内存溢出的风险。

net.ipv4.tcp_syn_retries选项表示在内核放弃建立连接之前发送SYN包的数量。

如果发送端要求关闭套接字,net.ipv4.tcp_fin_timeout选项决定了套接字保持在FIN-WAIT-2状态的时间。接收端可以出错并永远不关闭连接,甚至意外宕机。

net.ipv4.tcp_fin_timeout的默认值是60秒。需要注意的是,即使一个负载很小的Web服务器,也会出现因为大量的死套接字而产生内存溢出的风险。FIN-WAIT-2的危险性比FIN-WAIT-1要小,因为它最多只能消耗1.5KB的内存,但是其生存期长些。

net.ipv4.tcp_keepalive_time选项表示当keepalive启用的时候,TCP发送keepalive消息的频度。默认值是2(单位是小时)。

缓冲区队列:
net.core.somaxconn:选项的默认值是128, 这个参数用于调节系统同时发起的tcp连接数,在高并发的请求中,默认的值可能会导致链接超时或者重传,因此,需要结合并发请求数来调节此值。

由NGINX可接受的数目决定。默认值通常很低,但可以接受,因为NGINX 接收连接非常快,但如果网站流量大时,就应该增加这个值。内核日志中的错误消息会提醒这个值太小了,把值改大,直到错误提示消失。
注意: 如果设置这个值大于512,相应地也要改变NGINX listen指令的backlog参数。

net.core.netdev_max_backlog:选项表示当每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许发送到队列的数据包的最大数目。

4. PHP-FPM的优化

1)增加FastCGI进程数

把PHP FastCGI子进程数调到100或以上,在4G内存的服务器上200就可以建议通过压力测试获取最佳值。

2)增加 PHP-FPM打开文件描述符的限制

标签rlimit_files用于设置PHP-FPM对打开文件描述符的限制,默认值为1024。这个标签的值必须和Linux内核打开文件数关联起来,例如,要将此值设置为65 535,就必须在Linux命令行执行“ulimit -HSn 65536”。

然后 增加 PHP-FPM打开文件描述符的限制:

vim /path/to/php-fpm.conf

rlimit_files = 1024

把1024更改为 4096或者更高.
重启 PHP-FPM.

命令行下执行 ulimit -n 65536即可修改

设置 /etc/security/limits.conf,加入

  • hard nofile 65536

  • soft nofile 65536

3)适当增加max_requests

标签max_requests指明了每个children最多处理多少个请求后便会被关闭,默认的设置是500。
max_requests = 500

4.nginx.conf的参数优化

nginx要开启的进程数 一般等于cpu的总核数 其实一般情况下开4个或8个就可以。
每个nginx进程消耗的内存10兆的模样

orker_cpu_affinity
仅适用于linux,使用该选项可以绑定worker进程和CPU(2.4内核的机器用不了

nginx可以使用多个worker进程,原因如下:

use epoll
worker_processes
worker_connections 65535
keepalive_timeout 75
client_header_buffer_size 16k
large_client_header_buffers 4 32k
open_file_cache max 102400
open_file_cache_min_uses

5.访问日志
6.限流
limit_conn and limit_conn_zone:NGINX接受客户连接的数量限制,例如单个IP地址的连接。设置这些指令可以防止单个用户打开太多的连接,消耗超出自己的资源。
limit_rate:传输到客户端响应速度的限制(每个打开多个连接的客户消耗更多的带宽)。设置这个限制防止系统过载,确保所有客户端更均匀的服务质量。
limit_req and limit_req_zone:NGINX处理请求的速度限制,与limit_rate有相同的功能。可以提高安全性,尤其是对登录页面,通过对用户限制请求速率设置一个合理的值,避免太慢的程序覆盖你的应用请求(比如DDoS攻击)。
max_conns:上游配置块中服务器指令参数。在上游服务器组中单个服务器可接受最大并发数量。使用这个限制防止上游服务器过载。设置值为0(默认值)表示没有限制。
queue (NGINX Plus) :创建一个队列,用来存放在上游服务器中超出他们最大max_cons限制数量的请求。这个指令可以设置队列请求的最大值,还可以选择设置在错误返回之前最大等待时间(默认值是60秒)。如果忽略这个指令,请求不会放入队列。

7. 错误排查

1、Nginx 502 Bad Gateway

php-cgi进程数不够用、php执行时间长(mysql慢)、或者是php-cgi进程死掉,都会出现502错误

一般来说Nginx 502 Bad Gateway和php-fpm.conf的设置有关,而Nginx 504 Gateway Time-out则是与nginx.conf的设置有关

1)、查看当前的PHP FastCGI进程数是否够用:

netstat -anpo | grep "php-cgi" | wc -l

2)、部分PHP程序的执行时间超过了Nginx的等待时间,可以适当增加
nginx.conf配置文件中FastCGI的timeout时间,例如:
http {
......
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
......
}

2、413 Request Entity Too Large
解决:增大client_max_body_size
client_max_body_size:指令指定允许客户端连接的最大请求实体大小,它出现在请求头部的Content-Length字段. 如果请求大于指定的值,客户端将收到一个"Request Entity Too Large" (413)错误. 记住,浏览器并不知道怎样显示这个错误.
php.ini中增大
post_max_size 和upload_max_filesize

3 Ngnix error.log出现:upstream sent too big header while reading response header from upstream错误

1)如果是nginx反向代理
proxy是nginx作为client转发时使用的,如果header过大,超出了默认的1k,就会引发上述的upstream sent too big header (说白了就是nginx把外部请求给后端server,后端server返回的header 太大nginx处理不过来就导致了。

server {
        listen       80;
        server_name  *.xywy.com ;

        large_client_header_buffers 4 16k;

        location / {
          #添加这3行 
           proxy_buffer_size 64k;
           proxy_buffers   32 32k;
           proxy_busy_buffers_size 128k;

           proxy_set_header Host $host;
           proxy_set_header X-Real-IP       $remote_addr;
           proxy_set_header X-Forwarded-For  $proxy_add_x_forwarded_for;
    }
}      
  1. 如果是 nginx+PHPcgi
    错误带有 upstream: "fastcgi://127.0.0.1:9000"。就该多加:

fastcgi_buffer_size 128k;
fastcgi_buffers 4 128k;

server {
        listen       80;
        server_name  ddd.com;
        index index.html index.htm index.php;
   
        client_header_buffer_size 128k;
        large_client_header_buffers 4 128k;
        proxy_buffer_size 64k;
        proxy_buffers 8 64k;
        fastcgi_buffer_size 128k;
        fastcgi_buffers 4 128k;

        location / {
          ......
        }
} 
原文地址:https://www.cnblogs.com/xkus/p/7464118.html