nginx配置详解

events事件驱动配置

events {
    use epoll;                          #使用epoll类型IO多路复用模型 默认为select
   	worker_connections 204800;      	#最大连接数限制为20万
	accept_mutex on;                  	#各个Worker通过锁来获取新连接 默认为 on
}

accept_mutex指令用于配置各个Worker进程是否通过互斥锁有序接收新的连接请求。on参数表示各个Worker通过互斥锁有序接收新请求;off参数指每个新请求到达时会通知(唤醒)所有的Worker进程参与争抢,但只有一个进程可获得连接。配置off参数会造成“惊群”问题影响性能。

虚拟主机配置

虚拟主机的监听套接字配置
  1. 使用listen指令直接配置监听端口

    server {
        listen 80;
        ...
    }
    
  2. 使用listen指令配置监听的IP和端口

    server {
        listen 127.0.0.1:80;
        ...
    }
    
虚拟主机名称配置
#后台管理服务虚拟主机demo
 server {
    listen       80;
    server_name  admin.zhaojun.com;  #后台管理服务的域名前缀为admin
    location / {
      default_type 'text/html';
      charset utf-8;
    }
  }

 #文件服务虚拟主机demo
  server {
    listen  80;
    server_name  file.zhaojun.com;  #文件服务的域名前缀为admin
    location / {
      default_type 'text/html';
      charset utf-8;
    }
  }
  
 #默认服务虚拟主机demo
  server {
    listen  80  default;
    server_name  zhaojun.com  *.zhaojun.com;  #如果没有前缀,这就是默认访问的虚拟主机
    location / {
      default_type 'text/html';
      charset utf-8;
    }
    ...
  }

多个虚拟主机之间,匹配优先级从高到低大致如下:

  1. 字符串精确匹配:如果请求的域名为admin.zhaojun.com,那么首先会匹配到名称为admin.zhaojun.com的虚拟管理主机
  2. 左侧*通配符匹配:若浏览器请求的域名为xxx.zhaojun.com,则会匹配到*.zhaojun.com虚拟主机。为什么呢?因为配置文件中并没有server_name为xxx.zhaojun.com的主机,所以退而求其次,名称为*.zhaojun.com的虚拟主机按照通配符规则匹配成功。
  3. 右侧*通配符匹配:右侧*通配符和左侧*通配符匹配类似,只不过优先级低于左侧*通配符匹配。
  4. 正则表达式匹配:与通配符匹配类似,不过优先级更低。
  5. default_server:在listen指令后面如果带有default的指令参数,就代表这是默认的、最后兜底的虚拟主机,如果前面的匹配规则都没有命中,就只能命中default_server指定的默认主机。
错误页面配置
error_page code ... [=[response]] uri;

code表示响应码,可以同时配置多个;uri表示错误页面,一般为服务器上的静态资源页面。

例子:

  #后台管理服务器demo
  server {
    listen       80;
    server_name  admin.zhaojun.com;
    root /var/www/; 
      
    location / {
      default_type 'text/html';
      charset utf-8;
    }
    
    #设置错误页面
    error_page  404  /404.html;

    #设置错误页面
    error_page  500 502 503 504  /50x.html;
  }

error_page指令除了可用于server上下文外,还可用于http、server、location、if in location等上下文。

长连接相关配置
keepalive_timeout timeout [header_timeout];

配置项中的timeout参数用于设置保持连接超时时长,0表示禁止长连接,默认为75秒。

如果要配置长连接的一条连接允许的最大请求数,那么可以使用keepalive_requests指令,格式如下:

keepalive_requests  number;

配置项中的number参数用于设置在一条长连接上允许被请求的资源的最大数量,默认为100。

如果要配置向客户端发送响应报文的超时限制,那么可以使用下面的指令:

send_timeout time;

配置项中的time参数用于设置Nginx向客户端发送响应报文的超时限制,此处时长是指两次向客户端写操作之间的间隔时长,并非整个响应过程的传输时长。

访问日志配置

访问记录配置指令的完整格式如下:

access_log  path  [format  [buffer=size]  [gzip[=level]]  [flush=time]  [if=condition]];

其中,path表示日志文件的本地路径;format表示日志输出的格式名称。定义日志输出格式的配置指令为log_format,它的完整格式如下:

log_format  name  string  ...;

其中,name参数用于指定格式名称;string参数用于设置格式字符串,可以有多个。字符串中可以使用Nginx核心模块及其他模块的内置变量。

下面是一个比较完整的例子:

http {

  #先定义日志格式,format_main是日志格式的名字
  log_format  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_main.log  format_main;
    ...
}

修改配置后,需要重启Nginx。

