[07] Nginx_2

1. Nginx 概述#

Nginx 是一个高性能的 HTTP 和反向代理服务,也是一个 IMAP/POP3/SMTP 服务。

1.1 对比 Tomcat#

Nginx 的编写有一个明确目标就是超越 Apache Web 服务器的性能。

Nginx 在官方测试的结果中,能够支持 5w 个并行连接,而在实际的运作中,可以支持 2w~4w 个并行连接,作为对比 Tomcat 的并行连接数只有几百个。

Nginx 提供开箱即用的静态文件,使用的内存比 Apache 少得多,每秒可以处理大约 4 倍于 Apache 的请求,低并发下性能与 Apache 相当,有时候还低于,但是在高并发下 Nginx 能保持低资源低消耗高性能,还有高度模块化的设计,模块编写简单、简洁。

这种性能提升的代价是降低了灵活性,例如能够以每个文件为基础覆盖系统范围的访问设置( Apache 使用 .htaccess 文件来完成这个工作,而 Nginx 并没有内置这样的功能),以前,向 Nginx 添加第三方模块需要使用静态链接的模块从源代码重新编译应用程序。在版本 1.9.11 中部分地克服了这一点,增加了动态模块加载。但是,模块仍然必须与 Nginx 同时编译,而不是所有的模块都与这个系统兼容 —— 有些需要更老的静态链接过程。

1.2 Nginx 模块化#

整体采用模块化设计是 Nginx 的一个重大特点,甚至 HTTP 服务器核心功能也是一个模块。

旧版本的 Nginx 的模块是静态的,添加和删除模块都要对 Nginx 进行重新编译,1.9.11 以及更新的版本已经支持动态模块加载。

高度模块化的设计是 Nginx 的架构基础,Nginx 服务器被分解为多个模块,每个模块就是一个功能模块,只负责自身的功能,模块之间严格遵循“高内聚,低耦合”的原则。

  • 【核心模块】核心模块是 Nginx 服务器正常运行必不可少的模块,提供错误日志记录、配置文件解析、事件驱动机制、进程管理等核心功能。
  • 【标准 HTTP 模块】标准 HTTP 模块提供 HTTP 协议解析相关的功能,如:端口配置、网页编码设置、HTTP 响应头设置等。
  • 【可选 HTTP 模块】可选 HTTP 模块主要用于扩展标准的 HTTP 功能,让 Nginx 能处理一些特殊的服务,如:Flash 多媒体传输、解析 GeoIP 请求、SSL 支持等。
  • 【邮件服务模块】邮件服务模块主要用于支持 Nginx 的邮件服务,包括对 POP3 协议、IMAP 协议和 SMTP 协议的支持。
  • 【第三方模块】第三方模块是为了扩展 Nginx 服务器应用,完成开发者自定义功能,如:Json 支持、Lua 支持等。

1.3 反向代理#

Nginx 是一款自由的、开源的、高性能的 HTTP 服务器和反向代理服务器,同时也是一个 IMAP、POP3、SMTP 代理服务器。Nginx 可以作为一个 HTTP 服务器进行网站的发布处理,另外 Nginx 可以作为反向代理进行负载均衡的实现

正向代理

说反向代理之前,我们先看看正向代理,正向代理也是大家最常接触的到的代理模式,我们会从两个方面来说关于正向代理的处理模式,分别从软件方面和生活方面来解释一下什么叫正向代理。

在如今的网络环境下,我们如果由于技术需要要去访问国外的某些网站,此时你会发现位于国外的某网站我们通过浏览器是没有办法访问的,此时大家可能都会用一个 FQ 操作进行访问,FQ 的方式主要是找到一个可以访问国外网站的代理服务器,我们将请求发送给代理服务器,代理服务器去访问国外的网站,然后将访问到的数据传递给我们。

上述这样的代理模式称为正向代理,正向代理最大的特点是客户端非常明确要访问的服务器地址;服务器只清楚请求来自哪个代理服务器,而不清楚来自哪个具体的客户端。正向代理模式屏蔽或者隐藏了真实客户端信息

客户端必须设置正向代理服务器,当然前提是要知道正向代理服务器的 IP 地址,还有代理程序的端口。

总结来说,正向代理,它代理的是客户端,代客户端发出请求,是一个位于客户端和原始服务器之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端,客户端必须要进行一些特别的设置才能使用正向代理。

正向代理的用途:

  1. 访问原来无法访问的资源,如 Google
  2. 可以做缓存,加速访问资源
  3. 对客户端访问授权,上网进行认证
  4. 代理可以记录用户访问记录(上网行为管理),对外隐藏用户信息

反向代理

