深入浅出的Nginx超详细讲解

前言:

nginx 作为当今火爆的、高性能的 http 及反向代理服务,不管前端还是后端,都需要全面去了解,学习,实操。一句话:搞懂 nginx 如何使用以及工作逻辑对于程序员来说是必不可少的!

我们看看本文的大纲 先了解一下本文都讲了哪些东西,大纲如下:

  1. nginx 介绍
  2. nginx 安装
  3. nginx 目录一览
  4. nginx.conf 文件解读
  5. location 路由匹配规则
  6. 反向代理
  7. 负载均衡
  8. 动静分离
  9. 跨域
  10. 缓存
  11. 黑白名单
  12. nginx 限流
  13. https 配置
  14. 压缩
  15. 其他一些常用指令与说明
  16. 重试策略
  17. 最后总结

「一些说明:」

  1. 系统: centos7
  2. 本文使用 nginx 版本:nginx/1.24.0
  3. 关于 nginx 如何安装(本文不再赘述),参考之前我的一篇文章:Centos7 中安装 nginx
  4. 在学习 nginx 前,最好需要知道或者了解事件驱动思想以及几种常见多路复用 I/O 模型和 Reactor 模式,这样你才能从底层 更深刻的理解 nginx 的架构设计

1、nginx 介绍

为了有一个全面的认知,接下来我们先来看看 nginx 的架构以及一些特点。

1.1、nginx 特点

  1. 处理响应请求快(异步非阻塞 I/O,零拷贝,mmap,缓存机制)
  2. 扩展性好(模块化设计)
  3. 内存消耗低(异步非阻塞,多阶段处理)
  4. 具有很高的可靠性(无数次的生产验证,很多头部公司都在用)
  5. 热部署
  6. 高并发连接(事件驱动模型,多进程机制)
  7. 自由的 BSD 许可协议(可以自己修改代码后发布,包容性极强)

1.2、nginx 架构

深入浅出的 Nginx 超详细讲解从上边这张图,我们可以一览 nginx 的架构设计,首先我们可以直观得出nginx 的几大特点:

  1. 「事件驱动&异步非阻塞:」

    本质来说,「事件驱动是一种思想(事实上它不仅仅局限于编程)」 ,事件驱动思想是实现 「异步非阻塞特性」 的一个重要手段。对于 web 服务器来说,造成性能拉胯不支持高并发的常见原因就是由于使用了传统的 I/O 模型造成在内核没有可读/可写事件(或者说没有数据可供用户进程读写)时「用户线程」 一直在等待(其他事情啥也干不了就是干等等待内核上的数据可读/可写),这样的话其实是一个线程(ps:线程在 Linux 系统也是进程)对应一个请求,请求是无限的,而线程是有限的从而也就形成了并发瓶颈。而大佬们为了解决此类问题,运用了事件驱动思想来对传统 I/O 模型做个改造,即在客户端发起请求后,用户线程不再阻塞等待内核数据就绪,而是立即返回(可以去执行其他业务逻辑或者继续处理其他请求)。当内核的 I/O 操作完成后,内核系统会向用户线程发送一个事件通知,用户线程才来处理这个读/写操作,之后拿到数据再做些其他业务后响应给客户端,从而完成一次客户端请求的处理。事件驱动的 I/O 模型中,程序不必阻塞等待 I/O 操作的完成,也无需为每个请求创建一个线程,从而提高了系统的并发处理能力和响应速度。事件驱动型的 I/O 模型通常也被被称为 I/O 多路复用,即这种模型可以在一个线程中,处理多个连接(复用就是指多个连接复用一个线程,多路也即所谓的 多个连接),通过这种方式避免了线程间切换的开销,同时也使得用户线程不再被阻塞,提高了系统的性能和可靠性。nginx 支持事件驱动是因为他利用了操作系统提供的 I/O 多路复用接口,如 Linux 系统中,常用的 I/O 多路复用接口有 select/poll,epoll。这些接口可以监视多个文件描述符的状态变化,当文件描述符可读或可写时,就会向用户线程发送一个事件通知。用户线程通过事件处理机制(读取/写入数据)来处理这个事件,之后进行对应的业务逻辑完了进行响应。「简单一句话概括:」 事件驱动机制就是指当有读/写/连接事件就绪时 再去做读/写/接受连接这些事情,而不是一直在那里傻傻的等,也正应了他的名词: 【事件驱动!】,基于事件驱动思想设计的多路复用 I/O(如 select/poll,epoll),相对于传统 I/O 模型,达到了异步非阻塞的效果!

    既然提到了 select/poll,epoll 那么我们就简单说一下(注意我这里是简单描述,后续有时间会对相关知识点从源码层面做个系统的整理和图解):

    「select:」 将已连接的 Socket 都放到一个文件描述符集合,然后用户态调用 select 函数将文件描述符集合拷贝到内核里,让内核来检查是否有网络事件产生,检查的方式很粗暴,就是通过遍历文件描述符集合的方式,当检查到有事件产生后,将此 Socket 标记为可读或可写, 接着再把整个文件描述符集合拷贝回用户态里,然后用户态还需要再通过遍历的方法找到可读或可写的 Socket,然后再对其处理。

    「poll:」 poll 函数的话其实和 select 大差不差,唯一区别可能就是 socket 列表的结构有所不同,不再受 FD_SETSIZE 的限制。这里就不多说了。

    「epoll:」 epoll 在前边两者的基础上做了很大的优化,select/poll 都需要遍历整个 socket 列表,当检测到传入的 socket 可读/可写时,则 copy socket 列表给用户空间,用户态仍然需要遍历(因为内核 copy 给用户态的是整个 socket 列表)    ,而 epoll 则是通过红黑树结构将需要监控的 socket 插入到进去,然后当有 socket 可读时会通过回调机制来将其添加到可读列表中,然后内核将可读列表 copy 给用户态即可(据说此处使用了 mmap 这里我们不去验证探究,后续写相关文章时在深究吧),整个过程少了无效的遍历以及不用 copy 整个 socket 集合。

  2. 「多进程机制:」

    另外可以得知 nginx 有两种类型的进程,一种是 Master 主进程,一种是 Worker 工作进程。主进程主要负责 3 项工作:加载配置启动工作进程非停升级。另外 work 进程是主进程启动后,fork 而来的。假设 Nginx fork 了多个(具体在于你的配置)Worker 进程,并且在 Master 进程中通过 socket 套接字监听(listen)80 端口。然后每个 worker 进程都可以去 accept 这个监听的 socket。 当一个连接进来后,所有 Worker 进程,都会收到消息,但是只有一个 Worker 进程可以 accept 这个连接,其它的则 accept 失败,Nginx 保证只有一个 Worker 去 accept 的方式就是加锁(accept_mutex)。有了锁之后,在同一时刻,就只会有一个 Worker 进程去 accpet 连接,在 Worker 进程拿到 Http 请求后,就开始按照 worker 进程内的预置模块去处理该 Http 请求,最后返回响应结果并断开连接。其实如果熟悉 reactor 模型你会发现,nginx 的设计有 reactor 的影子,只不过 reactor 的主 reactor 是会负责 accept 的,而 nginx 的主进程(对应主 reactor) 是不会去 accept 的,而是交给了 worker 进程来处理。

    worker 进程除了 accept 连接之外,还会执行:网络读写、存储读写、内容传输、以及请求分发等等。而其代码的模块化设计,也使得我们可以根据需要对功能模块 进行适当的选择和修改,编译成符合特定需要/业务的服务器

  3. 「proxy cache(服务端缓存):」

    proxy cache 主要实现 nginx 服务器对客户端数据请求的快速响应。nginx 服务器在接收到被代理服务器的响应数据之后,一方面将数据传递给客户端,另一方面根据 proxy cache 的配置将这些数据缓存到本地硬盘上。当客户端再次访问相同的数据时,nginx 服务器直接从硬盘检索到相应的数据返回给用户,从而减少与被代理服务器交互的时间。在缓存数据时,运用了零拷贝以及 mmap 技术,使得数据 copy 性能大幅提升。

  4. 「反向代理:」

    nginx 的强大之处其中一个就是他的反向代理,通过反向代理,可以隐藏真正的服务,增加其安全性,同时便于统一管理处理请求,另外可以很容易的做个负载均衡,更好的面对高并发的场景。

1.3、nginx 模块

nginx 服务器由 n 多个模块组成,每个模块就是一个功能,某个模块只负责自身的功能,所以说对于 “高内聚,低耦合“ 的编程规则,在nginx身上可谓体现的淋漓尽致

「nginx 模块示意图如下:」深入浅出的 Nginx 超详细讲解

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

1.4、nginx 常见应用场景

nginx 常用场景挺多的,比如:

  • 反向代理
  • 负载均衡
  • 缓存
  • 限流
  • 黑/白名单
  • 静态资源服务
  • 动静分离
  • 防盗链
  • 跨域
  • 高可用
  • …….

其中我认为 「最最」 基础的也是应用最多的就是 「反向代理」,这里我们画个图简单看下什么是反向代理
(ps:其他的那些使用场景,我们先不做展开,放到下边一个个哔哔。)

所谓反向代理,其实很好理解就是代理的服务端(与之对应的正向代理一般代理的是客户端),「nginx 反向代理如下示意:」深入浅出的 Nginx 超详细讲解


「好了介绍了这么多,想必到这里应该对 nginx 有个大体的了解了吧,接下来我们安装并一个一个的分析介绍 nginx 的知识点。」

2、nginx 安装

关于 nginx 的安装在这里不再赘述,参考我之前的一篇文章:Centos7 中安装 nginx

3、nginx 目录一览

我们使用 tree /usr/local/nginx/ -L 2 命令查看一下 nginx 的目录,对其结构有个初步的认识:

[root@localhost /]# tree  /usr/local/nginx/  -L 2
/usr/local/nginx/
├── conf                        #存放一系列配置文件的目录
│   ├── fastcgi.conf           #fastcgi 程序相关配置文件
│   ├── fastcgi.conf.default   #fastcgi 程序相关配置文件备份
│   ├── fastcgi_params         #fastcgi 程序参数文件
│   ├── fastcgi_params.default #fastcgi 程序参数文件备份
│   ├── koi-utf           #编码映射文件
│   ├── koi-win           #编码映射文件
│   ├── mime.types        #媒体类型控制文件
│   ├── mime.types.default#媒体类型控制文件备份
│   ├── nginx.conf        #主配置文件
│   ├── nginx.conf.default#主配置文件备份
│   ├── scgi_params      #scgi 程序相关配置文件
│   ├── scgi_params.default #scgi 程序相关配置文件备份
│   ├── uwsgi_params       #uwsgi 程序相关配置文件
│   ├── uwsgi_params.default#uwsgi 程序相关配置文件备份
│   └── win-utf          #编码映射文件
├── html                 #存放网页文档
│   ├── 50x.html         #错误页码显示网页文件
│   └── index.html       #网页的首页文件
├── logs                 #存放 nginx 的日志文件
├── nginx-1.23.0.tar.gz # 我把压缩包下载到 url/local/nginx/目录了,不用管这个
├── sbin                #存放启动程序
│   ├── nginx           #nginx 启动程序
│   └── nginx.old       
└── test                # 我自己建的目录,不用管这个
    ├── abc
    └── cba

15 directories, 26 files