接下来,对以上实例中所有用到的Nginx内置变量进行简单说明,具体如下:

  1. request:记录用户的HTTP请求的起始行信息。
  2. $status:记录HTTP状态码,即请求返回的状态,例如200、404、502等。
  3. $remote_addr:记录访问网站的客户端地址。
  4. $remote_user:记录远程客户端用户名称。
  5. $time_local:记录访问时间与时区。
  6. $body_bytes_sent:记录服务器发送给客户端的响应body字节数。
  7. $http_referer:记录此次请求是从哪个链接访问过来的,可以根据其进行盗链的监测。
  8. $http_user_agent:记录客户端访问信息,如浏览器、手机客户端等。
  9. $http_x_forwarded_for:当前端有正向代理服务器时,此参数用于保持客户端真实的IP地址。该参数生效的前提:前端的代理服务器上进行了相关的x_forwarded_for设置。

Nginx核心模块内置变量

  1. $arg_PARAMETER:请求URL中以PARAMETER为名称的参数值。请求参数即URL的“?”号后面的name=value形式的参数对,变量$arg_name得到的值为value。

    另外,$arg_PARAMETER中的参数名称不区分字母大小写,例如通过变量$arg_name不仅可以匹配name参数,也可以匹配NAME、Name请求参数,Nginx会在匹配参数名之前自动把原始请求中的参数名调整为全部小写的形式。

  2. $args:请求URL中的整个参数串,其作用与$query_string相同。

  3. $binary_remote_addr:二进制形式的客户端地址。

  4. $body_bytes_sent:传输给客户端的字节数,响应头不计算在内。

  5. $bytes_sent:传输给客户端的字节数,包括响应头和响应体。

  6. $content_length:等同于$http_content_length,用于获取请求体body的大小,指的是Nginx从客户端收到的请求头中Content-Length字段的值,不是发送给客户端响应中的Content-Length字段值,如果需要获取响应中的Content-Length字段值,就使用$sent_http_content_length变量。

  7. $request_length:请求的字节数(包括请求行、请求头和请求体)。注意,由于$request_length是请求解析过程中不断累加的,如果解析请求时出现异常,那么$request_length是已经累加部分的长度,并不是Nginx从客户端收到的完整请求的总字节数(包括请求行、请求头、请求体)。

  8. $connection:TCP连接的序列号。

  9. $connection_requests:TCP连接当前的请求数量。

  10. $content_type:请求中的Content-Type请求头字段值。

  11. $cookie_name:请求中名称name的cookie值。

  12. $document_root:当前请求的文档根目录或别名。

  13. $uri:当前请求中的URI(不带请求参数,参数位于$args变量)。$uri变量值不包含主机名,如“/foo/bar.html”。此参数可以修改,可以通过内部重定向。

  14. $request_uri:包含客户端请求参数的原始URI,不包含主机名,此参数不可以修改,例如“/foo/bar.html?name=value”。

  15. $host:请求的主机名。优先级为:HTTP请求行的主机名>HOST请求头字段>符合请求的服务器名。

  16. $http_name:名称为name的请求头的值。如果实际请求头name中包含中画线“-”,那么需要将中画线“-”替换为下画线“_”;如果实际请求头name中包含大写字母,那么可以替换为小写字母。例如获取Accept-Language请求头的值,变量名称为$http_accept_language。

  17. $msec:当前的UNIX时间戳。UNIX时间戳是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒。

  18. $nginx_version:获取Nginx版本。

  19. $pid:获取Worker工作进程的PID。

  20. $proxy_protocol_addr:代理访问服务器的客户端地址,如果是直接访问,那么该值为空字符串。

  21. $realpath_root:当前请求的文档根目录或别名的真实路径,会将所有符号连接转换为真实路径。

  22. $remote_addr:客户端请求地址。

  23. $remote_port:客户端请求端口。

  24. $request_body:客户端请求主体。此变量可在location中使用,将请求主体通过proxy_pass、fastcgi_pass、uwsgi_pass和scgi_pass传递给下一级的代理服务器。

  25. $request_completion:如果请求成功,那么值为OK;如果请求未完成或者请求不是一个范围请求的最后一部分,那么值为空。

  26. $request_filename:当前请求的文件路径,由root或alias指令与URI请求结合生成。

  27. $request_length:请求的长度,包括请求的地址、HTTP请求头和请求主体。

  28. $request_method:HTTP请求方法,比如GET或POST等。

  29. $request_time:处理客户端请求使用的时间,从读取客户端的第一个字节开始计时。

  30. $scheme:请求使用的Web协议,如HTTP或HTTPS。

  31. $sent_http_name:设置任意名称为name的HTTP响应头字段。例如,如果需要设置响应头Content-Length,那么将“-”替换为下画线,大写字母替换为小写字母,变量为$sent_http_content_length。

  32. $server_addr:服务器端地址为了避免访问操作系统内核,应将IP地址提前设置在配置文件中。

  33. $server_name:虚拟主机的服务器名,如zhaojun.ink。

  34. $server_port:虚拟主机的服务器端口。

  35. $server_protocol:服务器的HTTP版本,通常为HTTP/1.0或HTTP/1.1。

  36. $status:HTTP响应代码。