明白了什么是正向代理,我们继续看关于反向代理的处理方式,购物网站某宝每天同时连接到网站的访问人数已经爆表,单个服务器远远不能满足人民日益增长的购买欲望了,此时就出现了一个大家耳熟能详的名词:分布式部署,也就是通过部署多台服务器来解决访问人数限制的问题。某宝网站中大部分功能也是直接使用Nginx进行反向代理实现的,并且通过封装 Nginx 和其他的组件之后起了个高大上的名字:Tengine(http://tengine.taobao.org)。

通过上述的图解大家就可以看清楚了,多个客户端给服务器发送的请求,Nginx 服务器接收到之后,按照一定的规则分发给了后端的业务处理服务器进行处理了,此时请求的来源也就是客户端是明确的,但是请求具体由哪台服务器处理的并不明确了,Nginx 扮演的就是一个反向代理角色。

客户端是无感知代理的存在的,反向代理对外都是透明的,访问者并不知道自己访问的是一个代理,因为客户端不需要任何配置就可以访问。反向代理,"它代理的是服务端,代服务端接收请求",主要用于服务器集群分布式部署的情况下,反向代理隐藏了服务器的信息

反向代理的作用:

  1. 保证内网的安全,通常将反向代理作为公网访问地址,Web服务器是内网。
  2. 负载均衡,通过反向代理服务器来优化网站的负载。

项目场景

通常情况下,我们在实际项目操作时,正向代理和反向代理很有可能会存在在一个应用场景中,正向代理代理客户端的请求去访问目标服务器,目标服务器是一个反向单利服务器,反向代理了多台真实的业务处理服务器,具体的拓扑图如下:

二者区别

  • 在正向代理中,Proxy 和 Client 同属于一个 LAN(图中方框内),隐藏了客户端信息;
  • 在反向代理中,Proxy 和 Server 同属于一个 LAN(图中方框内),隐藏了服务端信息;

实际上,Proxy 在两种代理中做的事情都是替服务器代为收发请求和响应,不过从结构上看正好左右互换了一下,所以把后出现的那种代理方式称为反向代理了。

2. Nginx 安装#

2.1 编译安装#

CentOS 下有两种安装 Nginx 方式,一种是 yum install,还有一种是编译安装,当然第一种方式更简单,但是有一定的缺点,比如说我们需要使用特定的第三方模块,这个时候就必须用编译安装这种方式了。

(1)安装编译环境

$ yum -y install autoconf automake make
$ yum -y install gcc gcc-c++
$ yum -y install wget pcre pcre-devel zlib zlib-devel openssl openssl-devel

(2)下载 Nginx 源码压缩包并解压

$ wget http://nginx.org/download/nginx-1.23.0.tar.gz
$ tar -zxvf nginx-1.21.1.tar.gz

查看目录结构:

cc 是用于编译的,对所有的操作系统的判断在 os 里面,其他所有文件都是为了辅助 configure 文件在执行的时候去判定支持哪些模块,当前的操作系统有哪些特性可以供 Nginx 使用。

然后我们再看 nginx 根目录下,conf 是配置文件的示例文件,方便我们在安装完以后可以直接把 conf 里面的配置文件复制到安装目录下面,CHANGES 这个文件里面描述了 Nginx 的哪些特性,configure 是一个用来生成中间文件进行编译前的一个必备动作。

接下来我们通过 ./configure --help | more 命令来查看一下:

如果我们不需要指定安装其他模块的话,我们默认只需要执行 –prefix 就可以了(该配置用来指定安装位置)。

如果我们需要安装其他模块就需要在编译的时候执行 –with 也就是说你需要安装的这个模块默认是没有安装进 Nginx 的,如果我们不需要某些模块这个时候我们就需要执行 –without,也就是说 without 后面跟着的模块默认是安装进 Nginx 的。

执行命令:./configure --prefix=/usr/local/nginx

(3)编译、安装

$ make && make install

(4)配置环境变量

编辑 /etc/profile 在最后一行添加:

export PATH=$PATH:/usr/local/nginx/sbin

生效环境变量:source /etc/profile

(5)关闭防火墙

$ systemctl status firewalld
$ systemctl stop firewalld
$ systemctl disable firewalld

2.2 YUM 安装#

(1)添加源

默认情况 Centos7 中无 Nginx 的源,最近发现 Nginx 官网提供了 CentOS 的源地址,因此可以如下执行命令添加源:

$ rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm

通过 yum search nginx 看看是否已经添加源成功。如果成功则执行下列命令安装 Nginx。

(2)安装、启动

# 安装 Nginx
$ yum install -y nginx
# 启动 Nginx
$ systemctl start nginx.service
# 开机自运行
$ systemctl enable nginx.service

3. Nginx 命令#

(1)启动

# 不指定默认为 NGINX_HOME/conf/nginx.conf
$ nginx
# 指定配置文件
$ nginx -c nginx.conf 

(2)停止

  1. nginx -s quit 命令会向 Nginx 主进程发送一个优雅退出的信号,Nginx 主进程会等待当前正在处理的请求完成,然后优雅地关闭工作进程,确保数据的完整性。
  2. nginx -s stop 命令会向 Nginx 主进程发送一个强制退出的信号,Nginx 主进程会立即关闭所有工作进程并停止服务,未完成的请求将被中断,可能会导致数据丢失或不完整。
  3. kill -9 命令可以发送一个强制终止的信号给 Nginx 进程。这个信号会立即终止进程,不给予进程进行清理和善后的机会。这意味着 Nginx 进程将会被强制终止,可能导致正在处理的连接被中断,未完成的请求无法正确处理。

使用 nginx -s stopkill -9 命令都可能导致正在处理的连接被中断,未完成的请求无法正确处理。这可能会造成数据丢失或不完整。因此,在正常情况下,应该优先考虑使用 nginx -s quit 命令来优雅地关闭 Nginx 服务器,以确保数据的完整性。

(3)重新加载配置文件

$ nginx -s reload

(4)检查配置文件

nginx -tnginx -T 都是用于检查 Nginx 配置文件的命令,但它们有稍微不同的作用。

  • nginx -t:这个命令用于测试 Nginx 配置文件的语法是否正确。当你修改了 Nginx 的配置文件后,可以使用 nginx -t 命令来验证配置文件的正确性。如果配置文件中存在语法错误或其他问题,该命令将显示错误消息并指示问题所在,否则会显示 "syntax is ok" 表示配置文件语法正确。
  • nginx -T:这个命令与 nginx -t 类似,但它会显示完整的 Nginx 配置信息,包括从各个配置文件中导入的内容。它会打印出 Nginx 的配置信息,包括全局配置、http 模块配置、server 模块配置等。这对于调试和查看完整的配置信息非常有用。

总结来说,nginx -t 用于检查 Nginx 配置文件的语法正确性,而 nginx -T 除了检查语法正确性外,还显示完整的 Nginx 配置信息。

# 使用默认位置的配置文件
$ nginx -t
# -c 指定要使用的配置文件路径,即 /your_path/nginx.conf
$ nginx -t -c /your_path/nginx.conf

(5)查看 Nginx 版本信息

$ nginx -v

4. 配置文件结构#

4.1 文件说明#

Nginx 配置文件一般位于 Nginx 安装目录下的 conf 目录下,整个文件以 block 形式组合而成,每一个 block 都使用 "{}" 大括号来表示,block 中可以嵌套其他 block 层级,其中 main 层是最高层次。

Nginx 配置文件主要有 4 部分,main(全局设置)、server(主机设置)、upstream(上游服务器设置,主要为反向代理、负载均衡相关配置)和 location(url 匹配特定位置的设置),每部分包含若干指令。

  • main 部分的设置影响其他所有部分的设置;
  • server 部分主要用于指定虚拟机主机域名,IP和端口;
  • upstream 的指令用于设置一系列的后端服务器,设置反向代理及后端服务器的负载均衡;
  • location 部分用于匹配网页位置(如根目录“/”、”/images”等)。

它们之间的关系是,server 继承 main,location 继承 server,upstream 既不会继承指令也不会被继承。

在这 4 个部分当中,每个部分都包含若干指令,这些指令主要包含 Nginx 的主模块指令、事件模块指令、HTTP 核心模块指令,同时每个部分还可以使用其他 HTTP 模块指令,例如 Http SSL 模块、HttpGzip Static 模块和 Http Addition 模块等。

真实的 Nginx 配置文件可能如下:

### 每个指令必须有分号结束 ###
# 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_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_PATH/etc/nginx/etc/nginx,主配置文件通常命名为 nginx.conf。为了保持整洁,部分配置可以放到单独的文件中,再自动地被包含到主配置文件。但应该注意的是,nginx 目前不支持 Apache 风格的分布式配置文件(如 .htaccess 文件),所有和 Nginx 行为相关的配置都应该位于一个集中的配置文件目录中。

根据上述文件,我们可以很明显的将 nginx.conf 配置文件分为 3 部分。

4.2 - 全局配置#

user nobody nobody;
worker_processes 2;
error_log logs/error.log notice;
pid logs/nginx.pid;
 
events {
    use epoll;
    worker_connections 65536;
}

(1)user

user 是一个主模块指令,指定 Nginx Worker 进程运行用户及用户组,默认由 nobody 账号运行。

这个地方如果写错了就会出现获取不到用户的错误。

(2)worker_processes

是个主模块指令,指定了 Nginx 要开启的进程数,每个 Nginx 进程平均耗费 10M~12M 内存,建议指定和 CPU 的数量一致即可。

这个地方如果配置配置了 worker_processes 2; 那么它的工作进程就有两个:

(3)error_log

是个主模块指令,用来定义全局错误日志文件,日志输出级别有 debug、info、notice、warn、error、crit 可供选择,其中,debug 输出日志最为最详细,而 crit 输出日志最少。

日志文件路径一般在 Nginx 安装目录的 logs 目录中。

(4)pid

是个主模块指令,用来指定进程 pid 的存储文件位置。进程号和 Nginx 的 Master 的进程号是一致的,只有 Nginx 运行时才存在,如果 Nginx 停止了 pid 也会被删除掉。

4.3 - 事件指令#

events 事件指令是设定 Nginx 的工作模式及连接数上限:

(1)use

use 是个事件模块指令,用来指定 Nginx 的工作模式。

Nginx 支持的工作模式有 select、poll、kqueue、epoll、rtsig 和 /dev/poll,其中 select 和 poll 都是标准的工作模式,kqueue 和 epoll 是高效的工作模式,不同的是 epoll 用在Linux平台上,而 kqueue 用在 BSD 系统中。对于 Linux 系统,epoll 工作模式是首选。

(2)worker_connections

也是个事件模块指令,用于定义 Nginx 每个进程的最大连接数,默认是 1024。最大客户端连接数由 worker_processes 和 worker_connections 决定,即 Max_client = worker_processes * worker_connections

在作为反向代理时,Max_client 变为:max_clients = worker_processes * worker_connections / 4。 进程的最大连接数受 Linux 系统进程的最大打开文件数限制,在执行操作系统命令 ulimit -n 65536 后 worker_connections 的设置才能生效。

4.4 - HTTP 服务配置#

这算是 Nginx 服务器配置中最频繁的部分,代理、缓存和日志定义等绝大多数功能和第三方模块的配置都在这里。

需要注意的是:http 块也可以包括 http 全局块、server 块。

Nginx 对 HTTP 服务器相关属性的配置代码如下:

http {
    include       mime.types;                                                   # 引入文件类型映射文件
    default_type  application/octet-stream;                                     # 如果没有找到指定的文件类型映射,使用默认配置
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '   # 日志打印格式
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    sendfile        on;                                                         # 启动零拷贝提高性能
    keepalive_timeout  65;                                                      # 设置keepalive长连接超时时间
    include /usr/local/openresty/nginx/conf/conf.d/*.conf;                      # 引入子配置文件
}

(1)include

include 是个主模块指令,实现对配置文件所包含的文件的设定,可以减少主配置文件的复杂度,可以将其他各个模块的具体配置分散在不同的文件夹中。

(2)default_type

default_type 属于 HTTP 核心模块指令,这里设定默认类型为二进制流,也就是当文件类型未定义时使用这种方式,例如在没有配置 PHP 环境时,Nginx 是不予解析的,此时,用浏览器访问 PHP 文件就会出现下载窗口。

(3)log_format

log_format 是 Nginx 的 HttpLog 模块指令,用于指定 Nginx 日志的输出格式。main/download 为此日志输出格式的名称,可以在 access_log 指令中引用。

http {

    # $remote_addr                 表示客户端的IP地址
    # $remote_user                 表示远程用户
    # $time_local                  表示访问时间
    # $request                     表示请求的URL和HTTP协议
    # $status                      表示服务器返回的状态码
    # $bytes_sent                  表示发送给客户端的字节数
    # $http_referer                表示请求来源的URL
    # $http_user_agent             表示客户端的用户代理
    # $gzip_ratio                  表示启用gzip压缩的压缩比率

    log_format main '$remote_addr - $remote_user [$time_local] '
        '"$request" $status $bytes_sent '
        '"$http_referer" "$http_user_agent" '
        '"$gzip_ratio"';

    # $http_range                  表示客户端请求的范围
    # $sent_http_content_range     表示服务器发送的内容范围
 
    log_format download '$remote_addr - $remote_user [$time_local] '
        '"$request" $status $bytes_sent '
        '"$http_referer" "$http_user_agent" '
        '"$http_range" "$sent_http_content_range"';

    # 将main日志格式应用于access_log指令中,指定日志文件的路径为/path/to/access.log
    access_log /path/to/access.log main;
    
    # 其他配置项...
}

5. Nginx 路由匹配#

5.1 准备工作#

(1)初始化目录

$ mkdir -p /root/www/nginx/{abc,bbs}/

(2)创建 index 文件

创建 abc 目录下的 index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>abc</title>
  <head>
  <body>
      <H1>
          www.abc.com
      </H1>
  </body>
</html>

创建 bbs 目录下的 index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>bbs</title>
  <head>
  <body>
      <H1>
          www.bbs.com
      </H1>
  </body>
</html>

(3)安装 echo 模块

涉及到 echo 操作的地方需要安装 echo 模块,使用 --add-module 重新编译安装。

$ ./configure  --prefix=/usr/local/nginx --add-module=/usr/local/nginx/modules/echo-nginx-module-master
$ make && make install

5.2 虚拟主机#

所谓虚拟主机,在 Web 服务里就是一个独立的网站站点,这个站点对应独立的域名(也可能是 IP 或端口),具有独立的程序及资源,可以独立地对外提供服务供用户访问。

在 Nginx 中,使用一个 server{} 标签来标识一个虚拟主机,一个 Web 服务里可以有多个虚拟主机标签对,即可以同时支持多个虚拟主机站点。

虚拟主机有两种类型:基于域名的虚拟主机、基于IP+端口的虚拟主机。

a. 完全匹配虚拟主机#

(1)在 conf.d 文件夹下创建 vhostserver.conf 配置文件(该文件在主配置文件中通过 include 指令被包含)

server {
        listen       80;
        charset utf-8;
        server_name  www.abc.com;

        location / {
            alias '/root/www/nginx/abc/';
            index  index.html index.htm;
            expires  7d;
        }
}

server {
        listen       80 ;
        charset utf-8;
        server_name  www.bbs.com;


        location / {
            alias '/root/www/nginx/bbs/';
            index  index.html index.htm;
            expires  7d;
        }
}

(2)配置本地 host

192.168.64.150 www.abc.com
192.168.64.150 www.bbs.com

(3)访问测试

b. 通配符配置虚拟主机#

(1)虚拟主机还支持通配符方式进行配置

server {
        listen       80;
        charset utf-8;
        server_name  *.com;


        location / {
            default_type text/html;
            echo "通配符在前";
        }
}

server {
        listen       80;
        charset utf-8;
        server_name  www.abc.*;


        location / {
            default_type text/html;
            echo "通配符在后";
        }
}

(2)访问测试

(3)虚拟主机匹配顺序

  • 最高优先级:完全匹配
  • 第二优先级:通配符在前
  • 第三优先级:通配符在后

c. 默认主机匹配#

如果有多个访问的域名指向这台 web 服务器,但某个域名未添加到 Nginx 虚拟主机中,就会访问默认虚拟主机(泛解析)。

server {
        # 将域名bbs.com 设置为默认虚拟主机
        listen       80 default;
        charset utf-8;
        server_name  www.bbs.com;


        location / {
            alias '/root/www/nginx/bbs/';
            index  index.html index.htm;
            expires  7d; 
        }
}

使用未配置的域名或者IP地址访问测试,会发现如果访问一个没有配置的虚拟主机就会跳到 bbs 的页面。

6. location 使用#

location 有“定位”的意思,根据请求不同的 URL 来进行不同的处理,在虚拟主机(server)中,location 配置是必不可少的,可以把网站不同的部分定位到不同的处理方式上。

允许根据用户请求的 URI 来匹配定义的各 location,匹配到时,此请求将被响应的 location 配置块中的配置所处理,例如做访问控制等功能。

【语法】location [修饰符] pattern {...}

修饰符 功能
前缀匹配,能够匹配以需要匹配的路径为前缀的 uri。
= 精确匹配
~ 正则表达式模式匹配,区分大小写
~* 正则表达式模式匹配,不区分大小写
^~ 精确前缀匹配,类似于无修饰符的行为,也是以指定模块开始,不同的是,如果模式匹配,那么就停止搜索其他模式了,不支持正则表达式
/ 通用匹配,任何请求都会匹配到。

6.1 前缀匹配#

没有修饰符表示必须以指定模式开始,指定模式前面没有任何修饰符,直接在 location 后写需要匹配的 uri,它的优先级次于正则匹配。

server {
    server_name www.tree6x7.com;
    charset   utf-8;
    location /abc {
        default_type text/html;
        echo "前缀匹配-abc...";
    }
}

那么如下内容可以就可以正确匹配:

  • www.tree6x7.com/abc
  • www.tree6x7.com/abc/
  • www.tree6x7.com/abc?xxx=yyy
  • www.tree6x7.com/abc.html

6.2 通用匹配#

通用匹配使用一个 / 表示,可以匹配所有请求,一般 Nginx 配置文件最后都会有一个通用匹配规则,当其他匹配规则均失效时,请求会被路由给通用匹配规则处理;如果没有配置通用匹配,并且其他所有匹配规则均失效时,Nginx 会返回 404 错误。

server {
    server_name www.tree6x7.com;
    charset   utf-8;
    location / {
        default_type text/html;
        echo "通用匹配-default";
    }
}

6.3 精确匹配#

精确匹配使用 表示,Nginx 进行路由匹配的时候,精确匹配具有最高的优先级,请求一旦精确匹配成功 Nginx 会停止搜索其他到匹配项。

server {
    server_name www.tree6x7.com;
    charset   utf-8;
    location = /abc {
        default_type text/html;
        echo "精确匹配-abc-accurate";
    }
}

那么如下内容可正确匹配:

  • www.tree6x7.com/abc
  • www.tree6x7.com/abc?xxx=yyy

如下内容则无法匹配:

  • www.tree6x7.com/abc/
  • www.tree6x7.com/abc/adcde

6.4 精确前缀匹配#

精确前缀匹配的优先级仅次于精确匹配,Nginx 对一个请求精确前缀匹配成功后,停止继续搜索其他到匹配项。

server {
    server_name www.tree6x7.com;
    charset   utf-8;
    location ^~ /abc {
        default_type text/html;
        echo "精确前缀匹配-abc-prefix";
    }
}

那么如下内容可以就可以正确匹配:

  • www.tree6x7.com/abc
  • www.tree6x7.com/abc/
  • www.tree6x7.com/abc?xxx=yyy

6.5 正则表达式#

正则匹配分为区分大小写和不区分大小写两种,分别用 ~~* 表示。一个请求精确匹配和精确前缀匹配都失败后,如果配置有相关的正则匹配 location,Nginx 会尝试对该请求进行正则匹配。需要说明的是正则匹配之间没有优先级一说,而是按照在配置文件中出现的顺序进行匹配,一旦匹配上一个,就会停止向下继续搜索。

(1)区分大小写 ~

server {
    server_name www.tree6x7.com;
    charset   utf-8;
    location ~ ^/abc$ {
        default_type text/html;
        echo "正则区分大小写-abc-regular-X";
    }
}

那么如下内容可以正确匹配:

  • www.tree6x7.com/abc
  • www.tree6x7.com/abc?xxx=yyy

如下内容则无法匹配:

  • www.tree6x7.com/abc/
  • www.tree6x7.com/ABC
  • www.tree6x7.com/abcde

(2)不区分大小写 ~*

server {
    server_name www.tree6x7.com;
    charset   utf-8;
    location ~* ^/abc$ {
        default_type text/html;
        echo "正则不区分大小写-abc-regular-Y";
    }
}

那么如下内容就可以正确匹配:

  • www.tree6x7.com/abc
  • www.tree6x7.com/abc?xxx=yyy
  • www.tree6x7.com/ABC

如下内容则无法匹配:

  • www.tree6x7.com/abc/
  • www.tree6x7.com/abcde

(3)完整案例

server {
        server_name www.tree6x7.com;
        default_type text/html;
        charset   utf-8;
        location = / {
            echo "规则A";
        }
        location = /login {
            echo "规则B";
        }
        location ^~ /static/ {
            echo "规则C";
        }
        location ^~ /static/files {
            echo "规则X";
        }
        location ~ \.(gif|jpg|png|js|css)$ {
            echo "规则D";
        }
        location ~* \.js$ {
            echo "规则E";
        }
        location /img {
            echo "规则Y";
        }
        location / {
           echo "规则F";
        }
}

6.6 小结#

a. 匹配顺序#

匹配顺序和优先级,由高到底依次为:

  1. 带有“=”的精确匹配优先
  2. 正则表达式
  3. 没有修饰符的精确匹配

具体匹配规则如下:

  1. = 精准匹配命中时,停止 location 动作,直接走精准匹配
  2. 一般匹配(含精确前缀匹配)命中时,先收集所有的普通匹配,最后对比出最长的那一条
  3. 如果最长的那一条普通匹配声明为精确前缀匹配,直接此条匹配,停止 location
  4. 如果最长的那一条普通匹配不是精确前缀匹配,继续往下走正则 location
  5. 按代码顺序执行正则匹配,当第一条正则 location 命中时,停止 location

【注】有多个正则表达式出现时,按照它们在配置文件中定义的顺序。

b. 匹配过程#

假设http请求路径为 http://192.168.0.132:8088/mvc/index?id=2 ,匹配过程如下:

  1. 将整个 url 拆解为:域名/端口/path/params
  2. 先由域名/端口,对应到目标 server 虚拟主机
  3. path 部分参与 location 匹配,path = path1匹配部分 + path2剩余部分
  4. 进入 location方法体内部流程
  5. 若是静态文件处理,则进入目标目录查找文件:① root 指令时找 path1+path2 对应的文件 ② alias 指令时找 path2 对应的文件
  6. 若是 proxy 代理,则形如 proxy_pass=ip:port 时转发 path1+path2 路径到 tomcat,形如 proxy_pass=ip:port/xxx 时转发 path2 路径到 tomcat。params 始终跟随转发。

c. 使用建议#

所以实际使用中,个人觉得至少有 3 个匹配规则定义,如下:

# 第 1 个必选规则是直接匹配网站根,通过域名访问网站首页比较频繁,使用这个会加速处理。
# 这里是直接转发给后端应用服务器了,也可以是一个静态首页。
location = / {
    proxy_pass http://tomcat:8080/index
}

# 第 2 个必选规则是处理静态文件请求,这是Nginx作为HTTP服务器的强项。
# 有两种配置模式:目录匹配或后缀匹配,任选其一或搭配使用。
location ^~ /static/ {
    alias /webroot/static/;
}
location ~* \.(gif|jpg|jpeg|png|css|js|ico)$ {
    root /webroot/res/;
}

# 第 3 个规则就是通用规则,用来转发动态请求到后端应用服务器。
# 非静态文件请求就默认是动态请求,自己根据实际把握。
location / {
    proxy_pass http://tomcat:8080/
}

7. Nginx 负载均衡#

负载均衡用于从“upstream”模块定义的后端服务器列表中选取一台服务器接受用户的请求,一个最基本的 upstream 模块是这样的,模块内的 server 是服务器列表:

upstream dynamicserver {
  server 172.16.44.47:9001; #tomcat 1
  server 172.16.44.47:9002; #tomcat 2
  server 172.16.44.47:9003; #tomcat 3
  server 172.16.44.47:9004; #tomcat 4
}

在 upstream 模块配置完成后,要让指定的访问反向代理到服务器列表:

# 其他页面反向代理到tomcat容器
location ~.*$ {
  index index.jsp index.html;
  proxy_pass http://dynamicserver;
}

这就是最基本的负载均衡实例,但这不足以满足实际需求。目前 Nginx 服务器的 upstream 模块支持 6 种方式的分配:

upstream dynamicserver {
        server 192.168.64.1:9001; #tomcat 1
        server 192.168.64.1:9002; #tomcat 2
        server 192.168.64.1:9003; #tomcat 3
        server 192.168.64.1:9004; #tomcat 4
}

server {
        server_name www.itcast.com;
        default_type text/html;
        charset   utf-8;

        location ~ .*$ {
            index index.jsp index.html;
            proxy_pass http://dynamicserver;
       }
}

7.1 常用参数#

参数 描述
server 反向服务地址,加端口
weight 权重
fail_timeout 与 max_fails 结合使用
max_fails 设置在fail_timeout参数设置的时间内最大失败次数,如果在这个时间内,所有针对该服务器的请求都失败了,那么认为该服务器会被认为是停机了
max_conns 允许最大连接数
fail_time 服务器会被认为停机的时间长度,默认为 10s
backup 标记该服务器为备用服务器,当主服务器停止时,请求会被发送到它这里。
down 标记服务器永久停机了
slow_start 当节点恢复,不立即加入

7.2 负载均衡策略#

只详细说明 Nginx 自带的负载均衡策略,第三方不多描述。

负载策略 描述
轮询 默认方式
weight 权重方式
ip_hash 依据ip分配方式
least_conn 最少连接方式
fair(第三方) 响应时间方式
url_hash(第三方) 依据 URL 分配方式

(1)轮询

最基本的配置方法,上面的例子就是轮询的方式,它是 upstream 模块默认的负载均衡默认策略,每个请求会按时间顺序逐一分配到不同的后端服务器。

# 动态服务器组
upstream dynamicserver {
  server 192.168.64.1:9001; #tomcat 1
  server 192.168.64.1:9002; #tomcat 2
  server 192.168.64.1:9003; #tomcat 3
  server 192.168.64.1:9004; #tomcat 4
}

server {
        server_name www.itcast.com;
        default_type text/html;
        charset   utf-8;

        location ~ .*$ {
            index index.jsp index.html;
            proxy_pass http://dynamicserver;
       }
}

缺省配置就是轮询策略。在轮询中,如果服务器宕机了,会自动剔除该服务器。此策略适合服务器配置相当,无状态且短平快的服务使用。

(2)weight

权重方式,在轮询策略的基础上指定轮询的几率。

upstream dynamicserver {
  server 192.168.64.1:9001  weight=2;                   #tomcat 1
  server 192.168.64.1:9002;                             #tomcat 2
  server 192.168.64.1:9003;                             #tomcat 3
  server 192.168.64.1:9004;                             #tomcat 4
}
  • weight 参数用于指定轮询几率,weight 的默认值为1。
  • weight 的数值与访问比率成正比,比如 Tomcat 7.0 被访问的几率为其他服务器的两倍。
  • 此策略可以与 least_conn 和 ip_hash 结合使用。
  • 权重越高分配到需要处理的请求越多,此策略比较适合服务器的硬件配置差别比较大的情况。

(3)ip_hash

指定负载均衡器按照基于客户端 IP 的分配方式,这个方法确保了相同的客户端的请求一直发送到相同的服务器,以保证 session 会话,这样每个访客都固定访问一个后端服务器,可以解决 session 不能跨服务器的问题。

upstream dynamicserver {
  ip_hash;                                              #保证每个访客固定访问一个后端服务器
  server 192.168.64.1:9001  weight=2;                   #tomcat 1
  server 192.168.64.1:9002;                             #tomcat 2
  server 192.168.64.1:9003;                             #tomcat 3
  server 192.168.64.1:9004;                             #tomcat 4
}
  • 在 Nginx 1.3.1 之前,不能在 ip_hash 中使用权重(weight)。
  • ip_hash 不能与 backup 同时使用。
  • 此策略适合有状态服务,比如 session。
  • 当有服务器需要剔除,必须手动 down 掉。

(4)least_conn

把请求转发给连接数较少的后端服务器,轮询算法是把请求平均的转发给各个后端,使它们的负载大致相同。但是,有些请求占用的时间很长,会导致其所在的后端负载较高,这种情况下 least_conn 这种方式就可以达到更好的负载均衡效果。

upstream dynamicserver {
  least_conn;                                           #把请求转发给连接数较少的后端服务器
  server 192.168.64.1:9001  weight=2;                   #tomcat 1
  server 192.168.64.1:9002;                             #tomcat 2
  server 192.168.64.1:9003;                             #tomcat 3
  server 192.168.64.1:9004;                             #tomcat 4
}

该策略适合请求处理时间长短不一造成服务器过载的情况。

7.3 重试策略#

现在对外服务的网站,很少只使用一个服务节点,而是部署多台服务器,上层通过一定机制保证容错和负载均衡。

a. 基础失败重试#

为了方便理解,使用了以下配置进行分析( proxy_next_upstream 没有特殊配置)

upstream dynamicserver {
      server 192.168.64.1:9001 fail_timeout=60s max_fails=3; #Server A
      server 192.168.64.1:9002 fail_timeout=60s max_fails=3; #Server B
}

max_fails=3 fail_timeout=60s 代表在 60s 内请求某一应用失败 3 次,认为该应用宕机,后等待 60s,这期间内不会再把新请求发送到宕机应用,而是直接发到正常的那一台,时间到后再有请求进来继续尝试连接宕机应用且仅尝试 1 次,如果还是失败,则继续等待 60s... 以此循环,直到恢复。

【模拟异常】模拟后端异常的方式是直接将对应服务关闭,造成 connect refused 的情况,对应 error 错误。

在最初始阶段,所有服务器都正常,请求会按照轮询方式依次转发给 AB 两个 Server 处理。假设这时 A 节点服务崩溃,端口不通,则会出现这种情况:

  1. 请求 1 转到 A 异常,再重试到 B 正常处理,A fails +1
  2. 请求 2 转到 B 正常处理
  3. 请求 3 转到 A 异常,再重试到 B 正常处理,A fails +1 达到 max_fails 将被屏蔽 60s
  4. 屏蔽 A 的期间请求都只转给 B 处理,直到屏蔽到期后将 A 恢复重新加入存活列表,再按照这个逻辑执行

如果在 A 的屏蔽期还没结束时,B 节点的服务也崩溃,端口不通,则会出现:

  1. 请求 1 转到 B 异常,此时所有线上节点异常,会出现:
    • AB 节点一次性恢复,都重新加入存活列表
    • 请求转到 A 处理异常,再转到 B 处理异常
    • 触发 no live upstreams 报错,返回 502 错误
    • 所有节点再次一次性恢复,加入存活列表
  2. 请求 2 依次经过 AB 均无法正常处理, 触发 no live upstreams 报错,返回 502 错误

b. 错误重试#

有时候我们系统出现 500 等异常的情况下,希望 Nginx 能够到其他的服务器进行重试,我们可以配置那些错误码才进行重试。

在 Nginx 的配置文件中,proxy_next_upstream 项定义了什么情况下进行重试,官网文档中给出的说明如下:

Syntax: proxy_next_upstream error | timeout | invalid_header | http_500 | http_502 | http_503 | http_504 | http_403 | http_404 | off ...;
Default:    proxy_next_upstream error timeout;
Context:    http, server, location

默认情况下,当请求服务器发生错误或超时时,会尝试到下一台服务器,还有一些其他的配置项如下:

错误状态 描述
error 与服务器建立连接,向其传递请求或读取响应头时发生错误
timeout 在与服务器建立连接,向其传递请求或读取响应头时发生超时
invalid_header 服务器返回空的或无效的响应
http_500 服务器返回代码为500的响应
http_502 服务器返回代码为502的响应
http_503 服务器返回代码为503的响应
http_504 服务器返回代码504的响应
http_403 服务器返回代码为403的响应
http_404 服务器返回代码为404的响应
http_429 服务器返回代码为429的响应(1.11.13)
non_idempotent 通常请求与非幂等方法(POST/LOCK/PATCH)不传递到请求是否已被发送到上游服务器(1.9.13)的下一个服务器,启用此选项显式允许重试此类请求。
off 禁用将请求传递给下一个服务器

这里面我们配置了 500 等错误的时候会进行重试:

upstream dynamicserver {
  server 192.168.64.1:9001 fail_timeout=60s max_fails=3; #tomcat 1
  server 192.168.64.1:9002 fail_timeout=60s max_fails=3; #tomcat 2
}

server {
        server_name www.itcast.com;
        default_type text/html;
        charset   utf-8;

        location ~ .*$ {
            index index.jsp index.html;
            proxy_pass http://dynamicserver;
            proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
       }
}

【模拟异常】在正常的情况下如果 500 错误会直接出现异常页面,现在我们加入了以上 500 重试策略,重试错误的流程和上面流程是一样的。

c. backup 服务器#

Nginx 支持设置备用节点,当所有线上节点都异常时启用备用节点,同时备用节点也会影响到失败重试的逻辑,因此单独列出来介绍。

upstream 的配置中,可以通过 backup 指令来定义备用服务器,其含义如下:

  1. 正常情况下,请求不会转到到 backup 服务器,包括失败重试的场景
  2. 当所有正常节点全部不可用时,backup 服务器生效,开始处理请求
  3. 一旦有正常节点恢复,就使用已经恢复的正常节点
  4. backup 服务器生效期间,不会存在所有正常节点一次性恢复的逻辑
  5. 如果全部 backup 服务器也异常,则会将所有节点一次性恢复,加入存活列表
  6. 如果全部节点(包括 backup)都异常了,则 Nginx 返回 502 错误

配置说明:

upstream dynamicserver {
  server 192.168.64.1:9001 fail_timeout=60s max_fails=3;  #Service A
  server 192.168.64.1:9002 fail_timeout=60s max_fails=3;  #Server B
  server 192.168.64.1:9003 backup;                        #Backup
}

server {
        server_name www.itcast.com;
        default_type text/html;
        charset   utf-8;

        location ~ .*$ {
            index index.jsp index.html;
            proxy_pass http://dynamicserver;
            proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
       }
}

在最初始阶段,所有服务器都正常,请求会按照轮询方式依次转发给 A/B 两个节点处理。当只有 A 异常的情况下,与上文没有 backup 服务器场景处理方式一致,这里就不重复介绍了。

假设在 A 的屏蔽期还没结束时,B 节点的服务也崩溃,端口不通,则会出现:

  • 请求 1 转到 B 处理,异常,此时所有线上节点异常,会出现:
    • AB 节点一次性恢复,都重新加入存活列表
    • 请求转到 A 处理异常,再重试到 B 处理异常,两者 fails 都 +1
    • 因 AB 都异常,启用 backup 节点正常处理,并且 AB 节点一次性恢复,加入存活列表
  • 请求 2 再依次经过 A、B 节点异常,转到 backup 处理,两者 fails 都达到 max_fails:
    • AB 节点都将会被屏蔽 60s,并且不会一次性恢复
    • backup 节点正式生效,接下来所有请求直接转到 backup 处理
    • 直到 AB 节点的屏蔽到期后,重新加入存活列表

假设 AB 的屏蔽期都还没结束时,C 节点的服务也崩溃,端口不通,则会出现:

  • 请求 1 转到 C 异常,此时所有节点(包括 backup)都异常,会出现:
    • ABC 三个节点一次性恢复,加入存活列表
    • 请求转到 A 处理异常,重试到 B 处理异常,最后重试到 C 处理异常
    • 触发 no live upstreams 报错,返回 502 错误
    • 所有节点再次一次性恢复,加入存活列表
  • 请求 2 依次经过 AB 节点异常,重试到 C 异常,最终结果如上个步骤,返回 502 错误

d. 限制重试方式#

默认配置是没有做重试机制进行限制的,也就是会尽可能去重试直至失败,Nginx 提供了以下两个参数来控制重试次数以及重试超时时间。

  • proxy_next_upstream_tries 设置重试次数,默认 0 表示无限制,该参数包含所有请求 upstream server 的次数,包括第一次后之后所有重试之和;
  • proxy_next_upstream_timeout 设置重试最大超时时间,默认 0 表示不限制,该参数指的是第一次连接时间加上后续重试连接时间,不包含连接上节点之后的处理时间。

配置说明:

upstream dynamicserver {
      server 192.168.64.1:9001 fail_timeout=60s max_fails=3; #Server A
      server 192.168.64.1:9002 fail_timeout=60s max_fails=3; #Server B
}

server {
        server_name www.itcast.com;
        default_type text/html;
        charset   utf-8;

        location ~ .*$ {
            index index.jsp index.html;
            proxy_pass http://dynamicserver;
            # 表示重试超时时间是3s
            proxy_connect_timeout 3s;
            # 表示在 6 秒内允许重试 3 次,只要超过其中任意一个设置,Nginx 会结束重试并返回客户端响应
            proxy_next_upstream_timeout 6s;
            proxy_next_upstream_tries 3;
       }
}

8. Nginx 常用案例#

8.1 代理静态文件#

server {
        listen       10086;
        server_name  www.itcast.com;
    
        location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-NginX-Proxy true;
        proxy_redirect off;
        }
        
        location /data/ {
            alias '/usr/local/data/';
            # 这里是重点,就是代理这个文件夹 
            expires    7d;
        }
}

8.2 反向代理#

server {
    listen       80;
    server_name  www.itcast.com;;

    location / {
        proxy_pass http://127.0.0.1:8080;
        index  index.html index.htm .jsp;
    }
}

8.3 跨域配置#

server {
        listen       80;
        server_name  www.itcast.com;

    if ( $host ~ (.*).itcast.com){
        set $domain $1;                      ##记录二级域名值
    }
    #是否允许请求带有验证信息
    add_header Access-Control-Allow-Credentials true;
    #允许跨域访问的域名,可以是一个域的列表,也可以是通配符*
    add_header Access-Control-Allow-Origin  *;
    #允许脚本访问的返回头
    add_header Access-Control-Allow-Headers 'x-requested-with,content-type,Cache-Control,Pragma,Date,x-timestamp';
    #允许使用的请求方法,以逗号隔开
    add_header Access-Control-Allow-Methods 'POST,GET,OPTIONS,PUT,DELETE';
    #允许自定义的头部,以逗号隔开,大小写不敏感
    add_header Access-Control-Expose-Headers 'WWW-Authenticate,Server-Authorization';
    #P3P支持跨域cookie操作
    add_header P3P 'policyref="/w3c/p3p.xml", CP="NOI DSP PSAa OUR BUS IND ONL UNI COM NAV INT LOC"';
    if ($request_method = 'OPTIONS') {      ##OPTIONS类的请求,是跨域先验请求
            return 204;                     ##204代表ok
     }
}

8.4 防盗链#

通过 Referer 实现防盗链比较基础,仅可以简单实现方式资源被盗用,构造 Referer 的请求很容易实现。

  • 场景:由于图片链接可以跨域访问,所以图片链接往往被其他网站盗用,从而增加服务器负担;
  • 解决:Nginx 可以通过 valid_referers 配置进行防盗链配置

【valid_referers 指令】指定合法的来源 'referer',就是白名单的意思。它决定了内置变量 $invalid_referer 的值,如果 referer 头部包含在这个合法网址里面,这个变量被设置为 0,否则设置为 1。需要注意的是这里并不区分大小写的。

# 需要防盗的后缀
location ~* \.(jpg|jpeg|png|gif|bmp|swf|rar|zip|doc|xls|pdf|gz|bz2|mp3|mp4|flv)$
    # 设置过期时间
    expires     30d;
    # none:允许没有http_refer的请求访问资源
    # blocked:允许不是 http:// 开头的,不带协议的请求访问资源;
    # 支持域名或IP:(1) 允许 192.168.0.1 的请求 (2) 允许域名 *.google.com 所有子域名
    valid_referers none blocked 192.168.0.1 *.google.com;
    if ($invalid_referer) {
        # return 403;
        # 盗链返回的图片,替换盗链网站所有盗链的图片
        rewrite ^/ https://site.com/403.jpg;
    }
    root  /usr/share/nginx/img;
}
posted @   tree6x7  阅读(105)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示
主题色彩