Nginx 学习笔记
系统学习 Nginx | Web开发 0 616

学习目标

  1. 理解流行工具生成的配置文件以及各种文档中的配置文件。
  2. 从头开始将Nginx配置为Web服务器、反向代理服务器和负载均衡器。
  3. 优化Nginx以获得最大的服务器性能。

简介

Nginx是一种高性能网络服务器,专注于高性能、高并发和低资源使用。核心是一个反向代理服务器。一般用于反向代理和静态资源托管。 对比Apache(httpd)的优势:

  1. 它可以处理更多的并发请求。
  2. 它可以在更低资源消耗的前提下更快地交付静态内容。

配置实验场地

虚拟机环境

推荐使用multipass创建虚拟机(MacOS用户可跳过这一步骤, 区别仅在于nginx配置文件的目录)

  1. 安装multipass
  2. 创建虚拟机(cloud-config.yml可参考简易配置)
    multipass launch --name master --cpus 2 --mem 2G --disk 10G --cloud-init cloud-config.yml
  3. 进入虚拟机的bash环境
    multipass shell master
  4. 确保nginx已经启动
    • 可以查看nginx服务的状态
      service nginx status
      -------------------------------------------
      ...
      Active: active (running) 
      ...
    • 如果没有启动,可执行启动命令
      sudo service nginx start

其他准备

为了更好的演示多域名下的Nginx配置,推荐使用switchhost修改客户端的配置

初次使用

配置第一个站点

nginx的配置文件在/etc/nginx/目录下(MacOS中则在/usr/local/etc/nginx/下),以.conf扩展名结尾。

  1. 初始化配置文件 原始的配置文件比较复杂,初次学习时可以将其备份下(将nginx.conf重命名为nginx.conf.bak),后创建新的nginx.conf,并输入如下内容:
    events {
    }
    http {
        server {
            listen 80;
            server_name local.com;
            return 200 "Bonjour, mon ami!\n";    
        }
    }
  2. 检验配置文件
    sudo nginx -t
  3. 配置无误后可重新加载配置文件
    sudo nginx -s reload
  4. 通过curl查看效果
    curl -i http://local.com

基础概念: 指令和上下文

指令分为简单指令(;结尾)和块指令({}括起来的指令) 能够在其中包含其他指令的块指令称为上下文(类似作用域)。 Nginx有四个核心上下文: * events: 用于设置nginx在一般级别处理请求的全局配置,具有唯一性 * http: 定义服务器将如何处理HTTP和HTTPS请求的配置,也具有唯一性 * server: 嵌套在http中,用于在单个主机内配置特定的虚拟服务器 * main: main上下文是配置文件本身,所有其他上下文都包含在main

静态内容服务

之前最基础的配置中,我们已经可以通过让Nginx响应一个纯文本了,下面尝试让Nginx提供静态文件。

获取源码

为了方便学习,可以在Nginx配置的根目录下创建www目录用于存放站点项目源代码。

git clone https://github.com/HenryJi529/nginx-demo-projects.git

提供静态内容

现在有了要提供的静态内容,更新nginx.conf

events {
}
http {    
    server {
        listen 80;
        server_name local.com;
        root /usr/local/etc/nginx/www/static-demo;
    }
}
root用于声明站点的根目录,是Nginx在收到相应请求后查找的路径

访问http://local.com 尽管Nginx已正确提供index.html,但似乎CSS样式不起作用。

静态文件类型处理

观察静态网站,得出css的路径为 http://local.com/css/style.css 通过curl查看请求请求报文首部

curl -I http://local.com/css/style.css
-------------------------------------------
HTTP/1.1 200 OK
Server: nginx/1.23.0
Date: Sun, 17 Jul 2022 11:02:00 GMT
Content-Type: text/plain
Content-Length: 474
Last-Modified: Wed, 22 Jun 2022 16:13:08 GMT
Connection: keep-alive
ETag: "62b33f94-1da"
Accept-Ranges: bytes
其中, Content-Type标记这个css文件为text/plain而不是text/css, 这就是网页格式未能展现的原因。

可以通过引入mime.types文件加以解决(MIME: Multipurpose Internet Mail Extensions)

events {
}
http {    
    include mime.types;
    server {
        listen 80;
        server_name local.com;
        root /usr/local/etc/nginx/www/static-demo;
    }
}
include关键字可以用于引入模块,便于模块化配置。 重新加载配置并通过curl查看css的响应首部
curl -I http://local.com/css/style.css
-------------------------------------------
HTTP/1.1 200 OK
Server: nginx/1.23.0
Date: Sun, 17 Jul 2022 11:45:10 GMT
Content-Type: text/css
Content-Length: 474
Last-Modified: Wed, 22 Jun 2022 16:13:08 GMT
Connection: keep-alive
ETag: "62b33f94-1da"
Accept-Ranges: bytes
Content-Type已经成功转化成text/css。 再通过浏览器访问页面:

