#### 第16章: #### Nginx Nginx 是一款免费开源的高性能HTTP服务器及反向代理服务器产品。并具有IMAP/POP3代理服务等功能。支持fastCGI、SSL、virtual Host、URL Rewrite、HTTP Basic Auth、Gzip等功能;并且支持第三方功能模块的扩展。 #### 16.1 Nginx常用功能 ##### HTTP代理和反向代理 在提供反向代理方面,Nginx服务器转发请求性能稳定,转发和业务配置分离,配置相当灵活。 Nginx的代理服务支持正则表达式匹配,根据不同的表达式匹配策略。Nginx对后端返回会进行异常判断,剔除异常主机。Nginx支持错误页面跳转功能。 ##### 负载均衡 Nginx的负载均衡主要是对大量的前端访问流量进行分流,以保证前端用户访问效率。也就是将前端访问流量分摊到后端网络节点分别处理,这样有效地减少了前端用户等待响应的时间。 Nginx的负载均衡策略分为两大类:内置策略和扩展策略。 内置策略: - 轮询 - 加权轮询 - IP hash 扩展策略: - url hash - fair ..... 内置策略是默认被编译进Nginx的内核,扩展策略需要手动将第三方模块编译进Nginx内核。 轮询:轮询是将每个前端请求顺序地逐一分配到后端节点,对于出现问题的节点会排除。 加权轮询:在轮询的基础上指定各后端节点被轮询到的几率。 IP hash:对前端的访问IP进行hash操作,根据hash结果分配到不同的后端节点上。 url hash:对前端访问的url进行hash操作,根据hash结果分配到不同的后端节点上。 IP hash与url hash都可以解决session的问题。 ##### Web缓存 Nginx在进行反向代理时,proxy_Cache指令集可以将后端返回内容进行URL缓存;fastCGI_Cache指令集可以对fastCGI的动态程序进行缓存(PHP-FPM和Nginx通过fastCGI协议进行通信);ngx_cache_purge指令集用于清除指定的URL缓存。 #### 16.2 Nginx 编译和安装 - http://nginx.org/en/download.html下载Linux系统版本,并且解压 :-: ![](https://img.kancloud.cn/a4/b1/a4b16c0d1d69b948a9e4dc7f9f1884f7_730x641.png) stable version 是稳定版本;Legacy versions 是过去版本。 - 进入解压后的Nginx文件夹使用configure脚本生成Makefile文件。 | 选项 | 说明 | | ----------------------- | ------------------------------------------------------------ | | --prefix=<path> | 指定Nginx安装路径 | | --sbin-path=<path> | 指定Nginx可执行文件安装路径。 | | --conf-path=<path> | 在未给定-c选项下,指定默认的nginx.conf路径。 | | --pid-path=<path> | 在nginx.conf未指定pid指定的情况下,指定默认的nginx.pid路径。 | | --lock-path=<path> | 指定nginx.lock文件的路径。此文件是nginx的锁文件,如果未指定默认为/var/lock/目录 | | --error-log-path=<path> | 在nginx.conf未指定error_log指令的情况下,指定默认的错误日志的路径。如果未指定,默认为<prefix/logs/access.log> | ​ nginx常见的configure脚本支持选项 - make && make install 进行编译安装 安装成功后的安装目录主要包括conf、html、logs、sbin。 conf存放了Nginx的所有配置文件。nginx.conf是Nginx服务器的主配置文件,其他配置文件用来配置Nginx的相关功能。 html存放了Nginx服务器运行过程中调用的一些html网页文件。 logs存放日志文件。 sbin存放唯一文件,也就是Nginx主程序。 #### 16.3 Nginx的启停控制 想要控制Nginx的启停,在linux环境中有多种方法。 - 将Nginx 设置成服务,使用类似`systemctl restart nginx`进行Nginx程序的启停。 - 通过信号控制Nginx启停。向Nginx主进程发送信号可以控制Nginx程序的启停。 | 信号 | 作用 | | --------- | ------------------------------------------------------------ | | TERM或INT | 快速停止Nginx服务 | | QUIT | 平缓停止Nginx服务 | | HUP | 使用新得配置文件启动进程,之后平缓停止原有进程。也就是"平滑重启" | | USR1 | 重新打开日志文件,用于日志切割 | | USR2 | 使用新版本的Nginx文件启动服务,之后平缓停止原有的Nginx进程。也就是"平滑升级" | | WINCH | 平缓停止worker process,用于Nginx服务平滑升级 | ​ Nginx服务可接受的信号 向Nginx发送信号有两种方法:使用nginx二进制文件;使用kill命令发送信号。 kill命令发送信号使用方法: ``` kill Singal PID //Singal指信号 ``` 或 ``` kill Singal 'PIDfilepath'//PIDfilepath指PID文件路径 ``` ##### 二进制文件启动Nginx 启动Nginx直接运行sbin目录下的Nginx二进制文件即可。 ##### 二进制文件停止Nginx服务 停止Nginx有两种:快速停止和平滑停止。快速停止指的是立即停止当前Nginx正在处理的所有网络请求。平滑停止指的是允许Nginx将当前正在处理的网络请求处理完成,不再接收新的请求,之后关闭连接停止工作。 停止Nginx的操作 ``` ./sbin/Nginx -g TERM |INT|QUIT ``` 或者 ``` kill TERM|INT|QUIT 'Nginx/logs/nginx.pid' ``` 或 ``` kill -9 | Sigkill 'Nginx/logs/nginx.pid' ``` ##### 二进制文件平滑重启Nginx服务 ``` ./sbin/Nginx -g HUP [-c newConFile] ``` 或使用新的配置文件代替旧的配置文件 ``` kill HUP '/Nginx/logs/nginx.pid' ``` ##### Nginx服务器升级 升级Nginx服务器版本可以直接停止当前Nginx服务再开启新的Nginx服务,但是会导致这一段时间用户无法访问服务器。平滑升级解决了这个问题,Nginx服务接收到USR2信号后,会启动新的Nginx服务,之后需要向旧的Nginx服务发送WINCH信号使之平滑停止。 ``` ./sbin/Nginx -g USR2 ``` ``` ./sbin/Nginx -g WINCH ``` #### 16.4 Nginx服务器基础配置指令 Nginx的配置文件在安装目录中的conf目录里,主配置文件名为nginx.conf。 ##### Nginx.conf文件的结构 ``` ... #全局块 events #events块 { ... } http #http块 { ... #http全局块 server #server块 { ... #server全局块 location #location块 { ... } location #location块 { ... } } server #server块 { ... #server全局块 location #location块 { ... } location #location块 { ... } } } ``` nginx.conf配置文件结构主要包括全局块、events块、http块。http块里可以有http全局块、多个server块。每个server块可以包括server全局块、多个location块。 Nginx的配置多数指令可以在多个域(块)起作用,所以如果在不同的两个层级同时出现相同的指令,则采用"就近原则"。比如在server块和location同时出现一条相同的指令,并且配置不同,则以location块中的配置为准。 1. 全局块:设置一些影响Nginx整体运行的配置指令。 2. events块:设置Nginx服务器与用户的网络连接。比如:最大连接数,是否允许同时接收多个网络连接,时间驱动模型等。 3. http块:Nginx中重要的配置部分,代理、缓存、日志定义等功能以及第三方模块配置都在这个模块中。http全局块中可以设置MIME-Type定义、文件引入、日志自定义、连接超时时间、单连接请求数上限等。 4. server块:一个server块可以作为一个逻辑独立的服务(或网站),每个server块的作用域只是server块自己(包括内部的location块)。比如可以在http块中设置多个server块匹配提供给用户多个网站。 5. location块:用于Nginx接收到除了IP或别名的字符在匹配上server块之后,将ip或者别名之后的字符进行匹配,再进行特定处理。地址定向、数据缓存、应答控制等功能等功能就是在这里实现。比如www.baidu.com/test/1/index.php,server块负责匹配www.baidu.com,而多个location负责匹配/test/1/index.php或者其他字符,再进行特定处理。 6. MIME-Type块:定义MIME类型。MIME类型指的是浏览器可以识别的格式。比如图片可以是gif、jpeg等格式。MIME-Type的例子 ``` include mime.types; default_type application/ocatet-stream ``` ``` #cat mime.type type{ #下面是识别类型 text/html #html htm shtml ... image/gif #gif ... application/x-javascript #js ... audio/midi #mid midi kar .... } ``` | 配置项 | 作用 | 配置位置 | | --------------------------------------- | ------------------------------------------------------------ | ---------------------- | | user user [group] | 指定可以运行Nginx的用户及用户组 | 全局块 | | worker_processes num | 配置允许生成的worker process数 | 全局块 | | pid filepath | 配置Nginx进程PID存放路径 | 全局块 | | error_log file\|stderr [level] | 配置错误日志的存放路径 | location及其以上块 | | include filepath | 引入配置文件 | 任意地方 | | accept_mutex on\|off | 设置网络连接序列化避免唤醒进程过多的"惊群"问题 | events块 | | multi_accept on\|off | 设置是否允许同时接收多个网络连接 | events块 | | use epoll\|poll\|select | 设置时间驱动模型 | events块 | | worker_connetions number | 设置每个worker processes允许同时开启的最大连接数量 | event块 | | access_log path [format [buffer=size]] | 配置记录Nginx提供服务过程应答前端请求的日志和日志格式。支持对服务日志的格式、大小、输出以及是否打开服务日志等进行配置 | http块 | | log_format name string | 配合access_log设置项,专门负责服务日志格式自定义 | http块 | | sendfile on\| off | 设置是否开启sendfile方式传输文件 | location、server、http | | sendfile_max_chunk num | 设置调用sendfile()传输的数据量最大不能超过这个值 | location、server、http | | keepalive_time timeout [header_timeout] | 设置http长连接时间,也就是一段时间打开后http保持这条连接复用的时间,默认75s。header_timeout是响应报文显示时间可以与timeout时间不一样 | location、server、http | | keepalive_requests number | 设置Nginx服务器和用户建立会话连接后使用本条连接的请求数量 | location、server、http | | listen address[:port] | 设置网络监听,监听IP以及监听的端口 | server块 | | server_name name ... | 配置基于名称的主机,一般配合listen使用,每个name就是一个域名或者IP,可以使用多个name,以空格分隔 | server块 | | index option | 设置主页 | location、server、http | | root path | 设置默认地址 | location、server、http | ​ Nginx.conf文件常见配置项 #### location 配置 location块多样的配置方式使Nginx服务具有灵活性。用法: ``` location [=|~|~*|^~] uri{...} ``` 例子: ``` #这里匹配所有请求到这个主机的请求 location / { root html; index index.html index.htm; deny 192.168.1.1; //禁止此ip访问 allow 192.168.1.0/24; //允许此ip访问 allow 10.1.1.0/16; allow 2001:0db8::/32; #deny all; //取消注释可以禁止所有访问呢 } #这里匹配到所有以.php结尾请求到这个主机的请求 location ~ \.php$ { root html; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } ``` 以上两个location块会按照匹配精度匹配。可以使用正则制作更多特定处理的location块。 ##### alias 可以使用alias 更改location的URI。 ``` location ~/data/(.+\.(htm|html))${ alias /location/www/$1 } ``` 当此location块接收到`/data/a.html`的请求匹配成功,会根据alias 指令在location/www/目录下找到a.html并响应请求。 ##### 设置错误页面 使用`error_page`指令设置错误页面。用法: ``` error_page code uri ``` ``` error_page 404 /404.html; error_page 403 /403.html; error_page 400 /400.html; error_page 301 /301.html; ``` ##### Nginx服务器基础配置实例 ``` #这里配置了开启Nginx服务的用户 user nobody; #这里配置了有多少个worker工作进程 worker_processes 1; #这里配置了错误日志的记录级别和存放路径 error_log logs/error.log; error_log logs/error.log notice; error_log logs/error.log info; #这里配置了PID存放路径 pid logs/nginx.pid; events { #这里配置了每个worker进程可以有多少个连接 worker_connections 1024; } http { #这里配置了MIME-Type类型 include mime.types; default_type application/octet-stream; #这里配置了服务日志格式 log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; #这里配置了服务日志的格式名称和日志文件路径 access_log logs/access.log main; #这里配置了是否可以使用sendfile传输文件 sendfile on; #tcp_nopush on; #这里配置了HTTP长连接复用时间 keepalive_timeout 65; #这里配置是否打开gzip压缩 #gzip on; #这里配置一个server服务 server { listen 80; #监听端口 server_name localhost; #主机域名、IP或名称 root /data/wwwroot/; #主目录路径 index index.php index.html index.htm; #首页文件 #charset koi8-r; #这里可以配置这个server块的服务日志,也可以直接使用http块的服务日志配置。 #access_log logs/host.access.log main; #location / { #} #这里配置404错误页面 #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # #这里配置500、502、503、504 的错误页面 error_page 500 502 503 504 /50x.html; #这里配置匹配到了50x.html后的location特殊处理 #location = /50x.html { #} # #location ~ \.php$ { # proxy_pass http://127.0.0.1; #} #这里配置以.php文件结尾的请求的特殊处理,一般用来处理php文件的请求 location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; #fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } #这里设置禁止或允许访问 #location ~ /\.ht { # deny all; #} } #这里设置另外一个server服务 #server { # listen 8000; # listen somename:8080; # server_name somename alias another.alias; # location / { # root html; # index index.html index.htm; # } #} #这里配置另一个server服务,专门用来配置ssl的 #server { # listen 443 ssl; # server_name localhost; # ssl_certificate cert.pem; # ssl_certificate_key cert.key; # ssl_session_cache shared:SSL:1m; # ssl_session_timeout 5m; # ssl_ciphers HIGH:!aNULL:!MD5; # ssl_prefer_server_ciphers on; # location / { # root html; # index index.html index.htm; # } #} ``` 对于自己的服务,可以适当删减或者增加服务和配置。 #### 16.5 Nginx模块 Nginx的模块分为`核心模块`、`标准模块`、`标准HTTP模块`、`可选HTTP模块`、`邮件服务模块`、`第三方模块`。 - 核心模块:提供Nginx运行的核心基本服务,进程管理、权限控制、错误日志、配置解析、正则表达式解析、驱动机制等。 - 标准HTTP模块:支持Nginx服务器的标准HTTP功能。 - 可选HTTP模块:用于扩展HTTP功能,使其能处理一些特殊的HTTP功能。 - 邮件服务模块:用于支持Nginx的邮件服务。 - 第三方模块:扩展Nginx服务器应用,可完成特殊的功能,一般由第三方机构或个人编写可编译进Nginx。 在Nginx编译目录里的obj目录中的ngx_modules.c文件包含了此版本Nginx快速编译后的固有声明。 :-: ![](https://img.kancloud.cn/34/13/34139d7b14646538e72f43de7fec5c6a_505x167.png) :-: ![](https://img.kancloud.cn/02/34/023492129f4f9df84a576b8d4ceba6cc_546x646.png) ##### 编译可选模块 可选模块在快速编译时不编译。如果需要使用相关可选模块的功能,必须在configure创建makefile文件时使用`--with-XXX`声明。例子: ``` ./configure --prefix=/usr/local/nginx --with-google_perftools_module --user=www --group=www --with-http_stub_status_module --with-http_gzip_static_module ``` 这样就在创建makefile文件时,添加了google_perftools_module和http_stub_status_module模块,。使用`make && make install` 编译安装,后就可以使用其功能了。 ##### Nginx的HTTP可选模块 | 模块 | 功能 | | --------------------------- | --------------------------------------------------------- | | ngx_http+addition_module | 在响应请求的页面开始或结尾添加文本信息 | | ngx_http_degradation_module | 在低内存的情况下允许Nginx服务器返回444或204错误 | | ngx_http_prel_module | 在Nginx的配置文件中可以使用perl脚本 | | ngx_http_gzip_module | 支持实时压缩响应客户端的输出数据流 | | ngx_http_gzip_static_module | 搜索并使用预压的以'.gz'为后缀名文件代替一般文件响应客户端 | | ngx_http_ssl_module | 对HTTPS/SSL支持 | ​ 部分常见的HTTP可选模块 不同的Nginx版本的HTTP可选模块的名称可能不一样。以上仅供参考。 ##### Nginx的邮件服务模块 目前的Nginx快速编译的时候不会编译邮件服务模块。邮件模块: - ngx_mail_core_module - ngx_mail_pop3_module - ngx_mail_imap_module - ngx_mail_smtp_module - ngx_mail_auth_http_module - ngx_mail_proxy_module - ngx_mail_ssl_module 不同的Nginx版本邮件服务模块的名称可能不一样。以上仅供参考。 ##### 第三方模块 Nginx的第三方模块目前在得到不断扩充,功能非常丰富。记录Nginx第三方模块的网站有许多,有兴趣的同学可以自行在wiki站点查找。 #### 16.6 Nginx的处理机制 Nginx服务器的与众不同是由于模块化的单一职责以及它对客户端请求的处理机制上。 Nginx并行处理请求工作有三种方式可选择:多进程方式、多线程方式、异步方式。 ##### 多进程方式 多进程方式指每当服务器接收到一个客户端请求就由主进程生成一个子进程来和该客户端建立连接进行交互,直到断开连接该子进程结束。 优势: - 设计简单。 - 子进程之间相互独立,请求互不打搅,当一个子进程出现问题不会蔓延到其他子进程。 - 子进程退出时资源由操作系统回收,不留下垃圾。 缺点: - 生成子进程需要进行内存复制等操作,在资源和时间上有一定额外开销 ##### 多线程方式 多线程方式指每当服务器接受到一个客户端请求就有主进程生成一个线程来和该客户端进行连接进行交互。 优势: - 由操作系统产生一个进程的开销远远小于产生一个进程的开销,减轻了Web服务器对系统资源的要求 缺点: - 多个线程位于同一个进程内,可以访问同一个内存空间,彼此之间相互影响 - 开发过程中不可避免需要开发者对内存进行管理,增加了其出错风险 - 服务器长时间运转错误的累计可能最终对整个服务器产生重大影响 ##### 异步方式 同步和异步来自发送方: 同步:发送方发送请求后,等待接收到接收方的响应,才发送下一个请求。 异步:发送放发送请求后,不需要等待接收方的响应就可以发送下一个请求。 在异步机制中,所有来之发送方的请求形成一个队列,接收方处理完成后通知发送方。 阻塞和非阻塞来自接收方: 在网络通信中,阻塞和非阻塞用来描述进程处理调用的方式,主要是指Socket的阻塞和非阻塞,实质是指I/O操作(网络I/O,指的是将网络数据放入内存的过程)。 Socket阻塞:当请求来到调用结果返回之前,当前线程挂起,直到调用结果返回,线程才进入就绪状态。 Soket的非阻塞:当请求到来调用结果不用立即返回,立刻返回执行下一个调用。 同步阻塞:发送方发送请求到接收方并等待结果,接收方进行I/O操作不做其他操作等待返回结果,再响应发送方,发送方才可以进行其他操作。 同步非阻塞:发送方发送请求到接收方并等待结果,接收方进行I/O操作并立即返回去做其他操作,直到I/O操作完成接收方获得结果响应发送方,发送方才可以进行其他操作。 异步阻塞:发送方发送请求到接收方不等待结果继续做其他操作,接收方进行I/O操作不能做其他操作,直到I/O操作完成接收方获得结果响应发送方。这种方式在实际中不使用。 异步非阻塞:发送方发送请求到接收方不等待结果继续其他操作,接收方进行I/O操作并立即去做其他操作,直到I/O操作完成将完成状态和结果通知接收方,接收方再响应发送方。 ##### Nginx服务器如何处理请求 Nginx可以处理大量并发请求,是因为它采用多进程和异步对外提供服务。一个子进程处理请求后不阻塞继续处理另外的请求,直到有结果通知该进程,该进程得到通知暂时挂起当前处理的事务,去响应发送方。Nginx可以配置工作进程数和工作进程最大处理请求数量。所有的Nginx工作进程都用于接收和处理客户端请求。 ##### Nginx的事件处理机制 Nginx服务器的工作进程调用I/O后就去做其他工作了;当I/O调用返回后,会通知工作进程。I/O调用如何将自己的状态通知工作进程呢? 1. 让工作进程在进行其他工作的过程中隔一段时间就去检查一下I/O的运行状态,完成就响应客户端,未完成就继续工作。 2. I/O调用在完成后主动通知工作进程。理想的解决方案。 select/poll/epoll/kqueue(事件驱动处理库)等系统调用就是用来支持第二种解决方案。这样的系统调用被称为`事件驱动模型`。它提供的机制是,进程不需要关心I/O操作的具体状态,并且进程可以处理多个并发请求。只需要等待来自I/O完成的通知。 ##### Nginx服务器的事件驱动模型 事件驱动模型一般由`事件收集器`,`事件发送器`,`事件处理器`组成。 - 事件收集器:收集来自用户(鼠标点击事件、键盘输入事件等),来自硬件(时钟事件等),来自软件(操作系统,应用程序本身等)的事件。 - 事件发送器:讲收集器收集到的事件分发到目标对象中。目标对象是事件处理器所处的位置。 - 事件处理器:负责具体事件的响应工作。 事件驱动程序可以由任何语言编写。Linux内核中已经集成大部分常用事件驱动程序(事件驱动处理库)。 比如Nginx的子进程异步过程中:事件收集器收集到带有Nginx子进程状态信息和操作系统完成I/O的事件,通过事件发送器发送到事件处理器,事件处理器通知Nginx子进程,子进程再获得相关结果响应请求方。 Nginx服务器响应和处理Web请求的过程就是基于事件驱动模型。 编写事件处理模型的程序时,"目标对象"中的"事件处理器"可以有几种实现方法: - "事件发送器"每传递过来一个请求,"目标对象"创建一个进程,调用"事件处理器"来处理请求。 - "事件发送器"每传递过来一个请求,"目标对象"创建一个线程,调用"事件处理器"来处理请求。 - "事件发送器"每传递过来一个请求,"目标对象"将其放入一个待处理事件列表,使用非阻塞I/O方式调用"事件处理器"来处理请求。 事件驱动处理库又被称为多路I/O复用方法:常见select模型、poll模型、epoll模型。 ##### select库 创建一个描述符集合。并且每个描述符需要关注上面的写事件、读事件、异常事件。所以创建三类事件描述符集合分别收集读事件描述符、写事件描述符、异常事件描述符。 调用select()函数等待事件发生,此时select阻塞。 然后轮询事件描述符集合中的每一个事件描述符,检查是否有相应事件发生。有就处理。 由于Linux的一切都是文件,所以每个事件描述符就是一个文件句柄。select默认最大文件句柄数量是1024。每次轮询都需要把所有句柄复制到内核空间去,并且由于轮询每次都要遍历`事件描述符文件句柄`。这样导致事件太多的时候性能下降。 ##### poll库 poll库和select库的步骤基本差不多。不同的是poll只创建一个事件描述符集合,事件描述符由链表连接以便轮询,每个事件描述符上分别设置了读事件、写事件、异常事件,轮询的时候可以同时检查这三个事件是否发生。 理论上在Linux系统里由于使用链表保存`事件描述符文件句柄`,就没有了监视文件数量的限制。 ##### epoll库 epoll是公认的非常优秀的事件驱动模型。它通过相关调用通知内核创建一个有N个事件描述符的事件列表;然后给这些描述符设置其所关注的事件,并将它添加到内核创建的事件描述符的事件列表中。 当某一事件发生后,内核将发生事件的描述符放入`就绪事件列表`并交由epoll库(epoll_creat建立的epoll对象),由epoll库进行处理。 由于就绪事件列表很短,不需要像select或者poll那样进行大量轮询,从而提高了效率。而且由于epoll不轮询事件描述符列表,只轮询就绪列表,所以其I/O效率不随着事件描述符增加而线性下降。 注意:Linux课程时讲过,大多数程序通过用户空间的系统调用将控制权交由内核以及内核空间进行处理。内核保留多个应用程序的上下文进行切换操作。 epoll的优点: 1.支持一个进程打开大数目的socket描述符(FD)。 2.IO效率不随FD数目增加而线性下降。 3.使用mmap加速内核与用户空间的消息传递。 4.内核微调。 ##### 其他驱动模型 rtsig、kqueue、dev/poll、eventport等模型有兴趣的同学下来自己了解。 ##### 所以为什么Nginx可以处理高并发 比较通俗的描述: * 接收分配请求靠epoll * 处理靠worker进程非阻塞。 接收分配请求靠epoll:将多个worker进程放进监听Socket指向的epoll对象引用的等待队列并阻塞(epoll\_create(),epoll\_wait),数据过来进入epoll对象引用的就绪队列。此时epoll分配唤醒其中一个worker进程(并加锁,否则所有worker进程都会处理此请求数据),遍历就绪队列里的socket取得数据进行处理。 处理靠worker进程非阻塞: 当被唤醒的worker进程处理到可能发生阻塞的地方,会注册一个事件就直接去干其他事了。比如向下游(后端)服务器转发请求,他会在发送完请求后,注册“如果下游返回了,告诉我一声,我再接着干”事件,然后它就休息去或干其他事情了。此时,如果再有请求进来,他就可以很快再按这种方式处理。一旦下游服务器返回了,会触发这个事件,worker进程会再来接手,这个请求会接着往下走。 #### 16.7 Nginx服务器架构 Nginx服务器启动会产生主进程,主进程会产生一个或者多个工作进程。 - 主进程作用:配置文件解析、数据结构初始化、模块配置和注册、信号处理、网络监听生成、工作进程生成和管理等。 - 工作进程作用:进程初始化、模块调用、请求处理等,提供Nginx服务,缓存管理。 可以大致将Nginx服务器架构分为主进程、工作进程、后端服务器和缓存等部分。 :-: ![](https://img.kancloud.cn/41/e8/41e8782d06763eedb2c4ce1e441312fc_350x367.png) ​ Nginx服务器架构 ##### Nginx的进程 1. 主进程作用: - 读取Nginx配置文件验证有效性和正确性。 - 建立、绑定、管理Socket。 - 生成、管理、结束工作进程。 - 接收外界指令,重启,退出,升级等。 - 开启日志文件、获取文件描述符。 - 编译处理Perl脚本。 2. 工作进程作用: - 接收客户端请求。 - 将请求送入各个功能模块处理。 - 与后端服务器通信,接收后端服务器的处理结果。 - I/O调用,获取后台服务器在内存中的响应数据。 - 获取数据缓存。 - 发送响应结果。 - 接收主进程指令,退出、重启等。 3. 缓存索引重建及管理进程: 主要负责缓存索引重建和缓存索引管理工作。 ##### 进程交互 Nginx服务器`主进程和工作进程的交互`、`工作进程之间的交互`都依赖管道机制。 1. 主进程和工作进程交互: 首先Nginx主进程依照配置文件fork出相关数量的工作进程,并建立一个`全局工作进程表`存放未退出的所有工作进程。每当生成工作进程后,主进程将新的工作进程加入工作进程表,并建立一个单向管道传递给工作进程。这个管道单向由主进程指向工作进程。该管道包含了主进程发出的指令、工作进程ID、工作进程在工作进程表中的索引和必要的文件描述符信息。 主进程与外交交互使用信号,当接受到需要处理的新信号,它通过管道向相关工作进程发送指令。工作进程获取可读事件,并解析指令,采取相应措施。 2. 工作进程之间交互: 也是基于管道通信。首先主进程会交给其中一个工作进程另一个工作进程的ID和针对该工作进程建立的管道句柄。获得ID和管道句柄的工作进程可以捕获相关指令,并解析指令进行相应操作。 ##### 后言 Nginx服务器各个系统模块通过网络、信号、通道等机制进行交互。事件处理机制在很大程度上降低了在网络负载繁重的情况下Nginx服务器对内存、磁盘的压力。同时又保证了Nginx服务器对客户端请求的响应。 但是即使在这样的情况下,Nginx工作进程任然可能阻塞,导致客户端请求超时。试想一下下面情况: - Nginx设置的超时时间为30秒 - Nginx有4个工作进程 - 每个工作进程可以处理100个连接 - 磁盘性能差 - 30秒内4个工作进程需要处理400个连接=400次同时的I/O调用 - 30秒内无法把所有I/O调用结果通知工作进程 此时,400个连接是否会有某些连接超时,超时的工作进程是否能够响应客户端呢? #### 16.8 Nginx服务器的高级配置 ##### 针对IPv4的内核优化 Nginx服务器运行在Linux系统上,所以我们可以调整Linux系统的配置以提高整体Nginx服务器性能。以下配置在 `/etc/sysctl.conf` 文件里面添加和修改。保存后使用`/sbin/sysctl -p`启动配置。 ``` net.ipv4.tcp_syncookies = 1 ``` 打开这个syncookies的目的实际上是:在服务器资源不足的情况下,尽量不要拒绝TCP的syn(连接)请求,尽量把syn请求缓存起来,留着过会儿有能力的时候处理这些TCP的连接请求。 ``` net.ipv4.conf.all.promote_secondaries = 1 net.ipv4.conf.default.promote_secondaries = 1 ``` 提升ipv4性能。 ``` net.core.netdev_max_backlog = 262144 ``` 默认128。设置每个网络接口接收数据包速率比内核处理这些数据包速率快的时候,允许发送到队列的数据包最大数量。 ``` net.core.somaxconn = 262144 ``` 默认值128.设置系统允许同时发起的TCP连接最大数量。 ``` net.ipv4.tcp_max_orphans = 262144 ``` 设置系统中允许多少个TCP套接字不被关联到任何用户文件句柄的最大数量。如果超过这个数量没有关联用户文件句柄的TCP套接字将被复位,同时给出警告信息。在系统内存比较充足的情况下可以增大这个参数。 ``` net.ipv4.tcp_max_syn_backlog = 262144 ``` 设置记录未收到客户端确认信息的连接请求的最大值。(TCP建立连接的时候需要确认,可以回忆以下TCP章节讲的三次握手) ``` net.ipv4.tcp_timestamps = 0 ``` 设置时间戳,避免包序号卷绕。 ``` net.ipv4.tcp_synack_retries = 1 ``` 设置内核放弃TCP连接之前向客户端发送SYN+ACK包的数量。三次握手内核需要发送一次SYN回应之前SYN的ACK。设置之后表示内核放弃连接之前发送一次SYN+ACK包。表示客户端发送请求连接,然后客户端直接发送数据到服务器。 ``` net.ipv4.tcp_syn_retries = 1 ``` 与上一个设置作用类似。 ##### 针对CPU的Nginx配置优化指令 ``` worker_processes 4; ``` 用于配置Nginx的工作进程数。 ``` worker_cpu_affinity 0001 0100 1000 0010; ``` 此命令和CPU核数相关,设置工作进程使用CPU的相关。可以回忆一下系统原理章节讲的一个CPU核可以处理一个控制流。 :-: ![](https://img.kancloud.cn/c7/3b/c73bfc1b8cca6aa39667edb78471f8bc_600x285.png) 如果是4核也可以将其worker_processes设置为8,此时worker_cpu_affinity可以这样设置: ``` worker_cpu_affinity 0001 0100 1000 0010 0001 0010 0100 1000; ``` 如果是8核可以这样设置: ``` worker_cpu_affinity 00000001 00000100 00001000 00000010 00000001 00000010 00000100 10000000; ``` ##### 与网络连接相关的配置 ``` keepalive_timeout 60 50; ``` 设置服务器与客户端保持连接时间和HTTP发送Keep-Alive头的截至时间。 ``` send_timeout 10s; ``` 设置Nginx服务器响应客户端连接的超时时间。 ``` client_header_buffer_size 4k; ``` 设置Nginx服务器允许客户端请求头的缓存区大小。 ``` multi_accept off; ``` 默认off。设置Nginx是否尽可能多地接收客户端地网络连接请求。 ##### 与事件驱动模型相关的指令 ``` use epoll ``` 指定使用事件模型。 ``` worker_connections number; ``` 设置工作进程最大可连接数量。与操作系统中进程可以打开最大文件句柄数量有关。 ``` worker_rlimit_sigpending limit; ``` 设置事件信号队列长度上限。 ``` epoll_events number; ``` 设置在epoll事件模型下Nginx服务器与内核之间可以传递事件的最大数量。 #### 16.9 Nginx服务器的Gzip压缩 ##### ngx_http_gzip-module模块 主要负责Gzip功能的开启和设置,在线实时动态压缩。 ``` gzip on | off; ``` 设置Gzip开启关闭。 ``` gzip_buffer number size; ``` 设置Nginx输出响应数据进行Gzip压缩需向系统申请number*size大小的空间用于存储压缩数据。 ``` gzip_comp_level level; ``` 设置压缩级别,1到9,1最低使用的系统资源最低。 ``` gzip_disable regex ...; ``` 设置针对不同的客户端请求选择性开启和关闭Gzip功能。regex根据客户端浏览器标志进行设置,例如User_Agent,UA。支持正则表达式。有兴趣的同学可以自行学习。 ``` gzip_http_version 1.0 | 1.1; ``` 设置针对不同的HTTP版本开启或关闭Gzip功能。 ``` gzip_min_length length; ``` 设置需要压缩的数据页面的最小字节数量。有些数据量很小压缩却出现压缩数据更大的情况。 ``` gzip_proxied off|expired .....; ``` 设置是否对后端服务器返回的结果进行Gzip压缩。有兴趣的同学可以自行学习。 ``` gzip_vary on | off; ``` 默认off。设置使用Gzip功能时响应头是否带有"Vary:Accept-Encoding"。 ``` gzip mime-type ...; ``` 设置需要进行Gzip压缩的MIME类型。 ##### ngx_http_gzip_static_module模块 主要负责搜索和发送经过Gzip功能预压缩的数据。这些数据以`.gz`作为后缀名存储在服务器上。如果客户端请求的数据被预压缩过,客户端浏览器支持Gzip压缩,就直接返回预压缩的数据。这个模块主要是静态压缩。会在Content-Length头指明报文体的长度。 ``` gzip_static on | off |always; ``` 是否开启此模块。 其他配置与上一个模块配置相同。 ##### nginx_http_gunzip_module模块 主要用于服务器对响应数据流进行Gzip解压和压缩。需要服务器本身有能力,特别是在客户端不支持Gzip的情况下。通俗讲就是为不支持gzip压缩的浏览器提供输压缩功能。 ``` gunzip on | off; ``` 是否开启此模块。 ```设置 gunzip_buffers number size; ``` 设置Nginx解压Gzip文件的缓存空间大小number*size。 #### 16.10 Nginx的Rewrite功能 主要提供重定向功能。非常灵活。 ##### Nginx后端服务器组配置 由HTTP模块ngx_http_upstream_module进行解析和处理。 1. upstream指令 ``` upstream name {...} ``` name是为后端组起的别名。`{}`中包括后端组的服务器。会按照前面章节将的论调策略选择后端组进行处理,比如IP hash、轮询、加权轮询等。 2. server指令 ``` server address [parameters] ``` address指服务器地址,可以是域名、包含端口号的IP地址,或者以'unix:'开头的进程通信的Unix Domain Socket。 parameters是为当前服务器配置更多属性,比如权重,和请求后计算失效次数。 3. ip_hash指令 用于轮询发送于后端组的哪台服务器。 4. keepalive指令 控制网络连接保持功能。 5. least_conn指令 用于配置Nginx服务器使用负载均衡策略为网络连接分配服务器组里的服务器。 例子: ``` upstream back { ip_hash; server myweb1.proxy.com; server myweb2.proxy.com; } ``` ##### Rewrite 功能 用于实现URL重写。可以在server块和location块中配置if指令和break指令,在使用Rewrite功能时也可以使用,以达到更灵活的配置。 Nginx配置文件设置变量用set, ``` set variable value ``` ``` set $num 20 ``` set未指定值的变量为空。 例如: ``` location /{ if($a){ set $id $1 break; } } ``` Rewrite功能只接收host之后的地址并且不包括get参数。比如http://www.baidu.com/test?a=1&b=2,实际上只接收/test。也可以通过其他全局参数得到get参数。有兴趣的同学可以自行学习Rewrite功能,其灵活多变的配置方式。Rewrite重写功能甚至可以通过重写让请求访问另外一个server块或location块。 Rewrite功能例子: ``` rewrite ^/ http://myweb3.test.com/; ``` ``` if($host ~ test\.com){ rewrite ^(.*) http://jump.myweb.name$1 permanent; } ``` ##### 目录合并 可以将http://myweb.tes.com/server/1/2/3/4/5.html重写成http://myweb.tes.com/server-1-2-3-4-5.html。 方便搜索引擎的优化。 ##### 防盗链 经常会有其他的网站服务器直接将我们的服务器里的连接挂到他们的服务上,这样增加了我们服务器的带宽使用和资源负载。可以使用valid_referes指令做防盗链功能。有兴趣的同学自行学习。 #### 16.11 Nginx正向代理和反向代理 正向代理:客户端请求过来,代理服务器负责将局域网客户机指向外部的资源。正向代理不支持外部对内部网络的访问。 反向代理:客户端请求过来,代理服务器负责将客户端请求指向局域网内部系统的某台资源服务器。 ##### 正向代理的指令 ``` resolver address ... [valid=time] ``` 该指令用于指定DNS服务器的IP地址。 ``` resolver_timeout time; ``` 设置DNS服务器解析域名超时时间。 ``` proxy_pass URL; ``` 设置代理服务器协议个地址。一般是个固定的值。 ##### 反向代理指令 反向代理非常重要。在负载均衡系统,或在Nginx服务器与后台服务器交互时(比如和PHP为主要语言的处理程序),反向代理选择具体的哪台服务器等都是至关重要的。 ``` proxy_pass URL; ``` 设置被代理服务器的地址,可以是传输协议、主机名称、IP加端口号、URI等,也可以接收UNIX-domain套接字路径。例如: ``` proxy_pass http://www.myweb.anme/uri; proxy_pass http://localhost:8000/uri; ``` 如果被代理服务器是服务器组,可以使用uptream指令,例如: ``` upstream pro { proxy_pass http://192.168.1.1/uri; proxy_pass http://192.168.1.2/uri; proxy_pass http://192.168.1.3/uri; } server{ listen 80; server_name www.myweb.com location / { proxy_pass pro; } } ``` 假设上面pro服务器组由三台服务器分别运行PHP程序。代理服务器Nginx得到请求后,会将请求轮询分别再次发送给这三台后台服务器进行处理。 ``` proxy_hide_header field; ``` 设置Nginx发送响应式需要隐藏的头部信息。 ``` proxy_pass_header field; ``` 设置Nginx发送响应时需要发送的头部信息。 ``` proxy_pass_request_body on | off; ``` 设置是否向上游代理服务器发送包体部分。 ``` proxy_set_header field value; ``` 设置更改接收到的请求的头部并发送给下游的被代理服务器。默认: ``` proxy_set_header Host $proxy_host; proxy_set_header Connection close; ``` 实例: ``` proxy_set_header Host $http_host; #将Host头的值填充成客户端地址 proxy_set_header Host $host; #将当前location块的server_name指令值填充到Host头 proxy_set_header Host $host:$proxy_host; #将server_name指令值listener指令值一起添加到Host头 ``` ``` proxy_set_body value; ``` 将Nginx服务器接收到的请求体变更并发送到下游被代理服务器。 ``` proxy_bind address; ``` 设置代理连接由指定主机处理。 ``` proxy_connect_timeout time; ``` 设置Nginx服务器与下游被代理服务器尝试建立连接的超时时间。 ``` proxy_read_timeout time; ``` 设置Nginx服务器向下游被代理服务器(组)发出read请求后,等待响应的超时时间。 ``` proxy_send_timeout time; ``` 设置Nginx服务器向下游被代理服务器(组)发出write请求后,等待响应的超时时间。 ``` proxy_http_version 1.0 | 1.1; ``` 设置用于Nginx服务器提供代理服务的HTTP协议版本。 ``` proxy_method POST|GET; ``` 设置Nginx服务请求转发给下游被代理服务器时的请求方法。 ``` proxy_ignore_client_abort on | off; ``` 设置客户端中断网络请求时,Nginx服务器是否中断对下游被代理服务器的请求。 ``` proxy_ignore_header field ...; ``` 设置响应头部,Nginx得到下游被代理服务器的响应后,不会处理被设置的头部。 ``` proxy_redirect rediirect replacement; proxy_redirect default; proxy_redirect off; ``` 用于修改下游被代理服务器响应的头部中的Location和Refresh。配合proxy_pass使用。有兴趣的同学可以自行学习下。对于中间有多个代理服务器的系统,如何将最下游服务器的Location和Refresh返回给最初请求的客户端就需要这个指令。 ``` proxy_intercept_errors on | off; ``` 配置Nginx状态是否开启。如果开启,下游被代理服务器返回状态码,Nginx则用error_page配置的状态页面返回给客户。如果没有开启,则Nginx直接将下游被代理服务器的状态响应给客户。 ``` proxy_headers_hash_max_size 512; ``` 设置存放HTTP报文头的哈希表容量。 ``` proxy_headers_hash_bucket_size sise; ``` 设置Nginx服务器申请存放HTTP报文头的哈希表容量的单位大小。 ``` proxy_next_uptream status ...; ``` 可以设置如果某下游被代理服务器(组)发生异常时,将请求顺次交由下一组被代理服务器组。 ``` proxy_ssl_session_reuse on | off; ``` 设置是否使用SSL安全协议会话连接被代理的服务器。 ##### Proxy Buffer 指令 Proxy Buffer启动后,Nginx服务器会异步地将被代理服务器的响应数据传递给客户端。服务器首先会将下游被代理服务器的响应装到Proxy Buffer里。每个Buffer装满后向客户端发送响应时,都处于BUSY状态。 ``` proxy_buffering on | off; ``` 设置是否打开Proxy Buffer。 ``` proxy_buffers numbei size; ``` 设置Proxy Buffer的个数和每个Buffer的大小。 ``` proxy_buffer_size size; ``` 设置Nginx服务器从下游被代理服务器获得第一部分响应数据的大小,一般是HTTP头部信息。 ``` proxy_busy_buffers_size; ``` 设置处于BUSY状态的缓存区总大小。 ``` proxy_temp_path path; ``` 设置存放代理服务器大体积响应数据的数据文件路径。 ``` proxy_max_temp_file_size size; ``` 该指令用于配置所有临时空间文件的总体积大小。 ``` proxy_temp_file_write_size size; ``` 配置同时写入临时文件的数据量的总大小,避免磁盘I/O负载过重导致性能下降。 ##### Proxy Cache 指令 Proxy Cache 主要提供缓存功能,用于提高I/O吞吐效率。 ``` proxy_cache on | off; ``` 设置是否开启proxy_cache。 ``` proxy_cache_bypass string ...; ``` 设置Nginx服务器向客户端响应时,不从缓存获取数据的条件。 ``` proxy_cache_key string; ``` 设置为缓存数据建立索引时使用的关键字。 ``` proxy_cache_lock on | off; ``` 设置是否开启缓存的锁功能。开启后只能有一个请求填充缓存中的某一项数据。 ``` proxy_cache_lock_timeout timeout; ``` 设置缓存锁开启后锁的超时时间。 ``` proxy_cache_min_uses; ``` 设置客户端相同请求发送最少次数,达到后做缓存。 ``` proxy_cache_path [level=level] keys_zone=name:size1 .....; ``` 设置Nginx服务器缓存数据的路径以及和缓存索引相关的内容。该指令比较复杂,有兴趣的同学查阅资料自行学习。 ``` proxy_cache_use_stale error|timeout|invalid_header|http_404|...|off|...; ``` 设置状态,当下游被代理的服务器处于这些状态时,以缓存响应。 ``` proxy_cache_valid time; ``` 针对不同的HTTP响应状态码设置不同的缓存时间。 ``` proxy_no_cache string ...; ``` 设置在什么情况下不使用Proxy Cache功能。 ``` proxy_store on | off; ``` 设置是否缓存下游被代理服务器的响应数据。 ``` proxy_store_access users:permissions ...; ``` 设置用户组或用户组对Proxy Store缓存的数据访问权限。 ##### 后言 后端程序是PHP的话,Nginx配置使用fastcgi_pass,而不是proxy_pass配置。因为PHP是支持FastCGI的应用程序,允许使用fastcgi_pass进行配置。当然也可以使用proxy_pass(不建议在PHP-FPM运行模式下使用这样的配置)。