location路由规则配置详解

location路由匹配发生在HTTP请求处理的find-config配置查找阶段,主要功能是:根据请求的URI地址匹配location路由表达式,如果匹配成功,就执行location后面的上下文配置块。

location语法详解
location  [=|~|~*|^~]  模式字符串  {
 ... 
}

按照匹配的符号不同,location路由匹配主要分成精准匹配、普通匹配、正则匹配、默认根路径匹配。下面逐一进行介绍。

  1. 精准匹配

        #精准匹配
    location = /lua {
        echo  "hit location: =/Lua"; #echo 需要安装echo模块
    }
    

    如果请求URI和精准匹配的模式字符串/lua完全相同,那么精准匹配通过。在所有的匹配类型中,精准匹配的优先级最高。

  2. 普通匹配

    location ^~  /lua {
        echo  "hit location: ^~ /lua";
    }
    

    普通匹配属于字符串前缀匹配,详细来说:如果请求路径URI头部匹配到location的模式字符串,那么匹配成功。如果匹配到多个前缀,那么最长模式匹配优先。

    例子:

    #普通匹配一
    location ^~  /lua {
          echo  "普通匹配:  ^~  /lua";
    }
    
    #普通匹配二,长一点
    location  ^~  /lua/long  {
          echo  "普通匹配:  ^~  /lua/long";
    }
    

    在浏览器中给Nginx发送http://localhost/lua/long/path的请求地址,输出了普通匹配location的结果为:普通匹配: ^~ /lua/long

    普通匹配是前缀匹配,也是Nginx默认的匹配类型。也就是说,类型符号“^~”可以省略,如果location没有任何匹配类型,就为普通的前缀匹配。如果一个URI命中多个location普通匹配,则最长的location普通匹配获胜。

  3. 正则匹配

    正则匹配的类型按照类型符号的不同可以细分为以下4种:

    1. ~:标准正则匹配,区分字母大小写,进行正则表达式测试,若测试成功,则匹配成功。
    2. ~*:标准正则匹配,不区分字母大小写,进行正则表达式测试,若测试成功,则匹配成功。
    3. !~:反向正则匹配,区分字母大小写,进行正则表达式测试,若测试不成功,则匹配成功。
    4. !~*:反向正则匹配,不区分字母大小写,进行正则表达式测试,若测试不成功,则匹配成功。

    例子:

    #正则匹配
    location  ~*hello\.(asp|php)$  {
        echo   "正则匹配: hello.(asp|php) ";
    }
    

    如果配置文件中存在多个正则匹配location,那么它们之间的规则是顺序优先的,只要匹配到第一个正则类型的location,就停止后面的正则类型的location测试。

  4. 默认根路径匹配

    根路径的路径规则就是使用单个“/”符号,示例如下:

    location  /  {
        echo "默认根路径匹配: /";
    }
    

    表面看上去,location/{…}根路径匹配非常类似普通匹配,但实际上该规则自成一类,虽然只有唯一的一个路径,但是此类规则优先级是最低的。

    最后总结一下4种location之间的匹配次序,大致如下:

    1. 类型之间的优先级:精准匹配>普通匹配>正则匹配>“/”默认根路径匹配。
    2. 普通匹配同类型location之间的优先级为最长前缀优先。普通匹配的优先级与location在配置文件中所处的先后顺序无关,而与匹配到的前缀长度有关。
    3. 正则匹配同类型location之间的优先级为顺序优先。只要匹配到第一个正则规则的location,就停止后面的正则规则的测试。正则匹配与location规则定义在配置文件中的先后顺序强相关。
常用的location路由配置
根路由规则

第一个应该配置的属于“/”根路由规则。“/”根路由规则可以路由到一个静态首页:

location  / {
      root   html;
      index  index.html index.htm;
}

表示在请求URI匹配到“/”根路由规则时,首先Nginx会在html目录下查找index.html文件,如果没有找到,就查找index.htm文件,将找到的文件内容返回给客户端。

“/”根路由规则也可以路由到一个访问很频繁的上游服务,比如Spring Cloud微服务架构中的服务网关:

location  / {
      proxy_pass http://127.0.0.1:7799/ ;
}

这里的127.0.0.1:7799假定为Zuul网关的IP和端口,当请求匹配到“/”根路由规则时,将直接转发给上游Zuul应用网关服务器。

静态文件路由规则