rootalias

rootalias都用于在提供静态服务时指定文件路径,但

  • alias只能用在location上下文中
  • 通过root获得的文件目录是root路径+location路径;通过alias获得的文件目录是alias路径(直接替换掉location路径)

动态路由

上一小节实现了一个简单的静态内容服务器配置,它获取与URI相匹配的站点文件,并进行相应。

然而,如果访问的URI无法匹配到站点文件,将返回默认的404页面

这一问题可以通过动态路由配置来解决 在本节,需要了解location上下文、变量、重定向、重写以及try_files指令。

位置匹配

前缀匹配

更新配置如下:

events {
}
http {
    include mime.types;
    server {
        listen 80;
        server_name local.com;
        root /usr/local/etc/nginx/www/static-demo;

        location /location1 {
            return 200 "location1\n";
        }
    }
}
这里引入了location上下文,这个上下文通常嵌套在server块中。

通过curl测试配置效果

curl -i http://local.com/location1
-------------------------------------------
HTTP/1.1 200 OK
Server: nginx/1.23.0
Date: Sun, 17 Jul 2022 12:51:28 GMT
Content-Type: text/plain
Content-Length: 10
Connection: keep-alive

location1
访问http://local.com/location1/, http://local.com/location11/, http://local.com/location11/1也能获得相同的结果。事实上通过这一配置,任何location1开头的路径都能被匹配到。这种匹配被称为前缀匹配

完全匹配

要进行完全匹配,需更新配置为

events {
}
http {
    include mime.types;
    server {
        listen 80;
        server_name local.com;
        root /usr/local/etc/nginx/www/static-demo;

        location = /location1 {
            return 200 "location1\n";
        }
    }
}
这种配置下,向除/location1外,甚至/location1/都会得到404响应。
curl -I http://local.com/location1/
-------------------------------------------
HTTP/1.1 404 Not Found
Server: nginx/1.23.0
Date: Sun, 17 Jul 2022 13:30:40 GMT
Content-Type: text/html
Content-Length: 153
Connection: keep-alive

完全匹配还有一些诡异的特性,可以看下面的例子

events {
}
http {
    include mime.types;
    server {
        listen 80;
        server_name local.com;
        location = /www {
            root /usr/local/etc/nginx;
        }
    }
}
通过实验可以知道,此时访问http://local.com/www仍然返回404。根据root与location的原理,此时匹配的应该是/usr/local/etc/nginx/www/index.html,虽然根目录上却有这样的文件,但因为完全匹配的机制,依旧引发了404。

正则匹配

events {
}
http {
    include mime.types;
    server {
        listen 80;
        server_name local.com;
        location ~ /hello[0-9] {
            return 200 "Hello\n";
        }
    }
}

通过~,告诉Nginx执行正则表达式匹配

curl -i http://local.com/hello1/12
-------------------------------------------
HTTP/1.1 200 OK
Server: nginx/1.23.0
Date: Sun, 17 Jul 2022 15:22:49 GMT
Content-Type: text/plain
Content-Length: 6
Connection: keep-alive

Hello
  • 实际上,不考虑优先级的情况下,前缀匹配也可以看作特殊的正则匹配,而完全匹配可以看作是路径加上$的正则匹配。
  • 默认情况下,正则表达式区分大小写,通过~*就可以转化为不区分大小写。

匹配优先级

匹配模式 标记符 优先级
完全匹配 = 1
优先前缀匹配 ^~ 2
正则匹配 ~ or ~* 3
前缀匹配 None 4

不妨验证一下:

  1. 更新Nginx配置如下:
    events {
    }
    http {
        include mime.types;
        server {
            listen 80;
            server_name local.com;
            location /hello7 {
                return 200 "Hello7\n";
            }
            location ^~ /hello8 {
                return 200 "hello8\n"; 
            }
            location ~ /hello[0-9] {
                return 200 "hello\n";        
            } 
        }
    }
  2. 通过curl查看结果:
    • 正则匹配与优先前缀匹配同时满足时,选择优先前缀匹配
      curl http://local.com/hello8
      -------------------------------------------
      hello8
    • 正则匹配与前缀匹配同时满足时,选择正则匹配
      curl http://local.com/hello7
      -------------------------------------------
      hello

此外, 值得注意的是,对于同属一个优先级的正则匹配,是按照在文件中的顺序进行匹配的

内部匹配

@用于定义一个location块,不能被客户端直接访问,只能被Nginx内部配置指令访问,如try_fileserror_page