从输出可以看到 nginx 分的很清晰,有配置目录,html 目录,log 目录,启动程序目录。

  • 「关于目录的一点小说明:」

    上边的仅仅是 nginx 的主目录,事实上,生效的主配置文件一定是/usr/local/nginx/conf.conf ?这不一定,而是取决于你启动 nginx 时候有没有指定 nginx.conf,实际使用中我发现我机器上有好几个地方都存在 nginx.conf 文件,使用 locate nginx.conf 看一下 如下图所示:深入浅出的 Nginx 超详细讲解那如何确定 nginx 当前生效的是哪个 nginx.conf 呢,很简单使用 nginx -T 命令即可查看当前生效的 nginx.conf,如下:可以看到我当前生效的是 /etc/nginx/nginx.conf 这个文件(我是使用的 systemctl start nginx.service 命令启动的,未指定用哪个文件启动,所以可以看出默认使用的是 /etc/nginx/nginx.conf 这个配置文件)深入浅出的 Nginx 超详细讲解另外还有一个就是 nginx 的日志,我发现我的 nginx 日志就不是在 /usr/local/nginx/logs/这个目录下,而是放到了/var/log/nginx/ 这个目录下了(ps:log 文件的存放和我的 nginx.conf 文件中的 access_log 配置有关系)。如下演示:深入浅出的 Nginx 超详细讲解

好了在了解了 nginx 整体的目录结构后,就来看看 「nginx.conf」 这个文件这个文件是 nginx 的核心配置,「想玩转 nginx,读懂这个配置文件是必不可少的一项基本功!」

4、nginx.conf 文件 解读

首先我们要知道nginx.conf 文件是由一个一个的指令块组成的,nginx 用{}标识一个指令块,指令块中再设置具体的指令(注意 指令必须以 ; 号结尾),指令块有全局块events 块http 块server 块localtion 块 以及 upstream 块。精简后的结构如下:

全局模块
event 模块
http 模块
    upstream 模块

    server 模块
        localtion 块
        localtion 块
        ....
    server 模块
        localtion 块
        localtion 块
        ...
    ....    

「各模块的功能作用如下描述:」

  1. 「全局模块:」 配置影响 nginx 全局的指令,比如运行 nginx 的用户名,nginx 进程 pid 存放路径,日志存放路径,配置文件引入,worker 进程数等。
  2. 「events 块:」 配置影响 nginx 服务器或与用户的网络连接。比如每个进程的最大连接数,选取哪种事件驱动模型(select/poll epoll 或者是其他等等 nginx 支持的)来处理连接请求,是否允许同时接受多个网路连接,开启多个网络连接序列化等。
  3. 「http 块:」 可以嵌套多个 server,配置代理,缓存,日志格式定义等绝大多数功能和第三方模块的配置。如文件引入,mime-type 定义,日志自定义,是否使用 sendfile 传输文件,连接超时时间,单连接请求数等。
  4. 「server 块:」 配置虚拟主机的相关参数比如域名端口等等,一个 http 中可以有多个 server。
  5. 「location 块:」 配置 url 路由规则
  6. 「upstream 块:」 配置上游服务器的地址以及负载均衡策略和重试策略等等

「下面看下 nginx.conf 长啥样并对一些指令做个解释:」

# 注意:有些指令是可以在不同指令块使用的(需要时可以去官网看看对应指令的作用域)。我这里只是演示
# 这里我以/usr/local/nginx/conf/nginx.conf 文件为例

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

#user  nobody; # 指定 Nginx Worker 进程运行用户以及用户组,默认由 nobody 账号运行
worker_processes  1;  # 指定工作进程的个数,默认是 1 个。具体可以根据服务器 cpu 数量进行设置, 比如 cpu 有 4 个,可以设置为 4。如果不知道 cpu 的数量,可以设置为 auto。 nginx 会自动判断服务器的 cpu 个数,并设置相应的进程数
#error_log  logs/error.log;  # 用来定义全局错误日志文件输出路径,这个设置也可以放入 http 块,server 块,日志输出级别有 debug、info、notice、warn、error、crit 可供选择,其中,debug 输出日志最为最详细,而 crit 输出日志最少。
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info; # 指定 error 日志位置和日志级别
#pid        logs/nginx.pid;  # 用来指定进程 pid 的存储文件位置

events {
    accept_mutex on;   # 设置网路连接序列化,防止惊群现象发生,默认为 on
    
    # Nginx 支持的工作模式有 select、poll、kqueue、epoll、rtsig 和/dev/poll,其中 select 和 poll 都是标准的工作模式,kqueue 和 epoll 是高效的工作模式,不同的是 epoll 用在 Linux 平台上,而 kqueue 用在 BSD 系统中,对于 Linux 系统,epoll 工作模式是首选
    use epoll;
    
    # 用于定义 Nginx 每个工作进程的最大连接数,默认是 1024。最大客户端连接数由 worker_processes 和 worker_connections 决定,即 Max_client=worker_processes*worker_connections 在作为反向代理时,max_clients 变为:max_clients = worker_processes *worker_connections/4。进程的最大连接数受 Linux 系统进程的最大打开文件数限制,在执行操作系统命令“ulimit -n 65536”后 worker_connections 的设置才能生效
    worker_connections  1024; 
}

# 对 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"';
    # 
    #access_log  logs/access.log  main; # 设置日志输出路径以及 日志级别
    sendfile        on; # 开启零拷贝 省去了内核到用户态的两次 copy 故在文件传输时性能会有很大提升
    #tcp_nopush     on; # 数据包会累计到一定大小之后才会发送,减小了额外开销,提高网络效率
    keepalive_timeout  65; # 设置 nginx 服务器与客户端会话的超时时间。超过这个时间之后服务器会关闭该连接,客户端再次发起请求,则需要再次进行三次握手。
    #gzip  on; # 开启压缩功能,减少文件传输大小,节省带宽。
    sendfile_max_chunk 100k; #每个进程每次调用传输数量不能大于设定的值,默认为 0,即不设上限。
    
    # 配置你的上游服务(即被 nginx 代理的后端服务)的 ip 和端口/域名
    upstream backend_server { 
        server 172.30.128.65:8080;
        server 172.30.128.65:8081 backup; #备机
    }

    server {
        listen       80; #nginx 服务器监听的端口
        server_name  localhost; #监听的地址 nginx 服务器域名/ip 多个使用英文逗号分割
        #access_log  logs/host.access.log  main; # 设置日志输出路径以及 级别,会覆盖 http 指令块的 access_log 配置
        
        # localtion 用于定义请求匹配规则。 以下是实际使用中常见的 3 中配置(即分为:首页,静态,动态三种)
       
        # 第一种:直接匹配网站根目录,通过域名访问网站首页比较频繁,使用这个会加速处理,一般这个规则配成网站首页,假设此时我们的网站首页文件就是: usr/local/nginx/html/index.html
        location = / {  
            root   html; # 静态资源文件的根目录 比如我的是 /usr/local/nginx/html/
            index  index.html index.htm; # 静态资源文件名称 比如:网站首页 html 文件
        }
        # 第二种:静态资源匹配(静态文件修改少访问频繁,可以直接放到 nginx 或者统一放到文件服务器,减少后端服务的压力),假设把静态文件我们这里放到了 usr/local/nginx/webroot/static/目录下
        location ^~ /static/ {
            alias /webroot/static/; 访问 ip:80/static/xxx.jpg 后,将会去获取/url/local/nginx/webroot/static/xxx.jpg 文件并响应
        }
        # 第二种的另外一种方式:拦截所有 后缀名是 gif,jpg,jpeg,png,css.js,ico 这些 类静态的的请求,让他们都去直接访问静态文件目录即可
        location ~* .(gif|jpg|jpeg|png|css|js|ico)$ {
            root /webroot/static/;
        }
        # 第三种:用来拦截非首页、非静态资源的动态数据请求,并转发到后端应用服务器 
        location / {
            proxy_pass http://backend_server; #请求转向 upstream 是 backend_server 指令块所定义的服务器列表
            deny 192.168.3.29; #拒绝的 ip (黑名单)
            allow 192.168.5.10; #允许的 ip(白名单)
        }
        
        # 定义错误返回的页面,凡是状态码是 500 502 503 504 总之 50 开头的都会返回这个 根目录下 html 文件夹下的 50x.html 文件内容
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
        
    }
    # 其余的 server 配置 ,如果有需要的话
    #server {
        ......
    #    location / {
               ....
    #    }
    #}
    
    # include /etc/nginx/conf.d/*.conf;  # 一般我们实际使用中有很多配置,通常的做法并不是将其直接写到 nginx.conf 文件,
    # 而是写到新文件 然后使用 include 指令 将其引入到 nginx.conf 即可,这样使得主配置 nginx.conf 文件更加清晰。
    
}

以上就是 nginx.conf 文件的配置了,主要讲了一些指令的含义,当然实际的指令有很多,我在配置文件并没有全部写出来,准备放到后边章节详细阐述这些东西,比如:「localtion 匹配规则,反向代理,动静分离,负载均衡策略,重试策略,压缩,https,限流,缓存,跨域这些」 我们都没细说,这些东西比较多比较细不可能把使用规则和细节都写到上边的配置文件中,所以我们下边一一解释说明关于这些东西的配置和使用方式。(另外值的注意的是: 因为有些指令是可以在不同作用域使用的,如果在多个作用域都有相同指令的使用,那么 nginx 将会遵循就近原则或者我愿称之为 「内层配置优先」。 eg: 你在 http 配了日志级别,也在某个 server 中配了日志级别,那么这个 server 将使用他自己配置的已不使用外层的 http 日志配置)

5、localtion 路由匹配规则

「什么是 location? :」 nginx 根据用户请求的 URI 来匹配对应的 location 模块,匹配到哪个 location,请求将被哪个 location 块中的配置项所处理。

location 配置语法:location [修饰符] pattern {…}

「常见匹配规则如下:」

修饰符 作用
无修饰符的前缀匹配,匹配前缀是 你配置的(比如说你配的是 /aaa) 的 url
= 精确匹配
~ 正则表达式模式匹配,区分大小写
~* 正则表达式模式匹配,不区分大小写
^~ ^~类型的前缀匹配,类似于无修饰符前缀匹配,不同的是,如果匹配到了,那么就停止后续匹配
/ 通用匹配,任何请求都会匹配到(只要你域名对,所有请求通吃!)

5.1、前缀匹配(无修饰符)

首先我提前创建了 prefix_match.html 文件,之后改一下 nginx.conf 文件(给前缀是 /prefixmatch 的请求返回 /etc/nginx/locatest/prefix_match.html 这个文件) ,如下:深入浅出的 Nginx 超详细讲解

然后在宿主机 hosts 中配置域名 172.30.128.65 www.locatest.com 映射后,观察到 nginx 服务器返回内容如下:深入浅出的 Nginx 超详细讲解

curl http://www.locatest.com/prefixmatch     ✅ 301
curl http://www.locatest.com/prefixmatch?    ✅ 301
curl http://www.locatest.com/PREFIXMATCH     ❌ 404
curl http://www.locatest.com/prefixmatch/    ✅ 200
curl http://www.locatest.com/prefixmatchmmm  ❌ 404
curl http://www.locatest.com/prefixmatch/mmm ❌ 404
curl http://www.locatest.com/aaa/prefixmatch/❌ 404

可以看到 域名/prefixmatch域名/prefixmatch? 返回了 301 ,原因在于 prefixmatch 映射的 /etc/nginx/locatest/ 是个目录,而不是个文件所以 nginx 提示我们 301,这个我们不用管没关系,总之我们知道:域名/prefixmatch域名/prefixmatch?域名/prefixmatch/ 这三个 url 通过我们配置的 「无修饰符前缀匹配规则」 都能匹配上就行了。