第二个应该配置的属于静态文件路由规则。对静态文件请求进行响应,这是Nginx作为HTTP服务器的强项。静态文件匹配规则有两种配置方式:目录匹配(前缀匹配)和后缀匹配(正则匹配),可以任选其一,也可以搭配使用。

例子:

root  /www/resources/static/;
 #前缀匹配
location ^~  /static/ {
        root  /www/resources/;
}

所有匹配/static/…规则的静态资源请求(如/static/img/1.png)都将路由到root指令所配置的文件目录/www/resources/static/下对应的某个文件(如/www/resources/static/img/1.png)。

后缀匹配(正则匹配)配置实例如下:

location ~*\.(gif|jpg|jpeg|png|css|js|ico)${
       root /www/resources/;
}   

所有匹配到以上正则规则的静态资源请求(如/static/img/2.png)都将路由到root指令所配置的文件目录/www/resources/static/下对应的某个文件(如/www/resources/static/img/2.png)。

Nginx的rewrite模块指令

Nginx的rewrite模块即ngx_http_rewrite_module标准模块,主要功能是重写请求URI,也是Nginx默认安装的模块。rewrite模块会根据PCRE正则匹配重写URI,然后根据指令参数或者发起内部跳转再一次进行location匹配,或者直接进行30x重定向返回客户端。

rewrite模块的指令就是一门微型的编程语言,包含set、rewrite、break、if、return等一系列指令。

set指令

set指令是由ngx_http_rewrite_module标准模块提供的,用于向变量存放值。在Nginx配置文件中,变量只能存放一种类型的值,因为只存在一种类型的值,那就是字符串。

set指令的配置项格式如下:

set $variable  value;

在Nginx配置文件中,变量定义和使用都要以$开头。Nginx变量名前面有一个$符号,这是记法上的要求。所有的Nginx变量在引用时必须带上$前缀。另外,Nginx变量不能与Nginx服务器预设的全局变量同名。

例子:

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

set配置指令对变量$a进行了赋值操作,把字符串hello world赋给了它。也可以直接把变量嵌入字符串常量中以构造出新的字符串。

set指令不仅有赋值的功能,还有创建Nginx变量的副作用,即当作为赋值对象的变量尚不存在时,它会自动创建该变量。比如在上面这个例子中,若$a这个变量尚未创建,则set指令会自动创建$a这个用户变量。

Nginx变量一旦创建,其变量名的可见范围就是整个Nginx配置,甚至可以跨越不同虚拟主机的server配置块。但是,对于每个请求,所有变量都有一份独立的副本,或者说都有各变量用来存放值的容器的独立副本,彼此互不干扰。Nginx变量的生命期是不可能跨越请求边界的。

rewrite指令

rewrite指令是由ngx_http_rewrite_module标准模块提供的,主要功能是改写请求URI。rewrite指令的格式如下:

rewrite  regrex  replacement  [flag];

如果regrex匹配URI,URI就会被替换成replacement的计算结果,其计算之后的字符串就是新的URI。

例子:

location  /download/  {
  rewrite  ^/download/(.*)/video/(.*)$   /view/$1/mp3/$2.mp3  last;
  rewrite  ^/download/(.*)/audio/(.*)*$  /view/$1/mp3/$2.rmvb  last;
  return  404;
}

location /view {
  echo "uri: $uri ";
}

在这个演示例子中,replacement中的占位变量$1、$2的值是指令参数regrex正则表达式从原始URI中匹配出来的子字符串,也叫正则捕获组,编号从1开始。

rewrite指令可以使用的上下文为:server、location、if in location。

如果rewrite同一个上下文中有多个这样的rewrite重新指令,匹配就会依照rewrite指令出现的顺序先后依次进行下去,匹配成功之后并不会终止,而是继续往下匹配,直到返回最后一个匹配的为止。如果想要中途中止,不再继续往下匹配,可以使用第3个指令参数flag。flag参数的值有last、break、redirect、permanent。

last

如果flag参数使用last值,并且匹配成功,那么停止处理任何rewrite相关的指令,立即用计算后的新URI开始下一轮的location匹配和跳转。前面的例子使用的就是last参数值。

在location上下文中的rewrite指令使用last指令参数会再次以新的URI重新发起内部重定向,再次进行location匹配,而新的URI极有可能和旧的URI一样再次匹配到相同的目标location中,这样死循环就发生了。当循环到第10次时,Nginx会终止这样无意义的循环并返回500错误。这一点需要特别注意。

break

如果flag参数使用break值,就如同break指令的字面意思一样,停止处理任何rewrite的相关指令,但是不进行location跳转。使用新的规则,直接发起一次http请求。

permanent

