Nginx 入门

77 阅读7分钟

Nginx 相关命令

Nginx 支持很多命令行参数,可以运行 nginx -help 查看,这里说一下最常用的命令

  • 启动 nginx:

    1. 默认配置文件启动:直接命令行输入 nginx 即可
    2. 指定配置文件启动:nginx -c your/path/to/config
  • 停止nginx

    1. nginx -s quit 等待当前 nginx 工作进程工作完后停止 nginx
    2. nginx -s stop 直接停止 nginx 进程
    3. killall 命令杀死进程
  • 重启 nginx: nginx -s reload 在 nginx 运行过程中,修改配置文件后,运行此命令重启 nginx 让新的配置文件生效

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

  • 查看 nginx 编译参数:nginx -V 展示 Nginx 源码编译时使用的参数配置


Nginx 的基础配置

Nginx 配置文件默认路径一般为 /etc/nginx/nginx.conf ,Nginx 配置文件结构如下图所示:

image-20220807172709602.png Nginx 每条配置在官方文档中都叫做指令,下文中也用指令这个说法


全局块

全局块中的指令针对于 Nginx 进程本身,控制 Nginx 的基本功能. 常用指令如下:

 user nobody; # 进程用户 默认为 nobody
 daemon on; # on/off 是否开启守护进程,通俗点说就是是否后台运行(生产环境下一般不需要设置,仅调试时使用)
 error_log  /var/log/nginx/error.log;  # 错误日志存储位置
 access_log /var/log/nginx/access.log;  # 正常请求日志存储位置
 worker_processes 5; # nginx 可以开启的工作进程数量,可以设置为 auto

error_log 和 access_log 也可以在 http 块、server 块、location 块中设置。


Event 块

Event 块中的指令,会影响到 Nginx 性能,这里的指令都是比较偏向底层的指令

 events {
     use epoll; # 指定 nginx 使用的事件模型;常见的有 select/poll/epoll,具体支持的事件模型类型需要在 Nginx 源码编译前指定
     worker_connections 5; # 每个工作进程支持的最大连接数
 };

Nginx 的进程模型如下图所示

image-20220807183433208.png

所以 Nginx 支持的最大连接数为 worker_processes * worker_connections


HTTP 块

HTTP 块中的指令,大多数是针对HTTP服务本身

 http {
     client_max_body_size 1m; # 请求体最大大小,与请求头中的 content-length 对比,如果请求体大小超过这个限制将会报错
     error_page 404 /404.html; # 配置在请求不成功的情况下返回的资源,比如状态码是 40x、50x
     keepalive_timeout 75; # http 连接的保活时间
     keepalive_requests 100; # 处于 keep-alive 状态的连接的最大数量
     log_not_found on;  # [on/off] 是否记录 not found 日志 
     sendfile on; # 使用I/O 操作中的 sendfile(零拷贝)
     types {
         text/html    html;
         image/gif    gif;
         image/jpeg   jpg;
     } # 设置资源的mime类型
 }

Server 块

Server 在nginx的概念中代表着虚拟站点,许多http块中的指令,在server块中也能使用,如果http块中与server块中对同一个指令进行了设置,则以server块中为准;

 http {
     # xxxxx
     server {
         client_max_body_size 1m;
         error_page 404 /404.html;
         keepalive_timeout 75;
         # xxxxx 更多 http 块中的配置 xxxxx
         listen 80; # 虚拟站点的ip和端口号
         server_name www.demo.com; # 虚拟站点的 hostname
     }
 }

listen 指令

listen 指令只能在 server 块中设置,listen 指令十分复杂以及多样化,指令格式如下:

listen address:port [ default [ backlog=num | rcvbuf=size | sndbuf=size | accept_filter=filter | deferred | bind | ssl ] ]

 listen 127.0.0.1:8000; # 监听本机的8000端口
 listen 127.0.0.1; # 监听本机的请求
 listen 8000; # 监听8000 端口,ip不限
 listen *:8000; # 监听8000 端口,ip 不限
 listen [::]:80 default ipv6only=on; # 只监听 IPv6的 80端口

Linux 会根据建立连接时获取到的 ip 和端口来和 listen 指令对比。通常情况下,listen 指令只用来指定端口号。

另外,listen 指令可以写多个:

 server {
     listen 80;
     listen 443;
 }

这表示 nginx 会监听 80 和 443 端口


server_name 指令

server_name 指令用来与 HTTP 请求头中的 Host 进行对比,并且可以在同一行写多个

 server {
     listen 80;
     server_name www.demo.com www.demo1.com *.demo.com www.demo.* ~^www\d+.demo.com$;
 }

*.demo.com 代表匹配以.demo.com结尾的 Host, www.demo.* 代表匹配以www.demo.开头的 Host, 以 '~' 开头的则是正则匹配 Host