ps:为了方便,我们下边的几个 location 规则演示不再跳转静态文件了,而是直接 return 一句话。

5.2、精确匹配( = )

为了演示精确匹配,我们再给 nginx.conf 文件增加一个 location 配置,如下标红处:深入浅出的 Nginx 超详细讲解

实际效果如下:深入浅出的 Nginx 超详细讲解

http://www.locatest.com/exactmatch      ✅ 200
http://www.locatest.com/exactmatch?    ✅ 200
http://www.locatest.com/exactmatch/     ❌ 404
http://www.locatest.com/exactmatchmmmm  ❌ 404
http://www.locatest.com/EXACTMATCH      ❌ 404

可以看出来精确匹配就是精确匹配,差一个字也不行!

5.3、前缀匹配( ^~ )

我们上边说了不带任何修饰符的前缀匹配(5.1 小节),这里我们看下 修饰符是 ^~的 前缀匹配和不带修饰符的前缀匹配有啥区别,先在 ngnx.conf 文件增加个 location 并配好如下:深入浅出的 Nginx 超详细讲解curl 效果如下:深入浅出的 Nginx 超详细讲解

curl http://www.locatest.com/exactprefixmatch     ✅ 200
curl http://www.locatest.com/exactprefixmatch/    ✅ 200
curl http://www.locatest.com/exactprefixmatch?    ✅ 200
curl http://www.locatest.com/exactprefixmatchmmm  ✅ 200
curl http://www.locatest.com/exactprefixmatch/mmm ✅ 200
curl http://www.locatest.com/aaa/exactprefixmatch ❌ 404
curl http://www.locatest.com/EXACTPREFIXMATCH     ❌ 404

可以看到带修饰符(^~)的前缀匹配 像:域名/exactprefixmatchmmm域名/exactprefixmatch/mmm 是可以匹配上的,而不带修饰符的前缀匹配这两个类型的 url 是匹配不上的直接返回了 404 ,其他的和不带修饰符的前缀匹配似乎都差不多。

5.4、正则匹配(~ 区分大小写)

ps:正则表达式的匹配,需要你对正则语法比较熟悉,熟悉语法后写匹配规则也就得心应手了。

添加个 location 并配置,如下:(  ^表示开头,$表示结尾)深入浅出的 Nginx 超详细讲解实际效果如下:深入浅出的 Nginx 超详细讲解

curl http://www.locatest.com/regexmatch      ✅ 200
curl http://www.locatest.com/regexmatch/     ❌ 404
curl http://www.locatest.com/regexmatch?     ✅ 200
curl http://www.locatest.com/regexmatchmmm   ❌ 404
curl http://www.locatest.com/regexmatch/mmm  ❌ 404
curl http://www.locatest.com/REGEXMATCH      ❌ 404
curl http://www.locatest.com/aaa/regexmatch  ❌ 404
curl http://www.locatest.com/bbbregexmatch   ❌ 404

可以看到~修饰的正则是区分大小写的。接下来我们看下 不区分大小写的匹配。

5.5、正则匹配(~* 不区分大小写)

改下 location 在修饰符~后加个  *深入浅出的 Nginx 超详细讲解看下实际效果:深入浅出的 Nginx 超详细讲解可以看到这次 curl http://www.locatest.com/REGEXMATCH 是可以匹配上的,说明 ~* 确实是不区分大小写的。

5.6、通用匹配( / )

通用匹配使用一个 / 表示,可以匹配所有请求,一般 nginx 配置文件最后都会有一个通用匹配规则,当其他匹配规则均失效时,请求会被路由给通用匹配规则处理,如果没有配置通用匹配,并且其他所有匹配规则均失效时,nginx 会返回 404 错误。深入浅出的 Nginx 超详细讲解通用匹配实际效果:深入浅出的 Nginx 超详细讲解可以看到通用匹配很好理解,只要你域名写对了,那么所有的 url 都会被匹配上,来者不拒的感觉。

5.7、关于 location 匹配优先级

上边我们说了 6 种 location 匹配规则,那么如果存在多个到底走哪个 location 呢?这就的说说 location 的匹配优先级了。先来看下 nginx 官网和 stackoverflow 上的资料如下:深入浅出的 Nginx 超详细讲解深入浅出的 Nginx 超详细讲解综上资料我们对「location 匹配优先级的总结如下:」

  1. 优先走精确匹配,精确匹配命中时,直接走对应的 location,停止之后的匹配动作。
  2. 无修饰符类型的前缀匹配^~ 类型的前缀匹配命中时,收集命中的匹配,对比出最长的那一条并存起来(最长指的是与请求 url 匹配度最高的那个 location)。
  3. 如果步骤 2 中最长的那一条匹配是^~类型的前缀匹配,直接走此条匹配对应的 location 并停止后续匹配动作;如果步骤 2最长的那一条匹配不是^~类型的前缀匹配(也就是无修饰符的前缀匹配),则继续往下匹配
  4. 按 location 的声明顺序,执行正则匹配,当找到第一个命中的正则 location 时,停止后续匹配。
  5. 都没匹配到,走通用匹配( / )(如果有配置的话),如果没配置通用匹配的话,上边也都没匹配上,到这里就是 404 了。

「如果非要给修饰符排个序的话就是酱样子:」= >  ^~  > 正则 > 无修饰符的前缀匹配 > /

ok 关于 location 就到这里,location 是一个很重要的点,学好这个才知道 nginx 到底是咋匹配 url 的。

6、反向代理

反向代理示意图我们上边说过,这里再次粘一下:深入浅出的 Nginx 超详细讲解

接下来我们开始用一个小 demo 来演示反向代理的使用

6.1、服务准备

首先将我本地的一个服务打成 胖 jar:深入浅出的 Nginx 超详细讲解然后使用 java -jar 方式启动服务,且指定端口为 8081:

java -jar /Users/hzz/myself_project/xzll/study-admin/study-admin-service/target/study-admin-service.jar --server.port=8081

深入浅出的 Nginx 超详细讲解最后使用 postman 测试接口是否正常(注意此时还没被 nginx 代理,而是直接调的服务):深入浅出的 Nginx 超详细讲解启动服务并验证接口无误后,接下来我们修改 nginx 配置文件。让 nginx 反向代理我们的服务。

6.2、修改 nginx.conf 文件

要让 nginx 代理 我们的 服务 很简单,简单描述一下就是 两步:

  1. 「通过 upstream 指令块来定义我们的上游服务(即被代理的服务)」
  2. 「通过 location 指令块中的 proxy_pass 指令,指定该 location 要路由到哪个 upstream」

配置好 1 和 2 后,如果来了请求后 会通过 url 路由到对应的 location, 然后 nginx 会将请求打到 upstream 定义的服务地址中去,下边我们看看:

使用 vi /etc/nginx/nginx.conf 命令修改 nginx.conf 文件,如下:深入浅出的 Nginx 超详细讲解(注意上边的 proxy_pass http://mybackendserver/   「后边这个斜线加和不加区别挺大的」加的话不会拼接/backend  , 而不加的话会拼接 /backend ,这一点我们在 15.5.3 小节会讲到,这里留意下就好)

6.3、测试反向代理

修改完后我们执行 nginx -s reload 命令重新加载 nginx 配置,然后再 potsman 中调用一下,如下:

ps:在第一次调用中出现了一个错误:
failed (13: Permission denied) while connecting to upstream:深入浅出的 Nginx 超详细讲解解决办法很简单在虚拟机执行命令:setenforce 0 来关闭seLinux 的限制即可,或者参考:stackoverflow 上的解决方案

解决后再次调用发现可以了:(注意 www.proxytest.com 是我随便写的域名没做 dns 解析,我是在请求发出方 宿主机 配了 host,所以才能请求通)深入浅出的 Nginx 超详细讲解同时也可使用命令   tail -n +1 -f access.log 观察到 nginx 日志输出如下:深入浅出的 Nginx 超详细讲解

6.4、反向代理流程与原理

对于上边演示的反向代理案例的流程与原理,我们来个示意图如下:(这个图比较重要)深入浅出的 Nginx 超详细讲解

接下来我们演示下负载均衡。

7、负载均衡

说到负载均衡很多人应该并不陌生,总而言之负载均衡就是:避免高并发高流量时请求都聚集到某一个服务或者某几个服务上,而是让其均匀分配(或者能者多劳),从而减少高并发带来的系统压力,从而让服务更稳定。对于 nginx 来说,负载均衡就是从 upstream 模块定义的后端服务器列表中按照配置的负载策略选取一台服务器接受用户的请求。

7.1、准备 3 个不同端口的 springboot 服务

想要演示负载均衡,我们首先得多搞几个服务,搞一个服务是没法儿演示的。所以我启动了 3 个不同端口(8081,8082,8083)的 springboot 服务,如下:

java -jar /Users/hzz/myself_project/xzll/study-admin/study-admin-service/target/study-admin-service.jar --server.port=8081
java -jar /Users/hzz/myself_project/xzll/study-admin/study-admin-service/target/study-admin-service.jar --server.port=8082
java -jar /Users/hzz/myself_project/xzll/study-admin/study-admin-service/target/study-admin-service.jar --server.port=8083

使用 command+D 对 iterm2 进行分屏 ,最左侧是 8081 端口,中间是 8082,右侧是 8083:深入浅出的 Nginx 超详细讲解接下来我们说一下负载策略再开始。

7.2、nginx 常用的负载策略:

负载策略 描述 特点
轮询 默认方式 1. 每个请求会按时间顺序逐一分配到不同的后端服务器
2. 在轮询中,如果服务器 down 掉了,会自动剔除该服务器
3. 缺省配置就是轮询策略
4. 此策略适合服务器配置相当,无状态且短平快的服务使用
weight 权重方式 1. 在轮询策略的基础上指定轮询的几率
2. 权重越高分配到的请求越多
3. 此策略可以与 least_conn 和 ip_hash 结合使用
4. 此策略比较适合服务器的硬件配置差别比较大的情况
ip_hash 依据 ip 的 hash 值来分配 1. 在 nginx 版本 1.3.1 之前,不能在 ip_hash 中使用权重(weight)
2. ip_hash 不能与 backup 同时使用
3. 此策略适合有状态服务,比如 session
4. 当有服务器需要剔除,必须手动 down 掉
least_conn 最少连接方式 1. 此负载均衡策略适合请求处理时间长短不一造成服务器过载的情况
fair(第三方) 响应时间方式 1. 根据后端服务器的响应时间来分配请求,响应时间短的优先分配
2. Nginx 本身不支持 fair,如果需要这种调度算法,则必须安装 upstream_fair 模块
url_hash(第三方) 依据 URL 分配方式 1. 按访问的 URL 的哈希结果来分配请求,使每个 URL 定向到一台后端服务器
2. Nginx 本身不支持 url_hash,如果需要这种调度算法,则必须安装 Nginx 的 hash 软件包

7.2.1、轮询

轮询策略是默认的,所以只需要如下这样修改配置文件就可以了:深入浅出的 Nginx 超详细讲解重启 nginx 后观察一下:深入浅出的 Nginx 超详细讲解以上图片可以看到,是按 upstream 中的先后顺序来进行轮询的。

7.2.2、weight

weight 指令用于指定轮询机率,weight 的默认值为 1,weight 的数值与访问比率成正比。
接下来我们指定 8082 端口的服务的 weight=2,如下:深入浅出的 Nginx 超详细讲解看下权重策略下的请求结果:深入浅出的 Nginx 超详细讲解可以看到在一轮轮询中,8081 命中 1 次,8082 由于配置了 weight=2 所以命中了 2 次,8083 命中了 1 次。即配置了 weight=2 的 8082 服务,命中几率是 8081 或者 8083 的两倍

7.2.3、ip_hash

设定 ip 哈希很简单,就是在你的 upstream 中 指定 ip_hash;即可,如下:深入浅出的 Nginx 超详细讲解重启 nginx 后看下效果:深入浅出的 Nginx 超详细讲解可以看到,由于我的访问 ip 总是固定的宿主机的 172.30.128.64 根据 hash 算法我的 ip 被匹配给了 8083 端口的服务,所以只要我不换 ip 不管我请求多少次,请求都是被 转发到了 8083 的服务上了。

7.2.4、least_conn

同 ip_hash 一样,设定最小连接数策略也很简单,就是在你的 upstream 中 指定 least_conn;即可,如下:深入浅出的 Nginx 超详细讲解由于我这里最小连接数看不出啥效果,所以就不演示截图了,知道怎么配置最小连接数即可。关于第三方的负载策略。不做过多说明了,可以看看:nginx 官方文档 获取其他的网上资料。

8、动静分离

在说动静分离前,我们要知道为何要做动静分离以及他能解决啥问题,首先,我们常见的 web 系统中会有大量的静态资源文件比如掘金主页面刷新后的 f12 如下:深入浅出的 Nginx 超详细讲解可以看到有很多静态资源,如果将这些资源都搞到后端服务的话,将会提高后端服务的压力且占用带宽增加了系统负载(要知道,静态资源的访问频率其实蛮高的)所以为了避免该类问题我们可以把不常修改的静态资源文件放到 nginx 的静态资源目录中去,这样在访问静态资源时直接读取 nginx 服务器本地文件目录之后返回,这样就大大减少了后端服务的压力同时也加快了静态资源的访问速度,何为静,何为动呢?:

  1. 「静:」 将不常修改且访问频繁的静态文件,放到 nginx 本地静态目录(当然也可以搞个静态资源服务器专门存放所有静态文件)
  2. 「动:」 将变动频繁/实时性较高的比如后端接口,实时转发到对应的后台服务

接下来我们将构造一个 html 页面,然后点击按钮后发送 get 请求到后端接口。流程如下:深入浅出的 Nginx 超详细讲解

8.1、准备工作

首先我们搞个 html(请原谅我这粗糙的前端代码😂😂),内容如下:深入浅出的 Nginx 超详细讲解之后使用

scp /Users/hzz/fsdownload/index_page.html root@172.30.128.65:/usr/local/nginx/test/static

命令将 index_page.html 文件上传到虚拟机。

8.2、修改 nginx.conf 文件

首先我们配置俩 location 规则,一个( /frontend )是读取静态文件,一个(/backend)是转发到 我们配置的 upstream 服务中去。如下:深入浅出的 Nginx 超详细讲解

8.3、演示

首先我们在浏览器输入:http://www.proxytest.com/frontend/ ,可以看到请求返回了一个 html 页面,其实就是我们刚才的  /usr/local/nginx/test/static/index_page.html 文件深入浅出的 Nginx 超详细讲解接着我们输入要查询的人名,之后点击 “调用 get 接口” 按钮,如下:深入浅出的 Nginx 超详细讲解返回数据:深入浅出的 Nginx 超详细讲解查看 nginx 访问日志可以看到两次请求的输出:深入浅出的 Nginx 超详细讲解

ok 到这里动静分离就演示完了,接下来我们看下跨域

9、跨域

9.1、为何会产生跨域?

产生跨域问题的主要原因就在于同源策略,为了保证用户信息安全,防止恶意网站窃取数据,同源策略是必须的,该政策由 Netscape 公司于 1995 年引入浏览器。目前,所有浏览器都实行这个政策。同源策略主要是指三点相同即:「协议+域名+端口 相同的两个请求」,则可以被看做「是同源」的,但如果「其中任意一点存在不同」,则代表是「两个不同源的请求」,同源策略会限制不同源之间的资源交互从而减少数据安全问题。

9.2、跨域演示

首先我在 nginx.conf 文件中加一个 server 配置也即将前后端配成不同的 server 并且监听的端口以及域名名称都不一致,从而造成访问前端服务和后端服务时候 这俩服务不是"同源", 如下:深入浅出的 Nginx 超详细讲解之后我修改 index_page 中的后端地址:深入浅出的 Nginx 超详细讲解重启 nginx,并配置宿主机的 hosts 文件:

172.30.128.65 www.front.com
172.30.128.65:90  www.backend.com

之后在浏览器中测试一下:深入浅出的 Nginx 超详细讲解可以看到浏览器提示我们受同源规则影响我们不能跨域访问资源。造成的原因是我的两个域名解析出来的端口不一致 一个是 80 一个是 90。不符合同源策略,所以必然会有跨域报错。

9.3、nginx 解决跨域

首先想解决跨越就得避免不同源,而我们可不可以 把对后端的代理 放在前端的 server 中呢(也就是说让前后端统一使用一个端口,一个 server_name)?答案是可以的,因为 server 支持多个 location 配置呀(一个 location 处理前端,一个 location 转发后端),我们改下配置文件试一把如下:深入浅出的 Nginx 超详细讲解之后重启 nginx 后在浏览器输入  http://www.xxxadminsystem.com/page/ ,效果如下:深入浅出的 Nginx 超详细讲解上边/page 请求返回了 html 页面之后我们输入参数点击“调用 get 接口”查看到后端接口的调用如下:深入浅出的 Nginx 超详细讲解深入浅出的 Nginx 超详细讲解从上边可以看到,我上边设想的方式是可行的。

  • 当然有些资料上有说使用 设置 header 的方式解决跨域,但是在实际测试中,设置 header 的方式始终没解决跨域,试了好久也没解决掉😂😂,有试过此方式解决的大佬帮忙看看我这是哪里配错了还是咋的在此提前感谢了。

    深入浅出的 Nginx 超详细讲解

10、缓存

在开头我们就介绍过,nginx 代理缓存可以在某些场景下有效的减少服务器压力,让请求快速响应,从而提升用户体验和服务性能,那么 nginx 缓存如何使用呢?在使用及演示前我们先来熟悉下相关的配置以及其含义,知道了这些才能更好的使用 nginx 缓存。

10.1、nginx 缓存配置参数表格一览

指令名称 作用解释 语法 默认配置 示例 作用域
proxy_cache 设置是否开启对后端响应的缓存。 proxy_cache zone | off; proxy_cache off; proxy_cache mycache; # 规定开启 nginx 缓存并且缓存名称为: mycache http, server, location
proxy_cache_valid 配置什么状态码可以被缓存,以及缓存时长 proxy_cache_valid [code …] time; 没有默认值 proxy_cache_valid 200 304 2m; # 对于状态为 200 和 304 的缓存文件,缓存时间是 2 分钟 http, server, location
proxy_cache_key 设置缓存文件的 key proxy_cache_key string; proxy_cache_key proxy_host$request_uri; proxy_cache_key “request_uri $cookie_user”; # 使用 host +请求的 uri 以及 cookie 拼接成缓存 key http, server, location
proxy_cache_path 指定缓存存储的路径,文件名为 cache key 的 md5 值,然后多级目录的话,根据 level 参数来生成,key_zone 参数用来指定在共享内存中缓存数据的名称和内存大小,比如 keys_zone=mycache:100m,inactive 用来指定缓存没有被访问后超时移除的时间,默认是 10 分钟,也可以自己指定比如 inactive=2h ;max_size 用来指定缓存的最大值,超过这个值则会自动移除最近最少使用(lru 淘汰算法)的缓存 这个指令对应的参数很多,具体见官网:proxy_cache_path proxy_cache_path path [levels=levels] [use_temp_path=on|off] keys_zone=name:size [inactive=time] [max_size=size] [min_free=size] [manager_files=number] [manager_sleep=time] [manager_threshold=time] [loader_files=number] [loader_sleep=time] [loader_threshold=time] [purger=on|off] [purger_files=number] [purger_sleep=time] [purger_threshold=time]; proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=mycache:128m inactive=3d max_size=2g; # 设置缓存存放的目录为/data/nginx/cache,并设置缓存名称为 mycache,大小为 128m, 三天未被访问过的缓存将自动清除,磁盘中缓存的最大容量为 2GB。 http
proxy_cache_bypass 定义不从缓存中获取响应数据的条件。如果字符串参数中至少有一个值不为空且不等于” 0 “,则不会从缓存中获取响应: proxy_cache_bypass string …; 没有默认值 proxy_cache_bypass arg_nocache$arg_comment; http, server, location
proxy_cache_min_uses 指定某一个相同请求在几次请求之后才缓存响应内容 proxy_cache_min_uses number; proxy_cache_min_uses 1; proxy_cache_min_uses 3; 规定某一个请求在第 3 次之后才走 nginx 缓存 http, server, location
proxy_cache_use_stale 指定后端服务器在返回什么状态码的情况下可以使用过期的缓存 proxy_cache_use_stale error timeout invalid_header http_500 http_502 http_503 … |off ; proxy_cache_use_stale off; proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504; # 规定服务在出现 error timeout,以及 502,503,504 时可使用过期缓存 http, server, location
proxy_cache_lock 默认不开启,开启后若出现并发重复请求,nginx 只让一个请求去后端读数据,其他的排队并尝试从缓存中读取; proxy_cache_lock on |off; proxy_cache_lock off; proxy_cache_lock on; # 开启缓存锁 http, server, location
proxy_cache_lock_timeout 等待缓存锁(proxy_cache_lock)超时之后将直接请求后端,且结果不会被缓存 proxy_cache_lock_timeout time; proxy_cache_lock_timeout 5s; proxy_cache_lock_timeout 6s; # 等待缓存锁超时(6ms)之后将直接请求后端,结果不会被缓存。 http, server, location
proxy_cache_methods 如果客户端请求方法在该指令中,则响应将被缓存。“GET”和“HEAD”方法总是被添加到列表中,尽管建议显式地指定它 proxy_cache_methods GET|HEAD |POST …; proxy_cache_methods GET HEAD; proxy_cache_methods GET HEAD PUT POST; # 规定 可缓存的方法有 :get head put post http, server, location
………….. ………. ………. ………. ………. ……….

事实上,「ngx_http_proxy_module」模块中「代理缓存 proxy_cache 相关的指令远不止这些」,在这里我不可能把所有都列出来,只列出上边那几个已经很占篇幅了,如果有需要请参考: nginx 官方文档, 在官方文档中,详细描述了 ngx_http_proxy_module 模块(包含了 proxy_cache 部分)的各种指令、作用以及使用方式,相信在遇到困难和疑惑时,官方文档永远是你最好的老师!如下:深入浅出的 Nginx 超详细讲解

10.2、nginx 缓存使用与效果演示

「首先我们想要的效果是:」「url+参数一样」 的请求的结果,缓存到 nginx。

接下来我们修改下 nginx.conf 文件,如下:

http{
    ...
    # 指定缓存存放目录为/usr/local/nginx/test/nginx_cache_storage,并设置缓存名称为 mycache,大小为 64m, 1 天未被访问过的缓存将自动清除,磁盘中缓存的最大容量为 1gb
    proxy_cache_path /usr/local/nginx/test/nginx_cache_storage levels=1:2 keys_zone=mycache:64m inactive=1d max_size=1g;
    ...

    server{
        ...
        #  指定 username 参数中只要有字母 就不走 nginx 缓存  
        if ($arg_username ~ [a-z]) {
             set $cache_name "no cache";
        }

        location  /interface {
                   proxy_pass http://mybackendserver/;
                   # 使用名为 mycache 的缓存空间
                   proxy_cache mycache;
                   # 对于 200 206 状态码的数据缓存 2 分钟
                   proxy_cache_valid 200 206 1m;
                   # 定义生成缓存键的规则(请求的 url+参数作为缓存 key)
                   proxy_cache_key $host$uri$is_args$args;
                   # 资源至少被重复访问 2 次后再加入缓存
                   proxy_cache_min_uses 3;
                   # 出现重复请求时,只让其中一个去后端读数据,其他的从缓存中读取
                   proxy_cache_lock on;
                   # 上面的锁 超时时间为 4s,超过 4s 未获取数据,其他请求直接去后端
                   proxy_cache_lock_timeout 4s;
                   # 对于请求参数中有字母的 不走 nginx 缓存
                   proxy_no_cache $cache_name; # 判断该变量是否有值,如果有值则不进行缓存,没有值则进行缓存
                   # 在响应头中添加一个缓存是否命中的状态(便于调试)
                   add_header Cache-status $upstream_cache_status;    
        }
        ...
}

深入浅出的 Nginx 超详细讲解

ps: 在上边配置文件中除了缓存相关的配置,我们还加了一个参数:

add_header Cache-status $upstream_cache_status;

这个参数可以方便从响应头看到是否命中了 nginx 缓存,方便我们观察,其不同的值有不同的含义,upstream_cache_status 的值集合如下:

MISS:请求未命中缓存
HIT:请求命中缓存。
EXPIRED:请求命中缓存但缓存已过期。
STALE:请求命中了陈旧缓存。
REVALIDDATED:Nginx 验证陈旧缓存依然有效。
UPDATING:命中的缓存内容陈旧,但正在更新缓存。
BYPASS:响应结果是从原始服务器获取的。

nginx.conf 文件配好后,在请求之前先看下数据库 “hzznb” 和 “张无忌” 都是存在的,如下:深入浅出的 Nginx 超详细讲解

接下来我们分别查询 张无忌 和 hzznb 来看看缓存命中情况:

查询 usernam=张无忌; 第一次(未命中,因为此 url+参数 之前没请求过缓存中确实没有):深入浅出的 Nginx 超详细讲解第二次也未命中就不截图了

第三次(未命中):深入浅出的 Nginx 超详细讲解第四次(「命中」):深入浅出的 Nginx 超详细讲解

查询 usernam=hzznb; 第一次(未命中):深入浅出的 Nginx 超详细讲解第 2,3 次也都是 miss 即未命中(截图略)

第四次:(仍然是未命中,说明我们在 nginx.conf 中配置的规则:”参数中带字母则不缓存“ 生效了!):深入浅出的 Nginx 超详细讲解

11、黑白名单

nginx 黑白名单比较简单,allow 后配置你的白名单,deny 后配置你的黑名单,在实际使用中,我们一般都是建个黑名单和白名单的文件然后再 nginx.copnf 中 incluld 一下,这样保持主配置文件整洁,也好管理。下边我为了方便就直接在主配置写了。

11.1、语法作用域

关于黑白名单的语法和作用,我们直接看下官网的示例:深入浅出的 Nginx 超详细讲解可以看到 ip 可以是 ipv4 也可以是 ipv6 也可以按照网段来配置,当然 ip 黑白配置可以在 http,server,location 和 limit_except 这几个域都可以区别只是作用粒度大小问题。当然 nginx 建议我们使用 ngx_http_geo_module 这个库,ngx_http_geo_module 库支持 按地区、国家进行屏蔽,并且提供了 IP 库,当需要配置的名单比较多或者根据地区国家屏蔽时这个库可以帮上大忙。

下面我们配置并演示一下:

11.2、黑白名单演示

「允许任何 ip 访问前端,然后禁止 172.30.128.64 访问后端」,nginx.conf 文件如下:深入浅出的 Nginx 超详细讲解访问前端,走/page 这个 location(可以访问成功):深入浅出的 Nginx 超详细讲解访问后端,走 interface 这个 location(显示 403 被禁止了):深入浅出的 Nginx 超详细讲解

12、nginx 限流

Nginx 主要有两种限流方式:按并发连接数限流(ngx_http_limit_conn_module)、按请求速率限流(ngx_http_limit_req_module 使用的令牌桶算法)。

关于  ngx_http_limit_req_module 模块,里边有很多种限流指令,官网资料一览:深入浅出的 Nginx 超详细讲解我们下面使用 ngx_http_limit_req_module 模块中的 limit_req_zone 和 limit_req 这两个指令来达到限制单个 IP 的「请求速率」 的目的。

12.1、nginx 限流配置解释

在 nginx.conf 中添加限流配置如下:

http{
    ...
    # 对请求速率限流
    limit_req_zone $binary_remote_addr zone=myRateLimit:10m rate=5r/s;

    server{
        location /interface{
            ...
            limit_req zone=myRateLimit burst=5  nodelay;
            limit_req_status 520;
            limit_req_log_level info;
        }
    }
}

深入浅出的 Nginx 超详细讲解对上图标红的配置做个解释:

「$binary_remote_addr」:表示基于 remote_addr(客户端 IP) 来做限流「zone=myRateLimit:10m」:表示使用 myRateLimit 来作为内存区域(存储访问信息)的名字,大小为 10M,1M 能存储 16000 IP 地址的访问信息,10M 可以存储 16W IP 地址访问信息「rate=5r/s」:表示相同 ip 每秒最多请求 5 次,nginx 是精确到毫秒的,也就是说此配置代表每 200 毫秒处理一个请求,这意味着自上一个请求处理完后,若后续 200 毫秒内又有请求到达,将拒绝处理该请求(如果没配 burst 的话)
「burst=5」:(英文 爆发 的意思),意思是设置一个大小为 5 的缓冲队列,若同时有 6 个请求到达,Nginx 会处理第一个请求,剩余 5 个请求将放入队列,然后每隔 200ms 从队列中获取一个请求进行处理。若请求数大于 6,将拒绝处理多余的请求,直接返回 503
「nodelay」:针对的是 burst 参数,burst=5 nodelay 这个配置表示被放到缓冲队列的这 5 个请求会立马处理,不再是每隔 200ms 取一个了。但是值得注意的是,即使这 5 个突发请求立马处理并结束,后续来了请求也不一定不会立马处理,因为虽然请求被处理了但是请求所占的坑并不会被立即释放,而是只能按 200ms 一个来释放,释放一个后 才将等待的请求 入队一个。
「另外两个:」 limit_req_status=520 表示当被限流后,nginx 的返回码,limit_req_log_level info 代表日志级别

「注意:」 如果不开启 nodelay 且开启了 burst 这个配置,那么将会严重影响用户体验(你想想假设 burst 队列长度为 100 的话每 100ms 处理一个,那队列最后那个请求得等 10000ms=10s 后才能被处理,那不超时才怪呢此时 burst 已经意义不大了)所以一般情况下 建议 burst 和 nodelay 结合使用,从而尽可能达到速率稳定,但突然流量也能正常处理的效果。

12.2、nginx 限流(针对请求速率)

为了突出 burst 和 nodealy 的作用,我们一步一步演示

12.2.1、限制每秒同一 ip 最多访问 5 次/1s

修改 nginx.conf,把 burst=5 nodelay 注释掉,如下:深入浅出的 Nginx 超详细讲解上边的配置意味着每秒最多处理 5 次同样 ip 的请求,我们使用 jmeter 设置 1 个线程循环 10 次,间隔时间为 100ms,效果如下(5 成功 5 失败):深入浅出的 Nginx 超详细讲解如果我们将间隔时间改 200 的话,是都可以成功的,因为一秒最多 5 次精确到毫秒其实就是最多 200ms 一次,而 200ms 一次正好没超过我们配置的 5r/s 的速率:深入浅出的 Nginx 超详细讲解运行 jemeter 发现间隔 200ms 访问一次的请求都成功了:深入浅出的 Nginx 超详细讲解

12.2.2、打开 burst 参数并设置成 5

现在我们的速率不变还是最多 5 次一秒,但是设置 burst=5 代表缓冲队列的长度为 5,nginx 每隔 200ms,从缓冲队列拿一个进行处理,配置如下:深入浅出的 Nginx 超详细讲解

之后我们配置线程数量为 15,每隔 100ms 掉一次,效果如下:深入浅出的 Nginx 超详细讲解可以看到共计 6 个请求被处理,第一个是被 nginx 进程直接处理,之后往 burst 塞了 5 个(每隔 200ms 拿一个进行处理)剩下的都被返回了 520 状态码代表被拒绝了,我们找一个(1-10 这个)被拒的看看状态码:深入浅出的 Nginx 超详细讲解

12.2.3、打开 nodelay

我们上边说过「打开 nodlay 的话」,代表放到 burst 队列的请求直接处理 ,「不再按速率 200ms/次 拿了」,配置如下:深入浅出的 Nginx 超详细讲解接下来我们还是配置 15 个线程,然后每个线程间隔 100ms 请求一次,看下效果:深入浅出的 Nginx 超详细讲解可以很明显的看到:开启 nodelay 后响应时间 10 几秒明显比不开启 nodelay 快很多,但是请求成功的还是 6 个,因为就像我们上边说的 ngdelay 虽然会即时处理,但是释放坑位是 200ms 释放一个 (也就是说即时开启了 nodelay 但释放令牌的速度是不变的) ,所以 nodelay 参数本质上并没有提高访问速率,而仅仅是让处于 burst 队列的请求 ”被快速处理“ 罢了。

12.3、nginx 限流(针对连接数量)

针对连接数量的限流和速率不一样,即使你速率是 1ms 一次,只要你连接数量不超过设置的,那么也访问成功。如果连接数超过设置的值将会请求失败。值得注意的是他是 ngx_http_limit_conn_module 模块中的,不要和 速率限流的 ngx_http_limit_req_module 模块搞混了。

配置如下:

http{
    # 针对 ip  对请求连接数限流
    ...
    limit_conn_zone $binary_remote_addr zone=myConnLimit:10m; 
    ...

    server{
       ...
       limit_conn myConnLimit 12;
    }
}    

深入浅出的 Nginx 超详细讲解简单对以上标黄处说明一下,limit_conn_zone $binary_remote_addr zone=myConnLimit:10m;代表的意思 是 基于连接数量限流,限流的对象是 ip 名称是 myConnLimit 存储空间大小 10mb(即存放某 ip 的访问记录),limit_conn myConnLimit 12;标识该 ip 最大支持 12 个连接超过则返回 503(被限流后状态码默认是 503,当然你也可以修改返回码 像上边的 针对请求速率限流 ,返回码就是 我修改的 520)。

使用 jmeter 搞 20 个线程,0 延迟,演示下效果:深入浅出的 Nginx 超详细讲解可以看到由于我们配置的并发数是 12,所以 20 个连接中有 8 个都被限了。这个理解起来似乎比速率限流(ngx_http_limit_req_module)简单些我们就不过多解释了。

13、https 配置

说到 https 大家应该并不陌生,我这里不啰嗦介绍了。一般我们安装的 nginx 模块都是不包含 ssl 模块的,所以需要手动安装下。安装完之后我们再说如何配置 https。

13.1、https_ssl 模块安装

首先我们使用 nginx -V (大写) 看下有没有安装 https_ssl 模块:

深入浅出的 Nginx 超详细讲解可以看到我已经安装了,实际上我可以直接使用 https_ssl 模块了但是为了文章完善性。我下边说一下 https_ssl 模块的安装步骤:

注意:下边的 https_ssl 模块是安装到我的旧版本 1.23.0 去了,而我当前生效运行的 nginx 是 1.24.0 ,使用ps -ef | grep nginx 命令可看出当前运行的 nginx:深入浅出的 Nginx 超详细讲解下边的安装 https_ssl 仅仅是为了内容全面,「而不是真正使用 1.23.0 版本的 nginx 或者 1.23.0 版本的 https_ssl 模块」(本文使用的版本都是 1.24.0 ,1.23.0 是我之前的一个 nginx 版本)。

没有 ssl 模块情况下,首先我们进入 nginx 解压目录:我的是 /usr/local/nginx/nginx-1.23.0/ 「总之就是找到你的 nginx 解压目录」)  ,之后在该目录执行命令 ./configure --prefix=/usr/local/nginx --with-http_ssl_module 来安装 ssl 模块,如下图:深入浅出的 Nginx 超详细讲解安装好后,在/usr/local/nginx/nginx-1.23.0/ 目录中执行 make 命令,重新编译 nginx,注意此处无需 make install,make 成功后,我们执行 cp ./objs/nginx /usr/local/nginx/sbin/ 命令将编译后的文件覆盖到 /usr/local/nginx/sbin/,之后执行 /usr/local/nginx/sbin/nginx -V,可看到 ssl 模块就安装成功了。操作记录图如下:深入浅出的 Nginx 超详细讲解

13.2、域名购买&解析&ssl 证书申请与验证

要配置 ssl 最好是有个域名,所以我花一杯酱香拿铁的💰买了一个域名(在买域名前需要进行域名模板实名,具体操作去腾讯云官网看这里不啰嗦了),我买的域名是: 「hzznb-xzll.xyz」

ps: 如果我没记错的话这是我的第一个域名,虽然他 10 块钱但是我很珍惜他😄😄,接下来可以在我的域名中看到已经成功了。

深入浅出的 Nginx 超详细讲解有域名了,下一步就需要配置 DNS 解析,让别人通过公网也访问到呀,所以点击上图的 「解析」 按钮后,来到下边的页面添加解析记录,如下:(主机记录 www 的结果是:www.hzznb-xzll.xyz ,@的结果是 hzznb-xzll.xyz, *的意思为泛解析,对应的结果是 xxx.hzznb-xzll.xyz):深入浅出的 Nginx 超详细讲解配置好解析后,我们开始申请一个免费的 ssl 证书并将其和我上边的域名绑定,如下:深入浅出的 Nginx 超详细讲解提交申请后,因为我们选择的手动 DNS 验证,所以接下来按照人家的提示手动配置(这个操作比较重要,不做这一步,证书验证肯定过不去,所以必须做并且做对):深入浅出的 Nginx 超详细讲解之后我们在我的证书看到已经完成验证,此时就可以下载 ssl 证书,然后配置我们的 nginx 了:深入浅出的 Nginx 超详细讲解因为我们接下来要配置到 nginx 反向代理服务器,所以这里选择下载 nginx 类型的 ssl 证书:深入浅出的 Nginx 超详细讲解

13.3、上传并配置 nginx 以及演示

下载到本地后是个 zip 我们解压之后会看到里边有 4 个文件分别是:

hzznb-xzll.xyz_bundle.crt 证书文件
hzznb-xzll.xyz_bundle.pem 证书文件(可忽略该文件)
hzznb-xzll.xyz.key 私钥文件
hzznb-xzll.xyz.csr CSR 文件 (CSR 文件是申请证书时由您上传或系统在线生成的,提供给 CA 机构。安装时可忽略该文件。)

之后我们仅需要把  hzznb-xzll.xyz.key 和 hzznb-xzll.xyz_bundle.crt 这俩货上传到我新建的 certificate 文件夹:/usr/local/nginx/certificate/  ,操作如下:深入浅出的 Nginx 超详细讲解上传完成:深入浅出的 Nginx 超详细讲解ssl 证书准备好后,我们需要配置一个 https 的 server(如下配置:)
下边的指令名称都有注释说明了各个指令是干啥的,我也就不啰嗦了:

# --------------------HTTPS 配置---------------------
    server {
        #SSL 默认访问端口号为 443
        listen 443 ssl; 
        #填写绑定证书的域名 
        server_name www.hzznb-xzll.xyz hzznb-xzll.xyz; 
        #请填写证书文件的相对路径或绝对路径
        ssl_certificate /usr/local/nginx/certificate/hzznb-xzll.xyz_bundle.crt; 
        #请填写私钥文件的相对路径或绝对路径
        ssl_certificate_key /usr/local/nginx/certificate/hzznb-xzll.xyz.key; 
        #停止通信时,加密会话的有效期,在该时间段内不需要重新交换密钥
        ssl_session_timeout 5m;
        #服务器支持的 TLS 版本
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; 
        #请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。
        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; 
        #开启由服务器决定采用的密码套件
        ssl_prefer_server_ciphers on;
    }    

可以看到我们上传到 /usr/local/nginx/certificate/目录下的 .crt 和.key 文件被使用到了。

现在我们仅仅是配好一个 https 类型的 server,光一个 server 没法访问也没意思,我们需要让给他配置上游服务以及路由规则,这里我们直接使用我们上边的 80 端口那个 server 中的 location 配置,直接 copy 过来,「https server 的配置」 如下:深入浅出的 Nginx 超详细讲解到这里,就可以通过 https 方式访问我们的页面和接口了。但是需要注意的是,由于我们的协议和域名换了,所以 index_page.html 里边的接口地址也得变了,如下:深入浅出的 Nginx 超详细讲解之后我们将 index_page.html 上传到 nginx 的 /usr/local/nginx/test/static/ 目录,并重启 nginx(nginx -s reload) 之后在浏览器访问试试,

注意:我最开始在配置 dns 解析时记录值配的是公网 ip,但是我发现好像不好使,总是连接失败:深入浅出的 Nginx 超详细讲解,后来改成 局域网 ip 172.30.128.65 发现可行了深入浅出的 Nginx 超详细讲解

修改云解析 dns 记录值为 172.30.128.65 后,访问效果如下:深入浅出的 Nginx 超详细讲解深入浅出的 Nginx 超详细讲解从上边可以看到,我们可以通过 https 方式访问前端页面和后台接口了。

13.4、http 跳转 https

一般情况下为了安全都不使用 http 方式访问页面 or 后台服务,但是有的人就是喜欢使用 http 访问怎么办?好说,我给 http 加个跳转,你访问 http://xxx.com 我给你跳转到 https://xxx.com,  想达到此效果需要先修改下 nginx.conf 文件:

server_name www.hzznb-xzll.xyz hzznb-xzll.xyz;
# 重定向到目标地址
return 301 https://$server_name$request_uri;

深入浅出的 Nginx 超详细讲解之后重启 nginx 看下演示效果:

首先请求 http://www.hzznb-xzll.xyz/page/ 会返回重定向地址,让你重新定向到 https://www.hzznb-xzll.xyz/page/深入浅出的 Nginx 超详细讲解请求(这一步浏览器会自动发起请求,无需手动点击或刷新啥的)定向后的目标地址: https://www.hzznb-xzll.xyz/page/深入浅出的 Nginx 超详细讲解可以看到 http 方式的请求被成功重定向到了 https server(443 端口对应的 server)。

14、压缩

压缩功能比较实用尤其是处理一些大文件时,而 gzip 是规定的三种标准 HTTP 压缩格式之一。目前绝大多数的网站都在使用 gzip 传输 HTML 、CSS 、 JavaScript 等资源文件。需要知道的是,并不是每个浏览器都支持 gzip 压缩,如何知道客户端(浏览器)是否支持 压缩 呢? 可以通过观察 某请求头中的 Accept-Encoding 来观察是否支持压缩,另外只有客户端支持也不顶事,服务端得返回 gzip 格式的文件呀,那么这件事 nginx 可以帮我们做,我们可以通过 Nginx 的配置来让服务端支持 gzip。服务端返回压缩文件后浏览器进行解压缩从而展示正常内容。

14.1、压缩前

首先应该明确的是我当前是没开启压缩的:深入浅出的 Nginx 超详细讲解

其次为了方便看出效果,我们先将之前那个 index_page.html 文件加点图片,加点文字给他的文件大小弄大点,如下:深入浅出的 Nginx 超详细讲解这里无需重启 nginx,看下「没开启压缩」 时候的效果:深入浅出的 Nginx 超详细讲解深入浅出的 Nginx 超详细讲解

14.2、压缩后

想要压缩就得配置 nginx,我们修改 nginx.conf 文件,在 http 指令块添加如下内容:

http {
    # 开启/关闭 压缩机制
    gzip on;
    # 根据文件类型选择 是否开启压缩机制
    gzip_types text/plain application/javascript text/css application/xml text/javascript image/jpeg image/jpg image/gif image/png  application/json;
    # 设置压缩级别,一共 9 个级别  1-9   ,越高资源消耗越大 越耗时,但压缩效果越好,
    gzip_comp_level 9;
    # 设置是否携带 Vary:Accept-Encoding 的响应头
    gzip_vary on;
    # 处理压缩请求的 缓冲区数量和大小
    gzip_buffers 32 64k;
    # 对于不支持压缩功能的客户端请求 不开启压缩机制
    gzip_disable "MSIE [1-6]."; # 比如低版本的 IE 浏览器不支持压缩
    # 设置压缩功能所支持的 HTTP 最低版本
    gzip_http_version 1.1;
    # 设置触发压缩的最小阈值
    gzip_min_length 2k;
    # off/any/expired/no-cache/no-store/private/no_last_modified/no_etag/auth 根据不同配置对后端服务器的响应结果进行压缩
    gzip_proxied any;
} 

几个指令的作用在注释中写明了这里不再过多解释。接下来我们重启 nginx,然后看下压缩前后的效果:

html 文件压缩:深入浅出的 Nginx 超详细讲解接口响应压缩:深入浅出的 Nginx 超详细讲解可以看到不管是 html 还是接口响应数据, 压缩后的体积变得非常小了,压缩的效果还是不错的,但是值得注意的是压缩后虽然体积变小了,但是响应的时间会变长,因为压缩/解压也需要时间呀!压缩功能似乎有点:「用时间换空间的感觉!」,当然压缩级别可以调的,你可以选择较低级别的压缩,这样既能实现压缩功能使得数据包体积降下来,同时压缩时间也会缩短是比较折中的一种方案(我在演示时为了效果,配置的压缩级别是 9 ,一共 9 个级别, 9 是最高级别的压缩等级)。

15、其他一些比较常用的指令与说明

关于 nginx 的指令其实太多了,有些常用的指令不说一下的话,有时候遇见了不懂啥意思,所以这里说一下 nginx 几个比较常用的指令(上边 nginx.conf 文件解读 以及某些小节中已经说了很多指令了,这里也不管重不重复吧,说明几个我觉得有必要讲的几个)

15.1、rewrite

rewrite 指令是通过正则表达式来改变 URI。可以同时存在一个或多个指令。需要按照顺序依次对 URL 进行匹配和处理,常用于重定向功能。

rewrite 语法如下:

语法: rewrite 正则表达式 要替换的内容 [flag];
默认:
作用域: server, location, if

其中 flag 有如下几个值:

「last:」  本条规则匹配完成后,继续向下匹配新的 location URI 规则。
「break:」  本条规则匹配完成即终止,不再匹配后面的任何规则。
「redirect:」  返回 302 临时重定向,浏览器地址会显示跳转新的 URL 地址。
「permanent:」  返回 301 永久重定向。浏览器地址会显示跳转新的 URL 地址。

下边我们演示下四种重写的效果
首先修改 nginx.conf 文件:

  server {
      listen 80 default;
      charset utf-8;
      server_name www.hzznb-xzll.xyz hzznb-xzll.xyz;

      # 临时(redirect)重定向配置
      location /temp_redir {
          rewrite ^/(.*) https://www.baidu.com redirect;
      }
      # 永久重定向(permanent)配置
      location /forever_redir {

          rewrite ^/(.*) https://www.baidu.com permanent;
      }

      # rewrite last 配置
      location /1 {
        rewrite /1/(.*) /2/$1 last;
      }
      location /2 {
        rewrite /2/(.*) /3/$1 last;
      }
      location /3 {
        alias  '/usr/local/nginx/test/static/';
        index location_last_test.html;
      }
    }

深入浅出的 Nginx 超详细讲解

「last 配置:」 可以看到我们定义 访问 http://hzznb-xzll.xyz/1/ 的请求被替换为 http://hzznb-xzll.xyz/2/ 之后再被替换为 http://hzznb-xzll.xyz/3/  最后找到/usr/local/nginx/test/static/location_last_test.html 这个文件并返回。
「redirect 配置:」 当访问 http://hzznb-xzll.xyz/temp_redir/ 这个请求会临时(302)重定向到百度页面
「permanent 配置:」 当访问 http://hzznb-xzll.xyz/forever_redir/ 这个请求会永久(301)重定向到百度页面

效果如下:深入浅出的 Nginx 超详细讲解

15.2、if

该指令用于条件判断,并且根据条件判断结果来选择不同的配置,其作用于为:server/location 块。这个指令比较简单,因为编程中 if 语句都是非常高频使用的,但是里边怎么写 就得说说 nginx 的全局变量了,因为我们很多时候,都是在对 比如:url  参数 ip 域名等等做比对或者判断(一般都使用正则的方式),而这些都在 nginx 全局变量中可以拿到,比如下边这个 if 判断就用到了全局变量:

     # 指定 username 参数中只要有字母 就不走 nginx 缓存
     if ($arg_username ~ [a-z]) {
         set $cache_name "no cache";
     }

正则表达式就不多说了,具体使用时查一下,我们下边列一下 nginx 的全局变量:

15.3、nginx 全局变量

变量 解释
$time_local 本地时间
$time_iso8601 ISO 8601 时间格式
$arg_name 请求中的的参数名,即“?”后面的 arg_name=arg_value 形式的 arg_name
$args 与$query_string 相同 等于 URL 当中的参数(GET 请求时),如 a=1&b=2
$document_uri 相同这个变量指当前的请求,不包括任何参数见args)
$request_uri 包含请求参数的原始 URI,不包含主机名,如:/aaa/bbb.html?a=1&b=2
$is_args 如果 URL 包含参数则为?,否则为空字符串
$query_string 与$args 相同 等于 URL 当中的参数(GET 请求时),如 a=1&b=2
$uri 当前请求的 URI,不包含任何参数
$remote_addr 获取客户端 ip
$binary_remote_addr 客户端 ip(二进制)
$remote_port 客户端 port
$remote_user 用于基本验证的用户名。
$host 请求主机头字段,否则为服务器名称,如:https://www.hzznb-xzll.xyz
$proxy_host proxy_pass 指令设置的后端服务器的域名(或者 IP 地址)
$proxy_port proxy_pass 指令设置的后端服务器的监听端口。
$http_host server_port 两个变量的结合
$request 用户请求信息,如:GET ?a=1&b=2 HTTP/1.1
$request_time 请求所用时间,单位毫秒
$request_method 请求的方法 比如 get post put delete update 等
$request_filename 当前请求的文件的路径名,由 root 或 alias 和 URI request 组合而成,如:/aaa/bbb.html
$status 请求的响应状态码,如:200
$body_bytes_sent 响应时送出的 body 字节数数量。即使连接中断,这个数据也是精确的,如:40,传输给客户端的字节数,响应头不计算在内;这个变量和 Apache 的 mod_log_config 模块中的“%B”参数保持兼容
$content_length 等于请求行的“Content_Length”的值
$content_type 等于请求行的“Content_Type”的值
$http_referer 引用地址
$http_user_agent 客户端 agent 信息,如:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 这个可以用来区分手机端还是 pc 端
$document_root 针对当前请求的根路径设置值
$hostname 主机名称
$http_cookie 客户端 cookie 信息
$cookie_COOKIE cookie COOKIE 变量的值
$limit_rate 这个变量可以限制连接速率,0 表示不限速
$request_body 记录 POST 过来的数据信息
$request_body_file 客户端请求主体信息的临时文件名
$scheme HTTP 方法(如 http,https)
$request_completion 如果请求结束,设置为 OK. 当请求未结束或如果该请求不是请求链串的最后一个时,为空(Empty),如:OK
$server_protocol 请求使用的协议,通常是 HTTP/1.0 或 HTTP/1.1,如:HTTP/1.1
$server_addr 服务器 IP 地址,在完成一次系统调用后可以确定这个值
$server_name 响应请求的服务器名称
$server_port 请求到达服务器的端口号,如:80
$connection 连接序列号
$connection_requests 当前通过连接发出的请求数
$nginx_version nginx 版本
$pid 工作进程的 PID
$pipe 如果请求来自管道通信,值为“p”,否则为“.”
$proxy_protocol_addr 获取代理访问服务器的客户端地址,如果是直接访问,该值为空字符串
$realpath_root 对应于当前请求的根目录或别名值的绝对路径名,所有符号连接都解析为真实路径。

