nginx 入门
nginx
主要内容整理自骏马金龙 博客园
1. nginx简介
nginx是一个优秀的web服务程序、反向代理程序。它采用非阻塞异步的套接字,使用epoll方式实现事件驱动,同时采用一个master+N个worker进程(默认)的方式处理请求,这种架构使得它在并发的处理能力上极其出色,可以比较轻松地解决C10K问题
2.nginx处理请求的过程简单说明
master进程用于管理worker进程,例如接收外界信号、向worker进程发送信号、销毁worker进程、启动worker进程等等。
nginx之所以性能良好,完全是由它的架构决定的。每个worker进程是业务处理进程,负责监听套接字、处理请求、响应请求、代理请求至后端服务器等。
作为web server处理静态资源时每个worker进程的大致流程:
- (1).监听套接字。
- (2).与客户端建立连接。
- (3).处理监听到的连接请求(加载静态文件)。
- (4).响应数据。
- (5).断开连接。
这几个过程是每个web server都具备的能力,但对于nginx来说,由于它的异步非阻塞,每个过程都不会阻塞(有些小过程必须阻塞的时候还是会阻塞),使得并发处理能力很好。
从监听套接字开始说。每个worker进程都是平等的,它们都可以去监听套接字,正常情况下不可避免地会造成争抢和"惊群问题",而nginx采用"争抢"accept互斥锁的方式,只有持有accept互斥锁的worker进程才有资格将连接请求接到自己的队列中并完成TCP连接的建立。但每个进程是相互独立而平等的,谁有资格去"争抢"互斥锁且有更大几率争抢成功?只要worker进程当前建立的连接数小于worker_connections指令指定的值(实际上源码中设置的是该值的7/8),就允许争抢互斥锁,因为连接数超过了该值的7/8表示已经非常繁忙。除了繁忙程度限制资格,还有epoll_wait的timeout的指标,等待越久的worker进程争抢能力越强。总之,在某一时刻,一定只有一个worker进程监听并accept新的连接请求。
当已经监听到连接请求时,worker进程与它进行三次握手,并最终accpet到自己的内存池中,并和客户端交互数据,处理客户端发送的http请求并响应数据给客户端。但是,nginx的高效就在于它的异步非阻塞,无论是在TCP连接进入ESTABLISHED之前,还是等待客户端发送请求,亦或者是等待加载本地静态资源的I/O,以及响应数据给客户端的任意一个过程中,nginx都是非阻塞的,在任意等待发生时都可以去处理其它事情。当等待的某个资源已经准备成功时将产生事件通知worker进程,worker进程可随后去处理。在此过程中,由于worker进程绑定在一个CPU核心上(推荐如此做),所有的连接都放在内存池中,这使得上下文切换时是极其轻量的,极大地减轻了CPU消耗。从理论上来说,当每个worker绑定了一个CPU核心时,它的并发处理能力主要依赖于内存的大小。
实际上apache httpd的event MPM也是异步非阻塞的,也可以采用epoll,但它采用的是多线程方式,虽然异步,但它的异步似乎不体现在并发能力上,而仅仅只是一些具有特殊状态的连接(如长连接)的异步处理,在处理过程中cpu还是不断地需要在各线程之间大量切换,并发能力并不比worker MPM强多少,相比nginx更是远远不如。
3. nginx 命令
[root@xuexi nginx]# nginx -h
nginx version: nginx/1.12.0
Usage: nginx [-?hvVtTq] [-s signal] [-c filename] [-p prefix] [-g directives]
Options:
-?,-h : this help
-v : 输出版本号
-V : 输出版本号以及编译选项
-t : 检查配置文件的语法
-T : 检查配置文件的语法并输出配置的内容
-q : 静默模式,不输出任何信息
-s signal : 向主进程发送信号:stop, quit, reopen, reload
-p prefix : 设置nginx的basedir(默认为编译时的prefix)
-c filename : 指定配置文件
-g directives : 提前设置全局指令
-V 选项输出编译选项
[root@xuexi nginx]# nginx -h
nginx version: nginx/1.12.0
Usage: nginx [-?hvVtTq] [-s signal] [-c filename] [-p prefix] [-g directives]
Options:
-?,-h : this help
-v : 输出版本号
-V : 输出版本号以及编译选项
-t : 检查配置文件的语法
-T : 检查配置文件的语法并输出配置的内容
-q : 静默模式,不输出任何信息
-s signal : 向主进程发送信号:stop, quit, reopen, reload
-p prefix : 设置nginx的basedir(默认为编译时的prefix)
-c filename : 指定配置文件
-g directives : 提前设置全局指令
- (1) . 使用默认配置文件直接启动nginx和指定配置文件启动nginx
nginx -c /usr/local/nginx/conf/nginx.conf
- (2) . 运行时重载配置文件.当nginx主进程接收到重载配置文件的命令后,它会先检查新配置文件语法,然后载入该配置文件到内存中并解析。然后,主进程fork一系列新的worker进程,并发送QUIT信号给旧的worker进程(graceful stop)。旧的工作进程接收到QUIT信号后,会停止接受新的连接请求,并继续处理旧的连接直到请求处理完成后才退出。
```bash
nginx -s reload
- (3). 运行时快速关闭nginx
nginx -s stop
- (4) .运行时优雅关闭nginx.所有的工作进程会停止接受新的连接,并继续服务旧的连接,直到所有请求完成后退出
nginx -s quit
- (5) 运行时重新打开日志文件
nginx -s reopen
4 nginx 模块及 http功能速览
Nginx的代码由一个核心和一系列的模块组成。
核心(core functionality)主要用于提供全局应用的基本功能,创建必要的运行时环境及确保不同模块之间平滑地进行交互等,对应于配置文件的main段和event段。核心涉及的指令官方文档: http://nginx.org/en/docs/ngx_core_module.html 。
还有很多功能都通过模块实现,nginx是高度模块化程序。如web相关的功能模块有"ngx_http_module",和mail相关的功能模块有"ngx_mailmodule",和tcp代理、负载均衡相关的功能模块有"ngx_stream*_module",这些类别的模块中又分为很多类别的模块,如http类别的模块中有基本核心模块、事件类模块、缓存类模块、SSL相关模块、负载均衡类模块upstream等等。
以下是http功能模块类中常见的模块。
| http类模块名 | 模块功能说明 |
|---|---|
| ngx_http_core_module | http核心模块,对应配置文件中的http段,包含很多指令,如location指令 |
| ngx_http_access_module | 访问控制模块,控制网站用户对nginx的访问,对应于配置文件中的allow和deny等指令 |
| ngx_http_auth_basic_module | 通过用户名和密码认证的访问控制,如访问站点时需要数据用户名和密码,指令包括auth_basic和auth_basic_user_file |
| ngx_http_charset_module | 设置网页显示字符集。指令之一为charset,如charset utf-8 |
| ngx_http_fastcgi_module | fastcgi模块,和动态应用相关。该模块下有非常多的子模块。 |
| ngx_http_flv_module | 支持flv视频流的模块,如边下边播 |
| ngx_http_mp4_module | 同flv模块 |
| ngx_http_gzip_module | 压缩模块,用来压缩nginx返回的响应报文。一般只压缩纯文本内容,因为压缩比例非常大,而图片等不会去压缩 |
| ngx_http_image_filter_module | 和图片裁剪、缩略图相关模块,需要安装gd-devel才能编译该模块 |
| ngx_http_index_module | 定义将要被作为默认主页的文件,对应指令为index。"index index.html,index.php" |
| ngx_http_autoindex_module | 当index指令指定的主页文件不存在时,交给autoindex指令,将自动列出目录中的文件autoindex |
| ngx_http_log_module | 和访问日志相关的模块,指令包括log_format和access_log |
| ngx_http_memcached_module | 和memcached相关的模块,用于从memcached服务器中获取相应响应数据 |
| ngx_http_proxy_module | 和代理相关,允许传送请求到其它服务器 |
| ngx_http_realip_module | 当nginx在反向代理的后端提供服务时,获取到真正的客户端地址,否则获取的是反向代理的IP地址 |
| ngx_http_referer_module | 实现防盗链功能的模块 |
| ngx_http_rewrite_module | 和URL地址重写相关的模块,需要安装pcre-devel才能编译安装该模块 |
| ngx_http_scgi_module | simple cgi,是cgi的替代品,和fastcgi类似,但更简单 |
| ngx_http_ssl_module | 提供ssl功能的模块,即实现HTTPS |
| ngx_http_stub_status_module | 获取nginx运行状态信息 |
| ngx_http_upstream | 和负载均衡相关模块 |
| 这些模块共同组成了nginx的http功能。 |
5. nginx配置文件简单说明
nginx的配置文件有很多个,如下。其中主配置文件为nginx.conf,其他配置文件在需要的时候使用include指令将其包含到主配置文件中。
dabr.conf enable-php-pathinfo.conf koi-win phpwind.conf uwsgi_params
dedecms.conf enable-ssl-example.conf mime.types sablog.conf uwsgi_params.default
discuz.conf fastcgi.conf mime.types.default scgi_params vhost
discuzx.conf fastcgi.conf.default nginx.conf scgi_params.default win-utf
drupal.conf fastcgi_params nginx.conf.default shopex.conf wordpress.conf
ecshop.conf fastcgi_params.default none.conf typecho2.conf wp2.conf
enable-php.conf koi-utf pathinfo.conf typecho.conf
root@DESKTOP-215JD71:/mnt/c/Windows/System32#
其中".default"后缀的是对应前缀配置文件的备份配置文件,".params"是对应前缀的参数文件。如fastcgi.conf是和fastcgi相关参数的配置文件,fastcgi.params是fastcgi的参数文件,fastcgi.conf.default是fastcgi.conf的备份文件。
关于主配置文件nginx.conf,由于Nginx高度模块化,所以它是分段配置的,核心模块为core,对应配置文件中的Main和Events段。此外还有其他一些模块。配置文件中每一个指令必须以分号";"结束,否则语法错误。
5.1 main 和 events 段
Main用于配置错误日志、进程及权限等相关的参数,Events用于配置IO模型,如epoll、kqueue、select或poll等,它们是必备模块。如下配置:
#user nobody; # worker进程身份,默认使用编译时指定值.语法为"user user_name [group_name]"
worker_processes 4; # worker进程数量,该指令值依赖因素较多,例如是否CPU密集型、是否IO密集型。
# 在初始时设置为cpu的总核数是一个不错的选择。
#error_log logs/error.log; # 错误日志文件,禁用错误日志"error_log /dev/null LEVEL;"
#error_log logs/error.log notice; # 级别:debug|info|notice|warn|error|crit|alert|emerg,默认为error
#error_log logs/error.log info;
#pid logs/nginx.pid;
#lock_file logs/nginx.lock;
events {
worker_connections 1024; # 每个worker进程的最大连接数
multi_accept on; # 是否一次性将监听到的连接全接收进来,默认为off,关闭时一次接收一条连接
accept_mutex on # 默认为on,开启时表示以串行方式接入新连接,否则将通报给所有worker。
# 这可能会浪费资源并产生不可预计的后果,例如惊群问题
}
*** 如果某个文件采用了相对路径,则其相对的基准为basedir。*** 例如编译时--prefix指定为/usr/local/nginx,则指定pid为logs/nginx.pid时,其实际路径为/usr/local/nginx/logs/nginx.pid。
worker_processes的值和work_connections的值决定了最大并发数量。例如上面的配置中,每个worker进程最大允许1024个连接,配置了4个worker进程,所以并发数量为10244=4096。但在反向代理场景中计算方法不同,因为nginx既要维持和客户端的连接,又要维持和后端服务器的连接,因此处理一次连接要占用2个连接,所以最大并发数计算方式为:worker_processesworker_connections/2。此外还需注意,除了和客户端的连接、与后端服务器的连接,nginx可能还会打开其他的连接,这些都会占用文件描述符,从而影响并发数量的计算。最后还需注意,最大并发数量还受"允许打开的最大文件描述符数量"限制,可以使用"worker_rlimit_nofile"指令修改或直接修改操作系统的对应内核参数。
可以在main段使用worker_cpu_affinity指令绑定CPU核心。nginx通过位数识别CPU核心以及核心数,指定位数上占位符为1表示使用该核心,占位符为0表示不使用该核心。例如2核cpu的位数分别为01和10,4核的位数分别为1000,0100,0010以及0001,同理8核和16核。在结合worker_processes指令一起使用时,要注意worker进程和核心的对应方式,例如:
# 每个worker分别对应cpu0/cpu1/cpu2/cpu3
worker_processes 4;
worker_cpu_affinity 0001 0010 0100 1000;
# 有4核心,但只有两worker,第一个worker对应cpu0/cpu2,第二个worker对应cpu1/cpu3
worker_processes 2;
worker_cpu_affinity 0101 1010;
在events段,可以使用"use"指令可以指定使用哪种I/O模型,Linux上默认是epoll,通常可以不用手动去设置,因为nginx默认会采用最佳配置。
以下是main段和events段的配置示例,大多数采用的默认值,需要修改的大致就是worker数量、每个worker最大连接数以及进程绑定cpu。
worker_processes 4;
events {
worker_connections 1024;
}
5.2 http段
5.2.0 配置文件概览
http段是由http相关模块支持的。以下是默认配置项。注意,http根段下使用相对路径是相对conf目录的,如"include extra/.conf"表示的是conf/extra/.conf;非根段内的相对路径如server段内使用相对路径是相对于的。
http {
include mime.types; # nginx支持的媒体文件类型。相对路径为同目录conf下的其他文件
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 on; # 启用sendfile传输模式,此模式是"零拷贝"
#tcp_nopush on; # 只在sendfile on时有效。让数据包挤满到一定程度才发送出去,挤满之前被阻塞
#keepalive_timeout 0; # keepalive的超时时间
keepalive_timeout 65;
#gzip on; # 是否启用gzip压缩响应报文
server { # 定义虚拟主机
listen 80; # 定义监听套接字
server_name localhost; # 定义主机名加域名,即网站地址
#charset koi8-r; # 默认字符集
#access_log logs/host.access.log main; # 访问日志路径
location / { # location容器,即URI的根
root html; # 站点根目录,即DocumentRoot,相对路径时为<prefix>/html
index index.html index.htm; # 站点主页文件
}
#error_page 404 /404.html; # 出现404 page not fount错误时,使用/404.html页响应客户端
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html; # 出现50x错误时,使用/50x.html页返回给客户端
location = /50x.html { # 定义手动输入包含/50x.html时的location
root html;
}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
}
5.2.2 root指令和alias指令
root指令设置站点根目录,即httpd的documentroot,但又有所不同,因为nginx可以在多个上下文位置处使用root指令,例如Location容器中。
如果配置如下:
location /i/ {
root /data/w3;
}
那么 nginx 将使用文件 /data/w3/i/top.gif 响应请求 /i/top.gif
*** root指令仅仅只是将匹配的URI追加在root路径后,如果要改变URI,应该使用alias指令,它会对URI进行替换。 ***
如 :
location /i/ {
alias /data/w3/images/;
}
那么nginx将使用文件 /data/w3/images/top.gif 响应请求 /i/top.gif. 因此,如果alias指令的路径最后一部分包含了URI,则最好使用root指令,而非alias指令,虽然它们都能成功响应。
location /images/ {
alias /data/w3/images/;
}
location /images/ {
*** 它们都能使用相对路径,相对的是prefix。*** 例如编译路径为/usr/local/nginx,则"root html"指的是"/usr/local/nginx/html"。
与root和alias指令相关的变量为$document_root、$readpath_root。*** 其中$document_root的值即是root指令、alias指令的值,而$realpath_root的值是对root、alias指令进行绝对路径换算后的值。***
5.2.3 location 容器
该指令对规范化后的URI进行匹配,并对匹配的路径封装一系列指令 . 语法:
location [= | ~ | ~* | ^~] uri {...}
-
其中 :
location /uri/ {}: 表示对 /uri/ 目录及其子目录下的所有文件都匹配.所以location / {}的匹配范围是最大的 -
location = /uri/ {}: 表示只对目录或文件进行匹配,不对目录中的文件和子目录进行匹配.所以一般只用来做文件匹配. -
location ~/uri/ {}: 表示区分大小写的正则匹配 -
location *~/uri/ {}:表示不区分大小写的正则匹配 -
location ^~/uri/ {}:表示禁用正则匹配,即精确字符串匹配,此时正则中的元字符将被解释成普通字符.
它们的匹配优先级规则为: *** nginx先检查URI的前缀路径,在这些路径中找到最精确匹配请求URI的路径。然后nginx按在配置文件中的出现顺序检查正则表达式路径,匹配上某个路径后即停止匹配并使用该路径的配置,否则使用最大前缀匹配的路径的配置。***
使用"="前缀可以定义URI和路径的精确匹配。如果发现匹配,则终止路径查找。例如请求"/"很频繁,定义"location = /"可以提高这些请求的处理速度,因为查找过程在第一次比较以后即结束。
以下是一个优先级的示例.
location = / {
[ configuration A ]
}
location / {
[ configuration B ]
}
location /documents/ {
[ configuration C ]
}
location ^~ /images/ {
[ configuration D ]
}
location ~* \.(gif|jpg|jpeg)$ {
[ configuration E ]
}
- 请求
/能匹配A和B,但最精确匹配为A。 - 请求
index.html的前缀/能匹配A和B,但A只能匹配/自身,因此最终匹配配置B.(前缀也能匹配E,但文件名无法匹配) - 请求
documents/document.html的前缀能匹配B和C,但C更精确,因此匹配配置C.(前缀也能匹配E,但文件名无法匹配) - 请求
/images/1.gif的前缀能匹配B、D和E,且D和E都是最长路径匹配,但^~优先级更高,因此匹配配置D。 - 请求
/documents/1.jpg的前缀能匹配B、C,同样也能匹配E,且E比B的匹配更精确,因此最终匹配配置E。
大致可以将规则简化为如下优先级:
(location = uri ) > (location ^~ uri) > (location *~|~ uri) > (location uri)
即等号优先级最高,非正则匹配次之,再之后是正则匹配,它们之间有位置的先后顺序,优先级最低的是没有使用任何符号的匹配.
由此也可以知道,location / {}这种方式是一种特殊的uri匹配,无论什么uri路径,都能往这里面装,可以认为它是默认匹配,当其它location都匹配不上时就会匹配它。
5.2.1 error_page指令
当出现对应状态码的错误时,指定返回的URI路径.语法为:
error_page code ... [=[response]] uri;
配置文件中的error_page部分默认为:
location / {
root html;
index index.html index.htm;
}
#error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
上面的配置文件中,假如取消了404错误的error_page行注释,当出现404错误时,其uri为/404.html,然后会对其进行location的匹配,由于只有"location / {}"能匹配到,所以它的目录为/html/,即404.html文件路径为/html/404.html。对于50x的error_page,其uri为"/50x.html",所以会对其进行location匹配,发现可以精确匹配到"location = /50x.html {}",当然"location / {}"也能匹配到,但是它的优先级更低,所以当出现50x错误时,将从/html目录下寻找50x.html,这里正好和"location / {}"重复了,但它们的匹配过程是不一样的。假如改为如下配置:
location / {
root html;
index index.html index.htm;
}
#error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /www/a.com/;
}
出现50x错误时,将返回/www/a.com/50x.html文件,而不再是/html/50x.html。
5.25 allow 和 deny
这两个指令由 ngx_http_access_module模块提供,用于允许或限制某些IP地址的客户端访问。nginx中的allow和deny规则很简单,从上向下匹配,只要匹配到就停止。例如:
allow 10.0.0.8
allow 192.168.100.0/24
deny all
允许10.0.0.8和192.168.100网段的访问,其他的都拒绝。
5.2.6 add_header添加相应首部字段
用于在响应首部中添加字段. 例如:
server {
add_header RealPath $realpath_root;
}
将添加一个名为RealPath的字段,值为变量realpath_root的值
[root@xuexi ~]# curl -I http://localhost/
HTTP/1.1 200 OK
Server: nginx/1.12.1
Date: Tue, 17 Oct 2017 08:10:14 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 17 Oct 2017 03:20:10 GMT
Connection: keep-alive
ETag: "59e576ea-264"
RealPath: /usr/local/nginx-1.12.1/html # 此为自定义添加字段
Accept-Ranges: bytes
5.3虚拟主机和server_name指令
nginx 使用server容器定义了一个虚拟主机.在nginx中,没有严格区分基于IP 和 基于名称的虚拟主机,它们通过listen指令和server_name指令结合起来形成不同的虚拟主机.
例如:
# 基于IP地址的虚拟主机
server {
listen 80;
server_name 192.168.100.25;
location / {
root /www/longshuai/;
index index.html index.htm;
}
}
server {
listen 80;
server_name 192.168.100.26;
location / {
root /www/xiaofang/;
index index.html index.htm;
}
}
# 基于名称的虚拟主机
server {
listen 80;
server_name www.longshuai.com;
location / {
root /www/longshuai/;
index index.html index.htm;
}
}
server {
listen 80;
server_name www.xiaofang.com;
location / {
root /www/xiaofang/;
index index.html index.htm;
}
}
# 基于端口的虚拟主机
server {
listen 80;
server_name 192.168.100.25;
location / {
root /www/longshuai/;
index index.html index.htm;
}
}
server {
listen 8080;
server_name 192.168.100.25;
location / {
root /www/xiaofang/;
index index.html index.htm;
}
}
其中 sever_name 指令可以定义多个主机名,第一个名字为虚拟主机的首要主机名.例如:
server_name example.com www.example.com;
主机名中可以含有星号("*"),以替代名字的开始部分或结尾部分(只能是起始或结尾,如果要实现中间部分的通配,可以使用正则表达式)。例如"*.example.org"不仅匹配www.example.org,也匹配www.sub.example.org。下面两条指令是等价的。
server_name example.com *.example.com www.example.*;
server_name .example.com www.example.*;
也可以在主机名中使用正则表达式,就是在名字前面补一个波浪线("~"):
server_name www.example.com ~^www\d+\.example\.com$;
nginx 允许定义空主机名:
server {
listen 80;
server_name "";
return 444;
}
这种主机名可以让虚拟主机处理没有"Host"首部的请求,而不是让指定"地址:端口"的默认虚拟主机来处理,而这正是本指令的默认设置。即使用非默认的虚拟主机处理请求头中不含"Host"字段的请求。一般这样的请求处理方式是直接丢弃请求,并返回一个非标准的状态码来立即关闭连接,例如上面的444。
如果server块中没有定义server_name,nginx使用空名字作为虚拟主机名。
5.4 虚拟主机的匹配规则
通过名字查找虚拟主机时,如果一个名字可以匹配多个置顶的配置,比如同时匹配上通配符和正则表达式,按下面优先级,使用先匹配上的虚拟主机:
- 确定的名称;
- 最长的以星号起始的通配符的名称.比如
*.example.com; - 最长的以星号结束的通配符名字,比如
mail.*; - 第一个匹配的正则表达式名字(按在配置文件中出现的顺序).
当开始接入请求时,(1)nginx首先判断请求的套接字,即IP和端口号;(2)然后在listen套接字中选择一个能匹配名称的虚拟主机,如果没有选出能匹配的名称,则使用该套接字的默认虚拟主机。默认情况下,监听套接字的默认虚拟主机为该套接字主机组中的第一个虚拟主机,但可以通过listen指令的default_server属性手动指定。
例如下面定义了4个虚拟主机:前3个都监听在192.168.100.1:80上,第四个监听在192.168.100.2:80上.
server {
listen 192.168.100.1:80;
server_name example.org www.example.org;
...
}
server {
listen 192.168.100.1:80 default_server;
server_name example.net www.example.net;
...
}
server {
listen 192.168.100.1:80;
server_name example.com www.example.com;
...
}
server {
listen 192.168.1.2:80;
server_name example.com www.example.com;
...
}
- 从192.168.100.1:80上请求www.example.com,能匹配虚拟主机3,于是使用虚拟主机3的配置进行响应;
- 从192.168.100.1:80上请求www.example.com,能匹配虚拟主机3,于是使用虚拟主机3的配置进行响应;
- 从192.168.100.2:80上请求www.example.com时,使用虚拟主机4进行响应;
- 从192.168.100.2:80上请求www.example.org时,由于无法匹配该套接字上的任何主机,于是使用默认虚拟主机响应,即虚拟主机4。
5.5 stub_status指令获取nginx状态信息
使用ngx_http_stub_status_module模块提供的功能可以获取nginx运行的状态信息。对应的指令只有一个,即stub_status。
例如,在某一个server下加上如下配置获取该server的状态信息。
server {
listen 80;
server_name www.longshuai.com;
location / {
root /www/longshuai/;
index index.html index.htm;
}
location /status {
stub_status on;
}
}
还可以明确指定访问该信息不记录日志,且提供访问控制,不让外界人随意获取信息。
location /status {
stub_status on;
access_log off;
allow 192.168.100.0/24;
deny all;
}
重载配置文件后,只需在浏览器中输入"主机名/status"即可获取信息
Active connections: 291
server accepts handled requests
16630948 16630948 31070465
Reading: 6 Writing: 179 Waiting: 106
状态信息如下:
- 第一行active connections:291表示当前处于活动状态的客户端连接数,包括正处于等待状态的连接。
- 第四行reading数量为6,表示nginx正在读取请求首部的数量,即正在从socket recv buffer中读取数据的数量;writing数量为179表示nginx正在将响应数据写入socket send buffer以返回给客户端的连接数量;waiting数量为106表示等待空闲客户端发起请求的客户端数量,包括长连接状态的连接以及已接入但socket recv buffer还未产生可读事件的连接,其值为active-reading-writing。
- 第二行accepts的数量为16630948表示从服务启动开始到现在已经接收进来的总的客户端连接数;handled的数量为16630948表示从服务启动以来已经处理过的连接数,一般handled的值和accepts的值相等,除非作出了连接数限定;requests的数量为服务启动依赖总的客户端请求数。一个连接可以有多个请求,所以可以计算出平均每个连接发出多少个请求。
- 以下是第二行几个参数的官方原文:
accepts:The total number of accepted client connections.
handled:The total number of handled connections. Generally, the parameter value is the same as accepts unless some resource limits have been reached (for example, the worker_connections limit).
requests:The total number of client requests.
6. 访问日志 access_log
- nginx的访问日志相关功能由ngx_http_log_module模块提供,指令包括log_format、access_log和open_log_file_cache。
- nginx的日志可以先缓冲到buffer中,一定时间后再写入到日志文件中。从buffer刷盘到本地日志文件中时,可以进行压缩。
- nginx的worker进程的运行身份需要有日志创建的权限,即对日志所在目录有写权限。
- 可以在多种上下文中定义是否开启日志以及日志的格式。最常见的三个上下文是http,server,location。
- open_log_file_cache指令存在的意义是为了缓存日志文件描述符。之所以提供这个指令,是因为nginx每次日志的写入(从缓存中刷盘到本地日志文件中)都会打开、关闭一次日志文件。对于日志写入极其频繁的机器可以使用该指令缓存日志文件描述符,使得在缓存有效期内都可以续写到旧日志文件中。
6.1 log_format指令
log_format指定日志的格式,语法如下:log\_format name string...;。其中name指定日志格式名称,配置文件中name名称不能重复。
以下是默认提供的main格式.
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
在main后面的是一堆变量,除了这些变量之外还有其他很多变量可用,但如非特殊需求,默认的main格式就已经够完美了。以下是变量的意义:
$remote_addr:客户端的地址。如果nginx提供的web服务在后端,如前面有代理服务器或负载均衡等设备时,该变量只能获取到前端的IP地址。此时要获取到真正客户端地址需要使用变量$http_x_forward_for,但要求前端服务器要开启x_forward_for设置。
$http_x_forward_for:如上所述。
$remote_user:远程客户端用户名称。
$time_local:记录访问时间和时区信息。
$request:记录用户访问时的url和http协议信息。如:"GET /favicon.ico HTTP/1.1"。
$status:记录客户端请求时返回的状态码,如成功的状态码为200,page not found的状态码为404。
$body_bytes_sent:记录服务器响应给客户端的主体大小。
$http_referer:记录此次请求是从哪个链接过来的,需要模块ngx_http_referer_module支持,默认已装,可以防止倒链问题。
$http_user_agent:记录客户端的浏览器信息。如使用什么浏览器发起的请求,是否是压力测试工具在测试等。
例如,以下是默认格式的日志信息。
192.168.100.27 - - [23/Apr/2017:23:08:56 +0800] "GET /index.html HTTP/1.0" 200 17 "-" "ApacheBench/2.3"
192.168.100.27 - - [23/Apr/2017:23:08:56 +0800] "GET /index.html HTTP/1.0" 200 17 "-" "ApacheBench/2.3"
192.168.100.27 - - [23/Apr/2017:23:08:56 +0800] "GET /index.html HTTP/1.0" 200 17 "-" "ApacheBench/2.3"
192.168.100.27 - - [23/Apr/2017:23:08:56 +0800] "GET /index.html HTTP/1.0" 200 17 "-" "ApacheBench/2.3"
192.168.100.1 - - [23/Apr/2017:23:08:56 +0800] "GET /status HTTP/1.1" 200 109 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko"
192.168.100.1 - - [23/Apr/2017:23:08:56 +0800] "GET /status HTTP/1.1" 200 109 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko"
192.168.100.1 - - [23/Apr/2017:23:08:56 +0800] "GET /status HTTP/1.1" 200 109 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko"
192.168.100.27 - - [23/Apr/2017:23:08:57 +0800] "GET /index.html HTTP/1.0" 200 17 "-" "ApacheBench/2.3"
192.168.100.27 - - [23/Apr/2017:23:08:57 +0800] "GET /index.html HTTP/1.0" 200 17 "-" "ApacheBench/2.3"
192.168.100.27 - - [23/Apr/2017:23:08:57 +0800] "GET /index.html HTTP/1.0" 200 17 "-" "ApacheBench/2.3"
6.2 access_log指令
access_log 主要用于定义使用什么格式的日志,日志存放路径.语法如下:
access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]];
access_log off;
path指定路径,path里可以使用变量。format指定日志格式,不写时或者配置文件中未配置access_log指令时,默认为combined。buffer=size指定日志缓冲区大小(默认64K),flush=time指定日志刷盘的时间,if=condition指定某些条件,gzip=level指定日志刷盘前先压缩的压缩级别(默认gzip=1)。
当指定了buffer或者gzip任意一个时,都会使用buffer先缓冲日志,然后再刷盘。
例如
access_log /spool/logs/nginx-access.log gzip buffer=32k;
6.3 日志文件的分割
默认 nginx 不会自动分割日志,也不支持在配置文件中使用 cronolog以及 rotatelogs(apache支持,因为支持管道传达).要实现nginx的日志分割,需要通过移动旧日志,生成新日志来实现.
mv old_log new_log
# 然后
nginx -s reload
# 或者
nginx -s reopen
# 或者
kill -s USR1 master_pid
pid可以通过pid文件获得。
kill -s USR1 $(cat /var/run/nginx/nginx.pid)
要实现自动分割,需要使用脚本,并设置定时任务。
cat /usr/local/nginx/logs/cut_longhsuai.sh
#!/bin/bash
# cut the access log for www.longshuai.com
basedir=/usr/local/nginx
old_log_path=$basedir/logs/access.log
log_save_path=$basedir/logs
save_log_name=access_$(date -d "yesterday" +"%Y%m%d").log
[ -f "$old_log_path" ] || exit 1
/bin/mv $old_log_path $log_save_path/$save_log_name
$basedir/sbin/nginx -s reopen
再添加定时任务计划.
chmod +x /usr/local/nginx/logs/cut_longshuai.sh
crontab -e
00 00 * * * /bin/sh /usr/local/nginx/logs/cut_longshuai.sh &>/dev/null
7. 配置web身份认证
需要输入用户名和密码才能访问站点的功能为web身份认证功能。nginx中由ngx_http_auth_basic_module模块提供该功能。指令包括auth_basic和auth_basic_user_file。这两个指令可以在http根段、server段、location段使用。
auth_basic string | off
auth_basic_user_file path/to/user_passwd_file # 密码文件使用相对路径时是相对conf目录的
passwd_file的格式为:
user1:passwd1
user2:passwd2
需要注意的是,密码文件不能使用明文密码的方式。该类文件一般使用apache提供的工具htpasswd生成,该工具在httpd-tools包中,具体用法见htpasswd命令。也可以使用openssl passwd生成相关密码然后复制到密码文件中。以下是一个示例:
server {
listen 80;
server_name www.longshuai.com www1.longshuai.com;
location / {
root /www/longshuai/;
index index.html index.htm;
auth_basic "Auth your name";
auth_basic_user_file /usr/local/nginx/htpasswd;
}
}
然后使用htpasswd生成密码文件/usr/local/nginx/conf/htpasswd。
yum -y install httpd-tools
htpasswd -b -c -m /usr/local/nginx/conf/htpasswd Jim 123456
htpasswd -b -m /usr/local/nginx/conf/htpasswd Tom 123456
htpasswd -b -m /usr/local/nginx/conf/htpasswd leolee 847559425
-b选项是表示batch模式,不用交互输入密码。-c表示创建密码文件,只能为第一个用户使用该选项,否则后面使用会覆盖前面已经创建过的。-m表示强制使用md5。Jim和Tom是需要验证的用户名,123456是密码。查看生成的密码文件。
[root@xuexi nginx]# cat htpasswd
Jim:$apr1$URdS8lcY$GyghBlYInlqfkto0ndnH1.
Tom:$apr1$B/orDxHj$1jIWk1X5XabpH3hcFQzoN1
然后重载配置文件,在浏览器中测试。测试时最好不要用IE。
也可以使用openssl passwd生成,下面的"-apr1"(数值1而非字母L的小写)选项是为apache密码做相关支持的。
openssl passwd -apr1 123456
$apr1$rD/3/kFH$L8KYfzAWFtHO8mOiR9dci1
# 或者加点盐
openssl passwd -apr1 -salt 'xyz' 123456
$apr1$xyz$j0n99G44SENB4NmJiwEfJ0
然后将得到的密码复制到密码文件中对应的用户名后面即可。如将得到的"$apr1$xyz$j0n99G44SENB4NmJiwEfJ0"作为"Admin"用户的验证密码。
Admin: $apr1$xyz$j0n99G44SENB4NmJiwEfJ0

8 配置 https
https功能由ngx_http_ssl_module模块提供。https连接的认证过程见SSL握手机制。简单版的过程如下:
- 客户端say hello,并发送一个随机数给服务端。
- 服务端say hello,并回复一个随机数给客户端。
- 服务端发送数字证书给客户端。
- 客户端使用信任的CA公钥解密数字证书得到服务端的公钥。
- 客户端使用服务端的公钥加密一个预备主密钥并发送给服务端。
- 服务端使用自己的私钥解密预备主密钥。
- 双方都得到了2个随机数加一个预备主密钥。通过这3个随机数形成一个会话主密钥,即session key。至此ssl握手结束,连接的认证过程也完毕。
对于服务端而言,在配置https时,需要提供自己的数字证书用于发送给客户端,还要提供自己的私钥用于解密客户端发送的预备主密钥。当ssl连接建立完成后,后续连接要传输的数据都采用session key进行加密,但注意session key是对称加密,客户端和服务端是一样的。
还需注意,在创建证书请求时,由于需要指定common name,这是需要使用https的站点,因此每个ssl证书只能为一个server_name提供https服务。
以下是配置文件中默认的关于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;
# }
#}
以下是建立https的相关过程:
- 自建CA。
cd /etc/pki/CA
(umask 077;openssl genrsa -out private/cakey.pem 2048)
openssl req -new -x509 -key private/cakey.pem -out cacert.pem -days 3650
- 建立相关序列和索引文件。
touch index.txt
echo "01" > serial
- nginx端生成证书请求。
(umask 077;openssl genrsa -out /usr/local/nginx/longshuai.key 2048)
openssl req -new -key /usr/local/nginx/longshuai.key -out /usr/local/nginx/longshuai.csr
- CA为证书请求颁发证书。
openssl ca -in /usr/local/nginx/longshuai.csr -out longshuai.crt
- 将颁发的证书发送给nginx端。这里由于CA和nginx在同一主机上,所以直接移动即可。
mv longshuai.crt /usr/local/nginx/
此时,在/usr/local/nginx/目录下就有了nginx自己的私钥文件和证书文件。
[root@xuexi nginx]# ls longshuai*
longshuai.crt longshuai.csr longshuai.key
- 修改配置文件,配置ssl相关选项。
server {
listen 443 ssl;
server_name www.longshuai.com;
ssl_certificate /usr/local/nginx/longshuai.crt;
ssl_certificate_key /usr/local/nginx/longshuai.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;
}
}
- 重载配置文件。
- 测试。在浏览器上输入
https:\\www.longshuai.com测试。将会提示证书安全存在问题,说明配置成功。以后只要在客户端安装证书即可正常访问。
平稳切换nginx版本
在说明如何稳定安全地升级、降级已经在运行中的nginx之前,需要先了解nginx支持的几种信号。以下几种是主进程可以接收的信号,注意worker进程也可以接收一些信号,但和主进程的信号处理机制有些不一样,且主进程支持的信号worker进程不一定支持。具体可见man nginx.
SIGINT, SIGTERM 立即杀掉nginx主(即所有进程)
SIGQUIT graceful stop主进程
SIGWINCH graceful stop所有的worker进程
SIGHUP reload配置文件,并使老的worker进程graceful stop
SIGUSR1 重新打开日志文件(Reopen log files)
SIGUSR2 在线切换nginx可执行程序(Upgrade the nginx executable on the fly)
graceful stop的行为是:(1)进程不再监听、接受新的请求;(2)进程继续处理正在处理的请求,但处理完成后销毁。
升级
如果想对一个已运行的nginx实例进行版本升级,或者因为重新编译了一个版本而替换旧版本,可以考虑按照以下一系列过程来平稳、安全地升级。当然,如果直接停止服务不会产生多大影响,直接停掉再启动新版本nginx实例更方便简单。
- 1.将新版本的nginx命令路径替换掉旧的nginx命令。
通常,对于编译安装的nginx来说,采用软链接的方式比较便捷。例如旧版本的安装路径为/usr/local/nginx-1.12.0,为其建立一个软链接/usr/local/nginx,如果有新版本/usr/local/nginx-1.12.1,只需修改软链接/usr/local/nginx的指向目标为/usr/local/nginx-1.12.1即可。这样/usr/local/nginx/sbin/nginx就会随着软链接的指向改变而指向新nginx程序。 - 2.对旧nginx实例的主进程发送USR2信号
kill -USR2 `cat /var/run/nginx/nginx.pid`
该信号提示nginx旧的主进程要升级,并执行新的nginx程序。例如步骤1中,旧的nginx主进程为/usr/local/nginx/sbin/nginx,但其指向的是/usr/local/nginx-1.12.0/sbin/nginx,发送该信号后仍将执行/usr/local/nginx/sbin/nginx,但此时因为软链接目标已改变,使得此时启动的nginx已经是/usr/local/nginx-1.12.1/sbin/nginx程序。
[root@xuexi ~]# ps aux | egrep '(ngin[x]|PI[D])'
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 103753 0.0 0.3 122720 7360 ? S 17:01 0:00 nginx: master process /usr/sbin/nginx
nginx 103754 0.0 0.1 125248 3520 ? S 17:01 0:00 nginx: worker process
nginx 103755 0.0 0.1 125248 3520 ? S 17:01 0:00 nginx: worker process
nginx 103756 0.0 0.1 125248 3520 ? S 17:01 0:00 nginx: worker process
nginx 103757 0.0 0.1 125248 3520 ? S 17:01 0:00 nginx: worker process
root 103919 0.0 0.3 122720 7364 ? S 17:44 0:00 nginx: master process /usr/sbin/nginx
nginx 103920 0.0 0.1 125248 3524 ? S 17:44 0:00 nginx: worker process
nginx 103921 0.0 0.1 125248 3524 ? S 17:44 0:00 nginx: worker process
nginx 103922 0.0 0.1 125248 3524 ? S 17:44 0:00 nginx: worker process
nginx 103923 0.0 0.1 125248 3524 ? S 17:44 0:00 nginx: worker process
此外,发送该信号后将会切换pid文件,旧的pid文件被重命名为nginx.pid.oldbin,记录的是旧的nginx主进程pid值,新的pid文件为nginx.pid,记录的是新启动的nginx的主进程pid值。
[root@xuexi ~]# ls /var/run/nginx*
/var/run/nginx.pid /var/run/nginx.pid.oldbin
-
- graceful stop旧的主进程号。
kill -QUIT `cat /var/run/nginx/nginx.pid.oldbin`
向旧的主进程号发送QUIT信号,该信号将使得主进程以graceful的方式关闭。这将使得旧的主进程、旧的worker进程不再接受任何新请求,但却会把正在处理过程中的请求处理完毕,然后被销毁退出。
- 4.更稳妥的方式是先让worker进程graceful stop,在新版本的nginx实例运行一小段时间后如果正常工作,再graceful stop旧的主进程。
kill -WINCH `cat /var/run/nginx/nginx.pid.oldbin`
# a period of time goes, graceful stop old master nginx
kill -QUIT `cat /var/run/nginx/nginx.pid.oldbin`
在发送WINCH信号给旧的主进程后,旧的worker进程将逐渐退出,但旧的主进程却会保留不退出。
[root@xuexi ~]# ps aux | egrep '(ngin[x]|PI[D])'
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 103753 0.0 0.3 122720 7432 ? S 17:01 0:00 nginx: master process /usr/sbin/nginx
root 103919 0.0 0.3 122720 7364 ? S 17:44 0:00 nginx: master process /usr/sbin/nginx
nginx 103920 0.0 0.1 125248 3524 ? S 17:44 0:00 nginx: worker process
nginx 103921 0.0 0.1 125248 3524 ? S 17:44 0:00 nginx: worker process
nginx 103922 0.0 0.1 125248 3524 ? S 17:44 0:00 nginx: worker process
nginx 103923 0.0 0.1 125248 3524 ? S 17:44 0:00 nginx: worker process
如果发现新版本的nginx实例不满意,可以直接向旧主进程号发送HUP信号,这样旧的主进程就会重新读取配置文件并fork新的worker进程,再将新的主进程号杀掉(可以graceful stop),就可以还原为旧版本的nginx实例。
2.降级
上面第4步其实就是最安全的降级方式。即:
kill -HUP `cat /var/run/nginx/nginx.pid.oldbin`
kill -QUIT `cat /var/run/nginx/nginx.pid`
但如果旧的主进程号已经被杀掉了,目前只有新版本的nginx实例在运行,那么只需以升级的步骤进行降级即可。即:
kill -USR2 `cat /var/run/nginx/nginx.pid`
kill -QUIT `cat /var/run/nginx/nginx.pid.oldbin`
3.一键升级脚本
以下是升级的脚本。
#!/bin/sh
#
# Legacy action script for "service nginx upgrade"
# Source function library.
[ -f /etc/rc.d/init.d/functions ] && . /etc/rc.d/init.d/functions
if [ -f /etc/sysconfig/nginx ]; then
. /etc/sysconfig/nginx
fi
prefix=/usr/local/nginx
prog=nginx
nginx=$prefix/sbin/nginx
conffile=$prefix/conf/nginx.conf
pidfile=/var/run/nginx.pid
SLEEPSEC=${SLEEPSEC:-1}
UPGRADEWAITLOOPS=${UPGRADEWAITLOOPS:-5}
oldbinpidfile=${pidfile}.oldbin
# 配置文件语法检查
${nginx} -t -c ${conffile} -q || return 6
echo -n $"Starting new master $prog: "
# 发送USR2信号升级nginx可执行程序
killproc -p ${pidfile} ${prog} -USR2
echo
for i in `/usr/bin/seq $UPGRADEWAITLOOPS`; do
/bin/sleep $SLEEPSEC
if [ -f ${oldbinpidfile} -a -f ${pidfile} ]; then
echo -n $"Graceful shutdown of old $prog: "
# graceful stop旧主nginx主进程
killproc -p ${oldbinpidfile} ${prog} -QUIT
echo
exit 0
fi
done
echo $"Upgrade failed!"
exit 1
1. nginx的反向代理功能
1.1 正向代理和反向代理
正向代理是众多内网客户机上网访问互联网上的网站时,将所有的请求交给内网前面处于公网上的"管家"服务器,由"管家"服务器代为请求想要访问的web服务器,然后将得到的结果缓存下来并提供给客户端,这是正向代理。"管家"服务器称为正向代理服务器。
反向代理是客户端访问web服务器时,请求发送到真实的web服务器的前端"助手"服务器上,由"助手"服务器决定将此请求转发给哪个真实的web服务器,外界客户端以为"助手"服务器就是真实的web服务器,而实际上它不是,也不需要安装任何web程序。"助手"服务器称为反向代理服务器
nginx是一个优秀的反向代理服务程序,通过反向代理可以实现负载均衡的效果。因为是通过反向代理实现的负载均衡,所以nginx实现的是七层负载均衡。它能够识别http协议,根据http报文将不同类型的请求转发到不同的后端web服务器上。后端的web服务器称为"上游服务器",即upstream服务器。
实际上,nginx和php-fpm结合的时候,指令fastcgi_pass实现的也是反向代理的功能,只不过这种代理功能是特定的功能,只能转发给php-fpm。
nginx的反向代理有几种实现方式:
- 仅使用模块ngx_http_proxy_module实现简单的反向代理。指令为proxy_pass。
- 仅使用模块ngx_http_proxy_module实现简单的反向代理。指令为proxy_pass。
- 使用ngx_http_memcached_module模块提供的功能,反向代理memcached缓存内容,指令为memcached_pass。
- 结合upstream模块实现更人性化的分组反向代理。
1.2 配置简单的反代实验
实验配置如下图:

反向代理服务器nginx-proxy(192.168.100.29)的配置。由于是做代理,所以配置文件的location段不再需要root、index等指令,只需几个和代理相关的指令即可。
server {
listen 80;
server_name www.longshuai.com;
location ~ \.(png|jpg|jpeg|bmp|gif)$ {
proxy_pass http://192.168.100.28:80;
}
location / {
proxy_pass http://192.168.100.30:80/;
}
location ~ \.(php|php5)$ {
proxy_pass http://192.168.100.25:80;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
提供动态服务的nginx服务器(192.168.100.25)的配置如下。
server {
listen 80;
server_name www.longshuai.com;
location / {
root /www/longshuai/;
index index.php index.html index.htm;
}
location ~ \.php$ {
root /php/;
fastcgi_pass 192.168.100.27:9000;
fastcgi_index test.php;
include fastcgi.conf;
}
}
其中php-fpm服务器(192.168.100.27)上的 /www/longshuai/index.php内容如下
<h1>page from php-fpm</h1>
<?php
phpinfo();
?>
LB1(192.168.100.28)和LB2(192.168.100.30)的web程序都是httpd。其中作为一般静态web服务器的LB2的配置文件没有任何修改,它的/var/www/html/index.html的内容如下:
<h1>LB2:static</h1>
作为图片服务器的LB1在配置文件中添加了如下几行。且其/var/www/html/下有一个图片文件a.png。
<Files ~ "\.(png|jpeg|jpg|bmp|gif)">
Order allow,deny
Allow from all
</Files>
经过以上的配置,可以实现如下图的功能。当访问www.longshuai.com的时候,任意以php结尾的文件请求都转发给nginx再由nginx交由php-fpm处理;任意以图片格式结尾(png/jpg/jpeg/bmp/gif)的请求都转发给LB1;任意非以上两种格式的请求都转发给LB2。

重载nginx-proxy/nginx/LB1/LB2上的nginx或者httpd。然后进行测试。


1.3 使用upstream模块实现分组反向代理
前面只使用了ngx_http_proxy_module来实现反向代理,但是其缺陷在于在nginx-proxy上定义的每条代理规则都只能转发到后台的某一台服务器上,即后端服务器无法分组。例如当图片服务器压力太大,添加一台服务器想要减轻图片服务器压力,但是仅使用proxy模块无法实现此类负载均衡到多台图片服务器上。
这时需要借助ngx_http_upstream_module模块,该模块用于定义后端服务器池,后端服务器也称为上游服务器(upstream),可以为每一种类型的后端服务器分一个组。然后在结合proxy_pass或其他代理指令将相应的请求转发到池内。
服务器池可以有多台服务器,多台服务器如何实现负载均衡和算法有关,默认是指定权重的加权均衡算法,还可以指定ip_hash算法实现同一个客户端IP发起的请求总是转发到同一台服务器上。还有一些其它算法,如最小连接数算法。最常用的还是加权算法,然后通过session共享的方式实现同一个客户端IP发起的请求转发到同一服务器上。
例如,下图描述的需求。当请求图片服务器时,可以将请求均衡到IP3和IP4两台服务器上,当请求其他静态内容,可以将请求均衡到IP5和IP6两台服务器上。

要实现这样的功能,nginx-proxy上的nginx配置文件内容大致如下:
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
# define server pool
upstream dynamic_pool {
server IP1:80;
}
upstream pic_pool {
server IP3:80 weight=2;
server IP4:80 weight=1;
}
upstream static_pool {
server IP5:80 weight=1;
server IP6:80 weight=1;
}
server {
listen 80;
server_name www.longshuai.com;
# define how to proxy
location ~ \.(php|php5)$ {
proxy_pass http://dynamic_pool;
}
location ~ \.(png|jpeg|jpg|bmp|gif)$ {
proxy_pass http://pic_pool;
}
location / {
proxy_pass http://static_pool;
}
}
}
1.4 ngx_http_proxy_module模块
1.4.1 指令及其意义
该模块默认安装。以下是相关指令的说明
| 指令 | 指令意义 |
|---|---|
| proxy_pass | 定义代理到哪台服务器或哪个upstream池 |
| proxy_set_header | 在代理服务器上设置http报头信息。如加上真实客户端地址"proxy_set_header X_Forwarded_For $remote_addr" |
| proxy_connect_timeout | 反向代理连接上游服务器节点的超时时间。发起方是proxy方,即等待握手成功的时间 |
| proxy_send_timeout | 上游服务器节点数据传给代理服务器的超时时间。即此时间段内,后端节点需要传完数据给代理服务器 |
| proxy_read_timeout | 定义代理服务器何时关闭和后端服务器连接的超时时长,默认为60秒,表示某次后端传输完数据给代理服务器后,如果60秒内代理服务器和后端服务器没有任何传输,则关闭此次连接。目的是避免代理服务器和后端服务器一直保持连接不断开而占用资源 |
1.4.2 proxy_pass
proxy_pass http[s]://{ [IP:PORT/uri/] | upstream_pool };
该指令在前文示例中已经演示过了。此处只说明注意点。
当proxy_pass所在的location中使用了正则匹配时,则proxy_pass(包括fastcgi_pass和memcached_pass)定义的转发路径都不能包含任何URI信息。另外,location中使用了尾随斜线,那么proxy_pass定义的转发路径也必须使用斜线,或者都不加尾随斜线。
例如下面的配置方式是允许的。当访问www.longshuai.com/forum/时将被转发到http://192.168.100.25:8080/bbs/上。
server_name www.longshuai.com;
location /forum/ {
proxy_pass http://192.168.100.25:8080/bbs/;
}
而如果使用了正则匹配,将是不允许的。如下。
server_name www.longshuai.com;
location ~ ^/forum/ {
proxy_pass http://192.168.100.25:8080/bbs/;
}
只能修改转发路径使其不包含URI。如下。此时请求www.longshuai.com/forum/将转发到 http://192.168.100.25:8080/forum/ 。
server_name www.longshuai.com;
location ~ ^/forum/ {
proxy_pass http://192.168.100.25:8080/;
}
1.4.3 proxy_set_header
可以在nginx配置文件中的http段、server段或location段设置proxy_set_header指令。设置该指令后,传输给上游服务器的报文首部将包含相关的信息,如设置客户端的真实IP地址。设置如下:
server {
listen 80;
server_name www.longshuai.com;
location ~ \.(png|jpg|jpeg|bmp|gif)$ {
proxy_pass http://192.168.100.28:80;
proxy_set_header X-Forwarded-For $remote_addr;
}
location / {
proxy_pass http://192.168.100.30:80/;
proxy_set_header X-Forwarded-For $remote_addr;
}
location ~ \.(php|php5)$ {
proxy_pass http://192.168.100.25:80;
proxy_set_header X-Forwarded-For $remote_addr;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
仅在代理服务器上设置了该头部字段后还不够,因为后端服务器仅仅只是获取到它,默认并没有将其记录下来。所以需要在后端的服务的日志格式设置上记录此项或者在其他有需求的地方设置它。nginx的日志格式和apache的日志设置格式不同,以下分别是两种web程序的设置方法:
# nginx上的日志设置格式
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;
# apache上的日志设置格式
LogFormat "%{X-Forwarded-For}i %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
以下是nginx日志上记录的信息
[root@xuexi nginx]# tail -1 logs/access.log
192.168.100.29 - - [26/Apr/2017:14:35:30 +0800] "GET /index.php HTTP/1.0" 200 76990 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36" "192.168.100.1"
以下是apache日志中记录的信息
[root@xuexi ~]# tail -1 /etc/httpd/logs/access_log
192.168.100.1 192.168.100.29 - - [26/Apr/2017:14:32:52 +0800] "GET /a.png HTTP/1.0" 200 2653 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36"
1.5 ngx_http_upstream_module模块
upstream模块定义上游服务器组。主要的指令有"upstream"、"server"、"ip_hash"。upstream指令必须定义在server段外面。
以下是一个综合示例定义方法,并非正确,只是放在一起方便比较用法。
upstream backend {
server 192.168.100.25;
server 192.168.100.26:80;
server www.longshuai.com;
server www.xiaofang.com:8080;
server 192.168.100.27 weight=2 max_fails=2 fail_timeout=2s;
server 192.168.100.28 down;
server 192.168.100.29 backup;
ip_hash; # 定义此项后,前面的server附加项weight和backup功能失效。
}
默认使用加权均衡算法,使用ip_hash指令可设置为ip_hash算法,但使用ip_hash指令后,如果server指令中使用了weight和backup选项,这两个功能将会失效。
其中server指令后可以跟的附加选项有:
weight:定义该后端服务器在组中的权重,默认为1,权重越大,代理转发到此服务器次数越多。max_fails和fail_timeout:定义代理服务器联系后端服务器的失败重联系次数和失败后重试等待时间。默认为1和10,如果设置为2和3,则表示只尝试联系2次,每次失败等待3秒后进行下一次重试,也就是说6秒后就能判定此后端服务器无法联系上,将负载均衡到下一台服务器上。常会配合proxy_next_upstream或者fastcgi_next_upstream或者memcached_next_upstream来指定失败时如何处理。down:将此后端服务器定义为离线状态。此功能不同于直接在配置文件中删除此服务器配置或注释它。常用于ip_hash均衡算法。当使用ip_hash算法时,如果某台服务器坏了需要将其从服务器组中排除,直接从配置文件中直接删除该服务器将会导致此前分配到此后端服务器的客户端重新计算IP_HASH值,而使用down则不会。backup:指定当其他非backup的server都联系不上时,将返回backup所定义的服务器内容。常用于显示sorry page。
当server指定后端服务器时使用的是主机名的方式时,需要在代理服务器上添加域名解析记录,如放到/etc/hosts中。
以下是一个配置示例。只定义了一个upstream组,所有请求都代理,权重为2比1,当192.168.100.28和192.168.100.30都断开联系时,将返回代理服务器本身配置的sorrypage。
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
upstream web_group {
server 192.168.100.28 weight=2 max_fails=2 fail_timeout=2s;
server 192.168.100.30 weight=1 max_fails=2 fail_timeout=2s;
server 127.0.0.1:80 backup;
}
server {
listen 127.0.0.1:80;
root /www/longshuai/;
index index.html;
}
server {
listen 80;
server_name www.longshuai.com;
location / {
proxy_pass http://web_group;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
然后在反向代理服务器上创建/www/longshuai/目录,并向目录下的index.html文件中写入"sorry...."。
mkdir -p /www/longshuai/
echo "<h1>sorry pages...</h1>" >/www/longshuai/index.html
重载代理服务器。在浏览器上输入www.longshuai.com并不断刷新,结果应该是2:1的返回权重。再依次测试停掉某一台后端服务器和两台后端都停掉的情况。
1.6 反向代理的各种情况
反向代理时,可以根据uri的后缀来代理,也可以根据uri中的目录来代理,还可以根据客户端浏览器类型来代理。例如手机访问网站时转发到某个后端服务器组,IE浏览器访问的转发到某一个后端服务器组等
# uri后缀代理。
location ~ \.(jpeg|jpg|png|bmp|gif)$ {
proxy_pass ...
}
# 目录代理。
location ~ /forum/ {
proxy_pass ...
}
# 浏览器类型代理。
location / {
if ($http_user_agent ~ "MSIE") {
proxy_pass...
}
if ($http_user_agent ~ "Chrome") {
proxy_pass...
}
}
1.7 nginx代理 memcached
该模块可以将请求代理至memcached server上,并立即从server上获取响应数据.例如:
location / {
set $memcached_key "$uri?$args";
memcached_pass 127.0.0.1:11211;
}
nginx代理memcached时,需要以变量$memcached_key作为key去访问memcached server上的数据。例如此处将$uri$args变量赋值给$memcached_key变量作为key去访问memcached服务器上对应的数据。
但这样的代理显然不符合真正的需求:没有实现memcached的分布式功能。当memcached server宕机时,nginx将无法从中获取任何数据。所以应该使用上游服务器组。例如:
upstream memcached {
server 127.0.0.1:11211;
server 127.0.0.1:11212;
server 127.0.0.1:11213;
server 127.0.0.1:11214;
}
server {
listen 80;
server_name dev.hwtrip.com;
location ^~ /cache/ {
set $memcached_key "$uri$args";
memcached_pass memcached;
但这也不适合,因为memcached是基于一致性哈希算法的,而upstream模块默认并不支持一致性哈希算法。可以通过upstream模块的hash指令或者另外使用一个第三方模块ngx_http_upstream_consistent_hash。
如果使用的是upstream模块的hash指令,配置如下:
upstream memcached {
hash "$uri$args" consistent;
server 127.0.0.1:11211;
server 127.0.0.1:11212;
server 127.0.0.1:11213;
server 127.0.0.1:11214;
}
这样,各上游主机就通过hash一致性的算法进行负载均衡。
如果使用的是第三方模块ngx_http_upstream_consistent_hash,则在模块添加成功后如下配置upstream组:
upstream memcached {
consistent_hash consistent;
server 127.0.0.1:11211;
server 127.0.0.1:11212;
server 127.0.0.1:11213;
server 127.0.0.1:11214;
}
2. nginx自带的缓存功能
nginx的ngx_http_proxy_module自带了缓存功能。有几种缓存:网页内容缓存,日志缓存,打开文件缓存,fastcgi缓存。fastcgi缓存功能应慎用,因为动态程序的前后逻辑可能改变了,缓存后的结果可能并非实际所需结果。
在说明nginx自带的缓存功能之前,需说明其缺陷。nginx的缓存功能主要用于缓存体积较小的页面资源,当数据较大时很容易出现瓶颈。在页面资源的缓存功能上,它属于业余玩家。而squid是科班出身,功能最全面,可以缓存大量数据,但架构太老,性能一般。varnish则是此类缓存的新贵,架构较新,内存管理完全交由操作系统内核,性能是squid的几倍之强,但缓存的内容不足squid。
本节所讲的主要是nginx自带缓存的网页内容缓存。当实现网页内容缓存时,作为web服务程序,它可以缓存自身返回给客户端的数据,包括读取的图片、文件等;作为代理,它可以缓存来自后端的数据。缓存后的数据在内存中有,也会放在设定的目录下。这样以后客户端继续请求相同资源时,可以直接从内存中或者自身的磁盘中获取并返回给客户端。当缓存超出指定的空间大小时,将会有一个专门的线程cache_manager来清理缓存。
定义的相关指令主要有3个:proxy_cache_path、proxy_cache、proxy_cache_valid。
proxy_cache_path:它的语法比较复杂,但用起来很简单。
proxy_cache_path path [levels=levels] [use_temp_path=on|off] keys_zone=name:size [inactive=time] [max_size=size] [manager_files=number] [manager_sleep=time] [manager_threshold=time] [loader_files=number] [loader_sleep=time] [loader_threshold=time] [purger=on|off] [purger_files=number] [purger_sleep=time] [purger_threshold=time];
proxy_cache_path path [levels=levels] keys_zone=name:size [max_size=size]这几项是一般使用的选项和必需项。以下为一示例。
proxy_cache_path /usr/local/nginx/cache_dir levels=1:2 keys_zone=cache_one:20m max_size=1g;
其中 :
path:定义缓存放在磁盘的哪个目录下。此处表示定义在/usr/local/nginx/cache_dir目录下。目录不存在会自动创建。levels:定义缓存目录的级别,同时定义缓存目录名称的字符数。例如levels=1:2:2表示3级目录,且第一级目录名1个字符,第二级目录2个字符,第三级目录2个字符。目录最多3级,目录名最多为2个字符。例如上例中levels=1:2产生的缓存文件路径可能是这样的"/usr/local/nginx/cache_dir/d/f1/50a3269acaa7774c02d4da0968124f1d",注意其中加粗的字体。keys_zone:定义缓存标识名称和内存中缓存的最大空间。name部分必须唯一,在后面会引用name来表示使用该缓存方法。max_size:定义磁盘中缓存目录的最大空间。即path定义的文件最大空间。
该指令定义后只是定义了一种缓存方法,并非开启了缓存。
proxy_cache:定义要使用哪个缓存方法。使用proxy_cache_path中的name来引用。
该指令定义后只是定义了一种缓存方法,并非开启了缓存
例如引用上例定义的cache_one.
proxy_cache cache_cache;
proxy_cache_valid:根据状态码来指定缓存有效期。
例如,下面的表示状态码为200和302的状态缓存1小时,状态码为404时即page not found的缓存只有1分钟,防止客户端请求一直错误,状态码为其他的则缓存5分钟。
proxy_cache_valid 200 302 1h;
proxy_cache_valid 404 1m;
proxy_cache_valid any 5m;
如果不指定状态码,只指定时间,则默认只缓存状态码200、301、302各5分钟,其他的状态码不缓存。
以下是代理服务器192.168.100.29上定义的缓存示例。
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
upstream web_group {
server 192.168.100.28 weight=2 max_fails=2 fail_timeout=2s;
server 192.168.100.30 weight=1 max_fails=2 fail_timeout=2s;
server 127.0.0.1:80 backup;
}
proxy_cache_path /usr/local/nginx/cache_dir levels=1:2 keys_zone=cache_one:20m max_size=1g;
server {
listen 127.0.0.1:80;
root /www/longshuai/;
index index.html;
}
server {
listen 80;
server_name www.longshuai.com;
# 在响应报文中添加缓存首部字段
add_header X-Cache "$upstream_cache_status from $server_addr";
location / {
proxy_pass http://web_group;
proxy_cache cache_one;
proxy_cache_valid 200 1h;
proxy_cache_valid 404 1m;
proxy_cache_valid any 5m;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
其中添加的一行"add_header X-Cache "$upstream_cache_status from $server_addr";"表示在响应报文的头部加上一字段X-Cache,其值为是否命中缓存的状态($upstream_cache_status),从哪台服务器上($server_addr)取得的缓存。 重载代理服务器的配置文件后,在客户端打开"开发者工具"进行测试。

由于是第一次提供缓存功能,所以结果是未命中缓存。此时已经将缓存保存下来了。 再进行测试,结果将命中缓存是"hit from 192.168.100.29"。

查看缓存目录。
[root@xuexi nginx]# tree /usr/local/nginx/cache_dir/
/usr/local/nginx/cache_dir/
├── 3
│ └── 26
│ └── 3abcc5796b407cf3db2716539d256263
└── d
└── f1
└── 3b37290aabefe7369a4680875f763f1d
如果想要删除缓存,只需删除对应的目录即可。
nginx URL重写
1.1 简介
url重写由ngx_http_rewrite_module模块提供,默认会安装,但该模块功能的实现需要pcre。URL重写技术不仅要求掌握几个指令的语法、熟悉简单的正则表达式,还需要尽量熟悉nginx的各个变量的意义,熟悉的变量越多越好。大多数需要用到的变量都是http_core模块提供的,它们的意义参见官方手册http_core内置变量。
rewrite模块主要有break、return、set、rewrite和if这5个指令。
break的作用是完成当前的作用集,不再执行rewrite指令return返回状态码。可用的状态码有204/301/302/303/307/308/400/402-406/408/410-411/413/416/500-504。return三种语法:
return code [text];
return code URL;
return URL;
set用于定义变量。赋给变量的值可以是一个变量、文本及文本变量的组合(语法:set variable value;)if用于设定判断条件。格式为if (condition) {}rewrite用于设定URL重写规则(语法:rewrite regex replacement [flag]😉
1.2 if 指令
if不支持嵌套,不支持"&&"和"||"多目运算符。语法为:
if (condition) {}
测试条件可以如下定义:
- 变量的比较可以使用"="和"!="运算符。
- 正则匹配可以使用""和"*",前者表示区分大小写的正则匹配,后者表示不区分大小写的匹配。
- 正则匹配可以在前面加上感叹号"!"和"!*"表示取反,即不匹配。
- "-f"和"!-f"判断文件是否存在。
- "-d"和"!-d"判断目录是否存在。
- "-e"和"!-e"判断文件或目录或软链接是否存在。
- "-x"和"!-x"判断文件是否可执行。
if支持的正则表达式可以使用$1至$9来实现反向引用
以下为几个示例:
# 当使用IE浏览器访问时,重定向到/msie/目录下的对应文件
if ($http_user_agent ~ MSIE) {
rewrite ^(.*)$ /msie/$1 break;
}
# 当http请求的方法为POST,则直接返回405状态码,即Method not Allowed
if ($request_method = POST) {
return 405;
}
# 当请求的资源文件不存在,则直接退出当前匹配,并代理至本机,这种情况下由本机来提供服务,如提供错误页面
if (!-f $request_filename) {
break;
proxy_pass http://127.0.0.1;
}
# 当访问的是longshuai.com下任意主机,则重定向到www.longshuai.com主机下的对应目录
if ($http_host ~* "^(.*)\.longshuai\.com$") {
set $domain $1;
rewrite ^(.*) http://www.longshuai.com/$domain/ break;
上面最后一种URL重写后的URL为一个新的主机名站点,但使用URL重写的效率比较低下,远不如直接为此站点独立定义一个虚拟主机。所以改写为:
server {
listen 80;
server_name .longshuai.com;
return 302 http://www.longshuai.com/$request_uri;
}
server {
listen 80;
server_name www.longshuai.com;
1.3 rewrite指令
rewrite 可以写在server段,location段和if段.语法:
rewrite regexp replacement [flag]
如果replacement部分以"http://"或"https://"或"$schema"开头,则直接临时重定向,见下表中的redirect标记。
flag是标记。有4种标记,它们的作用如下表。
| flag | 说明 |
|---|---|
| last | 停止处理当前上下文中的其他重写模块指令,并为重写后的uri再次进行上下文的匹配 |
| break | 和break指令一样,都是停止处理当前上下文中的其他重写模块指令 |
| redirect | 返回临时重定向状态码302。当replacement部分不是以"http://"或者"https://"或者"$schema"开头的时候使用,"$schema"变量表示使用的是什么协议 |
| permanent | 返回永久重定向状态码301 |
| 以上flag中,*** last和break用来实现URL改写,此时浏览器中的地址不会改变,但实际上在服务器上访问的资源和路径已经改变了。redirect和permanent用来实现URL跳转,浏览器中的地址会改变为跳转后的地址。*** |
在使用proxy_pass指令时要使用break标记。last标记在本条rewrite规则执行完后,继续在当前上下文对重写后的地址发起匹配请求,而break则在本次匹配完成后停止再次匹配。例如下面的两条重写规则。
rewrite "^/bbs/(.*)/images/(.*)\.jpg$" www.longshuai.com/bbs/$2/images/$1.jpg last;
rewrite "^/bbs/(.*)/images/(.*)\.jpg$" www.longshuai.com/bbs/$2/images/$1.jpg break;
如果访问的是www.longshuai.com/bbs/a/images/b.jpg则重写后为www.longshuai.com/bbs/b/images/a.jpg,但是重写后的地址仍然可以匹配到规则^/bbs/(.)/images/(.).jpg$,此时如果使用last标记,则会再次进行重写,最终导致URL重写循环,nginx默认支持10次循环,然后返回500状态码。而如果使用break标记,则在重写完成后不会再次匹配重写。
例如,下面的重写示例将会使得任意以longshuai.com结尾的访问重定向到www.longshuai.com。
server_name www.longshuai.com;
rewrite (.*).longshuai.com www.longshuai.com permanent;
下面的重写实例将使得www.longshuai.com/bbs/的访问都重定向到www.longshuai.com/forum/。
server {
listen 80;
server_name www.longshuai.com;
location /{
root /www/longshuai/;
index index.html;
rewrite "/bbs/(.*)" "/forum/$1" last;
}
}
1.4 URL重写和反向代理的区别
URL重写和反向代理都能将请求转发到其他主机上。但它们有很大的区别。
- URL重写可以实现一些反向代理不能实现的转发。
- URL重写可以实现浏览器地址改变。
- 反向代理更多的配合upstream实现负载均衡。URL重写无法直接通过转发实现负载均衡。
- 还有很多其他的区别,无需关心它们的区别,当某种需求既可以URL重写实现,也可以反向代理实现,随便用一种方法即可。



浙公网安备 33010602011771号