如果rewrite指令使用的flag参数的值是permanent,就表示进行外部重定向,也就是在客户端进行重定向。此时,服务器将新URI地址返回给客户端浏览器,并且返回301(永久重定向的响应码)给客户端。客户端将使用新的重定向地址再发起一次远程请求。

外部重定向与内部重定向是有本质区别的。从数量上说,外部重定向有两次请求,内部重定向只有一次请求。

redirect

如果rewrite指令使用的flag参数的值是redirect,就表示进行外部重定向,表现的行为与permanent参数值完全一样,不同的是返回302(临时重定向的响应码)给客户端。

if条件指令

if条件指令配置项的格式如下:

if (condition) {...}

当if条件满足时,执行配置块中的配置指令。if的配置块相当于引入了一个新的上下文作用域。if条件指令适用于server和location两个上下文。

condition条件表达式可以用到一系列比较操作符,大致如下:

  1. ==:相等。
  2. ~:区分字母大小写模式匹配。
  3. ~*:不区分字母大小写模式匹配。
  4. -f:判断文件是否存在。
  5. -d:判断目录是否存在。
  6. -e:判断请求的目录或者文件是否存在使用
  7. -x:判断请求的文件是否可执行使用

以上情况都可以在前面加感叹号进行取反操作如:!=、!-f

例子:

#if指令的演示程序
location /if_demo {
  if ($http_user_agent ~* "Firefox") {             #匹配Firefox浏览器
    return 403;
  }
  if ($http_user_agent ~* "Chrome") {              #匹配Chrome谷歌浏览器
    return 301;
  }
  if ($http_user_agent ~* "iphone") {              #匹配iPhone手机
    return 302;
  }
  if ($http_user_agent ~* "android") {             #匹配安卓手机
    return 404;
  }
    return 405;                #其他浏览器默认访问规则
}