如果一个 server 块中同时设置了 listen 和 server_name,那么当收到请求时,会首先匹配 listen指令的 IP+端口号,再从匹配到的 server 中匹配 server_name;如果所有 server 的server_name 都未匹配上,则由端口号匹配到的 server 中的第一个来处理。


Location 块

location 在 Nginx 中的概念为虚拟目录,也可以理解为资源位置。同样大部分 server 块中使用的指令,在 Location 块中也能使用(listen 和 server_name 除外),Location 块的作用就是匹配请求的 URI 并声明 URI 对应返回的资源。

 server {
     listen 80;
     server_name www.demo.com;
     
     location /image/logo.svg {
         root /data/dist;
     }
 } # 当请求url为 http://www.demo.com/image/logo.svg 时,返回服务器上路径为 /data/dist/image/logo.svg 的资源

location 指令匹配 URI 的方式有很多种,它们都是针对 URL 的 path 部分进行匹配,location 指令的格式为 location [=|~|~*|^~] /uri/


Location 块的匹配规则
  1. 正则匹配:正则匹配必须使用 ~* 或者 ~ 前缀

    • ~* 表示不区分大小写的正则匹配

      # 匹配以 /api 开头的path,不区分大小写
      location ~* ^/api { 
      }
      # http://www.demo.com/api  (yes)
      # http://www.demo.com/API  (yes)
      # http://www.demo.com/api/sdk  (yes)
      # http://www.demo.com/a/api  (no)
      
    • ~ 表示区分大小写的正则匹配

      # 匹配以 .gif、.jpg、.jpeg 结尾的path,区分大小写
      location ~ .(gif|jpg|jpeg)$ {
      }
      # http://www.demo.com/a.gif  (yes)
      # http://www.demo.com/a.GIF  (no)
      # http://www.demo.com/img/a.gif  (yes)
      # http://www.demo.com/a/a.png  (no)
      
  2. 文本匹配:文本匹配也可以添加前缀

    • 没有前缀,表示区分大小写匹配以某个字符串开头的 path

      # 匹配以 /api 开头的path,不区分大小写
      location /api { 
      }
      # http://www.demo.com/api  (yes)
      # http://www.demo.com/API  (yes)
      # http://www.demo.com/api/sdk  (yes)
      # http://www.demo.com/a/api  (no)
      
    • = 前缀表示精确匹配

      # 精确匹配 /api 
      location = /api { 
      }
      # http://www.demo.com/api  (yes)
      # http://www.demo.com/API  (no)
      # http://www.demo.com/api/sdk  (no)
      
    • ^~ 表示匹配以某个字符串开头的 path,并且不再进行正则匹配

      # 匹配以 /api 开头的path,不区分大小写(高优先级)
      location ^~ /api {
      }
      # 可 http://www.demo.com/api  (yes)
      # http://www.demo.com/api/sdk  (yes)
      # http://www.demo.com/a/api  (no)
      
  3. 命名 location

    • @ 开头,声明一个命名location,可以在内部使用

      location @error_page { # 声明一个名为 error_page 的 location
          # [some configure]
      }
      location /api {
          error_page 404 @error_page;
      }
      # 以 /api 开头的请求,未找到资源时,则内部重定向到 @error_page
      

在配置了多个 location 块后很容易出现一个请求可以被多个 location 块匹配成功的情况,然而 location 的匹配规则也是有优先级的,优先级从高到低如下:

location =      # 精确匹配,优先级最高 OK
location ^~     # 带前缀的文本匹配 ok
location ~      # 区分大小写的正则匹配 ok
location ~*   	# 不区分大小写的正则匹配 ok
location /path  # 没有前缀的文本匹配 wait
location /      # 其他location 块都没有匹配到,就会匹配这里,优先级最低

如果多个有同样优先级的 location 块都匹配成功

  • 文本匹配:采用规则文本最长的那一个 location 块

    # URI 中的 path 为 /api/sdk/get
    location /api { # 匹配失败
    }
    location /api/sdk { # 匹配成功
    }
    
  • 正则匹配:按照在配置文件中出现的位置,取最前面的一个


资源 Location

指明资源目录的方式有两种 root 指令和 alias 指令

举个栗子:现在请求的 url 中的 path 为 /page/log ;静态资源的位置为 /data/dist/page/log/index.html

# root 指令
location /page/log {
    root /data/dist; 
}
# alias 指令
location /page/log {
    alias /data/dist/page/log;
}

root 指令的含义是声明资源的根目录,而 alias 指令的含义是声明资源的虚拟目录。

使用 root 指令声明资源位置,Nginx 在查找资源时会按照 root + path 的路径去服务器上查找。

而使用 alias 声明资源位置,Nginx 查找资源的路径:将请求path中被匹配到的文本部分替换为 alias 路径