可以看到全局变量比较多,但是没关系在使用时再去详查就好了。

15.4、auto_index

一般用于快速搭建静态资源网站,比如我要给自己搞个书籍网站里边放些好书,以便需要时查看阅读,首先建一个 book 文件夹并往里放几本书:深入浅出的 Nginx 超详细讲解

之后我们配置 nginx, 使得访问 http://hzznb-xzll.xyz/book/ 时,返回 /usr/local/nginx/test/book/目录下的书籍,配置如下:

      location /book/ {
          root /usr/local/nginx/test;
          autoindex on; # 打开 autoindex,,可选参数有 on | off
          autoindex_format html; # 以 html 的方式进行格式化,可选参数有 html | json | xml
          autoindex_exact_size on; # 修改为 off,会以 KB、MB、GB 显示文件大小,默认为 on 以 bytes 显示出⽂件的确切⼤⼩
          autoindex_localtime off; # 显示⽂件时间 GMT 格式
      }

深入浅出的 Nginx 超详细讲解

重启 nginx 并在浏览器输入  http://hzznb-xzll.xyz/book/ (注意 book 后边的斜线 / 不能去掉,否则 404 了,具体原因我们下边马上会说),看下效果:深入浅出的 Nginx 超详细讲解

15.5、root&alias

root 和 alias 这俩货一般都是用于指定静态资源目录,但是还是有挺大区别的,虽然这是个小知识点但是如果你不清楚规则,很容易走弯路,所以这里阐明并演示这俩的区别。