events {
}
http {
    include mime.types;
    server {
        listen 80;
        server_name local.com;
        location / {
            error_page 418 = @queryone;
            error_page 419 = @querytwo;
            error_page 420 = @querythree;
            if ( $args ~ "service=one" ) { return 418; }
            if ( $args ~ "service=two" ) { return 419; }
            if ( $args ~ "service=three" ) { return 420; }
        }
        location @queryone {
            return 200 'do stuff for one\n';
        }
        location @querytwo {
            return 200 'do stuff for two\n';
        }
        location @querythree {
            return 200 'do stuff for three\n';
        }
    }
}
通过curl测试:
curl "http://local.com/1?service=one"
-------------------------------------------
do stuff for one

变量

定义与查看

Nginx通过set可以在配置文件的任何位置定义新变量, 具体用法如下:

# set $<variable_name> <variable_value>;
set name "Farhan"
set age 25
set is_working true
Nginx支持三种类型的变量 * 字符串 * 整型 * 布尔型

除去自己申明外,nginx还有内置变量

这里提供一个简单的方法设置与查看变量:

  1. 配置nginx.conf
    events {
    }
    http {
        include mime.types;
        server {
            listen 80;
            server_name local.com;
            return 200 "Host - $host\nURI - $uri\nArgs - $args\n";
        }
    }
  2. curl访问http://local.com/1?name=henry
    curl "http://local.com/1?name=henry&age=18"
    -------------------------------------------
    Host - local.com
    URI - /1
    Args - name=henry&age=18
    Name - henry
    显然,$host保存根地址,$uri保存相对于根的请求URI,而$args包含所有查询字符串。 此外,通过set $name $arg_name;可以将查询的字符串变量的值保存在了Nginx的新变量中。

常用的内置变量

  • $remote_addr: 存放了客户端的公网IP
  • $host保存根地址
  • $uri保存相对于根的请求URI
  • $request_uri: 包含请求参数的原始URI
  • $args包含所有查询字符串
  • $http_user_agent: 客户端浏览器的详细信息
  • $http_cookie: 客户端的cookie信息
  • $remote_port: 客户端访问Nginx服务器随机打开的端口
  • $request_method: 请求资源的方式,GET/PUT/DELETE等
  • $scheme: 请求的协议,如ftp,https,http等
  • $server_protocol: 保存了客户端请求资源使用的协议的版本,如HTTP/1.0,HTTP/1.1,HTTP/2.0等
  • $server_addr: 保存了服务器的IP地址
  • $server_name: 请求的服务器的主机名
  • $server_port: 请求的服务器的端口号

命令

return

该命令一般用于对请求的客户端直接返回状态码(3XX,4XX),或者用于测试路由(200)。 语法:

return code [text];
return [code] URL;

rewrite

用于通知客户端,请求的资源已经换地方,例如访问joke.morningstar529.com,转到morningstar529.com/joke/ 用法:

rewrite regex URL [flag];
关于flag中的redirectlast: * redirect: 返回包含 302 代码的临时重定向,在替换字符串不以http://等开头时使用 * permanent: 返回包含 301 代码的永久重定向

deny

用于禁止访问某个目录下的文件,用法如下:

location /directory {
    location ~ .*\.(scss)?$ {
        deny all;
    }
}

日志系统

默认情况下,nginx的日志文件位于/var/log/nginx/目录下(MacOS中则在/usr/local/var/log/nginx/下),以.log扩展名结尾。

ls -l /usr/local/var/log/nginx
-------------------------------------------
total 512
-rw-r--r--  1 henry529  admin  168023 Jul 20 10:13 access.log
-rw-r--r--  1 henry529  admin   79382 Jul 20 08:18 error.log

基础使用

  1. 配置nginx.conf
    events {
    }
    http {
        include mime.types;
        server {
            listen 80;
            server_name local.com;
            return 200 "success\n";
        }
    }
  2. 清空这两个文件
    rm /usr/local/var/log/nginx/access.log /usr/local/var/log/nginx/error.log
  3. 重新开启Nginx服务
    sudo nginx -s reload
    如果不向Nginx发送reopen信号,他会继续将日志写入之前打开的流,新文件将保持为空。
  4. 通过curl访问网站根目录
    curl -i http://local.com
    -------------------------------------------
    HTTP/1.1 200 OK
    Server: nginx/1.23.1
    Date: Sun, 24 Jul 2022 04:59:05 GMT
    Content-Type: text/plain
    Content-Length: 8
    Connection: keep-alive
    ...
    success
  5. 查看日志
    cat /usr/local/var/log/nginx/access.log
    -------------------------------------------
    127.0.0.1 - - [24/Jul/2022:12:55:16 +0800] "GET / HTTP/1.1" 200 8 "-" "curl/7.79.1"