# 请求 path 为 /page/log/error/
location /page/log {
    alias /data/dist/page/log;
}
# Nginx 返回资源的路径为 /data/dist/page/log/error

需要注意的是,如果 location 的规则是正则匹配,并且想要在 location 块中使用 alias 指令,按照官方文档的说法,这个正则表达式里面必须包含捕获(captures),并且alias 路径中需要用到这些捕获,例:

location ~ ^/image/(.+.(?:gif|jpe?g|png))$ {
    alias /data/dist/images/$1;
}

测试下来,个人理解文档中的捕获实际上就是 () ,一对圆括号里面的子表达式中所匹配到的字符串就是一个捕获,而这些捕获会被以变量的方式注入到location块内部 $1、$2、$3 ... ,如上例中就能捕获到请求中的图片文件名,然后以变量的方式拼接到 alias 路径的后面。

另外当未在请求 path 或者 alias 中指明文件名时,Nginx 会自动寻找路径对应文件夹下的 index.html;当然也可以使用 index 指令指定该目录下的默认资源

location /page/log {
    alias /data/dist;
    index demo.html
}
# 当请求 path 为 /page/log 时,Nginx 返回的资源是 /data/dist/demo.html

添加响应头

使用 add_header 指令可以添加http 响应头,这个指令在http块 、server 块和 location 块中都可以使用

location ~* .(gif|jpg|png|bmp|ico)$ {
   root /data/dist/image/
   add_header Cache-Control no-store;
}

语法为 add_header name value

另外 expires 需要单独设置

location ~* .(gif|jpg|png|bmp|ico)$ {
   root /data/dist/image/
   expires 1d;
}

代理 Location

location 块中使用代理的核心指令就是 proxy_pass

location / {
	proxy_pass http://www.baidu.com; # 将请求转发到 http://www.baidu.com 
}

proxy_pass 指令的值就是目标服务器的URL,但经过测试,如果是随便乱写的域名,Nginx 会报错

image-20220821163230357.png 至于报错信息中的upstream 是什么,后面会讲到。但是只要这个域名是真实有效的,或者存在于本机host文件中,就可以生效。


proxy_pass 的路径规则
  • 文本规则 location 中块,如果 proxy_pass 的 URL 中没有 path,则直接按原请求的path转发

    # 请求 path 为 /page/log/error
    location /page/log {
        proxy_pass http://127.0.0.1;
    }
    # 请求转发路径为 http://127.0.0.1/page/log/error
    
  • 文本规则 location 块中,如果 proxy_pass 的 URL 中有 path,则与alias在文本location中的规则一样

    # 请求 path 为 /page/log/error
    location /page/log {
        proxy_pass http://127.0.0.1/dist;
    }
    # 请求转发路径为 http://127.0.0.1/dist/error
    
  • 正则匹配规则的 location 块中,proxy_pass 的 URL 中不允许有path,否则,Nginx 会报错。请求会被直接按照原 path 转发到目标服务器

    # 请求 path 为 /page/log/error
    location ~ /page/log {
        proxy_pass http://127.0.0.1;
    }
    # 请求转发路径为 http://127.0.0.1/page/log/error
    

增加请求头

使用 proxy_set_header指令 可以对请求中的头部进行重定义、或者是增加请求头,再一并发送给被代理的服务器。例:

location ~ /page/log {
    proxy_pass http://127.0.0.1;
    proxy_set_header X-Real-IP       $remote_addr; # $remote_addr --请求发送方IP
    proxy_set_header Host            $host; # $host 代表请求头中的 host
}

上面的例子中用到了 nginx 内置变量,nginx 内置变量有很多,这里不列举,更多 nginx 内置变量看这里: nginx.org/en/docs/var…


Nginx 实现常见的其他功能

Nignx 的指令非常丰富且复杂,由于篇幅原因文中只能讲一些最基本的指令,达到快速入门并使用的目的。下文中将会介绍一些常见的功能是如何用 Nginx 配置实现的。


负载均衡

上文报错信息中提到了 upstream ,upstream 指令在 Nginx 中的作用是定义一个服务器分组,然后可以在 proxy_pass 中使用它;

upstream backend1 {
    server 192.168.10.1:8080 weight=1;
    server 192.168.10.2:8080 weight=2;
    server 192.168.10.3:8080 weight=3;
}
server {
    listen 80;
    location / {
        proxy_pass http://group1;
    }
}

上例中声明了一个名为 backend1 的服务器分组,并在location 中使用,那么此时这个location所在的server接收到的所有请求都会被转发到 backend1 中 的某一个 server 上。weight 代表权重,权重值越高,被分配的请求数量就越多,也就是负载越大。

upsteam 中的 server 还有一些其他参数 ,比如:

upstream backend2 {
    server 192.168.10.1:8080 max_conns=100 backup down ;
}
  • max_conns:最大连接数
  • backup 标记一个server 为备用的server,只有当其他server不可用时,才会使用这个
  • down 标记一个server 暂时不可用

Nginx 负载均衡策略

常见的 nginx 负载均衡策略有如下几种:

  1. 轮询(默认策略): upstream 中的 server 都不加权重(weight),Nginx 依次将请求转发到 server,这种策略的效果等于每个server的weight相同

  2. 加权轮询:通过添加 weight 参数进行加权处理,权重值越大,服务器越容易被访问,因此,性能好的服务器应适当加大权重值

  3. ip_hash:将某个客户端的ip的请求固定转发到同一台机器上

    upstream backend3 {
        ip_hash;
        server 192.168.10.1:8080;
        server 192.168.10.2:8080;
        server 192.168.10.3:8080;
    }
    # 优势是:解决了服务端与客户端之间的session共享问题
    # 缺点是:无法保证负载均衡,有可能某个服务器的负载很大而其他服务器空闲
    
  4. url_hash:与ip_hash类似,但是这种策略是基于URL的,将某个URL固定转发到某个机器上,主要解决了缓存命中率的问题。

  5. least_conn:将请求转发给连接数最少的 server

    upstream backend4 {
        least_conn;
        server 192.168.10.1:8080;
        server 192.168.10.2:8080;
        server 192.168.10.3:8080;
    }
    

解决跨域问题

反向代理

Nginx 反向代理常被用来作为解决跨域问题的手段。使用代理的方式解决跨域问题的核心逻辑是让静态资源请求和ajax请求的域名相同。在没有前后端分离的情况下,如下图所示:

image-20220822190750549.png

此时静态资源和接口服务(动态资源)在同一个Web Server 中,也就是在浏览器中静态资源请求与ajax请求的协议+域名+端口 是完全一样的(同源),不会受到跨域问题的影响;

当前后端分离以后(动静分离):

image-20220822191308639.png

此时动态资源server和静态资源server 分离,那么就无法让动态资源请求和静态资源请求保持同源,即使将这两个server放到同一个机器上,在不借助其他工具的情况下,是无法让动态资源请求和静态资源请求的url的端口号相同的。

使用 Nginx 反向代理解决跨域问题,如下图所示:

image-20220822191843224.png

动态资源请求和静态资源请求的目标都是 Nginx 服务,动态资源请求由Nginx 代理转发到真正的目标服务器,Nginx 收到响应以后,再响应给客户端。这样,对于浏览器来说动态资源请求和静态资源请求的目标同源,不受同源策略影响。配置示例如下

server {
    listen 80;
    error_log /log/error.log;
    access_log /log/access.log;
    
    location / { # 响应静态资源
        root /data/dist/
    }
    location ~ ^/api/sdk/ { # 代理 ajax 请求
        proxy_pass http://apiserver;
        proxy_set_header X-Real-IP       $remote_addr;
        proxy_set_header Host            $host;
    }
}

正向代理和反向代理的区别

正向代理和反向代理的主要区别是代理服务器的位置靠近哪一边,代理服务靠近客户端就是正向代理,靠近服务端就是反向代理。对于客户端来说能感知到的代理就是正向代理,感知不到的就是反向代理。比如上例中 Nginx 的反向代理,客户端(浏览器)是感知不到动态资源的请求是经过了一次代理的。正向代理的平时也有应用到,比如科学上网、前端本地开发的 webpack devServer。


访问控制

访问控制用到的两个关键指令:denyallow ,这两个指令都是针对 IP 的,在http块 、server 块和 location 块中都可以使用,下面以server块为例

设置黑名单,禁止某些 ip 或者 ip段访问,使用子网掩码指定 ip段

server {
	deny 192.168.10.1;
    deny 192.168.11.1/24;
}

设置白名单,只允许某些 ip 或者 ip段访问,通过 deny: all; 禁止所有IP访问,再通过 allow 设置白名单。

server {
	allow 192.168.10.1;
    allow 192.168.11.1/24;
    deny all;
}

GZIP

Gzip 是一种比较常见的压缩格式,在 nginx 中使用 gzip指令就可以开启 gzip 压缩,gzip 指令在http块 、server 块和 location 块中都可以使用

server {
	gzip on;
   
    gzip_min_length 1k; # 资源大于 1k 才开启压缩(资源大小从header中的content-length获取)
    gzip_http_version 1.1;  #设置压缩http协议的版本,默认是1.1
    gzip_types   text/html text/css image/gif font/woff; # 开启压缩的资源的 mime 类型
    gzip_comp_level 1; # gzip压缩比,1 压缩比最小处理速度最快,9 压缩比最大但处理最慢(传输快但比较消耗cpu)
}

参考文档

nginx.org/en/docs/

www.nginx.cn/doc/index.h…