15.5.1、root

说先说 root:

语法: root path;
默认值: root html;
作用域: http, server, location, if in location

为请求设置根目录。例如,使用以下配置:

location /static/ {
    root /usr/local/nginx/test;
}

此时,当你请求 http://www.hzznb-xzll.xyz/static/image2.jpg 时,/usr/local/nginx/test/static/image2.jpg  文件将被作为响应内容响应给客户端,也就是说 :
「root 指令会 将 /static/ 拼接到 /usr/local/nginx/test 后边
即完整目录路径为: /usr/local/nginx/test/static/」
演示效果如下:深入浅出的 Nginx 超详细讲解

15.5.2、alias

「alias」 中文意思别名,这个和 root 最大区别就是 「不会进行拼接」,下边我们改下 nginx.conf 文件来演示下:

location /static { # 注意一般 alias 的 url 都不带后边的/
     alias /usr/local/nginx/test/; # 使用 alias 时  这里的目录最后边一定要加/ 否则就 404
}

深入浅出的 Nginx 超详细讲解上边配置的意思就是当前你访问 http://www.hzznb-xzll.xyz/static/image2.jpg 时,nginx 会去 alias 配置的路径:/usr/local/nginx/test/目录下找 image2.jpg 这个文件从而返回。比如我现在的/usr/local/nginx/test/目录下没有 image2.jpg 文件则返回 404,如下:深入浅出的 Nginx 超详细讲解之后我们 copy image2.jpg 文件到 /usr/local/nginx/test/ 目录,然后看下效果:深入浅出的 Nginx 超详细讲解可以看到成功返回了。