配置日志系统

  1. 修改常规日志位置
    access_log /var/logs/nginx/admin.log;
  2. 修改错误日志位置及级别
    error_log /var/log/error.log warn
    有八个级别的错误消息:
    • debug – 有助于确定问题所在的有用调试信息。
    • info - 不需要阅读但可能很好了解的信息性消息。
    • notice - 发生了一些值得注意的正常现象。
    • warn - 发生了意外,但不必担心。
    • error - 某些事情不成功。
    • crit - 存在急需解决的问题。
    • alert - 需要迅速采取行动。
    • emerg - 系统处于无法使用的状态,需要立即关注。

反向代理

  • 定义: 客户端向Nginx服务器发起请求,而Nginx再将请求传递给后端;后端服务器处理完请求后,将其发送回Nginx,之后,Nginx将响应返给客户端
  • 特点: 整个过程中,客户端不知道谁在实际处理请求,所以要反向代理
  • 作用:
    • 提高访问速度(通过缓存)
    • 进行负载均衡
    • 保证后端服务安全

简易案例

配置nginx.conf:

events {
}
http {
    include mime.types;
    server {
        listen 80;
        server_name local.com;
        location / {                
            proxy_pass https://segmentfault.com/; 
        }
    }
}

访问http://local.com,将看到原始的思否站点:

搭配Python后端

  1. 在Nginx配置根目录下执行:
    python3 www/python-socket-demo/server.py
    -------------------------------------------
    Server started...
  2. 通过curl检测Python后端是否正常运行
    curl -i 127.0.0.1:8000
    -------------------------------------------
    HTTP/1.1 200 OK
    Content-Type: text/html
    ...
    Hello World
  3. 更新nginx配置如下:
    events {
    }
    http {
        include mime.types;
        server {
            listen 80;
            server_name local.com;
            location / {                
                proxy_pass http://localhost:8000; 
            }
        }
    }
  4. 通过浏览器检查反向代理是否成功 显示的正是后端的响应,说明反向代理成功

负载均衡

基本配置方法

由于Nginx的反向代理设计,可以轻松地将其配置为负载均衡器。

这里通过load-balancer-demo/目录下的三个server来演示下:

  1. 在三个终端中分别启动三个server:
    node www/load-balancer-demo/server-x.js 
  2. 更新nginx配置如下:
    events {
    }
    http {
        include mime.types;
        upstream backend_servers {        
            server localhost:3001;        
            server localhost:3002;        
            server localhost:3003;    
        }
        server {
            listen 80;
            server_name local.com;
            location / {            
                proxy_pass http://backend_servers;        
            }   
        }
    }
    其中,upstream是一组可以被视为单个后端的服务器。
  3. 通过curl检测:
    while sleep 0.5; do curl http://local.com; done
    -------------------------------------------  
    response from server - 1.
    response from server - 2.
    response from server - 3.
    response from server - 1.
    response from server - 2.
    response from server - 3.
    response from server - 1.
    response from server - 2.
    可以看到,负载均衡已经实现。

负载均衡策略

  • 轮询: 默认方式
    upstream webservers{
        server 192.168.100.128:8080;
        server 192.168.100.129:8080;
    }
  • weight: 权重方式,默认为1,权重越高,被分配的客户端请求就越多
    upstream webservers{
        server 192.168.100.128:8080 weight=90;
        server 192.168.100.129:8080 weight=10;
    }
  • ip_hash: 依据ip分配方式,这样每个访客可以固定访问一个后端服务
    upstream webservers{
        ip_hash;
        server 192.168.100.128:8080;
        server 192.168.100.129:8080;
    }
  • least_conn: 依据最少连接方式,把请求优先分配给连接数少的后端服务
    upstream webservers{
        least_conn;
        server 192.168.100.128:8080;
        server 192.168.100.129:8080;
    }
  • url_hash: 依据url分配方式,这样相同的ur会被分配到同一个后端服务
    upstream webservers{
        hash &request_uri;
        server 192.168.100.128:8080;
        server 192.168.100.129:8080;
    }
  • fair: 依据响应时间方式,响应时间短的服务将会被优先分配
    upstream webservers{
        server 192.168.100.128:8080;
        server 192.168.100.129:8080;
        fair;
    }

配置文件模块化

一般来说nginx.conf应该有nginx维护者而不是服务器管理员来更改。所以,在实际配置nginx中,不修改nginx.conf,而是通过原始配置文件中自带的include conf.d等命令,导入模块化的站点配置文件。

参考

  1. NGINX 完全手册
  2. 吐血整理-关于Nginx 的 location 匹配规则总结看这一篇就够了
编写
预览