注意格式:

  1. if与(之间必须要有空格,不然会报错:nginx: [emerg] unknown directive “if($http_user_agent” in ./conf/nginx.conf:86
  2. 比较符和比较值之间也要有空格,不然会报错:nginx: [emerg] invalid condition “$http_user_agent” in ./conf/nginx.conf:86
return指令

上面的演示代码中使用到了return指令,用于返回HTTP的状态码。return指令会停止同一个作用域的剩余指令处理,并返回给客户端指定的响应码。

return指令可以用于server、location、if上下文中,执行阶段是rewrite阶段。其指令的格式如下:

#格式一:返回响应的状态码和提示文字,提示文字可选
return  code  [text];

#格式二:返回响应的重定向状态码(如301)和重定向URL
return  code  URL;

#格式三:返回响应的重定向URL,默认的返回状态码是临时重定向302 
return  URL;
add_header指令

response header一般是以key:value的形式,例如Content-Encoding:gzip、Cache-Control:no-store,设置的命令如下:

add_header Cache-Control no-store
add_header Content-Encoding gzip

但是,有一个十分常用的response header为Content-Type,可以在它设置了类型的同时指定charset,例如text/html;charset=utf-8,由于其存在分号,而分号在配置文件中作为结束符,因此在配置时需要用引号把其引起来,配置如下:

add_header  Content-Type 'text/html; charset=utf-8';

另外,由于没有单独设置charset的key,因此要设置响应的charset就需要使用Content-Type来指定charset。

跨域设置

使用AJAX进行跨域请求时,浏览器会向跨域资源的服务端发送一个OPTIONS请求,用于判断实际请求是否安全或者判断服务端是否允许跨域访问,这种请求也叫作预检请求。跨域访问的预检请求是浏览器自动发出的,用户程序往往不知情,如果不进行特别的配置,那么客户端发出一次请求,在服务端往往会收到两个请求;一个是预检请求;另一个是正式的请求。后端的服务器(PHP或者Tomcat)如果不经过特殊的过滤,那么很容易将OPTIONS预检请求当成正式的数据请求。

对于客户端而言,只有预检请求返回成功,客户端才开始正式请求。在实际的使用场景中,预检请求比较影响性能,用户往往会有两倍请求的感觉,所以一般会在Nginx代理服务端对预检请求进行提前拦截,同时对预检请求设置比较长时间的有效期。

  upstream zuul {
    #server 192.168.233.1:7799;
    server "192.168.233.128:7799";
    keepalive 1000;
  }

  server {
    listen 80;
    server_name  nginx.server  *.nginx.server;
    default_type 'text/html';
    charset utf-8;

    #转发到上游服务器,但是 'OPTIONS' 请求直接返回空
    location  / {
      if ($request_method = 'OPTIONS') {
        add_header  Access-Control-Max-Age  1728000;
        add_header  Access-Control-Allow-Origin  *;
        add_header  Access-Control-Allow-Credentials   true;
        add_header  Access-Control-Allow-Methods   'GET, POST, OPTIONS';
        add_header  Access-Control-Allow-Headers   'Keep-Alive,User-Agent,X-Requested-With,\
If-Modified-Since,Cache-Control,Content-Type,token';
        return 204;
      }
      proxy_pass http://zuul/ ;
    }
  }

配置Nginx,加入Access-Control-Max-Age请求头,用来指定本次预检请求的有效期,单位为秒。上面结果中的有效期是20天(1 728 000秒),即允许缓存该条回应1 728 000秒,在此期间客户端不用发出另一条预检请求。

指令的执行顺序

大多数Nginx新手都会频繁遇到这样一个困惑:当同一个location配置块使用了多个Nginx模块的配置指令时,这些指令的执行顺序很可能会跟它们的书写顺序大相径庭。现在就来看这样一个令人困惑的例子:

location  /sequence_demo_1  {
  set    $a  foo;
  echo  $a;

  set    $a  bar;
  echo   $a;
}

在浏览器中的输出结果为:bar bar

为什么呢?

上面例子中的指令按照请求处理阶段的先后次序排序,实际的执行次序如下:

location  /sequence_demo_1  {
  #rewrite阶段的配置指令,执行在前面
      set    $a  foo;
      set    $a  bar;

  #content阶段的配置指令,执行在后面
      echo  $a;
      echo   $a;
}

set指令和echo指令分别在不同的阶段完成,所以echo是输出最终的值。

反向代理和负载均衡配置

proxy_pass反向代理指令

这里介绍的proxy_pass反向代理指令处于ngx_http_proxy_module模块,并且注册在HTTP请求11个阶段的content阶段。proxy_pass反向代理指令的格式如下:

proxy_pass 目标URL前缀;

当proxy_pass后面的目标URL格式为"协议"+“IP[:port]”+"/“根路径的格式时,表示最终的结果路径会把location指令的URI前缀也给加上,这里称为不带前缀代理。如果目标URL为"协议”+“IP[:port]”,而没有“/根路径”,那么Nginx不会把location的URI前缀加到结果路径中,这里称为带前缀代理。

  1. 不带location前缀的代理

    #不带location前缀的代理类型
    location /foo_no_prefix {
        proxy_pass http://127.0.0.1:8080/;
    }
    
  2. 带location前缀的代理

    #带location前缀代理
    location /foo_prefix {
        proxy_pass http://127.0.0.1:8080;
    }  
    

    以上两种的区别是,不带location前缀的代理在获取$uri时,是不带location配置指令的前缀/foo_no_prefix。而带前缀的是$uri带location配置指令的前缀/foo_no_prefix的。

  3. 带部分URI路径的代理

    #带部分URI路径的代理,实例1
    location /foo_uri_1 {
        proxy_pass http://127.0.0.1:8080/contextA/;
    }
    
    #带部分URI路径的代理,实例2
    location /foo_uri_2 {
        proxy_pass http://127.0.0.1:8080/contextA-;
    }
    

    无论是例子中的目标URI前缀/contextA/,还是目标URI前缀/contextA-,都加在了最终的代理路径上,只是在代理路径中去掉了location指令的匹配前缀。

proxy_set_header请求头设置指令

在反向代理之前,proxy_set_header指令能重新定义/添加字段传递给代理服务器的请求头。请求头的值可以包含文本、变量和它们的组合。它的格式如下:

#head_field表示请求头,field_value表示值
proxy_pass_header  head_field  field_value;

由于经过反向代理后,对于目标服务器来说,客户端在本质上已经发生了变化,因此后端的目标Web服务器无法直接拿到客户端的IP。假设后端的服务器是Tomcat,那么在Java中request.getRemoteAddr()取得的是Nginx的地址,而不是客户端的真实IP。

如果需要取得真实IP,那么可以通过proxy_set_header指令在发生反向代理调用之前将保持在内置变量$remote_addr中的真实客户端地址保持到请求头中(一般为X-real-ip),代码如下:

#不带location前缀的代理
location /foo_no_prefix/ {
    proxy_pass  http://127.0.0.1:8080/;
    proxy_set_header    X-real-ip  $remote_addr;
}

在Java端使用request.getHeader(“X-real-ip”)获取X-real-ip请求头的值就可以获得真正的客户端IP。

在整个请求处理的链条上可能不仅一次反向代理,可能会经过N多次反向代理。为了获取整个代理转发记录,也可以使用proxy_set_header指令来完成,在配置文件中进行如下配置:

#带location前缀的代理
location /foo_prefix {
    proxy_set_header     X-Forwarded-For  $proxy_add_x_forwarded_for;
    proxy_pass   http://127.0.0.1:8080;
}

这里使用了$proxy_add_x_forwarded_for内置变量,它的作用就是记录转发历史,其值的第一个地址就是真实地址$remote_addr,然后每经过一个代理服务器就在后面累加一次代理服务器的地址。

上面的演示程序中,如果在Java服务器程序中通过如下代码获取代理转发记录:

request.getHeader("X-Forwarded-For") 

那么Java程序获得的返回值为“192.168.233.128,127.0.0.1”,表示最初的请求客户端的IP为192.168.233.128,经过了127.0.0.1代理服务器。每经过一次代理服务器,都会在后边追加上它的IP,并且使用逗号隔开。

为了不丢失信息,反向代理的设置如下:

location /hello {
      proxy_pass   http://127.0.0.1:8080;
      proxy_set_header  Host  $host;
      proxy_set_header  X-real-ip  $remote_addr;
      proxy_set_header X-Forwarded-For  $proxy_add_x_forwarded_for;
      proxy_redirect  off;
 }

设置了请求头Host、X-real-ip、X-Forwarded-For,分别将当前的目标主机、客户端IP、转发记录保存在请求头中。

proxy_redirect指令的作用是修改从上游被代理服务器传来的应答头中的Location和Refresh字段,尤其是当上游服务器返回的响应码是重定向或刷新请求(如HTTP响应码是301或者302)时,proxy_redirect可以重设HTTP头部的location或refresh字段值。off参数表示禁止所有的proxy_redirect指令。

upstream上游服务器组

假设Nginx只有反向代理没有负载均衡,它的价值会大打折扣。Nginx在配置反向代理时可以通过负载均衡机制配置一个上游服务器组(多台上游服务器)。当组内的某台服务器宕机时仍能保持系统可用,从而实现高可用。

Nginx的负载均衡配置主要用到upstream(上游服务器组)指令,其格式如下:

语法:upstream  name { ... }
上下文:http配置块

upstream指令后面的name参数是上游服务器组的名称;upstream块中将使用server指令定义组内的上游候选服务器。

upstream指令的作用与server有点类似,其功能是加入一个特殊的虚拟主机server节点。特殊之处在于这是上游server服务组,可以包含一个或者多个上游server。

一个upstream负载均衡主机节点的配置实例如下:

#upstream负载均衡虚拟节点
upstream balanceNode { 
    server "192.168.1.2:8080";  #上游候选服务1
    server "192.168.1.3:8080";  #上游候选服务2
    server "192.168.1.4:8080";  #上游候选服务3
    server "192.168.1.5:8080";  #上游候选服务4
}

实例中配置的balanceNode相当于一个主机节点,不过这是一个负载均衡类型的特定功能虚拟主机。当请求过来时,balanceNode主机节点的作用是按照默认负载均衡算法(带权重的轮询算法)在4个上游候选服务中选取一个进行请求转发。

例子:

  #负载均衡主机组,给虚拟主机1与虚拟主机2做负载均衡
  upstream balance {
     server  "127.0.0.1:8080";  #虚拟主机1
     server  "127.0.0.1:8081";  #虚拟主机2
  }

  #虚拟主机1
  server {
    listen               8080;
    server_name      localhost;
    default_type 'text/html';
    charset utf-8;
    location / {
      echo    "server port:8080" ;
    }
  }

  #虚拟主机2
  server {
    listen               8081 ;
    server_name      localhost;
    default_type 'text/html';
    charset utf-8;
    location / {
      echo   "server port:8081" ;
    }
  }
  
  #虚拟主机3:默认虚拟主机
  server {
    listen       80 default;
    default_type 'text/html';
    charset utf-8;
    #负载均衡测试连接
    location /balance {
      proxy_pass http://balance;   #反向代理到负载均衡节点
    }
  }

访问http://localhost/balance 结果可以看出,upstream负载均衡指令起到了负载均衡的效果。默认情况下,upstream会依照带权重的轮询方式进行负载分配,每个请求按请求顺序逐一分配到不同的上游候选服务器。

upstream的上游服务器配置

upstream块中将使用server指令定义组内的上游候选服务器。内部server指令的语法如下:

语法:server  address  [parameters];
上下文:upstream配置块

此内嵌的server指令用于定义上游服务器的地址和其他可选参数,它的地址可以指定为域名或IP地址带有可选端口,如果未指定端口,就使用端口80。

内嵌的server指令的可选参数大致如下:

  1. weight=number(设置上游服务器的权重):默认情况下,upstream使用加权轮询(Weighted Round Robin)负载均衡方法在上游服务器之间分发请求。weight值默认为1,并且各上游服务器的weight值相同,表示每个请求按先后顺序逐一分配到不同的上游服务器,如果某个上游服务器宕机,就自动剔除。

    例子:

    #负载均衡主机组
    upstream balance {
        server  "127.0.0.1:8080"  weight=2;  #上游虚拟主机1,权重为2 
        server  "127.0.0.1:8081"  weight=1;  #上游虚拟主机2,权重为1
    }
    

    权重越大的节点,将被分发到更多请求。

  2. max_conns=number(设置上游服务器的最大连接数):max_conns参数限制到上游节点的最大同时活动连接数。默认值为零,表示没有限制。如果upstream服务器组没有通过zone指令设置共享内存,那么在单个Worker工作进程范围内对上游服务的最大连接数进行限制;如果upstream服务器组通过zone指令设置了共享内存,那么在全体的Worker工作进程范围内对上游服务进行统一的最大连接数限制。

  3. backup(可选参数):backup参数标识该server是备份的上游节点,当普通的上游服务(非backup)不可用时,请求将被转发到备份的上游节点;当普通的上游服务(非backup)可用时,备份的上游节点不接受处理请求。

  4. down(可选参数):down参数标识该上游server节点为不可用或者永久下线的状态。

  5. max_fails=number(最大错误次数):如果上游服务不可访问了,如何判断呢?max_fails参数是其中之一,该参数表示请求转发最多失败number次就判定该server为不可用。max_fails参数的默认次数为1,表示转发失败1次,该server即不可用。如果此参数设置为0,就会禁用不可用的判断,一直不断地尝试连接后端server。

  6. fail_timeout=time(失败测试的时间长度):这是一个失效监测参数,一般与上面的参数max_fails协同使用。fail_timeout的意思是失败测试的时间长度,指的是在fail_timeout时间范围内最多尝试max_fails次,就判定该server为不可用。fail_timeout参数的默认值为10秒。

server指令在进行max_conns连接数配置时,Nginx内部会涉及共享内存区域的使用,配置共享内存区域的指令为zone,其具体语法如下:

语法:zone  name  [size];
上下文:upstream配置块

zone的name参数设置共享内存区的名称,size可选参数用于设置共享内存区域的大小。如果配置了upstream的共享内存区域,那么其运行时状态(包括最大连接数)在所有的Worker工作进程之间是共享的。在name相同的情况下,不同的upstream组将共享同一个区,这种情况下,size参数的大小值只需设置一次。

下面是一个server指令和zone指令的综合使用实例:

upstream zuul {
zone  upstream_zuul  64k;     //名称为upstream_zuul,大小为64KB的共享内存区域
server "192.168.233.128:7799"    weight=5  max_conns=500;
server "192.168.233.129:7799"   fail_timeout=20s  max_fails=2; //默认权重为1
server "192.168.233.130:7799"    backup;  //后备服务
}
upstream的负载分配方式
加权轮询

默认情况下,upstream使用加权轮询(Weighted Round Robin)负载均衡方法在上游服务器之间分发请求,默认的权重weight值为1,并且各上游服务器weight值相同,表示每个请求按到达的先后顺序逐一分配到不同的上游服务器,如果某个上游服务器宕机,就自动剔除。

指定权重weight值,weight和分配比率成正比,用于后端服务器性能不均的情况。下面是一个简单的例子:

upstream backend {
    server 192.168.1.101 weight=1;
    server 192.168.1.102 weight=2;
    server 192.168.1.103 weight=3;
}
hash指令

基于hash函数值进行负载均衡,hash函数的key可以包含文本、变量或二者的组合。hash函数负载均衡是一个独立的指令,指令的格式如下:

语法:hash  key  [consistent];
上下文:upstream配置块

注意,如果upstream组中摘除掉一个server,就会导致hash值重新计算,即原来的大多数key可能会寻址到不同的server上。若配置有consistent参数,则hash一致性将选择Ketama算法。这个算法的优势是,如果有server从upstream组里摘除掉,那么只有少数的key会重新映射到其他的server上,即大多数key不受server摘除的影响,还走到原来的server。这对提高缓存server命中率有很大帮助。下面是一个简单的通过请求的$request_uri的hash值进行负载均衡的例子:

upstream backend {
    hash  $request_uri  consistent;
    server 192.168.1.101 ;
    server 192.168.1.102 ;
    server 192.168.1.103 ;
}
ip_hash指令

基于客户端IP的hash值进行负载平衡,这样每个客户端固定访问同一个后端服务器,可以解决类似session不能跨服务器的问题。如果上游server不可用,就需要手工摘除或者配置down参数。ip_hash是一条独立的指令,其使用的示例如下:

upstream backend {
    ip_hash;
    server 192.168.1.101:7777;
    server 192.168.1.102:8888;
    server 192.168.1.103:9999;
}

nginx配置详解
https://www.zhaojun.inkhttps://www.zhaojun.ink/archives/nginx-config
作者
卑微幻想家
发布于
2022-11-18
许可协议