15.5.3、proxy_pass 中的斜线与 root 和 alias 的相似之处

在我们上边的负载均衡以及动静锋利等等演示中,可以看到我们的 proxy_pass 的配置基本上都是这么配的:

proxy_pass http://mybackendserver/

这里有个东西和 root 与 alias 的规则很像,所以我们在这里也提一下,就是 http://mybackendserver/  后边这个斜线 /,如果你不写 / 则会将 location 的 url 拼接到路径后边,如果你写了则不会,下边我们演示下这样更直观些。修改 nginx.conf 如下:深入浅出的 Nginx 超详细讲解interface 这个 location 不会拼接 interface 到 http://mybackendserver/  后边,如下:深入浅出的 Nginx 超详细讲解

interface2 这个 location 「会拼接」 interface2 到 http://mybackendserver/  后边(从而导致接口 404),如下:深入浅出的 Nginx 超详细讲解

「看出来了吗,proxy_pass 值后边加斜线和不加斜线 ,区别是很大的。不加斜线 有点像 root(会拼接),加了斜线 有点像 alias (不会进行 拼接)。要牢记这个事情。」

15.6、upstream 中常用的几个指令

在 upstream 中,有些指令也是比较常用的所以我们这里也列一下:

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

16、重试策略

16.1、服务不可用重试

关于重试策略我们这里也说一下,重试是在发生错误时的一种不可缺少的手段,这样当某一个或者某几个服务宕机时(因为我们现在大多都是多实例部署),如果有正常服务,那么将请求 重试到正常服务的机器上去。

下边我们先修改下 nginx.conf 文件:

    upstream mybackendserver {
        # 60 秒内 如果请求 8081 端口这个应用失败
        # 3 次,则认为该应用宕机 时间到后再有请求进来继续尝试连接宕机应用且仅尝试 1 次,如果还是失败,
        # 则继续等待 60 秒...以此循环,直到恢复
        server 172.30.128.64:8081 fail_timeout=60s max_fails=3; 

        server 172.30.128.64:8082;
        server 172.30.128.64:8083;
    }

深入浅出的 Nginx 超详细讲解

16.2、错误重试

错误重试是你可以配置哪些状态下  才会执行重试,比如如下这个配置:

 # 指定哪些错误状态才执行 重试 比如下边的 error 超时,500,502,503 504
proxy_next_upstream error timeout http_500 http_502 http_503 http_504;

接下来我们使用 idea 启动 8082 端口,然后打个断点让 8082 这个服务超时(验证下超时重试),效果如下:深入浅出的 Nginx 超详细讲解可以看到 8082 因为超时,重试给 8081 然后 8081 也不可用重试给 8083 最终 8083 返回数据。

16.3、关于 backup

Nginx 支持设置备用节点,当所有线上节点都异常时会启用备用节点,同时备用节点也会影响到失败
重试的逻辑。

我们可以通过 backup 指令来定义备用服务器,backup 有如下特征:

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

接着我们修改下 nginx.conf 文件演示下 backup 的作用:

