呕心沥血整理,Nginx看这个就够了

Nginx

Nginx官方文档

OpenResty官方文档

一、Nginx概述

Nginx是免费,开源,高性能的HTTP和反向代理服务器,邮件代理服务器,通用TCP/UDP代理服务器

官方文档:https://www.nginx.cn/doc/

特性

  • 模块化设计,较好的扩展性
  • 高可靠性
  • 支持热部署:不停机更新配置文件,升级版本,更换日志文件
  • 低内存消耗:10000个keep-alive连接模式下的非活动连接,仅需2.5M内存
  • event-driven,aio,mmap,sendfile

基本功能

  • 静态资源的web服务器
  • http协议反向代理服务器
  • pop3/imap4协议反向代理服务器
  • FastCGI(LNMP),uWSGI(python)等协议
  • 模块化(非DSO),如zip,SSL模块

web服务相关的功能

  • 虚拟主机(server)
  • 支持keep-alive 和 管道连接
  • 访问日志(支持基于日志缓冲提高其性能)
  • url rewrrite
  • 路径别名
  • 基于IP及用户的访问控制
  • 支持速率限制及并发数限制
  • 重新配置和在线升级而无须中断客户的工作进程
  • Memcached的GET接口

正向代理和反向代理
在这里插入图片描述

正向代理,代理客户端。
反向代理,代理服务器。
反向代理:客户端无法感知代理,因为客户端访问网络不需要配置,只要把请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据,然后再返回到客户端。
在这里插入图片描述

Nginx在做反向代理时,提供性能稳定,并且能够提供配置灵活的转发功能。Nginx可以根据不同的正则匹配,采取不同的转发策略,比如图片文件结尾的走文件服务器,动态页面走web服务器,只要你正则写的没问题,又有相对应的服务器解决方案,你就可以随心所欲的玩。并且Nginx对返回结果进行错误页跳转,异常判断等。如果被分发的服务器存在异常,他可以将请求重新转发给另外一台服务器,然后自动去除异常服务器。

负载均衡

Nginx提供的负载均衡策略有2种:内置策略和扩展策略。内置策略为轮询,加权轮询,Ip hash。扩展策略很多。

Ip hash算法,对客户端请求的ip进行hash操作,然后根据hash结果将同一个客户端ip的请求分发给同一台服务器进行处理,可以解决session不共享的问题。
在这里插入图片描述

二、Nginx结构

Nginx 的高并发得益于其采用了 epoll 模型,与传统的服务器程序架构不同,epoll 是Linux 内核 2.6 以后才出现的,Nginx 采用 epoll 模型,异步非阻塞,而 apache 采用的是select 模型

Nginx 默认以 80 端口监听在服务器上,并且启动一个 master 进程,同时有 master 进程生成多个工作进程,当浏览器发起一个 HTTP 连接请求,每个进程都有可能处理这个连接。

Nginx有两种工作模式:master-worker模式和单进程模式(方便调试)。在master-worker模式下,有一个master进程和至少一个的worker进程,单进程模式顾名思义只有一个进程。

Nginx的程序架构:master/worker架构

  • 一个master架构:负载加载和分析配置文件、管理worker进程(启动,杀死,监控,发送消息/信号等)、平滑升级。不负责业务执行

  • 一个或多个worker进程:处理并响应用户请求。真正提供服务的是worker进程,通过worker进程来实现重启服务,平滑升级,更换日志文件,配置文件生效等功能。

  • 缓存相关的进程:

    • cache loader:载入缓存对象
    • cache manager:管理缓存对象

​ ​ ​ ​ ​ ​ ​ 首先每个 worker 进程都是从 Master 进程 fork 出来,在 Master 进程里面,建立好需要listen 的 socket(listenfd)之后,会 fork 出多个 worker 进程。
  所有 worker 进程的 listenfd 会在新连接到来时变得可读,为保证只有一个进程处理该连接,所有 worker 进程在注册 listenfd 读事件前抢 accept_mutex,抢到互斥锁的那个进程注册 listenfd 读事件,在读事件里调用 accept 接受该连接。
  当一个 worker 进程在 accept 这个连接之后,就开始读取请求、解析请求、处理请求,产生数据后,再返回给客户端,最后才断开连接,这样形成一个完整的请求流程。如图

在这里插入图片描述

master-worker这种模式:

  1. 稳定性高,只要还有worker进程存活,就能够提供服务,并且一个worker进程挂掉master进程会立即启动一个新的worker进程,保证worker进程数量不变,降低服务中断的概率。
  2. 配合linux的cpu亲和性配置,可以充分利用多核cpu的优势,提升性能
  3. 处理信号/配置重新加载/升级时可以做到尽可能少或者不中断服务

⭐️Nginx配置文件结构
默认的 nginx 配置文件 nginx.conf 内容结构如下:

...              #全局块

events {         #events块
   ...
}

http      #http块
{
    ...   #http全局块
    server        #server块
    { 
        ...       #server全局块
        location [PATTERN]   #location块
        {
            ...
        }
        location [PATTERN] 
        {
            ...
        }
    }
    server	#每个server用于定义一个虚拟主机
    {
      ...
      server_name 虚拟主机名
      root 主目录
      alias 路径别名
      location [PATTERN] 
      {
      	...
      	if CONDITION {
      	
      	}
      }
    }
    ...     #http全局块
}
mail{
	... #mail协议相关配置段
}
stream{
	... #stream服务器相关配置段
}
  • 1、全局块:配置影响nginx全局的指令。一般有运行nginx服务器的用户组,nginx进程pid存放路径,日志存放路径,配置文件引入,允许生成worker process数等。
  • 2、events块:配置影响nginx服务器或与用户的网络连接。有每个进程的最大连接数,选取哪种事件驱动模型处理连接请求,是否允许同时接受多个网路连接,开启多个网络连接序列化等。
  • 3、http块:可以嵌套多个server,配置代理,缓存,日志定义等绝大多数功能和第三方模块的配置。如文件引入,mime-type定义,日志自定义,是否使用sendfile传输文件,连接超时时间,单连接请求数等。
  • 4、server块:配置虚拟主机的相关参数,一个http中可以有多个server。
  • 5、location块:配置请求的路由,以及各种页面的处理情况。

如下:

########### 每个指令必须有分号结束。#################
#user administrator administrators;  #配置用户或者组,默认为nobody nobody。
#worker_processes 2;  #允许生成的进程数,默认为1
#pid /nginx/pid/nginx.pid;   #指定nginx进程运行文件存放地址
error_log log/error.log debug;  #制定日志路径,级别。这个设置可以放入全局块,http块,server块,级别以此为:debug|info|notice|warn|error|crit|alert|emerg
events {
    accept_mutex on;   #设置网路连接序列化,防止惊群现象发生,默认为on
    multi_accept on;  #设置一个进程是否同时接受多个网络连接,默认为off
    #use epoll;      #事件驱动模型,select|poll|kqueue|epoll|resig|/dev/poll|eventport
    worker_processes  1;		# nginx进程数,一般设置成和CPU个数一样
    worker_connections  1024;    #最大连接数,默认为512
}
http {
    include       mime.types;   #文件扩展名与文件类型映射表
    default_type  application/octet-stream; #默认文件类型,默认为text/plain
    #access_log off; #取消服务日志 
    
    # 日志格式
    log_format myFormat '$remote_addr$remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $http_x_forwarded_for'; #自定义格式
    access_log log/access.log myFormat;  #combined为日志格式的默认值
    
    sendfile on;   #允许sendfile方式传输文件,默认为off,可以在http块,server块,location块。
    sendfile_max_chunk 100k;  #每个进程每次调用传输数量不能大于设定的值,默认为0,即不设上限。
    keepalive_timeout 65;  #连接超时时间,默认为75s,可以在http,server,location块。

    upstream mysvr {   
      server 127.0.0.1:7878;
      server 192.168.10.121:3333 backup;  #热备
    }
    error_page 404 https://www.baidu.com; #错误页
    
    # 服务器配置
    server {
        keepalive_requests 120; #单连接请求上限次数。
        listen       4545;   #监听端口
        server_name  127.0.0.1;   #监听地址       
        location  ~*^.+$ {       #请求的url过滤,正则匹配,~为区分大小写,~*为不区分大小写。
           #root path;  #根目录
           #index vv.txt;  #设置默认页
           proxy_pass  http://mysvr;  #请求转向mysvr 定义的服务器列表
           deny 127.0.0.1;  #拒绝的ip
           allow 172.18.5.54; #允许的ip           
        } 
    }
}

上面是nginx的基本配置,需要注意的有以下几点:

1、几个常见配置项:

  • 1.$remote_addr 与 $http_x_forwarded_for 用以记录客户端的ip地址;
  • 2.$remote_user :用来记录客户端用户名称;
  • 3.$time_local : 用来记录访问时间与时区;
  • 4.$request : 用来记录请求的url与http协议;
  • 5.$status : 用来记录请求状态;成功是200;
  • 6.$body_bytes_s ent :记录发送给客户端文件主体内容大小;
  • 7.$http_referer :用来记录从那个页面链接访问过来的;
  • 8.$http_user_agent :记录客户端浏览器的相关信息;
remote_addr #客户端 IP 地址
remote_port #客户端端口
server_addr #服务端 IP 地址
server_port #服务端端口
server_protocol #服务端协议
binary_remote_addr #二进制格式的客户端 IP 地址
connection #TCP 连接的序号,递增
connection_request #TCP 连接当前的请求数量
uri #请求的URL,不包含参数
request_uri #请求的URL,包含参数
scheme #协议名, http 或 https
request_method #请求方法
request_length #全部请求的长度,包含请求行、请求头、请求体
args #全部参数字符串
arg_参数名 #获取特定参数值
is_args #URL 中是否有参数,有的话返回 ? ,否则返回空
query_string #与 args 相同
host #请求信息中的 Host ,如果请求中没有 Host 行,则在请求头中找,最后使用 nginx 中设置的 server_name 。
http_user_agent #用户浏览器
http_referer #从哪些链接过来的请求
http_via #每经过一层代理服务器,都会添加相应的信息
http_cookie #获取用户 cookie
request_time #处理请求已消耗的时间
https #是否开启了 https ,是则返回 on ,否则返回空
request_filename #磁盘文件系统待访问文件的完整路径
document_root #由 URI 和 root/alias 规则生成的文件夹路径
limit_rate #返回响应时的速度上限值

2、惊群现象:一个网路连接到来,多个睡眠的进程被同时叫醒,但只有一个进程能获得链接,这样会影响系统性能。

3、每个指令必须有分号结束。

4、log_format只能出现在http指令块中,而access_log则可以出现在http和server,location这些指令块中。

在nginx中存储值得指令继承规则是向上覆盖。当子配置存在时,直接覆盖父配置块, 子配置不存在时,直接使用父配置块。

三、Nginx模块

从结构上分为如下三类:
核心模块: HTTP模块、EVENT模块和MAIL模块
标准模块:

  • HTTP模块:ngx_http_* ,HTTP Access模块、HTTP FastCGI模块、HTTP Proxy模块、HTTP Rewrite模块;
    HTTP Core modules 默认功能
    HTTP Optional modules 需编译时指定
  • Mail模块:ngx_mail_*
  • Stream模块:ngx_stream_*

第三方模块:HTTP Upstream Request Hash模块、Notice模块 和 HTTP Access Key模块、Limit_req模块等;

从功能上分为如下三类:

  • Handlers(处理器模块):此类模块直接处理请求,并进行输出内容和修改headers信息等操作,Handlers处理器模块一般只能有一个;
  • Filters(过滤器模块):此类模块主要对其他处理器模块输出的内容进行修改操作,最后由Nginx输出;
  • Proxies(代理类模块):此类模块是Nginx的HTTP Upstream之类的模块,这些模块主要与后端一些服务比如FastCGl等进行交互,实现服务代理和负载均衡等功能。(upstream的模块提供数据转发功能,upstream使nginx跨越单机的限制,完成网络数据的接收、处理和转发)

在这里插入图片描述

  • http_ssl_module:实现服务器加密传输的模块,部署完成后可使用https://协议进行数据传输,保证数据传输过程的安全。

    worker_processes 1;
    http {
      server {
        listen               443;
        ssl                  on;
        ssl_certificate      /usr/local/nginx/conf/cert.pem;
        ssl_certificate_key  /usr/local/nginx/conf/cert.key;
        keepalive_timeout    70;
      }
    }
    
  • http_image_filter_module:通过该模块可以实现图片裁剪,将过大的图片裁剪为指定大小的图片,生成缩略图,保证传输速率,该选项默认不开启,需要人为指定 image_filter resize $h $w;

    编译安装参数配置:--with-http_image_filter_module  
    
    location /img/ {
        proxy_pass     http://backend;
        image_filter   resize  150 100;
        error_page     415   = /empty;
    
    }
     
    location = /empty {
        empty_gif;
    }
    
  • http_rewrite_module
    Nginx的地址重写模块,功能同Apache的一样,可以实现通过正则匹配来完成条件判断,然后进行域名或url的重写。例如:多域名、http ——》https

    • nginx rewrite 规则中 last、break、redirect、permanent 的含义。
  答:不写last和break - 那么流程就是依次执行这些rewrite
  1.rewrite break - url重写后,直接使用当前资源,不再执行location里余下的语句,完成本次请求,地址栏url不变
  2.rewrite last - url重写后,马上发起一个新的请求,再次进入server块,重试location匹配,超过10次匹配不到报500错误,地址栏url不变
  3.rewrite redirect – 返回302临时重定向,地址栏显示重定向后的url,爬虫不会更新url(因为是临时)
  4.rewrite permanent – 返回301永久重定向, 地址栏显示重定向后的url,爬虫更新url
  
  301	永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
  302	临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
  
  
  break
  作用域: server, location, if
  if ($slow) {
   limit_rate  10k;
   break;
  }
  
  if
  作用域: server, location
  匹配符:=,!=,~*,~,!~,!~*
  使用-f以及!-f检测一个文件是否存在
  使用-d以及!-d检测一个目录是否存在
  使用-e以及!-e检测是否存在一个文件,一个目录或者一个符号链接
  
  return
  作用域: server, location, if
  这个指令根据规则的执行情况,返回一个状态值给客户端。可使用值包括:204,400,402-406,408,410,411,413,416以及500-504。也可以发送非标准的444代码-未发送任何头信息下结束连接。
  
  rewrite
  作用域: server, location, if
  
  
  
  permanent 和 redirect关键字的区别
  rewrite … permanent 永久性重定向,请求日志中的状态码为301
  rewrite … redirect 临时重定向,请求日志中的状态码为302
  我们常用的80端口转443,即http转https的一种配置方案为:
  
  server {
      listen 80;
      server_name demo.com;
      rewrite ^(.*)$ https://${server_name}$1 permanent; 
  }
  server {
      listen       443;
      server_name  demo.com;
      charset utf-8;
      location / {
         alias   /data/web;
         index  index.html index.htm;
      }
}
  会返回301永久重定向到对应的https
  • nginx配置文件,rewrite和proxy_pass区别 (两种常见的资源定向)

    • 区别 https://www.jianshu.com/p/10ecc107b5ee https://blog.51cto.com/853056088/2126498

    • rewrite 通过使用flag可以终止定向后进一步的处理

    • 是否影响浏览器地址栏,proxy_pass不影响浏览器地址栏的url

    • proxy_pass配合upstream实现负载均衡

      • - proxy_set_header Host $host; 作用web服务器上有多个站点时,用该参数header来区分反向代理哪个域名。比如下边的示例。
        - proxy_set_header X-Forwarded-For $remote_addr; 作用是后端服务器上的程序获取访客真实IP,从该header头获取。部分程序需要该功能。
  • http_proxy_module
    Nginx反向代理功能,由于Nginx的高并发特性,很多时候我们都选择使用Nginx为网站的前置服务器,一般会和upstream模块一起使用,完成压力分摊工作。

      location / {
      	proxy_pass        http://localhost:8000;
      	proxy_set_header  X-Real-IP  $remote_addr;
      }
    
      如果想将/a的请求,直接转到80/a路径下
          location /a/ {
      		proxy_pass http://120.76.x.x:80/;
          }
      http://127.0.0.1:8000/a/  访问就会跳到80/a
      
      作用域:http,server,location
    

    搭建一台Nginx方向代理服务器,让后端服务器还能够获取到用户的IP地址
    1.反向代理NGINX端设置记录转发请求的HTTP头信息,记录原有的客户端IP和原有客户端请求的服务器地址
    2.后台服务端利用log_format指令设置日志格式,记录代理端返回来的日志信息

    一、配置反向代理端的nginx服务器
    在server后面增加如下这三个参数用于记录IP:

      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    

    二、在后端服务器配置如下
    在后端服务器的nginx_http处配置如下:
    配置log_format信息, 后续的日志后面也需要加上main这个参数

    log_format  main '$remote_addr $remote_user [$time_local] "$request" '                                                                                                                                                           
                      '$status $body_bytes_sent "$http_referer" '                                                                                                                                                                               
                      '$http_user_agent $http_x_forwarded_for $request_time $upstream_response_time $upstream_addr $upstream_status'; 
    
  • http_upstream_module
    Nginx的负载均衡模块,一般和http_proxy模块一起使用,用来对后台服务器的任务调度及分配,分配原则可以通过算法进行控制。常见模式:Nginx+Apache、Nginx+Tomcat

    upstream backend  {
      server backend1.example.com weight=5;
      server backend2.example.com:8080;
      server unix:/tmp/backend3;
    }
     
    server {
      location / {
        proxy_pass  http://backend;
      }
    }
    
    Ip hash算法,对客户端请求的ip进行hash操作,然后根据hash结果将同一个客户端ip的请求分发给同一台服务器进行处理,可以解决session不共享的问题。
    
    	upstream group1{
    		server 192.168.0.1:80 weight=1;
    		server 192.168.0.1:81 weight=1;
    	}
        server {
            listen       8000;
            server_name  localhost;
    		default_type text/html
    		
    		location /{
    			echo "hello";
    		}
    
    		location /a/ {
    			proxy_pass http://group1/;
    		}
    	}
    
  • http_gzip_module
    网络传输压缩模块,这个模块支持在线实时压缩输出数据流

    : gzip             on;   开启gzip模块
    : gzip_min_length  1000;
    : gzip_proxied     expired no-cache no-store private auth;
    : gzip_types       text/plain application/xml;
    
    内置变量 $gzip_ratio 可以获取到gzip的压缩比率
    
  • cache_purge
    实现缓存清除功能

ngx_http_core_module(location、rewrite)

ngx_http_core_module
与套接字相关的配置:
1、server{...}   在http块
配置一个虚拟主机
server{
	listen address[:PORT]|PORT;  
	server_name SERVER_NAME;
	root /PATH/TO/DUCUMENT_ROOT;
}