upstream mybackendserver {
    server 172.30.128.64:8081 fail_timeout=60s max_fails=3; # 60 秒内 如果请求某一个应用失败 3 次,则认为该应用宕机 时间到后再有请求进来继续尝试连接宕机应用且仅尝试 1 次,如果还是失败,则继续等待 60 秒...以此循环,直到恢复
    server 172.30.128.64:8082;
    server 172.30.128.64:8083 backup; # 设置 8083 位备机
}

深入浅出的 Nginx 超详细讲解

之后我们启动 8081 8082 8083 三个服务,然后先停掉 8081 再停掉 8082 看看效果:

深入浅出的 Nginx 超详细讲解可以看到即使 8081 不可用也只是去 8082 重试而不会到备机重试,如果 8081 8082 都不可用则请求重试到备机:8083

17、最后

为了方便粘贴以及观察,这里贴出我机器上完整的 nginx.conf 文件,如下:

17.1、贴出完整 nginx.conf 文件

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    # log_format  debug  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';
    #
    log_format  debug  ' $remote_user [$time_local]  $http_x_Forwarded_for $remote_addr  $request '  
                      '$http_x_forwarded_for '  
                      '$upstream_addr '  
                      'ups_resp_time: $upstream_response_time '  
                      'request_time: $request_time'; 

    access_log  /var/log/nginx/access.log  debug;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    upstream mybackendserver {
        server 172.30.128.64:8081 fail_timeout=60s max_fails=3; # 60 秒内 如果请求某一个应用失败 3 次,则认为该应用宕机 时间到后再有请求进来继续尝试连接宕机应用且仅尝试 1 次,如果还是失败,则继续等待 60 秒...以此循环,直到恢复
        server 172.30.128.64:8082;
        server 172.30.128.64:8083 backup; # 设置 8083 位备机
    }

     # 开启/关闭 压缩机制
    gzip on;
    # 根据文件类型选择 是否开启压缩机制
    gzip_types text/plain application/javascript text/css application/xml text/javascript image/jpeg image/jpg image/gif image/png  application/json;
    # 设置压缩级别,越高资源消耗越大越耗时,但压缩效果越好
    gzip_comp_level 9;
    # 设置是否携带 Vary:Accept-Encoding 的响应头
    gzip_vary on;
    # 处理压缩请求的 缓冲区数量和大小
    gzip_buffers 32 64k;
    # 对于不支持压缩功能的客户端请求 不开启压缩机制
    gzip_disable "MSIE [1-6]."; # 比如低版本的 IE 浏览器不支持压缩
    # 设置压缩功能所支持的 HTTP 最低版本
    gzip_http_version 1.1;
    # 设置触发压缩的最小阈值
    gzip_min_length 2k;
    # off/any/expired/no-cache/no-store/private/no_last_modified/no_etag/auth 根据不同配置对后端服务器的响应结果进行压缩
    gzip_proxied any;

    # 指定缓存存放目录为/usr/local/nginx/test/nginx_cache_storage,并设置缓存名称为 mycache,大小为 64m, 1 天未被访问过的缓存将自动清除,磁盘中缓存的最大容量为 1gb
    proxy_cache_path /usr/local/nginx/test/nginx_cache_storage levels=1:2 keys_zone=mycache:64m inactive=1d max_size=1g;
    # 对请求速率限流
    #limit_req_zone $binary_remote_addr zone=myRateLimit:10m rate=5r/s;
    # 对请求连接数限流
    limit_conn_zone $binary_remote_addr zone=myConnLimit:10m; 
    
    # --------------------HTTP 演示 配置---------------------
    server {
      listen 80 default;
      charset utf-8;
      server_name www.hzznb-xzll.xyz hzznb-xzll.xyz;

      #location /static/ {
      #    root /usr/local/nginx/test;  # /usr/local/nginx/test/static/xxx.jpg
      #}

      location /static { # 注意一般 alias 的 url 都不带后边的/ 
          alias /usr/local/nginx/test/; # 使用 alias 时  这里的目录最后边一定要加/ 否则就 404
      }
      # 测试 autoindex 效果
      location /book/ {
          root /usr/local/nginx/test;
          autoindex on; # 打开 autoindex,,可选参数有 on | off
          autoindex_format html; # 以 html 的方式进行格式化,可选参数有 html | json | xml
          autoindex_exact_size on; # 修改为 off,会以 KB、MB、GB 显示文件大小,默认为 on 以 bytes 显示出⽂件的确切⼤⼩
          autoindex_localtime off; # 显示⽂件时间 GMT 格式
      }
      
      # 临时重定向
      location /temp_redir {
          rewrite ^/(.*) https://www.baidu.com redirect;
      }
      # 永久重定向
      location /forever_redir {
          
          rewrite ^/(.*) https://www.baidu.com permanent;
      }
      # rewrite last 规则测试
      location /1 {
        rewrite /1/(.*) /2/$1 last;
      }
      location /2 {
        rewrite /2/(.*) /3/$1 last;
      }
      location /3 {
        alias  '/usr/local/nginx/test/static/';
        index location_last_test.html; 
      }
    }

    # --------------------HTTP 配置---------------------
    server {
      listen 80;
      charset utf-8;
      #server_name www.xxxadminsystem.com;
      server_name www.hzznbc-xzll.xyz hzznbc-xzll.xyz;
      # 重定向,会显示跳转的地址 server_name,如果访问的地址没有匹配会默认使用第一个,即 www.likeong.icu
      return 301 https://$server_name$request_uri;

        # # 指定 username 参数中只要有字母 就不走 nginx 缓存  
        # if ($arg_username ~ [a-z]) {
        #     set $cache_name "no cache";
        # }
        # # 前端页面资源
        # location  /page {
        #   alias  '/usr/local/nginx/test/static/';
        #   index index_page.html; 

        #   allow all;
        # }
        # # 后端服务
        # location  /interface {
        #   proxy_pass http://mybackendserver/;
        #   # 使用名为 mycache 的缓存空间
        #   proxy_cache mycache;
        #   # 对于 200 206 状态码的数据缓存 2 分钟
        #   proxy_cache_valid 200 206 1m;
        #   # 定义生成缓存键的规则(请求的 url+参数作为缓存 key)
        #   proxy_cache_key $host$uri$is_args$args;
        #   # 资源至少被重复访问 2 次后再加入缓存
        #   proxy_cache_min_uses 3;
        #   # 出现重复请求时,只让其中一个去后端读数据,其他的从缓存中读取
        #   proxy_cache_lock on;
        #   # 上面的锁 超时时间为 4s,超过 4s 未获取数据,其他请求直接去后端
        #   proxy_cache_lock_timeout 4s;
        #   # 对于请求参数中有字母的 不走 nginx 缓存
        #   proxy_no_cache $cache_name; # 判断该变量是否有值,如果有值则不进行缓存,没有值则进行缓存
        #   # 在响应头中添加一个缓存是否命中的状态(便于调试)
        #   add_header Cache-status $upstream_cache_status;
          
        #   limit_conn myConnLimit 12;

        # limit_req zone=myRateLimit burst=5  nodelay;
        # limit_req_status 520;
        # limit_req_log_level info;
        #}  
    }

    # --------------------HTTPS 配置---------------------
    server {
        #SSL 默认访问端口号为 443
        listen 443 ssl; 
        #填写绑定证书的域名 
        server_name www.hzznb-xzll.xyz hzznb-xzll.xyz; 
        #请填写证书文件的相对路径或绝对路径
        ssl_certificate /usr/local/nginx/certificate/hzznb-xzll.xyz_bundle.crt; 
        #请填写私钥文件的相对路径或绝对路径
        ssl_certificate_key /usr/local/nginx/certificate/hzznb-xzll.xyz.key; 
        #停止通信时,加密会话的有效期,在该时间段内不需要重新交换密钥
        ssl_session_timeout 5m;
        #服务器支持的 TLS 版本
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; 
        #请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。
        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; 
        #开启由服务器决定采用的密码套件
        ssl_prefer_server_ciphers on;
        
        # 指定 username 参数中只要有字母 就不走 nginx 缓存  
        if ($arg_username ~ [a-z]) {
            set $cache_name "no cache";
        }
        # 前端页面资源
        location  /page {
          alias  '/usr/local/nginx/test/static/';
          index index_page.html; 

          allow all;
        }
        # 后端服务
        location  /interface {
          proxy_pass http://mybackendserver/;

          # 指定哪些错误状态才执行 重试
          proxy_next_upstream error timeout http_500 http_502 http_503 http_504 http_404;


          # 使用名为 mycache 的缓存空间
          proxy_cache mycache;
          # 对于 200 206 状态码的数据缓存 2 分钟
          proxy_cache_valid 200 206 1m;
          # 定义生成缓存键的规则(请求的 url+参数作为缓存 key)
          proxy_cache_key $host$uri$is_args$args;
          # 资源至少被重复访问 2 次后再加入缓存
          proxy_cache_min_uses 3;
          # 出现重复请求时,只让其中一个去后端读数据,其他的从缓存中读取
          proxy_cache_lock on;
          # 上面的锁 超时时间为 4s,超过 4s 未获取数据,其他请求直接去后端
          proxy_cache_lock_timeout 4s;
          # 对于请求参数中有字母的 不走 nginx 缓存
          proxy_no_cache $cache_name; # 判断该变量是否有值,如果有值则不进行缓存,没有值则进行缓存
          # 在响应头中添加一个缓存是否命中的状态(便于调试)
          add_header Cache-status $upstream_cache_status;
          
          limit_conn myConnLimit 12;

        # limit_req zone=myRateLimit burst=5  nodelay;
        # limit_req_status 520;
        # limit_req_log_level info;
        } 

        # 后端服务
        location  /interface2 {
          proxy_pass http://mybackendserver;
          # 使用名为 mycache 的缓存空间
          proxy_cache mycache;
          # 对于 200 206 状态码的数据缓存 2 分钟
          proxy_cache_valid 200 206 1m;
          # 定义生成缓存键的规则(请求的 url+参数作为缓存 key)
          proxy_cache_key $host$uri$is_args$args;
          # 资源至少被重复访问 2 次后再加入缓存
          proxy_cache_min_uses 3;
          # 出现重复请求时,只让其中一个去后端读数据,其他的从缓存中读取
          proxy_cache_lock on;
          # 上面的锁 超时时间为 4s,超过 4s 未获取数据,其他请求直接去后端
          proxy_cache_lock_timeout 4s;
          # 对于请求参数中有字母的 不走 nginx 缓存
          proxy_no_cache $cache_name; # 判断该变量是否有值,如果有值则不进行缓存,没有值则进行缓存
          # 在响应头中添加一个缓存是否命中的状态(便于调试)
          add_header Cache-status $upstream_cache_status;
          
          limit_conn myConnLimit 12;

        # limit_req zone=myRateLimit burst=5  nodelay;
        # limit_req_status 520;
        # limit_req_log_level info;
        } 

    }

    # include /etc/nginx/conf.d/*.conf;
}

17.2、个人絮叨

这篇文章写了前后有 20 天,中间遇到很多的问题,都一一解决了,到这里我感到收获颇多,因为我之前对 nginx 说实话了解的并不多,这篇文章也基本补齐了对 nginx 的认识和使用。另外值的一说的是,nginx 可不止本文这些东西,功能海了去了,更多的请查阅 nginx 官网,里边每一个模块,每一个指令以及作用都说的清清楚楚,官网戳这里:nginx 官网文档 。

© 版权声明

☆ END ☆
喜欢就点个赞吧
点赞0 分享
图片正在生成中,请稍后...