include /etc/nginx/conf.d/vhosts/*.conf;
server{
	listen 8080;   
	//加上default就是默认,不加就按vhosts目录下的文件顺序找。 
	//listen 80 default_server;  listen IP地址:80 default_server
	server_name www.b.com;
	root /data/siteb;
}


2、listen PORT|address[:port]|unix:/PATH/TO/SOCKET_FILE
listen address[:port][default_server][ssl][http2|spdy][backlog=number][rcvbuf=size][sndbuf=size]
	default_server  设定为默认虚拟主机
	ssl				限制仅能够通过ssl连接提供服务
	backlog=number  超过并发连接数后,新请求进入后援队列的长度
	rcvbuf=size	    接收缓冲区大小
	sndbuf=size     发送缓冲区大小
注意:
(1)基于port;
listen PORT;       指令监听在不同的端口
(2)基于ip的虚拟主机
listen IP:PORT;    IP地址不同
(3)基于hostname
server_name fqdn;  指令指向不同的主机名

3、server_name name...;
虚拟主机的主机名称后可跟多个由空白字符分隔的字符串
支持*通配任意长度的任意字符
  server_name *.abc.com www.abc.*
支持~起始的字符做正则表达式模式匹配,性能原因慎用(要计算匹配,最好精确)
  server_name  ~^www\d+\.abc\.com$
  说明:\d 表示[0-9]
匹配优先级机制从高到低:
(1)首先是字符串精确匹配 如:www.abc.com
(2)右侧*通配符 如:*.abc.com
(3)右侧*通配符 如:www.abc.*
(4)正则表达式 如:~^.*\.abc\.com$
(5)default_server


4.tcp_nodelay on|off;
在keepalived模式下的连接是否启用TCP_NODELAY选项
当为off时,延迟发送,合并多个请求后再发送
默认On时,不延迟发送
可用于:http,server,location

5、sendfile on|off
是否启用sendfile功能,在内核中封装报文直接发送
默认off

6、server_tokens on|off|build|string
是否在相应报文的Server首部显示nginx版本

匹配,root、location、alias

7、root
设置web资源的路径映射;用于指明请求的URL所对应的文档的目录路径,可用于http,server,location,if in location
server{
	...
	root /data/www/vhosts;
}
示例:http://www.abc.com/images/logo.jpg
         -->/data/www/vhosts/images/logo.jpg
访问/images/logo.jpg 相当于访问 /data/www/vhosts/images/logo.jpg


8、location [=|~|~*|^~]uri{..}
   location @name{..}
放在server块和location块
  在一个server中location配置段可存在多个,用于实现从uri到文件系统的路径映射;nginx会根据用户请求的URI来检查定义的所有location,并找出一个最佳匹配,而后应用其配置
示例:
	server{...
		server_name www.abc.com;
		location /images/{
			root /data/imgs/;
			}
	}
	http://www.abc.com/images/logo.jpg
		--> /data/imgs/images/logo.jpg
访问images的时候,实际访问的是/data/imgs下的 images

精确匹配:
=:对URI做精确匹配;
		location = /{
		...
		}
		http://www.abc.com/  匹配
		http://www.abc.com/index.html  不匹配
		
正则匹配
^~:对URI的最左边部分做匹配检查,不区分字符大小写,一般用来匹配目录
~:对URI做正则表达式模式匹配,区分字符大小写
~*:对URI做正则表达式模式匹配,不区分字符大小写

内部location
@

不带符号:匹配起始于此uri的所有uri
匹配优先级从高到低: =,^~,~/~*,不带符号(相当于直接一个杠/)
匹配顺序:精确匹配、记录最长前缀匹配、使用第一个正则匹配、使用前面记录的最长前缀匹配

示例1:匹配http://www.abc.com/
location = / {
	...
}

示例2:匹配http://www.abc.com/index.html
location / {
	...
}

示例3:匹配http://www.abc.com/documents/linux.txt
location /documents/{
	...
}

示例4:匹配http://www.abc.com/documents/logo.jpg
location ~* \.(gif|jpg|jpeg)${
	...
}

示例5:匹配以/images/开头
location ^~ /images/  

示例6:匹配数字,字母和下划线
location ~ /\w

nginx中location匹配顺序
    首先先检查使用前缀字符定义的location,选择最长匹配的项并记录下来。
    如果找到了精确匹配的location,也就是使用了=修饰符的location,结束查找,使用它的配置。
    然后按顺序查找使用正则定义的location,如果匹配则停止查找,使用它定义的配置。
    如果没有匹配的正则location,则使用前面记录的最长匹配前缀字符location。

location匹配规则小结:
1 =|^~|~| 普通文本 四个优先级较高的先匹配
(2,4都是匹配开头,优先级不一样)
2 同优先级的,匹配程度较高的先匹配
3 匹配程度也一样,则写在前面的先匹配

9.alias
	路径别名,文档映射的另一种机制;仅能用于location上下文
示例:
访问 http://www.abc.com/bbs/index.php
location /bbs/{
	alias /web/forum/;
} --> /web/forum/index.html  实际访问

location /bbs/{
	root /web/forum/;
} --> /web/forum/bbs/index.html  实际访问

注意:location中使用root指令和alias指令的意义不同
(a)root,给定的路径对应于location中的/uri/左侧的/
(b)alias,给定的路径对应location中的/uri/右侧的/
10.index file ...;
指定默认网页文件,注意:ngx_http_index_module模块

11、error_page code .. [=[response]] uri;   在server块
模块:ngx_http_core_module
定义错误页,以指定的响应状态码进行响应
可用位置:http,server,location,if in location
error_page 404 /404.html
error_page 404 = 200 /404.html   访问其实是404,强行把状态改为200

示例:
server{
error_page 404 /404.html;
location /404.html{
	root /data/sitea/error/;
	}
}
#mkdir /data/sirea/error/
vim /data/sitea/error/404.html


12、try_files file ... uri;
	try_files file ... = code;
按顺序检查文件是否存在,返回第一个找到的文件或文件夹(结尾加斜线表示为文件夹),如果所有的文件或文件夹都找不到,会进行一个内部重定向到最后一个参数。只有最后一个参数可以引起一个内部重定向,之前的参数只设置内部URI的指向。最后一个参数是回退URI且必须存在,否则会出现内部500错误
location /images/{
	//假设找/images/a.jpg,如果没有a.jpg就返回default.gif
	try_files $uri /images/default.gif;
}
location /{
	try_files $uri $uri/index.html $uri.html = 404;
}

定义客户端请求的相关配置

13、keepalive_timeout timeout [header_timeout];
设定保持连接超时时长,0表示禁止长连接,默认为75s

14、keepalive_requests number;
在一次长连接上锁允许请求的资源的最大数量,默认为100

15、keepalive_disable none|broswer...
对哪种浏览器禁用长连接

16、send_timeout time;
向客户端发送响应报文的超时时长,此处是指两次写操作之间的间隔时长,而非整个响应过程的传输时长

17、client_body_buffer_size size;
用于接收每个客户端请求报文的body部分的缓冲区大小;默认为16k;超出此大小时,其将被暂存到磁盘上的由下面client_body_temp_path指令所定义的位置

18、client_body_temp_path path [level1 [level2 [level3]]];
设定存储客户端请求报文的body部分的临时存储路径及子目录结构和数量
目录名为16进制的数字;
client_body_temp_path /var/tmp/client_body 1 2 2
1 1级目录占1位16进制,即2^4=16个目录 0-f
2 2级目录占2位16进制,即2^8=256个目录 00-ff
2 3级目录占2位16进制,即2^8=256个目录 00-ff

对客户端进行限制的相关配置

19、limit_rate rate;
限制响应给客户端的传输速率,单位是bytes/second
默认值0表示无限制

20、limit_except method ... {...},仅用于location
限制客户端使用除了指定的请求方法之外的其他方法
method:GET,HEAD,POST,PUT,DELETE
MKCOL,COPY,MOVE,OPTIONS,PROPFIND,
PROPPATCH,LOCK,UNLOCK,PATCH
limit_except GET{
	allow 192.168.1.0/24;
	deny all;
}除了GET和HEAD之外其他方法仅允许192.168.1.0/24网段主机使用

文件操作优化的配置

21、aio on|off|threads|[=pool];
是否启用aio功能,默认不启动

22、directio size|off;
当文件大于等于给定大小时,例如directio 4m,同步(直接)写磁盘,而非写缓存

23、open_file_cache off;
open_file_cache max=N [inactive=time];
nginx可以缓存以下三种信息:
(1)文件元数据:文件的描述符、文件大小和最近一次的修改时间
(2)打开的目录结构
(3)没有找到的或者没有权限访问的文件的相关信息
max=N:可缓存的缓存项上限;达到上限后会使用LRU(最近最少)算法实现管理
inactive=time:缓存项的非活动时长,在此处指定的时长内未被命中的活命中的次数少于open_file_cache_min_uses指令所指定的次数的缓存项即非活动项,将被删除

24、open_file_cache_errors on|off;
是否缓存查找时发生错误的文件一类的信息
默认值为off

25、open_file_cache_min_uses number;
open_file_cache指令的inactive参数指定的时长内,至少被命中此处指定的次数方可被归类为活动项
默认值为1

26、open_file_cache_valid time;
缓存项有效性的检查频率
默认值为60s

ngx_http_access_module

实现基于ip的访问控制功能
1、allow address|CIDR|unix:|all;
2、deny address|CIDR|unix:|all;
http,server,location,limit_except
自上而下检查,一旦匹配,将生效,条件严格的置前
示例:
location /{
	deny 192.168.1.1;
	allow 192.168.1.0/24;
	allow 10.1.1.0/16;
	allow 2001:0db8::/32;
	deny all
}
实现基于用户的访问控制

使用basic机制进行用户认证

1、auth_basic string|off;
2、auth_basic_user_file file;
	location /admin/{
		auth_basic "Admin Area";   # 提示信息
		auth_basic_user_file /etc/nginx/conf.d/vhosts/nginxuser;   # 其实也可以创建隐藏文件 .nginxuser
	}
	用户口令文件:
	1、明文文本:格式name:password:comment
	2、加密文本:由htpasswd命令实现  (通过httpd-tools。 #rpm -ivh httpd-tools)

#htpasswd -cm nginxuser httpuser1  第一次创建要加参数c
#htpasswd -m nginxuser httpuser2

ngx_http_stub_status_module

用于输出nginx的基本状态信息

Active connections:291
server accepts handled requests
	    16630948 16630948 31070465
上面三个数字分别对应accepts,handled,requests三个值
Reading:6 Writing:179 Waiting:106

Activeconnections:当前状态,活动状态的连接数
accepts:统计总值,已经接受的客户端请求的总数
handled:统计总值,已经处理完成的客户端请求的总数
requests:统计总值,客户端发来的总的请求数
Reading:当前状态,正在读取客户端请求报文首部的连接的连接数 (请求头)
Writing:当前状态,正在向客户端发关响应报文过程中的连接数 (响应头)
Waiting:当前状态,正在等待客户端发出请求的空闲连接数 (等待的请求数)

1、stub_status;
示例:
location /status{
	stub_status;
	#allow 172.16.0.0/16;
  #deny all;
}
作用域:server,location

ngx_http_log_module

指定日志格式记录请求。搭配access_log

只能在http的作用域下
#vim /etc/nginx/nginx.conf
1、log_format name string ...;
string可以使用nginx核心模块及其它模块内嵌的变量

2、access_log path [format[buffer=size][gzip[=level]][flush=time][if=condition]];
access_log off;
访问日志文件路径,格式及相关的缓冲的配置
buffer=size
flush=time

配合access_log 
access_log可以配置在 http,server,location,if in location,limit_except 这些作用域

日志时间格式:log_format
$time_iso8601
#vim /etc/nginx/nginx.conf 修改[$time_local]为[$time_iso8601]

在这里插入图片描述
在这里插入图片描述

日志缓存

3、open_log_file_cache max=N[inactiv=time][min_uses=N][valid=time];
open_log_file_cache off;
缓存各日志文件相关的元数据信息
max:缓存的最大文件描述符数量
min_uses:在inactive指定的时长内访问大于等于此值方可被当作活动项
inactive:非活动时长
valid:验证缓存中各缓存项是否为活动项的时间间隔

ngx_http_gzip_module

用gzip方法压缩响应数据,节约带宽

用gzip方法压缩响应数据,节约带宽
1、gzip on|off;
启用或禁用gzip压缩
2、gzip comp-level level;
压缩比由低到高 1到9,默认:1
3、gzip_disable regex ...;
匹配到客户端浏览器不执行压缩
4、gzip_min_length length;
启用压缩功能的响应报文大小阈值
5、gzip_http_version 1.0|1.1;
设定启用压缩功能时,协议的最小版本。 默认:1.1版本
6、gzip_buffers number size;
支持实现压缩功能时缓冲区数量及每个缓存区的大小。 默认:324k或168k
7、gzip_types mime-type ...;
指明仅对哪些类型的资源执行压缩操作;即压缩过滤器
默认包含有text/html,不用显示指定,否则出错
8、gzip_vary on|off;
如果启用压缩,是否在响应报文首部插入“Vary:Accept-Encoding”
9、gzip_proxied off|expired|no-cache|no-store|private|no_last_modified|no_etag|auth|any ...;
nginx充当代理服务器时,对于后端服务器的响应报文,在何种条件下启用压缩功能
off:不启用压缩
expired,no-cache,no-store,private:对后端服务器的响应报文首部Cache-Control值任何一个,启用压缩功能
示例:
	gzip on;
	gzip_comp_level 6
	gzip_min_length 64;  字节
	gzip_proxied any;
	gzip_types text/xml text/css application/javascript text/plain;  默认html压缩
	gzip

🌟ngx_http_limit

  • 连接频率限制 - limit_conn_module
  • 请求频率限制 - limit_req_module

ngx_http_limit_req_module

漏桶算法
在这里插入图片描述

算法思想是:

  • 水(请求)从上方倒入水桶,从水桶下方流出(被处理);
  • 来不及流出的水存在水桶中(缓冲),以固定速率流出;
  • 水桶满后水溢出(丢弃)。
  • 这个算法的核心是:缓存请求、匀速处理、多余的请求直接丢弃。
    相比漏桶算法,令牌桶算法不同之处在于它不但有一只“桶”,还有个队列,这个桶是用来存放令牌的,队列才是用来存放请求的。

从作用上来说,漏桶和令牌桶算法最明显的区别就是是否允许突发流量(burst)的处理,漏桶算法能够强行限制数据的实时传输(处理)速率,对突发流量不做额外处理;而令牌桶算法能够在限制数据的平均传输速率的同时允许某种程度的突发传输。

Nginx按请求速率限速模块使用的是漏桶算法,即能够强行保证请求的实时处理速度不会超过设置的阈值。

ngx_http_limit_req_module 模块可以通过定义的 键值来限制请求处理的频率。它可以限制来自单个IP地址的请求处理频率。 限制的方法是通过一种“漏桶”的方法——固定每秒处理的请求数,推迟过多的请求处理。

示例

http {
    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
#设置一块共享内存限制域的参数,它可以用来保存键值的状态。 它特别保存了当前超出请求的数量。 键的值就是指定的变量(空值不会被计算)。
#状态被存在名为“one”,最大10M字节的共享内存里面。对于这个限制域来说 平均处理的请求频率不能超过每秒一次。
#键值是客户端的IP地址。 如果不使用$remote_addr变量,而用$binary_remote_addr变量, 可以将每条状态记录的大小减少到64个字节,这样1M的内存可以保存大约1万6千个64字节的记录。  
    ...
    server {
        ...
        location /search/ {
            limit_req zone=one burst=5;  # 限制平均每秒不超过一个请求,同时允许超过频率限制的请求数不多于5个。
        }
}

作用域:http,server,location

如果请求的频率超过了限制域配置的值,请求处理会被延迟,所以 所有的请求都是以定义的频率被处理的。 超过频率限制的请求会被延迟,直到被延迟的请求数超过了定义的阈值 这时,这个请求会被终止,并返回503 (Service Temporarily Unavailable) 错误。这个阈值的默认值等于0。

如果不希望超过的请求被延迟,可以用nodelay参数:limit_req zone=one burst=5 nodelay;

limit_req zone 参数配置

Syntax:	limit_req zone=name [burst=number] [nodelay];
Default:	—
Context:	http, server, location
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
作用域:http
  • 第一个参数:$binary_remote_addr 表示通过remote_addr这个标识来做限制,“binary_”的目的是缩写内存占用量,是限制同一客户端ip地址。
  • 第二个参数:zone=one:10m表示生成一个大小为10M,名字为one的内存区域,用来存储访问的频次信息。
  • 第三个参数:rate=1r/s表示允许相同标识的客户端的访问频次,这里限制的是每秒1次,还可以有比如30r/m的。
limit_req zone=one burst=5 nodelay;
作用域:http、server、location
  • 第一个参数:zone=one 设置使用哪个配置区域来做限制,与上面limit_req_zone 里的name对应。
  • 第二个参数:burst=5,burst爆发的意思,这个配置的意思是设置一个大小为5的缓冲区当有大量请求(爆发)过来时,超过了访问频次限制的请求可以先放到这个缓冲区内。
  • 第三个参数:nodelay,如果设置,超过访问频次而且缓冲区也满了的时候就会直接返回503,如果没有设置,则所有请求会等待排队。

日志相关 limit_req_log_level

语法:limit_req_log_level info | notice | warn | error;
默认值:limit_req_log_level error;
上下文:http, server, location

设置你所希望的日志级别,当服务器因为频率过高拒绝或者延迟处理请求时可以记下相应级别的日志。 延迟的记录的日志级别比拒绝的记录低一个级别;比如, 如果设置“limit_req_log_level notice”, 延迟的日志就是info级别。

Syntax:	limit_req_status code;
Default:	
limit_req_status 503;
Context:	http, server, location

设置拒绝请求的返回值。值只能设置 400 到 599 之间。

ngx_http_limit_conn_module

这个模块用来限制单个IP的请求数。并非所有的连接都被计数。只有在服务器处理了请求并且已经读取了整个请求头时,连接才被计数。

Syntax:	limit_conn zone number;
Default:	—
Context:	http, server, location

一次只允许每个IP地址一个连接。

limit_conn_zone $binary_remote_addr zone=addr:10m;

server {
    location /download/ {
        limit_conn addr 1;
    }

可以配置多个limit_conn指令。例如,以下配置将限制每个客户端IP连接到服务器的数量,同时限制连接到虚拟服务器的总数。

limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;

server {
    ...
    limit_conn perip 10;
    limit_conn perserver 100;
}

如果区域存储耗尽,服务器会将错误返回给所有其他请求。

Syntax:	limit_conn_zone key zone=name:size;
Default:	—
Context:	http
limit_conn_zone $binary_remote_addr zone=addr:10m;

当服务器限制连接数时,设置所需的日志记录级别。

Syntax:	limit_conn_log_level info | notice | warn | error;
Default:	
limit_conn_log_level error;
Context:	http, server, location

设置拒绝请求的返回值。

Syntax:	limit_conn_status code;
Default:	
limit_conn_status 503;
Context:	http, server, location

实战

1、限制访问速率
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server { 
    location / { 
        limit_req zone=mylimit;
    }
}

上述规则限制了每个IP访问的速度为2r/s,并将该规则作用于根目录。

2、burst缓存处理

Nginx按照毫秒级精度统计,超出限制的请求直接拒绝。这在实际场景中未免过于苛刻,真实网络环境中请求到来不是匀速的,很可能有请求“突发”的情况,也就是“一股子一股子”的。Nginx考虑到了这种情况,可以通过burst关键字开启对突发请求的缓存处理,而不是直接拒绝。

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server { 
    location / { 
        limit_req zone=mylimit burst=4;
    }
}

加入了burst=4,意思是每个key(此处是每个IP)最多允许4个突发请求的到来。

通过burst参数,我们使得Nginx限流具备了缓存处理突发流量的能力。

但是请注意:burst的作用是让多余的请求可以先放到队列里,慢慢处理。如果不加nodelay参数,队列里的请求不会立即处理,而是按照rate设置的速度,以毫秒级精确的速度慢慢处理。

3、nodelay降低排队时间

通过设置burst参数,我们可以允许Nginx缓存处理一定程度的突发,多余的请求可以先放到队列里,慢慢处理,这起到了平滑流量的作用。但是如果队列设置的比较大,请求排队的时间就会比较长,用户角度看来就是RT变长了,这对用户很不友好。有什么解决办法呢?nodelay参数允许请求在排队的时候就立即被处理,也就是说只要请求能够进入burst队列,就会立即被后台worker处理,请注意,这意味着burst设置了nodelay时,系统瞬间的QPS可能会超过rate设置的阈值。nodelay参数要跟burst一起使用才有作用。

实例二的配置,加入nodelay选项:

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server { 
    location / { 
        limit_req zone=mylimit burst=4 nodelay;
    }
}

总体耗时变短

但是请注意,虽然设置burst和nodelay能够降低突发请求的处理时间,但是长期来看并不会提高吞吐量的上限,长期吞吐量的上限是由rate决定的,因为nodelay只能保证burst的请求被立即处理

4、自定义返回值
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server { 
    location / { 
        limit_req zone=mylimit burst=4 nodelay;
        limit_req_status 598;
    }
}

默认情况下 没有配置 status 返回值的状态:404

https://www.cnblogs.com/biglittleant/p/8979915.html

四、Nginx命令

Nginx:默认为启动nginx

-h 查看帮助选项

-V 查看版本和配置选项

-t 测试配置nginx.conf文件是否有语法错误

-s reload 重新加载Nginx配置文件,平滑启动Nginx。 或者kill -HUP /var/run/nginx.pid

-s stop 强制停止Nginx服务

-s quit 优雅地停止Nginx服务(即处理完所有请求后再停止服务)

-c filename.conf 使用另一个配置文件

#查看 nginx 进程
ps -ef|grep nginx
#完美停止 nginx
kill -QUIT `cat /var/run/nginx.pid`
#快速停止 nginx
kill -TERM `cat /var/run/nginx.pid`
或者
kill -INT `cat /var/run/nginx.pid`
#完美停止工作进程(主要用于平滑升级)
kill -WINCH `cat /var/run/nginx.pid`
#强制停止 nginx
pkill -9 nginx
#检查对 nginx.conf 文件的修改是否正确
nginx -t -c /etc/nginx/nginx.conf 或者 nginx -t
#停止 nginx 的命令
nginx -s stop 或者 pkill nginx
#查看 nginx 的版本信息
nginx -v
#查看完整的 nginx 的配置信息
nginx -V

五、Nginx优化

Nginx服务器高性能优化–轻松实现10万并发访问量

如何使Nginx轻松实现10万并发访问量。

通常来说,一个正常的 Nginx Linux 服务器可以达到 500,000 – 600,000 次/秒 的请求处理性能,如果Nginx服务器经过优化的话,则可以稳定地达到 904,000 次/秒 的处理性能,大大提高Nginx的并发访问量。

分析:nginx要成功响应请求,会有如下两个限制:

1、nginx接受的tcp连接多,能否建立起来?

2、nginx响应过程,要打开许多文件,能否打开?

所以,只要我们针对上面两个限制进行优化,就能大幅提升Nginx的效率。

在这里插入图片描述

Nginx优化配置项:

1)优化 workprocess,cpu

worker_processes 8;      // 根据CPU核数配置
worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000  00100000 01000000 10000000;

2)事件处理模型优化
nginx的连接处理机制在于不同的操作系统会采用不同的I/O模型,Linux下,nginx使用epoll的I/O多路复用模型,在freebsd使用kqueue的IO多路复用模型,在solaris使用/dev/pool方式的IO多路复用模型,在windows使用的icop等等。要根据系统类型不同选择不同的事务处理模型,我们使用的是Centos,因此将nginx的事件处理模型调整为epoll模型。

events {
    worker_connections  10240;    //
    use epoll;
}

说明:在不指定事件处理模型时,nginx默认会自动的选择最佳的事件处理模型服务。

3)设置work_connections 连接数

 worker_connections  10240;

4)每个进程的最大文件打开数

worker_rlimit_nofile 65535;  # 一般等于ulimit -n系统值

5)keepalive timeout会话保持时间

keepalive_timeout  60;

6)GZIP压缩性能优化

gzip on;       #表示开启压缩功能
gzip_min_length  1k; #表示允许压缩的页面最小字节数,页面字节数从header头的Content-Length中获取。默认值是0,表示不管页面多大都进行压缩,建议设置成大于1K。如果小于1K可能会越压越大
gzip_buffers     4 32k; #压缩缓存区大小
gzip_http_version 1.1; #压缩版本
gzip_comp_level 6; #压缩比率, 一般选择4-6,为了性能gzip_types text/css text/xml application/javascript;  #指定压缩的类型 gzip_vary on; #vary header支持

7)proxy超时设置

proxy_connect_timeout 90;
proxy_send_timeout  90;
proxy_read_timeout  4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k

8)高效传输模式

sendfile on; # 开启高效文件传输模式。
tcp_nopush on; #需要在sendfile开启模式才有效,防止网路阻塞,积极的减少网络报文段的数量。将响应头和正文的开始部分一起发送,而不一个接一个的发送。

Linux系统内核层面:(具体还得看文档)

https://blog.csdn.net/chenlin465373800/article/details/78924780

Nginx要达到最好的性能,出了要优化Nginx服务本身之外,还需要在nginx的服务器上的内核参数。

这些参数追加到/etc/sysctl.conf,然后执行sysctl -p 生效。

1)容纳更多等待连接的网络连接数

#表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数。  
net.ipv4.tcp_max_syn_backlog = 4096

2)允许等待中的监听

net.core.somaxconn = 65535   # 场景:经常处理新请求(request)的高负载的服务

对于一个TCP链接,Server与Client需要通过三次握手来建立网络链接,当三次握手成功之后,我们就可以看到端口状态由LISTEN转为ESTABLISHED,接着这条链路上就可以开始传送数据了
 
net.core.somaxconn是Linux中的一个内核(kernel)参数,表示socket监听(listen)的backlog上限。
什么是backlog?backlog就是socket的监听队列,当一个请求(request)尚未被处理或者建立时,它就会进入backlog。
而socket server可以一次性处理backlog中的所有请求,处理后的请求不再位于监听队列中。
当Server处理请求较慢时,导致监听队列被填满后,新来的请求就会被拒绝。 默认值是128

3) tcp连接重用

net.ipv4.tcp_tw_recycle = 1 
net.ipv4.tcp_tw_reuse = 1   

4)不抵御洪水攻击

net.ipv4.tcp_syncookies = 0    #当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭 
net.ipv4.tcp_max_orphans = 262144  #该参数用于设定系统中最多允许存在多少TCP套接字不被关联到任何一个用户文件句柄上,主要目的为防止Ddos攻击

5)最大文件打开数

在命令行中输入如下命令,即可设置Linux最大文件打开数。

ulimit -n 30000

其他小优化

1.隐藏版本信息

一般来说,软件的漏洞都与版本有关,隐藏版本号是为了防止恶意用户利用软件漏洞进行攻击

http {
	...
	server_tokens off; # 隐藏版本号
	server{
	
	}
}
重新加载    nginx -s reload

2.优化nginx进程个数

nginx.conf下 worker_processes 2

Nginx 有 Master 和 worker 两种进程,Master 进程用于管理 worker 进程,worker 进程用于 Nginx 服务

worker 进程数应该设置为等于 CPU 的核数,高流量并发场合也可以考虑将进程数提高至 CPU 核数 * 2

  1. grep -c processor /proc/cpuinfo # 查看CPU核数
  2. vim nginx.conf # 设置worker进程数
    worker_processes 2;
    user nginx nginx;
    
  3. 检查语法,并重新加载nginx
    ps -ef | grep nginx | grep -v grep # 验证是否为设置的进程数

3.调整nginx单个进程允许的客户端最大连接数

worker_connections 15000;

(1) 控制 Nginx 单个进程允许的最大连接数的参数为 worker_connections ,这个参数要根据服务器性能和内存使用量来调整
(2) 进程的最大连接数受 Linux 系统进程的最大打开文件数限制,只有执行了 “ulimit -HSn 65535” 之后,worker_connections 才能生效
(3) 连接数包括代理服务器的连接、客户端的连接等,Nginx 总并发连接数 = worker 数量 * worker_connections, 总数保持在3w左右

cat /usr/local/nginx/conf/nginx.conf
worker_processes 2;
worker_cpu_affinity 01 10;
user nginx nginx;
events {
use epoll;
worker_connections 15000;
}

4.开启高效文件传输模式

(1) sendfile 参数用于开启文件的高效传输模式,该参数实际上是激活了 sendfile() 功能,sendfile() 是作用于两个文件描述符之间的数据拷贝函数,这个拷贝操作是在内核之中的,被称为 “零拷贝” ,sendfile() 比 read 和 write 函数要高效得多,因为 read 和 write 函数要把数据拷贝到应用层再进行操作
(2) tcp_nopush 参数用于激活 Linux 上的 TCP_CORK socket 选项,此选项仅仅当开启 sendfile 时才生效,tcp_nopush 参数可以允许把 http response header 和文件的开始部分放在一个文件里发布,以减少网络报文段的数量

cat /usr/local/nginx/conf/nginx.conf
......
http {
	include mime.types;
	server_names_hash_bucket_size 512;
	default_type application/octet-stream;
	sendfile on; # 开启文件的高效传输模式
	tcp_nopush on; # 激活 TCP_CORK socket 选择
	tcp_nodelay on; # 数据在传输的过程中不进缓存
	keepalive_timeout 65;
	server_tokens off;
	include vhosts/*.conf;
}

tcp_nodelay这其中采取了Nagle算法
数据只有在写缓存中累积到一定量之后,才会被发送出去,这样明显提高了网络利用率(实际传输数据payload与协议头的比例大大提高)。但是这又不可避免地增加了延时;与TCP delayed ack这个特性结合,这个问题会更加显著,延时基本在40ms左右。当然这个问题只有在连续进行两次写操作的时候,才会暴露出来。

连续进行多次对小数据包的写操作,然后进行读操作,本身就不是一个好的网络编程模式;在应用层就应该进行优化。
 

5.Nginx gzip压缩实现性能优化

ngx_http_gzip_module 配置在http标签端
gzip on 默认是关闭的
https://www.cnblogs.com/kevingrace/p/10018914.html

[root@localhost ~]# vim /usr/local/nginx/conf/nginx.conf

#修改配置为
gzip on;               #开启gzip压缩功能
gzip_min_length 1k;    #设置允许压缩的页面最小字节数
gzip_buffers 4 16k;    #设置压缩缓冲区大小,此处设置为4个16K内存作为压缩结果流缓存
gzip_http_version 1.1; #压缩版本
gzip_comp_level 2;     #设置压缩比率,最小为1,处理速度快,传输速度慢;9为最大压缩比,处理速度慢,传输速度快
gzip types text/css text/xml application/javascript; #制定压缩的类型
gzip vary on;          #选择支持vary header;改选项可以让前端的缓存服务器缓存经过gzip压缩的页面

如下是线上常使用的Gzip压缩配置

http {
.......
    gzip  on;
    gzip_min_length  1k;
    gzip_buffers     4 16k;
    gzip_http_version 1.1;
    gzip_comp_level 9;
    gzip_types       text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php application/javascript application/json;
    gzip_disable "MSIE [1-6]\.";
    gzip_vary on;
 
}

6.nginx防爬虫优化

if ($http_user_agent ~* "qihoobot|Baiduspider|Googlebot|Googlebot-Mobile|Googlebot-Image|Mediapartners-Google|Adsbot-Google|Yahoo! Slurp China|YoudaoBot|Sosospider|Sogou spider|Sogou web spider|MSNBot") {
return 403;
} 禁止百度 谷歌等等来爬取我们的网站

=:对URI做精确匹配
^~:对URI的最左边部分做匹配检查,不区分字符大小写 
~:对URI做正则表达式模式匹配,区分字符大小写 
~*:对URI做正则表达式模式匹配,不区分字符大小写 
不带符号:匹配起始于此uri的所有uri 匹配优先级从高到低: =,^~,~/~*,不带符号(相当于直接一个杠/) 

🌟7.控制nginx并发连接数量

limit_conn
https://www.cnblogs.com/biglittleant/p/8979915.html
https://www.cnblogs.com/pengyunjing/p/10662612.html

Nginx官方版本限制IP的连接和并发分别有两个模块:

  • limit_req_zone 用来限制单位时间内的请求数,即速率限制,采用的漏桶算法 “leaky bucket”。
  • limit_req_conn 用来限制同一时间连接数,即并发限制。

访问限制模块limit_req_zone 和limit_req_conn两个组件来对客户端访问目录和文件的访问频率和次数进行限制

1. 限制单个 IP 的并发连接数   addr注意
限制连接数:
要限制连接,必须先有一个容器对连接进行计数,在http段加入如下代码:
"zone=" 给它一个名字,可以随便叫,这个名字要跟下面的 limit_conn 一致
$binary_remote_addr = 用二进制来储存客户端的地址,1m 可以储存 32000 个并发会话
....
http {
	include mime.types;
	default_type application/octet-stream;
	sendfile on;
	keepalive_timeout 65;
	limit_conn_zone $binary_remote_addr zone=addr:10m; # 用于设置共享内存区域,addr 是共享内存区域的名称,10m 表示共享内存区域的大小
	
	server {
		listen 80;
		server_name www.abc.com;
		location / {
			root html/www;
			index index.html index.htm;
			limit_conn addr 1; # 限制单个IP的并发连接数为1
			}
	}
}
http {
	limit_conn_zone $binary_remote_addr zone=addr:10m;

接下来需要对server不同的位置(location段)进行限速,比如限制每个IP并发连接数为1,则
	server {
		listen 80;
		server_name 192.168.11.128;
		index index.html index.htm index.php;
		limit_conn addr 1; #是限制每个IP只能发起1个连接 (addr 要跟 limit_conn_zone 的变量对应)
		limit_rate 100k; #限速为 100KB/秒
		root html;

注意事项:
limit_rate 100k; //是对每个连接限速100k。这里是对连接限速,而不是对IP限速!如果一个IP允许两个并发连接,那么这个IP就是限速limit_rate * 2

8.nginx错误页面优雅显示

http {
	location / {
		root html/www;
		index index.html index.htm;
		error_page 400 401 402 403 404 405 408 410 412 413 414 415 500 501 502 503 506 = http://www.xxxx.com/error.html;
将这些状态码的页面链接到 http://www.xxxx.com/error.html ,也可以单独指定某个状态码的页面
如 error_page 404 /404.html
	}
}

9.防盗链

什么是防盗链:简单地说,就是某些不法网站未经许可,通过在其自身网站程序里非法调用其他网站的资源,然后在自己的网站上显示这些调用的资源,使得被盗链的那一端消耗带宽资源 。

(1) 根据 HTTP referer 实现防盗链:referer 是 HTTP的一个首部字段,用于指明用户请求的 URL 是从哪个页面通过链接跳转过来的

(2) 根据 cookie 实现防盗链:cookie 是服务器贴在客户端身上的 “标签” ,服务器用它来识别客户端

根据 referer 配置防盗链:

#第一种,匹配后缀
location ~ .*.(gif|jpg|jpeg|png|bm|swf|flv|rar|zip|gz|bz2)$ { # 指定需要使用防盗链的媒体资源
  access_log off; # 不记录防盗链的日志=
  expires 15d; # 设置缓存时间
  valid_referers none blocked *.test.com *.abc.com; # 表示这些地址可以访问上面的媒体资源
  # 
  if ($invalid_referer) { # 如果地址不如上面指定的地址就返回403
  return 403
	}
}

#第二种,绑定目录
location /images {
  root /web/www/img;
  vaild_referers nono blocked *.spdir.com *.spdir.top;
  if ($invalid_referer) {
  return 403;
  }
}

valid_referers 指令详解
该指令后面可以接 none blocked serevr_names string或者是正则表达式

  • 第一个参数none 代表没有referer

  • 第二个参数blocked 代表有referer但是被防火墙或者是代理给去除了

  • 第三个参数string或者表达式 用来匹配referer
    nginx会通过查看referer字段和valid_referers后面的referer列表进行匹配,如果匹配到了就invalid_referer字段值为0 否则设置该值为1

10.优化nginx连接超时时间

  1. 什么是连接超时

当服务器建立的连接没有接收处理请求时,可以在指定的时间内让它超时自动退出

  1. 连接超时的作用

(1) 将无用的连接设置为尽快超时,可以保护服务器的系统资源(CPU、内存、磁盘)

(2) 当连接很多时,及时断掉那些建立好的但又长时间不做事的连接,以减少其占用的服务器资源

(3) 如果用户请求了动态服务,则 Nginx 就会建立连接,请求 FastCGI 服务以及后端 MySQL 服务,设置连接超时,使得在用户容忍的时间内返回数据

  1. 连接超时存在的问题

(1) 服务器建立新连接是要消耗资源的,因此,连接超时时间不宜设置得太短,否则会造成并发很大,导致服务器瞬间无法响应用户的请求

(2) 有些 PHP 站点会希望设置成短连接,因为 PHP 程序建立连接消耗的资源和时间相对要少些

(3) 有些 Java 站点会希望设置成长连接,因为 Java 程序建立连接消耗的资源和时间要多一些,这时由语言的运行机制决定的

  1. 设置连接超时

(1) keepalive_timeout :该参数用于设置客户端连接保持会话的超时时间,超过这个时间服务器会关闭该连接

(2) client_header_timeout :该参数用于设置读取客户端请求头数据的超时时间,如果超时客户端还没有发送完整的 header 数据,服务器将返回 “Request time out (408)” 错误

(3) client_body_timeout :该参数用于设置读取客户端请求主体数据的超时时间,如果超时客户端还没有发送完整的主体数据,服务器将返回 “Request time out (408)” 错误

(4) send_timeout :用于指定响应客户端的超时时间,如果超过这个时间,客户端没有任何活动,Nginx 将会关闭连接

(5) tcp_nodelay :默认情况下当数据发送时,内核并不会马上发送,可能会等待更多的字节组成一个数据包,这样可以提高 I/O 性能。但是,在每次只发送很少字节的业务场景中,使用 tcp_nodelay 功能,等待时间反而会比较长

http {
  include mime.types;
  server_names_hash_bucket_size 512;
  default_type application/octet-stream;
  sendfile on;
  keepalive_timeout 65;
  tcp_nodelay on;
  client_header_timeout 15;
  client_body_timeout 15;
  send_timeout 25;
  include vhosts/*.conf;
}

11.限制文件上传大小

client_max_body_size

client_max_body_size 用于设置最大的允许客户端请求主体的大小,在请求首部中有 "Content-Length" ,如果超过了此配置项,客户端会收到 413 错误,即请求的条目过大

http {
	include mime.types;
	server_names_hash_bucket_size 512;
	default_type application/octet-stream;
	sendfile on;
	keepalive_timeout 65;
	server_tokens off;
	client_max_body_size 8m; # 设置客户端最大的请求主体大小为8M
	include vhosts/*.conf;
}

12、绑定Nginx进程到不同的CPU上

默认情况下,Nginx 的多个进程有可能跑在某一个 CPU 或 CPU 的某一核上,导致 Nginx 进程使用硬件的资源不均,因此绑定 Nginx 进程到不同的 CPU 上是为了充分利用硬件的多 CPU 多核资源的目的。

  1. 查看cpu个数

grep -c processor /proc/cpuinfo # 查看CPU核数

cpu的个数不同绑定亲和力方法也不同

worker_processes 2; # 2核CPU的配置
worker_cpu_affinity 01 10;
worker_processes 4; # 4核CPU的配置
worker_cpu_affinity 0001 0010 0100 1000;
worker_processes 8; # 8核CPU的配置
worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 1000000;

六、Nginx算法

常用四种算法:
1.RR:(默认)轮询,每个请求按时间顺序逐一轮流分配到后端服务器;
2.weight:指定轮询几率,weight和访问比率成正比,根据后端服务器性能分配;
3.ip_hash:每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。
4.fair:(扩展策略),默认不被编译nginx内核,根据后端服务器响应时间判断负载情况,选择最轻的进行处理。

轮询策略:实现请求的按顺序转发,即从服务srv1–srv2–srv3依次来处理请求

http{
	upstream myapp1 {
		server srv1.example.com;
		server srv2.example.com;
		server srv3.example.com;
	}
	
	server {
		listen 80;
		location / {
			proxy_pass http://myapp1;
		}
	}
}

加权轮询策略:请求将按照服务器的设置权重来实现请求转发和处理,如下所示,最终请求处理数将为3:1:1,一般情况下加权的依据是根据服务器配置来决定的,即配置好的机器分配的权重高。

	upstream myapp1 {
      server srv1.example.com weight=3;
      server srv2.example.com;
      server srv3.example.com;
	}

最少连接数策略:请求将转发到连接数较少的服务器上

	upstream myapp1 {
			least_conn;
      server srv1.example.com;
      server srv2.example.com;
      server srv3.example.com;
	}

Main全局配置常见的配置指令分类

  • 正常运行必备的配置
  • 优化性能相关的配置
  • 用于调试及定位问题相关的配置
  • 事件驱动相关的配置

帮助文档:http://nginx.org/en/docs/

正常运行必备的配置

 1、user
 Syntax:  user user [group];
 Default:  user nobody nobody;
 Context:  main
 指定worker进程的运行身份,如组不指定,默认和用户名同名
 2、pid /PATH/TO/PID_FILE
 指定存储nginx主进程PID的文件路径
 3、include file|mask
 指明包含进来的其他配置文件片断
 4、load_module file
 模块加载配置文件:/usr/share/nginx/modules/*.conf
 指明要装在的动态模块路径:/usr/lib64/nginx/modules
 性能优化相关的配置:
 1、worker_processes number | auto
 worker进程的数量;通常应该为当前主机的cpu的物理核心数
 
 2、worker_cpu_affinity cpumask ...
 worker_cpu_affinity auto [cpumask] 提高缓存命中率
 CPU MASK:00000001:0号CPU
      00000010:1号CPU
      10000000:8号CPU
  worker_cpu_affinity 0001 0010 0100 1000;
  worker_cpu_affinity 0101 1010;
  
 3、worker_priority number
 指定worker进程的nice值,设定worker进程的优先级:[-20,20]
 
 4、worker_rlimit_nofile number
 worker进程所能够打开的文件数量上限,如65535
 事件驱动相关的配置:
 events{
   ...
 }
 1、worker_connections number
 每个worker进程所能够打开的最大并发连接数数量,如10240
 总最大并发数:worker_processes * worker_connections
 2、use method
 指明并发连接请求的处理方法,默认自动选择最优方法
   use epoll;
 3、accept_mutex on|off 互斥
 处理新的连接请求的方法;on指由各个worker轮流处理新请求,off指每个新请求的到达都会通知(唤醒)所有的worker进程,但只有一个进程可获得连接,造成'惊群',影响性能
 
 示例:
 worker_processes 2; 
 worker_rlimit_nofile 65535;
 #pid logs/nginx.pid; 
 events { 
    worker_connections 65535; 
 }
 调试和定位问题:
 1、daemon on|off
 是否以守护进程方式运行nginx,默认是守护进程方式。  配置放在全局块,off之后可以到前台运行
 2、master_process on|off
 是否以master/worker模型运行nginx;默认为on。   off将不启动worker
 3、error_log filename [level]
 错误日志文件及其级别;出于调试需要,可设定为debug;但debug仅在编译时使用了“--with-debug”选项时才有效
 方式:file /path/logfile;
 stderr:发送到标准错误
 syslog:server-address[,parameter=values]:发送到syslog memory:size 内存
 level:debug|info|notice|warn|error|crit|alter|emerg
 location ^~ /sta/ {  
    alias /usr/local/nginx/html/static/;  
 }
 location ^~ /tea/ {  
    root /usr/local/nginx/html/;  
 }
 server {
     listen      8082;
     server_name localhost;
 
     location ^~ /root/ {
         root    /data/www/root/;
         index   index.html index.htm;
     }
 
     location ^~ /alias/ {
         alias   /data/www/alias/;
         index   index.html index.htm;
     }
 }
 server {
     listen      8082;
     server_name localhost;
 
     location ^~ /alias/ {
         echo "WITH: /";
     }
 
     location ^~ /alias {
         echo "WITHOUT: /";
     }
 }
 server {
     listen        7000;
     server_name   localhost;
     
     location / {
         root  /data/www/proxy_pass;
     }
 }
 # 代理不带项目名称,没有 /
 server {
     listen      7001;
     server_name locahost;
     
     location /proxy/ {
         proxy_pass  http://127.0.0.1:7000;
     }
 }
 
 # 代理不带项目名称,但是有 /
 server {
     listen      7002;
     server_name locahost;
     
     location /proxy/ {
         proxy_pass  http://127.0.0.1:7000/;
     }
 }
 
 # 代理带项目名称,没有 /
 server {
     listen      7003;
     server_name locahost;
     
     location /proxy/ {
         proxy_pass  http://127.0.0.1:7000/other;
     }
 }
 
 # 代理带项目名称,但是有 /
 server {
     listen      7004;
     server_name locahost;
     
     location /proxy/ {
         proxy_pass  http://127.0.0.1:7000/other/;
     }
 }

在 proxy_pass 中,当我们不是 / 匹配而是带有自定义项目名匹配的时候:

  1. proxy_pass 后面带 /,我们的自定义的项目名就不会被视作路径的一部分去查找后端。

  2. proxy_pass 后面不带 /,我们自定义的项目名会当成路径的一部分添加到代理后端的查找中。

当我们在 proxy_pass 代理的导致中还包含项目名称的时候:

  1. 当后面还跟了项目名,我们自定义的匹配项目名就都不会再作为请求的一部分去查找后端。

  2. 当后面的项目名不带 / 的时候,除去我们自定义部分,后面的 URI 会直接拼接到我们 proxy_pass 上面,由于他们之间没有 / 分隔,所以会组成一个新的路径去查后端。

  3. 当后面的项目带 / 的时候,则会在拼接的时候相当于多了个 / 的分隔。

https://www.cnblogs.com/Dy1an/p/11254973.html


 

Nginx:后面有无/的区别

调试和定位问题

通过 ps -elf | grep nginx 找到 nginx 的worker进程ID 通过 cat /proc/13016/limits 查看,注意其中的Max open files

  • 1.connections不是随便设置的,而是与两个指标有重要关联,一是内存,二是操作系统级别的“进程最大可打开文件数”。
  • 2.内存:每个连接数分别对应一个read_event、一个write_event事件,一个连接数大概占用232字节,2个事件总占用96字节,那么一个连接总共占用328字节,通过数学公式可以算出100000个连接数大概会占用 31M = 100000 * 328 / 1024 / 1024,当然这只是nginx启动时,connections连接数所占用的nginx。
  • 3.进程最大可打开文件数:进程最大可打开文件数受限于操作系统,可通过 ulimit -n 命令查询,以前是1024,现在是65535, nginx提供了worker_rlimit_nofile指令,这是除了ulimit的一种设置可用的描述符的方式。 该指令与使用ulimit对用户的设置是同样的效果。此指令的值将覆盖ulimit的值,如:worker_rlimit_nofile xxx; 设置ulimits:ulimit -SHn xxx

WEB虚拟主机案例

vhost匹配

vhost的三种形式:

  • 完整字符串(exact)
  • 通配符(*)(asterisk)
  • 正则表达式(regular)

匹配顺序:Exact > asterisk(prefix > suffix)>regular

1)在企业生产环境中,通常Nginx不仅仅只发布一套默认网站,会发布多个网站、提高服务器的利用率,避免资源的浪费。
2)将Nginx WEB服务器发布多套网站的方法,称为虚拟主机(多个网站),Nginx发布虚拟主机的方式主要有以下三种:

  • 基于IP的虚拟主机:同一个8888端口,不同的IP访问IP地址; servername IP

  • 基于端口的虚拟主机:同一个IP地址,不同的访问端口; listen xx

  • 基于域名的虚拟主机:同一个8888端口,同一个IP地址,不同的访问域名。 √ servername domain_name

3)基于Nginx WEB服务器,通过同一个8888端口,同一个IP地址,不同的访问域名,发布两个网站,两个网站对应的域名分别是:wp.kolorbgg.top、love.kolorbgg.top,要求用户通过浏览器实现两个网站页面的访问,操作的步骤和方法如下:

#去除Nginx主配置文件中#和空行 三种方式;
awk '!/#/' nginx.conf|awk '!/^$/'   不看井号和空行,加!
sed -e '/#/d' -e '/^$/d' nginx.conf
grep -aivE "#|^$" nginx.conf

如上配置文件过滤、删除,nginx.conf主配置文件Server虚拟主机的核心代码:

	server {
        listen       8888;
        server_name  wp.kolorbgg.top;
        location / {
            root   html/nextcloud;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

    server {
        listen       8888;
        server_name  love.kolorbgg.top;
        location / {
            root   html/love;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

创建两个虚拟主机的发布目录&默认引导文件index.html,操作的方法和指令如下:

#创建两套网站发布目录;
mkdir -p /usr/local/nginx/html/love/
mkdir -p /usr/local/nginx/html/nextcloud/
#往nextcloud发布目录中index.html写入测试数据;
cat>/usr/local/nginx/html/nextcloud/index.html <<EOF
<h1>nextcloud Test Pages.</h1>
<hr color=red>
EOF
#往love发布目录中index.html写入测试数据;
cat>/usr/local/nginx/html/love/index.html <<EOF
<h1>love Test Pages.</h1>
<hr color=red>
EOF

[root@aliyun conf]# /usr/local/nginx/sbin/nginx -s reload

Nginx的生命周期(stage)

  • ngx_event_accept()
  • ngx_http_init_connection()
  • ngx_http_wait_request_handler()
  • ngx_http_process_request_line()
  • ngx_http_process_request_headers()
  • ngx_http_core_run_phases()
  • ngx_http_finalize_request()
  • ngx_http_finalize_connection()

Nginx FAQ

1、Nginx的优缺点

优点:

  • 轻量级服务,比Apache占用更少的内存及资源
  • 并发能力强,nginx处理请求是异步非阻塞的,而apache则是阻塞型的,在高并发下nginx能保持低资源低消耗高性能
  • 处理静态页面上表现的更好(简单、占资源少)
  • 高度模块化的设计,编写模块相对简单
  • 社区活跃,各种高性能模块产出迅速

缺点:

  • 动态处理上需要使用fastcgi连接PHP的FPM服务,相比Apache不占优势

nginx子进程处理用户的进程(静态资源左边一块就能完成)
如果nginx接触的是动态资源请求,就会将动态资源请求交给fastcgi,让fastcgi去找php服务php-fpm(专门与nginx对接的服务),再去查询数据库。
在这里插入图片描述

Nginx适合做静态处理,简单,效率高。 Apache适合做动态处理,稳定,功能强
并发较高的情况下优先选择Nginx,并发要求不高的情况下两者都可以,规模稍大的可以使用Nginx作为反向代理,然后将动态请求负载均衡到后端Apache上。
在这里插入图片描述

为什么Nginx的并发能力强,资源消耗低?

答:Nginx以异步非阻塞方式工作

  • 客户端发送request,服务器分配work进程来处理
  • 能立即处理完的,处理后work进程释放资源,进行下一个request的处理
  • 不能立即处理完的work进程注册返回事件,然后接着去处理其他request
  • 当之前的request结果返回后,触发返回事件,由空闲work进程接着处理通过这种快速处理,快速释放请求的方式,达到同样的配置可以处理更大并发量的目的。

同步阻塞型和异步阻塞型
同步:小明收到快递将送达的短信,在楼下一直等到快递送达。
异步:小明收到快递将送达的短信,小明不会下楼去取,而是快递小哥到楼下后,打电话通知小明,然后小明下楼梯取快递。

2、Nginx是如何连接PHP进行页面解析的

Nginx支持PHP
1.Nginx支持fastcgi功能(默认支持)
2.PHP编译时开启FPM服务(编译时指定)
3.在Nginx配置文件中添加匹配规则(匹配后缀是.php)
PHP就是个解析器,被别人调用来解析代码
Nginx没法直连PHP,因此需要中间连接接口PHP-FPM的9000端口。Nginx通过自身的fastcgi工具连接PHP-FPM。

在这里插入图片描述

3、Nginx和Tomcat之间的数据传输过程(动静分离)

当用户请求进来后,由Nginx处理静态请求,静态请求处理完直接返回。
如果是动态请求,则由Nginx反向代理和负载均衡到两个Tomcat。

在这里插入图片描述

Nginx动静分离

动静分离是在反向代理的基础上实现的
在这里插入图片描述

Tomcat处理动态资源,Nginx响应静态资源

动态资源做负载均衡转发到tomcat服务器处理,每个资源都需要花1-3ms来获取,而且响应头没有Cache-Control字段

静态资源访问本地磁盘html文件夹里的静态资源,静态资源都是直接从磁盘获取,响应头都有Cache-Control字段,静态资源的请求时间均为0ms。

4、Nginx处理一个请求的过程

  1. Nginx 使用一个多进程模型来对外提供服务,其中一个 master 进程,
  2. 多个 worker 进程。master 进程负责管理 Nginx 本身和其他 worker 进程。
  3. worker 进程中有一个函数,执行无限循环,不断处理收到的来自客户端的请求,并进行处理,直到整个 Nginx 服务被停止。

⭐️阅读agentzh-nginx教程

set变量

初识变量(用户自定义变量)

可以直接把变量嵌入到字符串常量中

以构造出新的字符串:

set $a hello; 
set $b "$a, $a";

这里我们通过已有的Nginx变量$a的值,来构造变量$b的值,于是这两条指令顺序执行完之后,$a的值是hello,而$b的值则是hello, hello. 这种技术在 Perl 世界里被称为变量插值(variable interpolation),它让专门的字符串拼接运算符变得不再那么必要。我们在这里也不妨采用此术语。

我们来看一个比较完整的配置示例:

server {
	listen 8080;
	location /test { 
		set $foo hello;
		echo "foo: $foo"; 
	}
}

这个例子省略了 nginx.conf 配置文件中最外围的 http 配置块以及 events 配置块。使用 curl 这个 HTTP 客户端在命令行上请求这个 /test 接口,
我们可以得到
$ curl 'http://localhost:8080/test' 
foo: hello

在这里插入图片描述

nginx配置报错 nginx: [emerg] unknown directive "echo"
实际上,Nginx并没有内置echo这个指令,所以你贸然使用时,自然会提示说无法识别的指令。它是由agentzh(章亦春)开发的第3方模块,是功能强大的调试工具。
https://openresty.org/cn/download.html

安装后,配置文件默认路径:

  • /usr/local/etc/openresty/nginx.conf
  • /usr/local/Cellar/openresty/1.19.3.1_1/nginx/logs/error.log
  • cat /usr/local/var/log/nginx/error.log
    在这里插入图片描述

如果想通过 echo指令直接输出含有美元符$的字符串,有什么办法?

  • 使用ngx_geo提供的配置指令gemo来为变量 $dollar 赋予字符串 “$”,这样我们在下面需要使用美元符的地方,就直接引用我们的 $dollar 变量就可以了。其实 ngx_geo 模块最常规的用法是根据客户端的 IP 地址对指定的 Nginx 变量进行赋值,这里只是借用它以便“无条件地”对我们 的 $dollar 变量赋予“美元符”这个值。

    geo $dollar { 
    	default "$";
    }
    server {
      listen 80;
      location /test {
      echo "This is a dollar sign: $dollar";
    	} 
    }
    
    测试结果:
    kolor@kolordeMacBook-Pro ~ % curl http://127.0.0.1/test
    This is a dollar sign: $
    

内部跳转

涉及“内部跳转”的例子:

server {
  listen 80;
  location /foo { 
  	set $a hello;
  	echo_exec /bar; 
  }
  location /bar {
  	echo "a = [$a]";
  } 
}

这里我们在location /foo中,使用第三方模块ngx_echo提供的echo_exec配置指令,发起到location /bar的“内部跳转”。所谓“内部跳转”,就 是==在处理请求的过程中,于服务器内部,从一个 location 跳转到另一个 location 的过程==。这不同于利用 HTTP 状态码 301 和 302 所进行的“外部跳 转”,因为后者是由 HTTP 客户端配合进行跳转的,而且在客户端,用户可以通过浏览器地址栏这样的界面,看到请求的 URL 地址发生了变化。内部跳 转和Bourne Shell(或Bash)中的exec命令很像,都是“有去无回”。

既然是内部跳转,当前正在处理的请求就还是原来那个,只是当前的 location 发生了变化,所以还是原来的那一套 Nginx 变量的容器副本。对应到上 例,如果我们请求的是/foo这个接口,那么整个工作流程是这样的:先在location /foo中通过set指令将 a 变 量 的 值 赋 为 字 符 串 h e l l o , 然 后 通 过 e c h o e x e c 指 令 发 起 内 部 跳 转 , 又 进 入 到 l o c a t i o n / b a r 中 , 再 输 出 a变量的值赋为字符串hello,然后通 过echo_exec指令发起内部跳转,又进入到location /bar中,再输出 ahelloechoexeclocation/bara变量的值。因为 a 还 是 原 来 的 a还是原来的 aa,所以我们可以期望得到hello这行输 出。测试证实了这一点:

$ curl localhost:80/foo 
a = [hello]

但如果我们从客户端直接访问/bar接口,就会得到空的$a变量的值,因为它依赖于location /foo来对$a进行初始化。

从上面这个例子我们看到,一个请求在其处理过程中,即使经历多个不同的 location 配置块,它使用的还是同一套 Nginx 变量的副本。这里,我们也 首次涉及到了“内部跳转”这个概念。

值得一提的是,标准 ngx_rewrite 模块的 rewrite 配置指令其实也可以发起“内部跳转”,例如上面那个例子用 rewrite 配置指令可以改写成下面这样的形式:

server {
  listen 8080;
  location /foo { 
  	set $a hello;
  	rewrite ^ /bar; 
  }
  location /bar {
  	echo "a = [$a]";
	} 
}

其效果和使用 echo_exec 是完全相同的。后面我们还会专门介绍这个 rewrite 指令的更多用法,比如发起 301 和 302 这样的“外部跳转”。 从上面这个例子我们看到,Nginx 变量值容器的生命期是与当前正在处理的请求绑定的,而与 location 无关。

 

结论

  • Nginx 变量的创建和赋值操作发生在全然不同的时间阶段。Nginx 变量的创建只能发生在 Nginx 配置加载的时候,或者说 Nginx 启动的时候; 而赋值操作则只会发生在请求实际处理的时候 这意味着不创建而直接使用变量会导致启动失败,同时也意味着我们无法在请求处理时动态地创建新的 Nginx变量。

  • Nginx 变量一旦创建,其变量名的可见范围就是整个 Nginx 配置,甚至可以跨越不同虚拟主机的 server 配置块。

    但如果被其中一块使用,比如某一块location,那么另一块location再引用的时候就是空字符串

    Nginx 变量名的可见范围虽然是整个配置,但每个请求都有所有变量的独立副本,或者说都有各变量用来 存放值的容器的独立副本,彼此互不干扰

  • 对于 Nginx 新手来说,最常见的错误之一,就是将 Nginx 变量理解成某种在请求之间全局共享的东西,或者说“全局变量”。而事实上,Nginx 变量的生命期是不可能跨越请求边界的。

  • Nginx 变量的另一个常见误区是认为变量容器的生命期,是与location 配置块绑定的。

    实际上 Nginx 变量值容器的生命期是与当前正在处理的请求绑定的,而与 location 无关。

预定义变量(一)内建变量

用户自定义变量

set
- 执行阶段
- 生命周期

map
- 执行阶段
- 缓存

前面我们接触到的都是通过 set 指令隐式创建的 Nginx 变量。这些变量我们一般称为“用户自定义变量”,或者更简单一些,“用户变量”。既然有“用户自定义变量”,自然也就有由 Nginx 核心和各个 Nginx 模块提供的“预定义变量”,或者说“内建变量”(builtin variables)。

如:
1.uri,url,etc
2.http_xxx,http_response_xxx
3.arg_xxx
4.cookie_xxx

Nginx 内建变量最常见的用途就是获取关于请求或响应的各种信息。例如由 ngx_http_core 模块提供的内建变量 $uri,可以用来获取当前请求的 URI(经过解码,并且不含请求参数),而 $request_uri 则用来获取请求最原始的 URI (未经解码,并且包含请求参数)。请看下面这个例子:

location /test {
  echo "uri = $uri";
  echo "request_uri = $request_uri";
}

另一个特别常用的内建变量其实并不是单独一个变量,而是有无限多变种的一群变量,即名字以 arg_ 开头的所有变量,我们估且称之为 $arg_XXX 变量群。 一个例子是 $arg_name,这个变量的值是当前请求名为 name 的 URI 参数的值,而且还是未解码的原始形式的值。我们来看一个比较完整的示例:

location /test {
  echo "name: $arg_name"; 
  echo "class: $arg_class";
}

在这里插入图片描述

其实 $arg_name 不仅可以匹配 name 参数,也可以匹配 NAME 参数,抑或是 Name,等等:

Nginx 会在匹配参数名之前,自动把原始请求中的参数名调整为全部小写的形式。

如果你想对 URI 参数值中的 %XX 这样的编码序列进行解码,可以使用第三方 ngx_set_misc 模块提供的 set_unescape_uri 配置指令:

location /test {
  set_unescape_uri $name $arg_name; 
  set_unescape_uri $class $arg_class;
	echo "name: $name";
	echo "class: $class";
}

在这里插入图片描述

从这个例子我们同时可以看到,这个 set_unescape_uri 指令也像 set 指令那样,拥有自动创建 Nginx 变量的功能。

像 $arg_XXX 这种类型的变量拥有无穷无尽种可能的名字,所以它们并不对应任何存放值的容器。而且这种变量在 Nginx 核心中是经过特别处理的,第 三方 Nginx 模块是不能提供这样充满魔法的内建变量的。

类似 $arg_XXX 的内建变量还有不少,比如用来取 cookie 值的 $cookie_XXX 变量群,用来取请求头的 $http_XXX 变量群,以及用来取响应头的 $sent_http_XXX 变量群。

需要指出的是,许多内建变量都是只读的,比如我们刚才介绍的 $uri 和 $request_uri. 对只读变量进行赋值是应当绝对避免的,因为会有意想不到的后果,比如:

? location /bad {
? set $uri /blah;
? echo $uri;          
?}
这个有问题的配置会让 Nginx 在启动的时候报错: 
[emerg] the duplicate "uri" variable in ...

如果你尝试改写另外一些只读的内建变量,比如 $arg_XXX 变量,在某些 Nginx 的版本中甚至可能导致进程崩溃。

预定义变量(二)

也有一些内建变量是支持改写的,其中一个例子是 $args. 这个变量在读取时返回当前请求的 URL 参数串(即请求 URL 中问号后面的部分,如果有的话),而在赋值时可以直接修改参数串。我们来看一个例子:

location /test {
	set $orig_args $args; 
	set $args "a=3&b=4";
	echo "original args: $orig_args";
	echo "args: $args"; 
}

这里我们把原始的 URL 参数串先保存在 $orig_args 变量中,然后通过改写 $args 变量来修改当前的 URL 参数串,最后我们用 echo 指令分别输出 $orig_args 和 $args 变量的值。接下来我们这样来测试这个 /test 接口:

$ curl 'http://localhost:8080/test' 
original args:
args: a=3&b=4

$ curl 'http://localhost:8080/test?a=0&b=1&c=2' 
original args: a=0&b=1&c=2
args: a=3&b=4

在第一次测试中,我们没有设置任何 URL 参数串,所以输出 $orig_args 变量的值时便得到空。而在第一次和第二次测试中,无论我们是否提供 URL 参数串,参数串都会在location /test中被强行改写成a=3&b=4.

需要特别指出的是,这里的 $args 变量和 $arg_XXX 一样,也不再使用属于自己的存放值的容器。当我们读取 $args 时,Nginx 会执行一小段代码,从 Nginx 核心中 专门存放当前 URL 参数串的位置去读取数据; 而当我们改写 $args 时,Nginx 会执行另一小段代码,对相同位置进行改写。Nginx 的其他部 分在需要当前 URL 参数串的时候,都会从那个位置去读数据,所以我们对 $args 的修改会影响到所有部分的功能。我们来看一个例子:

location /test {
  set $orig_a $arg_a;
  set $args "a=5";
  echo "original a: $orig_a"; 
  echo "a: $arg_a";
}

这里我们先把内建变量 $arg_a 的值,即原始请求的 URL 参数 a 的值,保存在用户变量 $orig_a 中,然后通过对内建变量 $args 进行赋值,把当前请求的参数串改写为 a=5 ,最后再用 echo 指令分别输出 $orig_a 和 $arg_a 变量的值。因为对内建变量 $args 的修改会直接导致当前请求的 URL 参数串 发生变化,因此内建变量 $arg_XXX 自然也会随之变化。测试的结果证实了这一点:
在这里插入图片描述

我们看到,因为原始请求的 URL 参数串是 a=3, 所以 $arg_a 最初的值为 3, 但随后通过改写 $args 变量,将 URL 参数串又强行修改为 a=5, 所以最终 $arg_a 的值又自动变为了 5.

我们再来看一个通过修改 $args 变量影响标准的 HTTP 代理模块 ngx_proxy 的例子:

server {
	listen 8080;
	location /test {
    set $args "foo=1&bar=2";
    proxy_pass http://127.0.0.1:8081/args;
	} 
}

server {
  listen 8081;
  location /args {
  	echo "args: $args";
	} 
}

这里我们在 http 配置块中定义了两个虚拟主机。第一个虚拟主机监听 8080 端口,其 /test 接口自己通过改写 $args 变量,将当前请求的 URL 参数串 无条件地修改为 foo=1&bar=2. 然后 /test 接口再通过 ngx_proxy 模块的 proxy_pass 指令配置了一个反向代理,指向本机的 8081 端口上的 HTTP 服务 /args。 默认情况下, ngx_proxy 模块在转发 HTTP 请求到远方 HTTP 服务的时候,会自动把当前请求的 URL 参数串也转发到远方。

而本机的8081端口上的HTTP服务正是由我们定义的第二个虚拟主机来提供的。我们在第二个虚拟主机的location /args中利用echo指令输出当 前请求的 URL 参数串,以检查 /test 接口通过 ngx_proxy 模块实际转发过来的 URL 请求参数串。

我们来实际访问一下第一个虚拟主机的 /test 接口:

$ curl 'http://localhost:8080/test?blah=7'
args: foo=1&bar=2

我们看到,虽然请求自己提供了URL参数串blah=7,但在location /test中,参数串被强行改写成了foo=1&bar=2.接着经由proxy_pass指令将 我们被改写掉的参数串转发给了第二个虚拟主机上配置的 /args 接口,然后再把 /args 接口的 URL 参数串输出。事实证明,我们对 $args 变量的赋值 操作,也成功影响到了 ngx_proxy 模块的行为。

在读取变量时执行的这段特殊代码,在 Nginx 中被称为取处理程序(get handler);

而改写变量时执行的这段特殊代码,则被称为 存处理程序(set handler)。

不同的 Nginx 模块一般会为它们的变量准备不同的“存取处理程序”,从而让这些变量的行为充满魔法。

结论

  • $arg_XXX变量群

  • Nginx 会在匹配参数名之前,自动把原始请求中的参数名调整为全部小写的形式。

  • 许多内建变量都是只读的,避免对内建变量赋值

  • 默认情况下, ngx_proxy 模块在转发 HTTP 请求到远方 HTTP 服务的时候,会自动把当前请求的 URL 参数串也转发到远方。

  • $arg_XXX 的实现方式。Nginx 根本不会事先就解析好 URL 参数串,而是在 用户读取某个 $arg_XXX 变量时,调用其“取处理程序”,即时去扫描 URL 参数串。类似地,内建变量 $cookie_XXX 也是通过它的“取处理程序”,即时去 扫描 Cookie 请求头中的相关定义的。

预定义变量(三)(取处理程序、map)

在设置了“取处理程序”的情况下,Nginx 变量也可以选择将其值容器用作缓存,这样在多次读取变量的时候,就只需要调用“取处理程序”计算一次。

下面就来看一个这样的例子:

map $args $foo { 
	default 0; 
	debug 1;
}
server {
	listen 80;
	location /test {
		set $orig_foo $foo; 
		set $args debug;
    echo "original foo: $orig_foo";
    echo "foo: $foo"; 
	}
}

这里首次用到了标准 ngx_map 模块的 map 配置指令,这个 map 指令就可以用于定义两个 Nginx 变量之间的映射关系,或者说是函数关系。回到上面这个例子,我们用map指令定义了用户变量$foo$args内建变量之间的映射关系。特别地,用数学上的函数记法y = f(x)来说,我们的 $args就是“自变量” x,而 $foo 则是“因变量” y,即 $foo 的值是由 $args 的值来决定的 ,或者按照书写顺序可以说,我们将 $args 变量的值映射到了 $foo 变量上。

再来看 map 指令定义的映射规则:

map $args $foo { 
   default 0; 
   debug 1;
}
  • 花括号中第一行的 default 是一个特殊的匹配条件,即当其他条件都不匹配的时候,这个条件才匹配。 当这个默认条件匹配时,就把“因变量” $foo 映 射到值 0.
  • 而花括号中第二行的意思是说,如果“自变量” $args 精确匹配了 debug 这个字符串,则把“因变量” $foo 映射到值 1.
  • 将这两行合起来,我们就得到如下完整的映射规则:当 $args 的值等于 debug 的时候,$foo 变量的值就是 1,否则 $foo 的值就为 0.

明白了map指令的含义,再来看location /test.在那里,我们先把当前$foo变量的值保存在另一个用户变量$orig_foo中,然后再强行把$args 的值改写为 debug,最后我们再用 echo 指令分别输出 $orig_foo$foo的值。

从逻辑上看,似乎当我们强行改写 $args 的值为 debug 之后,根据先前的 map 映射规则,$foo 变量此时的值应当自动调整为字符串 1, 而不论 $foo原 先的值是怎样的。然而测试结果并非如此:

$ curl 'http://localhost:80/test' 

original foo: 0
foo: 0

第一行输出指示 $orig_foo 的值为 0,这正是我们期望的:上面这个请求并没有提供 URL 参数串,于是 $args 最初的取值就是空,再根据我们先前定 义的映射规则,$foo 变量在第一次被读取时的值就应当是 0(即匹配默认的那个 default 条件)。

而第二行输出显示,在强行改写 $args 变量的值为字符串 debug 之后,$foo 的条件仍然是 0 ,这显然不符合映射规则,因为当 $args 为 debug 时,$foo 的值应当是 1. 然后结果却不尽人意?

那就是 $foo 变量在第一次读取时,根据映射规则计算出的值被缓存住了。刚才我们说过,Nginx 模块可以为其创建的变量选择使用值容器,作为其“取处理程序”计算结果的缓存。显然, ngx_map 模块认为变量间的映射计算足够昂贵,需要自动将因变量的计算结果缓存下来,这样在当前请求的处理过程中如果再次读取这个因变量,Nginx 就可以直接返回缓存住的结果,而不再调用该变量的“取处理程序”再行计算了。

为了进一步验证这一点,我们不妨在请求中直接指定 URL 参数串为 debug:
在这里插入图片描述

现在 $orig_foo 的值就成了 1,因为变量 $foo 在第一次被读取时,自变量 $args 的值就是 debug,于是按照映射规则,“取处理程序”计算 返回的值便是 1. 而后续再读取 $foo 的值时,就总是得到被缓存住的 1 这个结果,而不论 $args 后来变成什么样了。

map 指令其实是一个比较特殊的例子,因为它可以为用户变量注册“取处理程序”,而且用户可以自己定义这个“取处理程序”的计算规则。当然,此规则 在这里被限定为与另一个变量的映射关系。同时,也并非所有使用了“取处理程序”的变量都会缓存结果,例如我们前面在 (二) 中已经看到 $arg_XXX 并不会使用值容器进行缓存。

类似 ngx_map 模块,标准的 ngx_geo 等模块也一样使用了变量值的缓存机制。

很多 Nginx 新手都会担心如此“全局”范围的 map 设置会让访问所有虚拟主机的所有 location 接口的请求都执行一遍变量值的映射计算,然而事实并非 如此。前面我们已经了解到 map 配置指令的工作原理是为用户变量注册 “取处理程序”,并且实际的映射计算是在“取处理程序”中完成的,而“取处理程序”只有在该用户变量被实际读取时才会执行(当然,因为缓存的存在,只在请求生命期中的第一次读取中才被执行),所以对于那些根本没有用到相关 变量的请求来说,就根本不会执行任何的无用计算。

在计算领域被称为“惰性求值”(lazy evaluation), 与之相对的便是“主动求值” (eager evaluation)。

  • set $b "$a,$a";
    这里会在执行 set 规定的赋值操作时,“主动”地计算出变量 $b 的值,而不会将该求值计算延缓到变量 $b 实际被读取的时候。

结论

  • 变量值的缓存机制

    Nginx 模块可以为其创建的变量选择使用值容器,作为其“取处理程序”计算结果的缓存。显然, ngx_map 模块认为变量间的映射计算足够昂贵,需要自动将因变量的计算结果缓存下来,这样在当前请求的处理过程中如果再次读取这个因变量,Nginx 就可以直接返回缓存住的结果,而不再调用该变量的“取处理程序”再行计算了

  • map 指令只能在 http 块中 使用

  • map 配置指令的工作原理是为用户变量注册 “取处理程序”,并且实际的映射计算是在“取处理程序”中完成的,而**“取处理程序”只有在该用户变量被实际读取时才会执行**(当然,因为缓存的存在,只在请求生命期中的第一次读取中才被执行),所以对于那些根本没有用到相关 变量的请求来说,就根本不会执行任何的无用计算。

预定义变量(四)(主请求、子请求)

在 Nginx 世界里有两种类型的“请求”,一种叫做“主请求”(main request),而另一种则叫做“子请求”(subrequest)

  • 所谓“主请求”,就是由 HTTP 客户端从 Nginx 外部发起的请求。我们前面见到的所有例子都只涉及到“主请求”,包括之前那两个使用 echo_exec 和 rewrite 指令发起“内部跳转”的例子。

  • 而“子请求”则是由 Nginx 正在处理的请求在 Nginx 内部发起的一种级联请求。“子请求”在外观上很像 HTTP 请求,但实现上却和 HTTP 协议乃至网络通信一点儿关系都没有。它是 Nginx 内部的一种抽象调用,目的是为了方便用户把“主请求”的任务分解为多个较小粒度的“内部请求”,并发或串行地访问 多个 location 接口,然后由这些 location 接口通力协作,共同完成整个“主请求”。

  • “子请求”的概念是相对的,任何一个“子请求”也可以再发 起更多的“子子请求”,甚至可以玩递归调用(即自己调用自己)。当一个请求发起一个“子请求”的时候,按照 Nginx 的术语,习惯把前者称为后者的“父 请求”(parent request)。

一个使用子请求的例子:

location /main { 
	echo_location /foo; 
	echo_location /bar;
}
location /foo { 
	echo foo;
}

location /bar { 
	echo bar;
}

这里在location /main中,通过第三方ngx_echo模块的echo_location指令分别发起到/foo和/bar这两个接口的GET类型的“子请求”。由 echo_location 发起的“子请求”,其执行是按照配置书写的顺序串行处理的,即只有当 /foo 请求处理完毕之后,才会接着处理 /bar 请求。这两个“子请求”的输出会按执行顺序拼接起来,作为 /main 接口的最终输出:
在这里插入图片描述

我们看到,“子请求”方式的通信是在同一个虚拟主机内部进行的,所以 Nginx 核心在实现“子请求”的时候,就只调用了若干个 C 函数,完全不涉及任何网络或者 UNIX 套接字(socket)通信。我们由此可以看出“子请求”的执行效率是极高的。

回到先前对 Nginx 变量值容器的生命期的讨论,我们现在依旧可以说,它们的生命期是与当前请求相关联的。每个请求都有所有变量值容器的独立副 本,只不过当前请求既可以是“ 主请求”,也可以是“子请求”。即便是父子请求之间,同名变量一般也不会相互干扰。让我们来通过一个小实验证明一下 这个说法:

location /main { 
	set $var main;
	echo_location /foo; 
	echo_location /bar;
	echo "main: $var"; 
}
location /foo { 
	set $var foo;
	echo "foo: $var"; 
}
location /bar { 
	set $var bar;
	echo "bar: $var"; 
}

在这个例子中,我们分别在 /main,/foo 和 /bar 这三个 location 配置块中为同一名字的变量,$var,分别设置了不同的值并予以输出。特别地, 我们在 /main 接口中,故意在调用过 /foo 和 /bar 这两个“子请求”之后,再输出它自己的 $var 变量的值。请求 /main 接口的结果是这样的:
在这里插入图片描述

显然,/foo 和 /bar 这两个“子请求”在处理过程中对变量 $var 各自所做的修改都丝毫没有影响到“主请求” /main. 于是这成功印证了“主请求”以及各 个“子请求”都拥有不同的变量 $var 的值容器副本。

不幸的是,一些 Nginx 模块发起的“子请求”却会自动共享其“父请求”的变量值容器,比如第三方模块 ngx_auth_request.

下面是一个例子:

location /main { 
	set $var main;
	auth_request /sub;
	echo "main: $var"; 
}
location /sub { 
	set $var sub;
	echo "sub: $var";
}

这里我们在 /main 接口中先为 $var 变量赋初值 main,然后使用 ngx_auth_request 模块提供的配置指令 auth_request,发起一个到 /sub 接口的“子 请求”,最后利用 echo 指令输出变量 $var 的值。而我们在 /sub 接口中则故意把 $var 变量的值改写成 sub. 访问 /main 接口的结果如下:
在这里插入图片描述

我们看到,/sub 接口对 $var 变量值的修改影响到了主请求 /main. 所以 ngx_auth_request 模块发起的“子请求”确实是与其“父请求”共享一套 Nginx 变量 的值容器。

 

“为什么‘子请求’ /sub 的输出没有出现在最终的输出里呢?”
那就是因为 auth_request 指令会自动忽略“子请求”的响应体,而只检查“子请求”的响应状态码。 当状态码是 2XX 的时候,auth_request 指令会忽略“子请求”而让 Nginx 继续处理当前的请求,否则它就会立即中断当前(主)请求的执行,返回相应的出错页。 在我们的例子中,/sub “子请求”只是使用 echo 指令作了一些输出,所以 隐式地返回了指示正常的 200 状态码。

ngx_auth_request 模块这样父子请求共享一套 Nginx 变量的行为,虽然可以让父子请求之间的数据双向传递变得极为容易,但是对于足够复杂的配置, 却也经常导致不少难于调试的诡异 bug. 因为用户时常不知道“父请求”的某个 Nginx 变量的值,其实已经在它的某个“子请求”中被意外修改了。诸如此类 的因共享而导致的不好的“副作用”,让包括 ngx_echo, ngx_lua,以及 ngx_srcache 在内的许多第三方模块都选择了禁用父子请求间的变量共享。

预定义变量(五)

Nginx内建变量用在 “子请求” 的上下文中时,其行为也会变得有些微妙。

通过前面章节中我们已经知道,许多内建变量都不是简单的“存放值的容器”,它们一般会通过注册“存取处理程序”来表现得与众不同,而它们即使有存 放值的容器,也只是用于缓存“存取处理程序”的计算结果。

我们之前讨论过的 $args 变量正是通过它的“取处理程序”来返回当前请求的 URL 参数串。因 为当前请求也可以是“子请求”,所以在“子请求”中读取 $args,其“取处理程序”会很自然地返回当前“子请求”的参数串。

location /main {
	echo "main args: $args"; 
	echo_location /sub "a=1&b=2";
}
location /sub {
	echo "sub args: $args";
}

这里在 /main 接口中,先用 echo 指令输出当前请求的 $args 变量的值,接着再用 echo_location 指令发起子请求 /sub. 这里值得注意的是,我们在 echo_location 语句中除了通过第一个参数指定“子请求”的 URI 之外,还提供了第二个参数,用以指定该“子请求”的 URL 参数串(即 a=1&b=2)。最后 我们定义了 /sub 接口,在里面输出了一下 $args 的值。请求 /main 接口的结果如下:

image-20201218190412849

显然,当 $args 用在“主请求” /main 中时,输出的就是“主请求”的 URL 参数串,c=3;而当用在“子请求” /sub 中时,输出的则是“子请求”的参数串,a=1&b=2。这种行为正符合我们的直觉

与 $args 类似,内建变量 $uri 用在“子请求”中时,其“取处理程序”也会正确返回当前“子请求”解析过的 URI:

location /main {
	echo "main uri: $uri"; 
	echo_location /sub;
}
location /sub {
	echo "sub uri: $uri";
}

请求 /main的结果是

image-20201218191154372这种行为依然符合我们的直觉

————

但不幸的是,并非所有的内建变量都作用于当前请求。少数内建变量只作用于“主请求”,比如由标准模块 ngx_http_core 提供的内建变量 $request_method

变量 $request_method 在读取时,总是会得到“主请求”的请求方法,比如 GET、POST 之类。我们来测试一下:

location /main {
   echo "main method: $request_method"; 
   echo_location /sub;   # 这个指令是get请求
}

location /sub {
   echo "sub method: $request_method";
}

在这个例子里,/main 和 /sub 接口都会分别输出 $request_method 的值。同时,我们在 /main 接口里利用 echo_location 指令发起一个到 /sub 接口的GET “子请求”。我们现在利用 curl 命令行工具来发起一个到 /main 接口的 POST 请求:

curl --data hello 'http://localhost/main'

image-20201218191744278

这里我们利用 curl 程序的 --data 选项,指定 hello 作为我们的请求体数据,同时 --data 选项会自动让发送的请求使用 POST 请求方法。测试结果 证明了我们先前的预言, $request_method 变量即使在 GET “子请求” /sub 中使用,得到的值依然是“主请求” /main 的请求方法,POST.

有的读者可能觉得我们在这里下的结论有些草率,因为上例是先在“主请求”里读取(并输出) $request_method 变量,然后才发“子请求”的,所以这些读 者可能认为这并不能排除 $request_method 在进入子请求之前就已经把第一次读到的值给缓存住,从而影响到后续子请求中的输出结果。不过,这样的顾 虑是多余的,因为我们前面在 (五) 中也特别提到过,缓存所依赖的变量的值容器,是与当前请求绑定的,而由 ngx_echo 模块发起的“子请求”都禁用了 父子请求之间的变量共享,所以在上例中, $request_method 内建变量即使真的使用了值容器作为缓存(事实上它也没有),它也不可能影响到 /sub 子 请求。

把例子的执行顺序修改一下,重新测试,结果依旧是POST

location /main {
	 echo_location /sub;   # 这个指令是get请求
   echo "main method: $request_method"; 
}

location /sub {
   echo "sub method: $request_method";
}

————

由此可见,我们并不能通过标准的 $request_method 变量取得“子请求”的请求方法。

为了达到我们最初的目的,我们需要求助于第三方模块 ngx_echo 提供 的内建变量 $echo_request_method:

location /main {
  echo "main method: $echo_request_method"; 
  echo_location /sub;
}
location /sub {
	echo "sub method: $echo_request_method";
}
image-20201218193923732

可以看到,父子请求分别输出了它们各自不同的请求方法,POST 和 GET.
类似 $request_method,内建变量 $request_uri 一般也返回的是“主请求”未经解析过的 URL,毕竟“子请求”都是在 Nginx 内部发起的,并不存在所谓的“未解析的”原始形式。

————

内建变量的值缓存在共享变量的父子请求之间起了作用,这无疑是灾难性的。

在(四)看到ngx_auth_request 模块发起的“子请求”是与其“父请求”共享一套变量的。下面是一个这样的可怕例子:

map $uri $tag { 
	default 0; 
	/main 1; 
	/sub 2;
}
server {
	listen 80;
	location /main { 
			auth_request /sub; 
			echo "main tag: $tag";
	}
	location /sub {
			echo "sub tag: $tag";
	} 
}
image-20201218195717467

这里我们使用久违了的 map 指令来把内建变量 $uri 的值映射到用户变量 $tag 上。当 $uri 的值为 /main 时,则赋予 $tag 值 1,当 $uri 取值 /sub 时, 则赋予 $tag 值 2,其他情况都赋 0. 接着,我们在 /main 接口中先用 ngx_auth_request 模块的 auth_request 指令发起到 /sub 接口的子请求,然后再 输出变量 $tag 的值。

其实道理很简单,因为我们的 $tag 变量在“子请求” /sub 中首先被读取,于是在那里计算出了值 2(因为 u r i 在 那 里 取 值 / s u b , 而 根 据 m a p 映 射 规 则 , uri 在那里取值 /sub,而根据 map 映射规 则, uri/submaptag 应当取值 2),从此就被 $tag 的值容器给缓存住了。而 auth_request 发起的“子请求”又是与“父请求”共享一套变量的,于是当 Nginx 的执 行流回到“父请求”输出 $tag 变量的值时,Nginx 就直接返回缓存住的结

结论

  • 缓存所依赖的变量的值容器,是与当前请求绑定的

  • 并不能通过标准的 r e q u e s t m e t h o d 变 量 取 得 “ 子 请 求 ” 的 请 求 方 法 。 可 以 借 助 第 三 方 模 块 n g x e c h o 提 供 的 内 建 变 量 request_method 变量取得“子请求”的请求方法。 可以借助第三方模块ngx_echo提供的内建变量 requestmethodngxechoecho_request_method

预定义变量(六)

没有值的变量也有两种特殊的值:一种是**“不合法”(invalid),另一种是“没找到”(not found)**。

举例说来,当 Nginx 用户变量 f o o 创 建 了 却 未 被 赋 值 时 , foo 创建了却未被赋值时, foofoo 的值便是“不合法”;而如果当前请求的 URL 参数串中并没有提及 XXX 这个参数,则 $arg_XXX 内建变量的值便是“没找到”。

无论是“不合法”也好,还是“没找到”也罢,这两种 Nginx 变量所拥有的特殊值,和空字符串("")这种取值是完全不同的,

由 set 指令创建的变量未初始化就用在“变量插值”中时,效果上等同于空字符串,但那是因为 set 指令为它创建的变量自动注册了一个“取处理程序”,将“不合法”的变量值转换为空字符串。

下面一个例子

location /foo {
	echo "foo = [$foo]";
}

location /bar { 
	set $foo 32;
	echo "foo = [$foo]"; 
}
image-20201218200642914

从输出上看,未初始化的 $foo 变量确实和空字符串的效果等同。

然后查看Nginx错误日志error.log,可以看到警告。

[warn] 5765#0: *1 using uninitialized "foo" variable, ...

这一行警告是谁输出的呢?

答案是set指令为$foo注册的“取处理程序”。当/foo接口中的echo指令实际执行的时候,它会对它的参数"foo = $foo]" 进行“变量插值”计算。于是,参数串中的 $foo 变量会被读取,而 Nginx 会首先检查其值容器里的取值,结果它看到了“不合法”这个特殊值,于是它这才决定继续调用 $foo 变量的“取处理程序”。于是 $foo 变量的“取处理程序”开始运行,它向 Nginx 的错误日志打印出上面那条警告消息,然后返回一个空字符串作为 $foo 的值,并从此缓存在 $foo 的值容器中。

细心的读者会注意到刚刚描述的这个过程其实就是那些支持值缓存的内建变量的工作原理只不过 set 指令在这里借用了这套机制来处理未正确初始化 的 Nginx 变量。值得一提的是,只有“不合法”这个特殊值才会触发 Nginx 调用变量的“取处理程序”,而特殊值“没找到”却不会。

上面这样的警告一般会指示出我们的 Nginx 配置中存在变量名拼写错误,抑或是在错误的场合使用了尚未初始化的变量。因为值缓存的存在,这条警告 在一个请求的生命期中也不会打印多次。当然, ngx_rewrite 模块专门提供了一条 uninitialized_variable_warn 配置指令可用于禁止这条警告日志。

——

刚才提到,内建变量 $arg_XXX 在请求 URL 参数 XXX 并不存在时会返回特殊值“找不到”,但遗憾的是在 Nginx 原生配置语言(我们估且这么称呼它)中 是不能很方便地把它和空字符串区分开来的,比如:

location /test {
	echo "name: [$arg_name]";
}

这里我们输出 $arg_name 变量的值同时故意在请求中不提供 URL 参数 name:
$ curl 'http://localhost:8080/test' 
name: []
我们看到,输出特殊值“找不到”的效果和空字符串是相同的。因为这一回是 Nginx 的“变量插值”引擎自动把“找不到”给忽略了。

那么我们究竟应当如何捕捉到“找不到”这种特殊值的踪影呢?换句话说,我们应当如何把它和空字符串给区分开来呢?

显然,下面这个请求中,URL 参数 name 是有值的,而且其值应当是空字符串:

$ curl 'http://localhost:8080/test?name='
name: []

幸运的是,通过第三方模块 ngx_lua,我们可以轻松地在 Lua 代码中做到这一点。请看下面这个例子:

注意:这里代码格式也很重要!!一开始格式不对得不到结果  
        location /test {
            content_by_lua '
                if ngx.var.arg_name == nil then
                    ngx.say("name: missing")
                else
                    ngx.say("name: [", ngx.var.arg_name, "]")
                end
            ';
        }

在 /test 接口中使用了 ngx_lua 模块的content_by_lua 配置指令,嵌入了一小段我们自己的 Lua 代码 来对 Nginx 变量 $arg_name 的特殊值进行判断。在这个例子中,当 $arg_name 的值为“没找到”(或者“不合法”)时,/foo 接口会输出 name: missing 这一行结果:

image-20201218205033582

因为这是我们第一次接触到 ngx_lua 模块,所以需要先简单介绍一下。 ngx_lua 模块将 Lua 语言解释器(或者 LuaJIT 即时编译器)嵌入到了 Nginx 核心 中,从而可以让用户在 Nginx 核心中直接运行 Lua 语言编写的程序。我们可以选择在 Nginx 不同的请求处理阶段插入我们的 Lua 代码。这些 Lua 代码既 可以直接内联在 Nginx 配置文件中,也可以单独放置在外部 .lua 源码文件(或者 Lua 字节码文件)里,然后在 Nginx 配置文件中引用这些文件的路径。

回到上面这个例子,我们在 Lua 代码里引用 Nginx 变量都是通过 ngx.var 这个由 ngx_lua 模块提供的 Lua 接口。比如引用 Nginx 变量 $VARIABLE 时,就 在 Lua 代码里写作 ngx.var.VARIABLE 就可以了。当 Nginx 变量 $arg_name 为特殊值“没找到”(或者“不合法”)时, ngx.var.arg_name 在 Lua 世界中 的值就是 nil,即 Lua 语言里的“空”(不同于 Lua 空字符串)。我们在 Lua 里输出响应体内容的时候,则使用了 ngx.say 这个 Lua 函数,也是 ngx_lua 模 块提供的,功能上等价于 ngx_echo 模块的 echo 配置指令。

现在,提供空字符串取值的name参数

image-20201218205353072

在这种情况下,Nginx 变量 $arg_name 的取值便是空字符串,这既不是“没找到”,也不是“不合法”,因此在 Lua 里,ngx.var.arg_name 就返回 Lua空字符串(""),和刚才的 Lua nil 值就完全区分开了。

这种区分在有些应用场景下非常重要,比如有的 web service 接口会根据 name 这个 URL 参数是否存在来决定是否按 name 属性对数据集合进行过滤,而显然提供空字符串作为 name 参数的值,也会导致对数据集中取值为空串的记录进行筛选操作。

不过,标准的 $arg_XXX 变量还是有一些局限,比如我们用下面这个请求来测试刚才那个 /test 接口:

image-20201218205732822此时,$arg_name 变量仍然读出“找不到”这个特殊值,这就明显有些违反常识。

此外, $arg_XXX 变量在请求 URL 中有多个同名 XXX 参数时,就只会 返回最先出现的那个 XXX 参数的值,而默默忽略掉其他实例:

image-20201218205927427

要解决这些局限,可以直接在 Lua 代码中使用 ngx_lua 模块提供的 ngx.req.get_uri_args 函数。

结论

  • 由 set 指令创建的变量未初始化就用在“变量插值”中时,效果等同于空字符串,但那是因为 set 指令为它创建的变量自动注册了一个“取处理程序”,将“不合法”的变量值转换为空字符串。

  • 通过第三方模块 ngx_lua 可以解决特殊值“找不到” 和 空字符串的区别

预定义变量(七)

与 $arg_XXX 类似,我们在 (一) 中提到过的内建变量 $cookie_XXX 变量也会在名为 XXX 的 cookie 不存在时返回特殊值“没找到”:

location /test { 
    content_by_lua '
        if ngx.var.cookie_user == nil then 
            ngx.say("cookie user: missing")
        else
            ngx.say("cookie user: [", ngx.var.cookie_user, "]")
        end 
    ';
}

以 arg_ 开头的所有变量,我们估且称之为 $arg_XXX 变量群。

利用curl命令行工具的–cookie name=value选项可以指定name=value为当前请求携带的cookie(通过添加相应的Cookie请求头)。下面是若干次测试结果:

image-20201221094526304

在 Lua 里访问未创建的 Nginx 用户变量时,在 Lua 里也会得到 nil 值,而不会像先前的例子那样直接让 Nginx 拒绝加载配置:

location /test {
    content_by_lua '
        ngx.say("$blah = ", ngx.var.blah)
    ';
}
image-20201221095024030

这里假设我们并没有在当前的 nginx.conf 配置文件中创建过用户变量 $blah,然后我们在 Lua 代码中通过 ngx.var.blah 直接引用它。上面这个配置可以顺利启动,因为 Nginx 在加载配置时只会编译 content_by_lua 配置指令指定的 Lua 代码而不会实际执行它,所以 Nginx 并不知道 Lua 代码里面引用 了 $blah 这个变量。于是我们在运行时也会得到 nil 值。而 ngx_lua 提供的 ngx.say 函数会自动把 Lua 的 nil 值格式化为字符串 “nil” 输出。

值得注意的地方是,我们在 content_by_lua 配置指令的参数中提及了 b l a h 符 号 , 但 却 并 没 有 触 发 “ 变 量 插 值 ” ( 否 则 N g i n x 会 在 启 动 时 抱 怨 ∗ ∗ blah 符号,但却并没有触发“变量插值”(否则 Nginx 会在 启动时抱怨 ** blah(Nginxblah 未创建**)。这是因为 content_by_lua 配置指令并不支持参数的“变量插值”功能。我们前面在中提到过,配置指令的参数是否允 许“ 变量插值”,其实取决于该指令的实现模块。

由 set 指令创建的变量未初始化就用在“变量插值”中时,效果上等同于空字符串,但那是因为 set 指令为它创建的变量自动注册了一个“取处理程序”,将“不合法”的变量值转换为空字符串。

(由 set 指令创建的变量在未初始化时确实是“不合法”,但一旦尝试 读取它们时,Nginx 就会自动调用其“取处理程序”,而它们的“取处理程序”会自动返回空字符串并将之缓存住。于是我们最终得到的是完全合法的空字符串。)

location /foo { 
    content_by_lua '
        if ngx.var.foo == nil then 
            ngx.say("$foo is nil")
        else
            ngx.say("$foo = [", ngx.var.foo, "]")
        end 
    ';
}
location /bar { 
    set $foo 32;
    echo "foo = [$foo]"; 
}

测试结果:可以看到在 Lua 里面读取未初始化的 Nginx 变量 $foo 时得到的是空字符串。

image-20201221100310625

数组

虽然前面反复指出 Nginx 变量只有字符串这一种数据类型,但这并不能阻止像 ngx_array_var 这样的第三方模块让 Nginx 变量也能存 放数组类型的值。

location /test {
    array_split "," $arg_names to=$array; 
    array_map "[$array_it]" $array; 
    array_join " " $array to=$res;
    echo $res; 
}

这个例子中使用了 ngx_array_var 模块的 array_split、 array_map 和 array_join 这三条配置指令,其含义很接近 Perl 语言中的内建函数 split、map 和 join(当然,其他脚本语言也有类似的等价物)。我们来看看访问 /test 接口的结果:

image-20201221101026956

我们看到,使用 ngx_array_var 模块可以很方便地处理这样具有不定个数的组成元素的输入数据,例如此例中的 names URL 参数值就是由不定个数的逗 号分隔的名字所组成。不过,这种类型的复杂任务通过 ngx_lua 来做通常会更灵活而且更容易维护。

执行顺序

当同一个 location 配置块使用了多个 Nginx 模块的配置指令时,这些指令的执行顺序很可能会 跟它们的书写顺序大相径庭。于是许多人选择了“ 试错法”,然后他们的配置文件就时常被改得一片狼藉。

rewrite阶段指令

set
set_by_lua
rewrite
rewrite_by_lua
more_set_input_headers

(pre)access阶段指令

real_ip
allow
deny

content阶段

proxy_pass
echo

执行顺序(一)开头

现在就来看这样一个令人困惑的例子:

location /test { 
set $a 32;
echo $a;
set $a 56; 
echo $a;
}

结果:
56
56

首先需要知道 Nginx 处理每一个用户请求时,都是按照若干个不同阶段(phase)依次处理的。
Nginx 的请求处理阶段共有 11 个之多,我们先介绍其中 3 个比较常见的。按照它们执行时的先后顺序,依次是 rewrite 阶段、access 阶段以及content 阶段

所有 Nginx 模块提供的配置指令一般只会注册并运行在其中的某一个处理阶段。比如上例中的 set 指令就是在 rewrite 阶段运行的,而 echo 指令就只会 在 content 阶段运行。前面我们已经知道,在单个请求的处理过程中,rewrite 阶段总是在 content 阶段之前执行,因此属于 rewrite 阶段的配置 令也总是会无条件地在 content 阶段的配置指令之前执行。

于是在同一个 location 配置块中, set 指令总是会在 echo 指令之前执行

实际的执行顺序应当是

set $a 32; 
set $a 56; 
echo $a; 
echo $a;

即先在 rewrite 阶段执行完这里的两条 set 赋值语句,然后再在后面的 content 阶段依次执行那两条 echo 语句。分属两个不同处理阶段的配置指令之 间是不能穿插着运行的。

知道了实际执行顺序,没有机会在第二条set语句之前用echo输出,最后还是输出56、56。这怎么解决?

通过引入新的用户变量$saved_a在改写 $a 之前及时保存了 $a 的初始值。而对于多条 set 指令而言,它们之间的执行顺序是由 ngx_rewrite 模块 来保证与书写顺序相一致的。同理, ngx_echo 模块自身也会保证它的多条 echo 指令之间的执行顺序。

location /test { 
	set $a 32;
	set $saved_a $a;  #就第三个变量。 set $b $a;也没啥问题
	set $a 56;
	
	echo $saved_a;
	echo $a;
}

访问测试:
$ curl 'http://127.0.0.1/test'
32
56

执行顺序(二) rewrite—set

当 set 指令用在 location 配置块中时,都是在当前请求的 rewrite 阶段运行的。事实上,在此上下文中, ngx_rewrite 模块中的几 乎全部指令,都运行在 rewrite 阶段。不过,值得一提的是,当这些指令使用在 server 配置块 中时,则会运行在一个我们尚未提及的更早的处理阶段,server-rewrite 阶段。

在 Nginx 变量漫谈(六) 中初识了第三方模块 ngx_lua,它提供的 set_by_lua 配置指令也和 ngx_set_misc 模块的指令一样,可以和 ngx_rewrite 模块的 指令混合使用。 set_by_lua 指令支持通过一小段用户 Lua 代码来计算出一个结果,然后赋给指定的 Nginx 变量。和 set 指令相似, set_by_lua 指令也有自动创建不存在的 Nginx 变量的功能。

location /test {
        set $a 32;
        set $b 56;
        set_by_lua $c "return ngx.var.a + ngx.var.b";
        set $equation "$a + $b = $c";
        echo $equation;
}
image-20201224165438115

先将 $a 和 $b 变量分别初始化为 32 和 56,然后利用 set_by_lua 指令内联一行我们自己指定的 Lua 代码,计算出 Nginx 变量 $a 和 $b 的“代数 和”(sum),并赋给变量 $c,接着利用“变量插值”功能,把变量 $a、 $b 和 $c 的值拼接成一个字符串形式的等式,赋予变量 $equation,最后再用 echo 指令输出 $equation 的值。

这个例子值得注意的地方是:首先,我们在 Lua 代码中是通过 ngx.var.VARIABLE 接口来读取 Nginx 变量 $VARIABLE 的;其次,因为 Nginx 变量的值只有字符串这一种类型,所以在 Lua 代码里读取 ngx.var.a 和 ngx.var.b 时得到的其实都是 Lua 字符串类型的值 “32” 和 “56”;接着,我们对两个字符串作加法运算会触发 Lua 对加数进行自动类型转换(Lua 会把两个加数先转换为数值类型再求和);然后,我们在 Lua 代码中把最终结果通过 return 语句返回给外面的 Nginx 变量 $c;最后, ngx_lua 模块在给 $c 实际赋值之前,也会把 return 语句返回的数值类型的结果,也就是 Lua 加法计算得出的“和”,自动转换为字符串(这同样是因为 Nginx 变量的值只能是字符串)。

执行顺序(三) rewrite-access

第三方模块 ngx_lua 提供的 rewrite_by_lua 配置指令也和 more_set_input_headers 一样运行在 rewrite 阶段的末尾

location /test {
        set $a 1;
        rewrite_by_lua "ngx.var.a = ngx.var.a + 1";
        set $a 56;
        echo $a;
}

$ curl 'http://127.0.0.1/test'
57

既然 more_set_input_headers 和 rewrite_by_lua 指令都运行在 rewrite 阶段的末尾,那么它们之间的先后顺序又是怎样的呢?

答案是:不一定。我们应当避免写出依赖它们二者间顺序的配置。

———————— 在 rewrite 阶段之后,有一个名叫 access 的请求处理阶段

**在 rewrite 阶段之后,有一个名叫 access 的请求处理阶段。**Nginx 变量漫谈(五) 中介绍过的第三方模块 ngx_auth_request 的指令就运行在 access 阶段。在 access 阶段运行的配置指令多是执行访问控制性质的任务,比如检查用户的访问权限,检查用户的来源 IP 地址是否合法

例如,标准模块 ngx_access 提供的 allow 和 deny 配置指令可用于控制哪些 IP 地址可以访问

location /hello {
	allow 127.0.0.1;  #支持169.200.179.4/24 写法
	deny all;
	echo "hello world";
}

这个 /hello 接口被配置为只允许从本机(IP 地址为保留的 127.0.0.1)访问,而从其他 IP 地址访问都会被拒(返回 403 错误页)。 ngx_access 模块 自己的多条配置指令之间是按顺序执行的,直到遇到第一条满足条件的指令就不再执行后续的 allow 和 deny 指令。如果首先匹配的指令是 allow,则会继 续执行后续其他模块的指令或者跳到后续的处理阶段;而如果首先满足的是 deny 则会立即中止当前整个请求的处理,并立即返回 403 错误页。所以看 上面这个例子,如果是从本地访问的,则首先匹配allow 127.0.0.1这一条语句,于是Nginx就继续往下执行其他模块的指令以及后续的处理阶段; 而如果是从其他机器访问,则首先匹配的则是deny all这一条语句,即拒绝所有地址,它会导致403错误页立即返回给客户端。

值得一提的是, ngx_access 模块还支持所谓的“CIDR 记法”来表示一个网段,例如 169.200.179.4/24 则表示路由前缀是 169.200.179.0(或者说子 网掩码是 255.255.255.0)的网段。

执行顺序(四)access

ngx_lua 模块提供了配置指令 access_by_lua,用于在 access 请求处理阶段插入用户 Lua 代码。这条指令运行于 access 阶段的末尾,因此总是在 allow 和 deny 这样的指令之后运行,虽然它们同属 access 阶段。一般我们通过 access_by_lua 在 ngx_access 这样的模块检查过客户端 IP 地址之后,再通过 Lua 代码执行一系列更为复杂的请求验证操作,比如实时查询数据库或者其他后端服务,以验证当前用户的身份或权限。

一般我们通过 access_by_lua 在 ngx_access 这样的模块检查过客户端 IP 地址之后,再通过 Lua 代码执行一系列更为复杂的请求验证操作,比如实时查询数据库或者其他后端服务,以验证当前用户的身份或权限。

利用 access_by_lua 来实现 ngx_access 模块的 IP 地址过滤功能

1、
location /hello { 
	access_by_lua '
		if ngx.var.remote_addr == "127.0.0.1" then 
			return
		end
		ngx.exit(403) 
	';
	echo "hello world"; 
}


效果等同于之前看的
2、
location /hello { 
	allo 127.0.0.1;
	deny all;
	echo "hello world";
}

在 Lua 代码中通过引用 Nginx 标准的内建变量 $remote_addr 来获取字符串形式的客户端 IP 地址,然后用 Lua 的 if 语句判断是否为本机地址,即是 否等于 127.0.0.1. 如果是本机地址,则直接利用 Lua 的 return 语句返回,让 Nginx 继续执行后续的请求处理阶段(包括 echo 指令所处的 content 阶段);而如果不是本机地址,则通过 ngx_lua 模块提供的 Lua 函数 ngx.exit 中断当前的整个请求处理流程,直接返回 403 错误页给客户端。

两个例子效果一样,但性能不同。ngx_access组比access_by_lua组快了大约一个数量级

执行顺序(五)content

content阶段指令:proxy_pass、echo

content 阶段属于一个比较靠后的处理阶段,运行在先前介绍过的 rewrite 和 access 这两个阶段之后。当和 rewrite、access 阶段的指令一起使 用时,这个阶段的指令总是最后运行,例如:

location /test {
  # rewrite phase
  set $age 1;
  rewrite_by_lua "ngx.var.age = ngx.var.age + 1";
  # access phase
  deny 10.32.168.49;
  access_by_lua "ngx.var.age = ngx.var.age * 3";
  # content phase
	echo "age = $age"; 
}

$ curl 'http://127.0.0.1/test'
age = 6

即使改变它们的书写顺序,也不会影响到执行顺序。其中, set 指令来自 ngx_rewrite 模块,运行于 rewrite 阶段;而 rewrite_by_lua 指令来自 ngx_lua 模 块,运行于 rewrite 阶段的末尾;接下来, deny 指令来自 ngx_access 模块,运行于 access 阶段;再下来, access_by_lua 指令同样来自 ngx_lua 模 块,运行于 access 阶段的末尾;最后,我们的老朋友 echo 指令则来自 ngx_echo 模块,运行在 content 阶段。

进一步地,在 rewrite 和 access 这两个阶段,多个模块的配置指令可以同时使用,譬如上例中的 set 指令和 rewrite_by_lua 指令同处 rewrite 阶段, 而 deny 指令和 access_by_lua 指令则同处 access 阶段。但不幸的是,这通常不适用于 content 阶段。

绝大多数 Nginx 模块在向 content 阶段注册配置指令时,本质上是在当前的 location 配置块中注册所谓的“内容处理程序”(content handler)。每一 个 location 只能有一个“内容处理程序”,因此,当在 location 中同时使用多个模块的 content 阶段指令时,只有其中一个模块能成功注册“内容处理程序”。考虑下面这个有问题的例子:

? location /test {
? echo hello;
? content_by_lua 'ngx.say("world")'; 
?}

这里,ngx_echo 模块的 echo 指令和 ngx_lua 模块的 content_by_lua 指令同处 content 阶段,于是只有其中一个模块能注册和运行这个 location 的“内容处理程序”:
$ curl 'http://localhost:8080/test' 
world

应当避免在同一个 location 中使用多个模块 的 content 阶段指令。

content_by_lua 'ngx.say("hello")';

总结

  • 使用多条 echo 指令是没问题的,因为它们同属 ngx_echo 模块,而且 ngx_echo 模块规定和实现了它们之间的执行顺序。值得一提的是,并非所有模 块的指令都支持在同一个 location 中被使用多次,例如 content_by_lua 就只能使用一次

    错误的写法:
    ? location /test {
    ? content_by_lua 'ngx.say("hello")';
    ? content_by_lua 'ngx.say("world")'; 
    ?}
    
    正确的写法:
    location /test {
    	content_by_lua 'ngx.say("hello") ngx.say("world")';
    }
    
    即在 content_by_lua 内联的 Lua 代码中调用两次 ngx.say 函数,而不是在当前 location 中使用两次 content_by_lua 指令。
    
  • 类似地, ngx_proxy 模块的 proxy_pass 指令和 echo 指令也不能同时用在一个 location 中,因为它们也同属 content 阶段。不少 Nginx 新手都会犯类似下面这样的错误:

    location /test {
    	echo "before...";
    	proxy_pass http://127.0.0.1:8080/foo; 
    	echo "after...";
    }
    

    要实现这个例子希望达到的效果,需要改用 ngx_echo 模块提供的 echo_before_body 和 echo_after_body 这两条配置指令:

    实现这个例子希望达到的效果,需要改用 ngx_echo 模块提供的 echo_before_body 和 echo_after_body 这两条配置指令:
    location /test {
      echo_before_body "before..."; 
      proxy_pass http://127.0.0.1:8080/foo; 
      echo_after_body "after...";
    }
    
    
  • 使用多个模块的 content 阶段指令时,只有其中一个模块能成功注册“内容处理程序”

执行顺序(六) ngx_index 模块, ngx_autoindex 模块

在一个 location 中使用 content 阶段指令时,通常情况下就是对应的 Nginx 模块注册该 location 中的“内容处理程 序”。那么当一个 location 中未使用任何 content 阶段的指令,即没有模块注册“内容处理程序”时,content 阶段会发生什么事情呢?谁又来担负起 生成内容和输出响应的重担呢?答案就是那些把当前请求的 URI 映射到文件系统的静态资源服务模块。当存在“内容处理程序”时,这些静态资源服务模 块并不会起作用;反之,请求的处理权就会自动落到这些模块上。

Nginx 一般会在 content 阶段安排三个这样的静态资源服务模块(除非你的 Nginx 在构造时显式禁用了这三个模块中的一个或者多个,又或者启用了这 种类型的其他模块)。按照它们在 content 阶段的运行顺序,依次是 **ngx_index 模块, ngx_autoindex 模块,以及 ngx_static 模块。**下面就来逐一介绍一下这三个模块。

ngx_indexngx_autoindex模块都只会作用于那些URI以/结尾的请求,例如请求GET /cats/,而对于不以/结尾的请求则会直接忽略,而 ngx_static 模块则刚好相反,直接忽略那些 URI 以 / 结尾的请求。

ngx_index 模块主要用于在文件系统目录中自动查找指定的首页文件,类似 index.html 和 index.htm 这样的,例如:
location / {
	root /var/www/;
	index index.htm index.html; 
}

如果index文件不存在,则放弃处理权给 content 阶段的下一个模块。

echo_exec 指令和 rewrite 指令可以发起“内部跳转”。这种跳转会自动修改当前请求的 URI,并且重新匹 配与之对应的 location 配置块,再重新执行 rewrite、access、content 等处理阶段。因为是“内部跳转”,所以有别于 HTTP 协议中定义的基于 302 和 301 响应的“外部跳转”,最终==用户的浏览器的地址栏也不会发生变化,依然是原来的 URI 位置。==而 ngx_index 模块一旦找到了 index 指令中列举的 文件之后,就会发起这样的“内部跳转”,仿佛用户是直接请求的这个文件所对应的 URI 一样。

删除index网页

因为 ngx_index 模块找不到 index 指令指定的文件(在这里就是 index.html),接着把处理权转给 content 阶段的后续模块,而后续的模 块也都无法处理这个请求,于是 Nginx 只好放弃,输出了错误页,并且在 Nginx 错误日志中留下了类似这一行信息:

[error] 28789#0: *1 directory index of "/var/www/" is forbidden
所谓 directory index 便是生成“目录索引”的意思,典型的方式就是生成一个网页,上面列举出 /var/www/ 目录下的所有文件和子目录。而运行在

ngx_index 模块之后的 ngx_autoindex 模块就可以用于自动生成这样的“目录索引”网页。我们来把上例修改一下:

location / {
	root /var/www/;
	index index.html;
	autoindex on; 
}

当你的文件系统中存在 /var/www/index.html 时,优先运行的 ngx_index 模块就会发起“内部跳转”,根本轮不到 ngx_autoindex 执行。

执行顺序(七)nginx_static与配置前缀

location / 中没有使用运行在 content 阶段的模块指令,于是也就没有模块注册这个 location 的“内容处理程 序”,处理权便自动落到了在 content 阶段“垫底”的那 3 个静态资源服务模块。

在 content 阶段默认“垫底”的最后一个模块便是极为常用的 ngx_static 模块。这个模块主要实现服务静态文件的功能。

“配置前缀”是由什么来决定的呢?

默认情况下,就是 Nginx 安装时的根目录(或者说 Nginx 构造时传递给 ./configure 脚本的 --prefix 选项的 路径值)。如果 Nginx 安装到了 /usr/local/nginx/ 下,则“配置前缀”便是 /usr/local/nginx/,同时默认的“文档根目录”便是 /usr/local/nginx/html/. 不过,我们也可以在启动 Nginx 的时候,通过 --prefix 命令行选项临时指定自己的“配置前缀”路径。假设我们启动 Nginx 时使用的命令是

nginx -p /home/agentzh/test/
则对于该服务器实例,其“配置前缀”便是 /home/agentzh/test/,而默认的“文档根目录”便是 /home/agentzh/test/html/. “配置前缀”不仅会决定默认的“文档根目录”,还决定着 Nginx 配置文件中许多相对路径值如何解释为绝对路径。

==获取当前“ 文档根目录”的路径==有一个非常简便的方法,那就是请求一个肯定不存在的文件所对应的资源名,然后看日志。

初学者常犯的一个错误是忘记配置 content 阶段的模块指令,而他们自己其实并不期望使用 content 阶段缺省运行的静态资源服务,例如:

location /auth {
	access_by_lua '
		-- abc --
	';
}
显然,这个 /auth 接口只定义了 access 阶段的配置指令,即 access_by_lua,并未定义任何 content 阶段的配置指令。于是当我们请求 /auth 接口 时,在 access 阶段的 Lua 代码会如期执行,然后 content 阶段的那些静态文件服务会紧接着自动发生作用,直至 ngx_static 模块去文件系统上找 名为 auth 的文件。而经常地,404 错误页会抛出,除非运气太好,在对应路径上确实存在一个叫做 auth 的文件。所以,一条经验是,当遇到意外的 404 错误并且又不涉及静态文件服务时,应当首先检查是否在对应的 location 配置块中恰当地配置了 content 阶段的模块指令,例如 content_by_lua、 echo 以及 proxy_pass 之类。

总结:

  • ngx_indexngx_autoindex模块都只会作用于那些URI以/结尾的请求,例如请求GET /cats/,而对于不以/结尾的请求则会直接忽略,而 ngx_static 模块则刚好相反,直接忽略那些 URI 以 / 结尾的请求。

  • 很多初学者会想当然地把 404 错误理解为某个 location 不存在,其实上面这个例子表明,即使 location 存在并成功匹配,也是可能返回 404 错误 页的。因为决定着 404 错误页的是抽象的“资源”是否存在,而非某个具体的 location 是否存在。

  • 当遇到意外的 404 错误并且又不涉及静态文件服务时,应当首先检查是否在对应的 location 配置块中恰当地配置了 content 阶段的模块指令,例如 content_by_lua、 echo 以及 proxy_pass 之类

执行顺序(八)(11个阶段)

Nginx 处理请求的过程一共划分为 11 个阶段,按照执行顺序依次是 post-read、server-rewrite、find- config、rewrite、post-rewrite、preaccess、access、post-access、try-files、content 以及 log.

post-read

在这里插入图片描述

如果在请求上例中的 /test 接口时没有指定 X-My-IP 请求头,或者提供的 X-My-IP 请求头的值不是合法的 IP 地址,那么 Nginx 就不会对来源地址进行改写

$ curl -H 'X-My-IP: abc' localhost:8080/test 
from: 127.0.0.1

如果从另一台机器访问这个 /test 接口,那么即使指定了合法的 X-My-IP 请求头,也不会触发 Nginx 对来源地址进行改写。 这是因为上例已经使用 set_real_ip_from 指令规定了来源地址的改写操作只对那些来自 127.0.0.1 的请求生效。这种过滤机制可以避免来自其他不受信任的地址的恶意欺骗。 当然,也可以通过 set_real_ip_from 指令指定一个 IP 网段(利用 (三) 中介绍过的“CIDR 记法”)。此外,同时配置多个 set_real_ip_from 语句也是允许 的,这样可以指定多个受信任的来源地址或地址段。

下面是一个例子:
set_real_ip_from 127.0.0.0/24;

 

ngx_realip模块实际作用是什么?

答案是:当 Nginx 处理的请求经过了某个 HTTP 代理服务器的转发时,这个模块就变得特别有用。当原始的用户请求经过转发之后,Nginx 接收到的请求的来源地址无一例外地变成了该代理服务器的 IP 地址,于是 Nginx 以及 Nginx 背后的应用就无法知道原始请求的真实来源。所以,一般我们会在 Nginx 之前的代理服务器中把请求的原始来源地址编 码进某个特殊的 HTTP 请求头中(例如上例中的 X-My-IP 请求头),然后再在 Nginx 一侧把这个请求头中编码的地址恢复出来。这样 Nginx 中的后续处理阶段(包括 Nginx 背后的各种后端应用)就会认为这些请求直接来自那些原始的地址,代理服务器就仿佛不存在一样。正是因为这个需求,所以 ngx_realip 模块才需要在第一个处理阶段,即 post-read 阶段,注册处理程序,以便尽可能早地改写请求的来源。

尽量在 server 配置块中配置 ngx_realip 这样的模块,以避免上面介绍的这种棘手的例外情况。

server-rewrite

post-read 阶段之后便是 server-rewrite 阶段。**当 ngx_rewrite 模块的配置指令直接书写在 server 配置块中时,基本上都是运行在 server-rewrite 阶段。**下面就来看这样的一个例子:

server {
	listen 8080;
	location /test {
		set $b "$a, world"; 
		echo $b;
	}
	set $a hello; 
}

这里,配置语句set $a hello直接写在了server配置块中,因此它就运行在server-rewrite阶段。而server-rewrite阶段要早于rewrite 阶段运行,因此写在location配置块中的语句set b " b " b"a, world"便晚于外面的set $a hello语句运行。该例的测试结果证明了这一点:

`$ curl localhost:8080/test`
hello,world

由于 server-rewrite 阶段位于 post-read 阶段之后,所以 server 配置块中的 set 指令也就总是运行在 ngx_realip 模块改写请求的来源地址之后。 来看下面这个例子:

server {
  listen 8080;
  set $addr $remote_addr;
  set_real_ip_from 127.0.0.1; 
  real_ip_header X-Real-IP;
  location /test {
  	echo "from: $addr";
  } 
}
请求 /test 接口的结果如下:
$ curl -H 'X-Real-IP: 1.2.3.4' localhost:8080/test
from: 1.2.3.4
在这个例子中,虽然 set 指令写在了 ngx_realip 的配置指令之前,但仍然晚于 ngx_realip 模块执行。所以 $addr 变量在 server-rewrite 阶段被 set 指令赋值时,从 $remote_addr 变量读出的来源地址已经是经过改写过的了。
find-config阶段

紧接在 server-rewrite 阶段后边的是 find-config 阶段。这个阶段并不支持 Nginx 模块注册处理程序,而是由 Nginx 核心来完成当前请求与 location 配置块之间的配对工作。

换句话说,在此阶段之前,请求并没有与任何 location 配置块相关联。因此,对于运行在 find-config 阶段之 前的 post-read 和 server-rewrite 阶段来说,只有 server 配置块以及更外层作用域中的配置指令才会起作用。这就是为什么只有写在 server 配置块中的 ngx_rewrite 模块的指令才会运行在 server-rewrite 阶段,这也是为什么前面所有例子中的 ngx_realip 模块的指令也都特意写在了 server 配 置块中,以确保其注册在 post-read 阶段的处理程序能够生效。

💗rewrite阶段

rewrite阶段指令:

1.set

2.set_by_luarewrite_by_lua

3.rewrite

4.rewrite_by_lua

5.more_set_input_headers

1、2 (同理 rewrite和rewrite_by_lua)

set_by_lua 指令支持通过一小段用户 Lua 代码来计算出一个结果,然后赋给指定的 Nginx 变量。和 set 指令相似, set_by_lua 指令也有自动创建不存在的 Nginx 变量的功能。

location /test {
        set $a 32;
        set $b 56;
        set_by_lua $c "return ngx.var.a + ngx.var.b";
        set $equation "$a + $b = $c";
        echo $equation;
}

在这里插入图片描述

location /test {
        set $a 1;
        rewrite_by_lua "ngx.var.a = ngx.var.a + 1";
        set $a 56;
        echo $a;
}

$ curl 'http://127.0.0.1/test'
57

从 rewrite 阶段开始,location 配置块中的指令便可以产生作用。前面已经介绍过,当 ngx_rewrite 模块的指令用于 location 块中时,便是运行在 这个 rewrite 阶段。另外, ngx_set_misc 模块的指令也是如此,还有 ngx_lua 模块的 set_by_lua 指令和 rewrite_by_lua 指令也不例外。

rewrite 阶段再往后便是所谓的 post-rewrite 阶段。这个阶段也像 find-config 阶段那样不接受 Nginx 模块注册处理程序,而是由 Nginx 核心完成 rewrite 阶段所要求的“内部跳转”操作(如果 rewrite 阶段有此要求的话)。

server {
  listen 8080;
  location /foo { 
    set $a hello;
    rewrite ^ /bar; 
  }
  location /bar {
  	echo "a = [$a]";
  } 
}

这里在location /foo中通过rewrite指令把当前请求的URI无条件地改写为/bar,同时发起一个“内部跳转”,最终跳进了location /bar中。这 里比较有趣的地方是“内部跳转”的工作原理。==“内部跳转”本质上其实就是把当前的请求处理阶段强行倒退到 find-config 阶段,以便重新进行请求 URI 与 location 配置块的配对。==比如上例中,运行在 rewrite 阶段的 rewrite 指令就让当前请求的处理阶段倒退回了 find-config 阶段。由于此时 当前请求的URI已经被rewrite指令修改为了/bar,所以这一次换成了location /bar与当前请求相关联,然后再接着从rewrite阶段往下执行。

不过这里更有趣的地方是,**倒退回 find-config 阶段的动作并不是发生在 rewrite 阶段,而是发生在后面的 post-rewrite 阶段。**上例中的 rewrite 指令只是简单地指示 Nginx 有必要在 post-rewrite 阶段发起“内部跳转”。这个设计对于 Nginx 初学者来说,或许显得有些古怪:“为什么不直接在 rewrite 指令执行时立即进行跳转呢?”答案其实很简单,那就是为了在最初匹配的 location 块中支持多次反复地改写 URI

当然,如果在 server 配置块中直接使用 rewrite 配置指令对请求 URI 进行改写,则不会涉及“内部跳转”,因为此时 URI 改写发生在 server-rewrite阶段,早于执行 location 配对的 find-config 阶段。

preaccess(post-rewrite后,access前)

运行在 post-rewrite 阶段之后的是所谓的 preaccess 阶段。该阶段在 access 阶段之前执行,故名 preaccess.
标准模块 ngx_limit_req 和 ngx_limit_zone 就运行在此阶段,前者可以控制请求的访问频度,而后者可以限制访问的并发度。

server {
listen 8080;
location /test { 
set_real_ip_from 127.0.0.1; 
real_ip_header X-Real-IP;
set $addr $remote_addr;
echo "from: $addr"; }
}

$ curl -H 'X-Real-IP: 1.2.3.4' localhost:8080/test
from: 127.0.0.1

这里,我们在 rewrite 阶段将 $remote_addr 的值保存到了用户变量 $addr 中,然后再输出。因为 rewrite 阶段先于 preaccess 阶段执行,所以当ngx_realip 模块尚未在 preaccess 阶段改写来源地址时,最初的来源地址就已经在 rewrite 阶段被读取了。

变量赋值操作 在改写新地址之前。
在这里插入图片描述

access阶段

pre(access)阶段指令:real_ip、allow、deny

access 阶段。前面我们已经知道了,标准模块 ngx_access、第三方模块 ngx_auth_request 以及第三方模块 ngx_lua 的 access_by_lua 指令就运行在这个阶段。

access 阶段之后便是 post-access 阶段。从这个阶段的名字,我们也能一眼看出它是紧跟在 access 阶段后面执行的。这个阶段也和 post- rewrite 阶段类似,并不支持 Nginx 模块注册处理程序,而是由 Nginx 核心自己完成一些处理工作。post-access 阶段主要用于配合 access 阶段实现标准 ngx_http_core 模块提供的配置指令 satisfy 的功能。

对于多个 Nginx 模块注册在 access 阶段的处理程序, satisfy 配置指令可以用于控制它们彼此之间的协作方式。比如模块 A 和 B 都在 access 阶段注册 了与访问控制相关的处理程序,那就有两种协作方式,一是模块 A 和模块 B 都得通过验证才算通过,二是模块 A 和模块 B 只要其中任一个通过验证就 算通过。第一种协作方式称为 all 方式(或者说“与关系”),第二种方式则被称为 any 方式(或者说“或关系”)。

默认情况下,Nginx 使用的是 all 方 式。下面是一个例子:

location /test { 
	satisfy all;
  deny all;  验证1
  access_by_lua 'ngx.exit(ngx.OK)';  验证2
  echo something important; 
}

这里,我们在/test接口中同时配置了ngx_access模块和ngx_lua模块,这样access阶段就由这两个模块一起来做检验工作。其中,语句deny all 会让 ngx_access 模块的处理程序总是拒绝当前请求,而语句 access_by_lua 'ngx.exit(ngx.OK)' 则总是允许访问。当我们通过 satisfy 指令配置了 all 方式时,就需要 access 阶段的所有模块都通过验证,但不幸的是,这里 ngx_access 模块总是会拒绝访问,所以整个请求就会被拒

然而,如果我们把上例中的satisfy all语句更改为satisfy any,即请求反而最终通过了验证。这是因为在 any 方式下,access 阶段只要有一个模块通过了验证,就会认为请求整体通过了验证,而在上例中, ngx_lua 模块的 access_by_lua 语句总是会通过验证的。

try-files阶段

在这里插入图片描述
紧跟在 post-access 阶段之后的是 try-files 阶段。这个阶段专门用于实现标准配置指令 try_files 的功能,并不支持 Nginx 模块注册处理程序。

try_files 指令在许多 FastCGI 应用的配置中都有用到。

try_files 指令接受两个以上任意数量的参数,每个参数都指定了一个 URI. 这里假设配置了 N 个参数,则 Nginx 会在 try-files 阶段,依次把前 N-1 个 参数映射为文件系统上的对象(文件或者目录),然后检查这些对象是否存在。一旦 Nginx 发现某个文件系统对象存在,就会在 try-files 阶段把当前 请求的 URI 改写为该对象所对应的参数 URI(但不会包含末尾的斜杠字符,也不会发生 “内部跳转”)。**如果前 N-1 个参数所对应的文件系统对象都不存在,try-files 阶段就会立即发起“内部跳转”到最后一个参数(即第 N 个参数)所指定的 URI。**当 try_files 指令处理到它的最后一个参数时,总是直接执行“内部跳转”,而不论其对应的文件系统对象是否存在。

try_files 指令本质上只是有条件地改写当前请求的 URI,而这里说的“条件”其实就是**文件系统上的对象是否存在。**当“条 件”都不满足时,它就会无条件地发起一个指定的“内部跳转”。当然,除了无条件地发起“内部跳转”之外, try_files 指令还支持直接返回指定状态码的 HTTP 错误页,例如:

try_files /foo /bar/ =404;

这行配置是说,当/foo和/bar/参数所对应的文件系统对象都不存在时,就直接返回404 Not Found错误页。注意这里它是如何使用等号字符前缀 来标识 HTTP 状态码的。

总结:

  • Nginx 处理请求的过程一共划分为 11 个阶段,按照执行顺序依次是 post-read、server-rewrite、find- config、rewrite、post-rewrite、preaccess、access、post-access、try-files、content 以及 log.

  • set_real_ip_from的重要性。如果从另一台机器访问这个 /test 接口,那么即使指定了合法的 X-My-IP 请求头,也不会触发 Nginx 对来源地址进行改写。

  • ==“内部跳转”本质上其实就是把当前的请求处理阶段强行倒退到 find-config 阶段,以便重新进行请求 URI 与 location 配置块的配对。==倒退回 find-config 阶段的动作并不是发生在 rewrite 阶段,而是发生在后面的 post-rewrite 阶段。

  • 为什么不直接在 rewrite 指令执行时立即进行跳转呢?”答案其实很简单,那就是为了在最初匹配的 location 块中支持多次反复地改写 URI

  • 如果在 server 配置块中直接使用 rewrite 配置指令对请求 URI 进行改写,则不会涉及“内部跳转”,因为此时 URI 改写发生在 server-rewrite阶段,早于执行 location 配对的 find-config 阶段。

  • 尽量在 server 配置块中配置 ngx_realip 这样的模块,这个模块的注册处理程序有点复杂。

  • location uri正则表达式

    . :匹配除换行符以外的任意字符
    ? :重复0次或1+ :重复1次或更多次
    * :重复0次或更多次
    \d :匹配数字
    ^ :匹配字符串的开始
    $ :匹配字符串的结束
    {n} :重复n次
    {n,} :重复n次或更多次
    [c] :匹配单个字符c
    [a-z] :匹配a-z小写字母的任意一个
    (a|b|c) : 属线表示匹配任意一种情况,每种情况使用竖线分隔,一般使用小括号括括住,匹配符合a字符 或是b字符 或是c字符的字符串
    \ 反斜杠:用于转义特殊字符
    

应用场景

一:HTTP服务器

Nginx本身也是一个静态资源的服务器,当只有静态资源的时候,就可以使用Nginx来做服务器,如果一个网站只是静态页面的话,那么就可以通过这种方式来实现部署。

1、首先在文档根目录 Docroot(/usr/local/var/www)下创建html目录, 然后在html中放一个test.html;
即/usr/local/var/www/html/test.html

2、配置nginx.conf中的server

user mengday staff;

http {
    server {
        listen       80;
        server_name  localhost;
        client_max_body_size 1024M;

        # 默认location
        location / {
            root   /usr/local/var/www/html;
            index  index.html index.htm;
        }
    }
}

3、访问测试

  • http://localhost/ 指向/usr/local/var/www/index.html, index.html是安装nginx自带的html
  • http://localhost/test.html 指向/usr/local/var/www/html/test.html
    注意:如果访问图片出现403 Forbidden错误,可能是因为nginx.conf 的第一行user配置不对

4、指令简介

  • server : 用于定义服务,http中可以有多个server块
  • listen : 指定服务器侦听请求的IP地址和端口,如果省略地址,服务器将侦听所有地址,如果省略端口,则使用标准端口
  • server_name : 服务名称,用于配置域名
  • location : 用于配置映射路径uri对应的配置,一个server中可以有多个location, location后面跟一个uri,可以是一个正则表达式, / 表示匹配任意路径, 当客户端访问的路径满足这个uri时就会执行location块里面的代码
  • root : 根路径,当访问http://localhost/test.html,“/test.html”会匹配到”/”uri, 找到root为/usr/local/var/www/html,用户访问的资源物理地址=root + uri = /usr/local/var/www/html + /test.html=/usr/local/var/www/html/test.html
  • index : 设置首页,当只访问server_name时后面不跟任何路径是不走root直接走index指令的;如果访问路径中没有指定具体的文件,则返回index设置的资源,如果访问http://localhost/html/ 则默认返回index.html

5、location uri正则表达式

. :匹配除换行符以外的任意字符
? :重复0次或1+ :重复1次或更多次
* :重复0次或更多次
\d :匹配数字
^ :匹配字符串的开始
$ :匹配字符串的结束
{n} :重复n次
{n,} :重复n次或更多次
[c] :匹配单个字符c
[a-z] :匹配a-z小写字母的任意一个
(a|b|c) : 属线表示匹配任意一种情况,每种情况使用竖线分隔,一般使用小括号括括住,匹配符合a字符 或是b字符 或是c字符的字符串
\ 反斜杠:用于转义特殊字符

二、静态服务器

通常会提供一个上传的功能,其他应用如果需要静态资源就从该静态服务器中获取。

1、在/usr/local/var/www 下分别创建images和img目录,分别在每个目录下放一张test.jpg

http {
    server {
        listen       80;
        server_name  localhost;

        set $doc_root /usr/local/var/www;

        # 默认location
        location / {
            root   /usr/local/var/www/html;
            index  index.html index.htm;
        }

        location ^~ /images/ {
            root $doc_root;
       }

       location ~* \.(gif|jpg|jpeg|png|bmp|ico|swf|css|js)$ {
           root $doc_root/img;
       }
    }
}

自定义变量使用set指令,语法 set 变量名值;引用使用变量名值;引用使用变量名; 这里自定义了doc_root变量。

结尾:四层与七层

为什么四层比七层效率高?

四层是TCP层,使用IP+端口四元组的方式。只是修改下IP地址,然后转发给后端服务器,TCP三次握手是直接和后端连接的。只不过在后端机器上看到的都是与代理机的IP的established而已,LVS中没有握手。


7层代理则必须要先和代理机三次握手后,才能得到7层(HTT层)的具体内容,然后再转发。意思就是代理机必须要与client和后端的机器都要建立连接。显然性能不行,但胜在于七层,人工可操作性高,能写更多的转发规则。

  • 5
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值