Nginx指引

通过本文,你将了解Nginx的基本使用和Nginx的基本知识。

Nginx 安装(Ubuntu)

直接使用apt命令安装nginx:

1
2
3
4
# 更新源
> sudo apt update
# 安装
>sudo apt install nginx

Nginx的启用、停止和重新加载配置

nginx启动以后,可以通过”nginx -s <signal>“的指令来对nginx执行启用、停止、和重新加载配置文件的操作,singnal说明如下:

  • stop: 快速关闭
  • quit: 优雅关闭(等处理完所有处理中的请求后再关闭)
  • reload: 重新加载配置文件
  • reopen: 重新打开日志文件

例如:我们要等所有当前正在处理的请求结束后停止nginx进程,我们可以执行以下命令:

nginx -s quit

当我们修改了nginx的配置文件后,并不会马上生效。要使其生效需要执行以下命令:

nginx -s reload

一旦nginx的master 进程接收到reload指令,它会先去检查config文件的合法性并尝试应用新的配置。如果可以成功应用,master进程将开启一个新的worder进程并发送一个关闭命令的消息到旧的worker进程。否则master进程将回滚更改并继续使用旧配置。旧的worker进程收到关闭命令后,停止接受新的连接并继续处理正在处理中的请求,直到所有请求都处理完毕后结束进程。

配置文件结构

nginx由配置文件中指定的指令控制模块组成。指令分为简单指令块指令。简单指令由名称和参数组成,以空格分隔并以分号(;)结尾。块指令结构同简单指令一样,不同的是块指令通过{}来包住指令,如果块指令包含了其他指令,这个被称为上下文(context)。比如:events、http、server、location。

放置在配置文件中其他任何上下文中的指令,可以称为主上下文(main context)。events和http指令在主上下文里,server在http指令中,location在server指令中。

#符号及之后的内容是注释。

部署静态页面

web服务器一项重要的任务就是提供静态文件的访问。下面,我们将实现通过不同的URL来访问服务器上不同路径下文件:/data/wwww目录下面存放静态文件,/data/images目录现面存放图片,我们通过配置location来实现通过url(http://127.0.0.1)访问/data/wwww目录下的index.html文件,通过(http://127.0.0.1/images/avta.png)访问/data/images目录下的avta.png图片。

1.在/etc/nginx/conf.d/目录下创建test.conf文件,文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
server {
listen 80;
server_name 127.0.0.1;
charset utf-8;
client_max_body_size 20m;
# 浏览器输入ip时,会访问/data/www目录下的index.html文件
location / {
root /data/www;
}
# 浏览器输入ip/iamges/avta.png时,会访问/data/images/目录下的avta.png文件
location /images/ {
root /data;
}

2.重新加载配置文件

sudo nginx -s reload

3.浏览器访问nginx下的文件

127.0.0.1

127.0.0.1/images/avta.png

设置简单的代理服务

nginx的常见用途之一是服务器代理。服务器接受处理请求,把请求结果返回给代理服务,代理服务返回给客户端。

现在我们在服务器上5000端口有个服务,服务器上访问如下结果:

1
2
curl http://127.0.0.1:5000/v2/_catalog
{"repositories":["ingress","nginx"]}

nginx对外暴露端口80,IP地址http://172.22.15.212/,现在当我们访问http://172.22.15.212/v2/_catalog能请求到5000端口的内容。

配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
server {
listen 80;
server_name 172.22.15.212;

charset utf-8;
#charset koi8-r;
client_max_body_size 20m;
location /v2/_catalog {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $proxy_host;
proxy_set_header X-Real-IP $remote_addr;
}
}

重新加载配置nginx -s reload后,访问http://172.22.15.212/v2/_catalog后返回

1
{"repositories":["ingress","nginx"]}

Nginx怎么处理一个请求

基于名称的虚拟服务(Name-based virtual servers)

nginx首先决定收到的请求由哪个服务/服务器来进行处理。如下例子,首先有三个服务都监听80端口,配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
listen 80;
server_name example.org www.example.org;
...
}

server {
listen 80;
server_name example.net www.example.net;
...
}

server {
listen 80;
server_name example.com www.example.com;
...
}

在上面的配置中,nginx先根据请求的header中的host字段来决定该请求由哪个服务去处理,如果都没有匹配上,则让默认的配置去处理。在上面的例子中,默认的处理则是第一个server(nginx默认的方式)。也可以通过在listen指令后面添加default_server显式的设置默认的处理服务,如下:

1
2
3
4
5
server {
listen 80 default_server;
server_name example.net www.example.net;
...
}

default_server 要在0.8.21及以上版本支持,更早的版本请使用default

如何禁止处理未定义的服务名称

如果不允许处理在header中携带host字段的请求,可以如下定义:

1
2
3
4
5
server {
listen 80;
server_name "";
return 444;
}

在上面,通过设置server_name为一个空的字符串,它将匹配没有“Host”头字段的请求,并返回一个特殊的 nginx 非标准代码 444 来关闭连接。

基于名称和IP的混合虚拟服务

让我们来看一个监听不同IP地址的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
listen 192.168.1.1:80;
server_name example.org www.example.org;
...
}

server {
listen 192.168.1.1:80;
server_name example.net www.example.net;
...
}

server {
listen 192.168.1.2:80;
server_name example.com www.example.com;
...
}

在此配置中,nginx 首先根据服务器块的监听指令测试请求的 IP 地址和端口。然后,它根据与 IP 地址和端口匹配的服务器块的 server_name 条目测试请求的“主机”标头字段。如果server_name未匹配到,则采用默认的servier处理。例如在IP为192.168.1.1:80端口收到域名为www.example.com的请求,他将采用192.168.1.1:80 默认server,即第一个配置处理。

如前所述,默认服务器是监听端口的一个属性,可以为不同的端口定义不同的默认服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
listen 192.168.1.1:80;
server_name example.org www.example.org;
...
}

server {
listen 192.168.1.1:80 default_server;
server_name example.net www.example.net;
...
}

server {
listen 192.168.1.2:80 default_server;
server_name example.com www.example.com;
...
}

一个简单的PHP站点配置

下面我们通过一个简单的例子来看看nginx如何来为一个请求选择对应的服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server {
listen 80;
server_name example.org www.example.org;
root /data/www;

location / {
index index.html index.php;
}

location ~* \.(gif|jpg|png)$ {
expires 30d;
}

location ~ \.php$ {
fastcgi_pass localhost:9000;
fastcgi_param SCRIPT_FILENAME
$document_root$fastcgi_script_name;
include fastcgi_params;
}
}

无论上面的location顺序如何,nginx 首先搜索由文字字符串给出的最具体的前缀位置。在上面的配置中,唯一的前缀位置是“/”,并且由于它匹配任何请求,因此将用作最后的手段。然后 nginx 按照配置文件中列出的顺序检查正则表达式给出的位置。第一个匹配的表达式停止搜索,nginx 将使用这个位置。如果没有正则表达式匹配请求,则 nginx 使用之前找到的最具体的前缀位置。

需要注意的,所有类型的location仅匹配不带参数的请求部分。

现在让我们看看上面的配置如何处理请求:

  • “/logo.gif”,首先匹配到“/”location,然后匹配到正则表达式\.(gif|jpg|png)$,因此,它会被后面的location处理,使用root /data/wwww指令匹配到文件 /data/www/logo.gif返回给客户端。
  • “/inde.php”,也是首先匹配到“/”location,然后匹配到正则表达式\.php$。因此,它被后面的location处理,并将请求传给监听localhost:9000的FastCGI服务,fastcgi_param 指令将 FastCGI 参数 SCRIPT_FILENAME 设置为“/data/www/index.php”,FastCGI 服务器执行该文件。变量 $document_root 等于 root 指令的值,变量 $fastcgi_script_name 等于请求 URI,即“/index.php”。
  • “/about.html”,只匹配到“/”location,因此在该位置处理。使用指令“root /data/www” 将请求映射到文件 /data/www/about.html 并返回客户端。
  • “/”,这个请求相对比较复杂,首先它匹配到“/”location,然后index指令会根据root指令来判断是否存在 /data/www/index.html 文件,如果不存在,则查找index.php文件,若存在则该指令执行内部重定向到“/index.php”,并且 nginx 再次搜索location,就好像请求是由客户端发送的一样。正如我们之前看到的,重定向的请求最终将由 FastCGI 服务器处理。

Server配置中的server_name

server块指令中的server_name指令用于决定路由请求到哪一个服务器去处理。它支持精确匹配、通配符以及正则。看下面例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server {
listen 80;
server_name example.org www.example.org;
...
}

server {
listen 80;
server_name *.example.org;
...
}

server {
listen 80;
server_name mail.*;
...
}

server {
listen 80;
server_name ~^(?<user>.+)\.example\.net$;
...
}

通过名称路由服务,当匹配多个时,比如通配符和正则,将按照下面的规则来进行选择最终匹配:

  1. 精确匹配
  2. 以*开头的最长匹配,如:*.example.org
  3. 以*结尾的最长匹配,如:mail.*
  4. 按照在文件出现的顺序,第一个匹配的正则表达式

通配符

*通配符只能在名称的开始或者结尾,并且时在.的边上。像www.*.example.orgw*.example.org都是不合法的。可以使用正则表达式指定这些名称,例如~^www\..+\.example\.org$~^w.*\.example\.org$。星号可以匹配多个名称部分。名称*.example.org不仅匹配 www.example.org,还匹配 www.sub.example.org

注意:.example.org形式的特殊通配符名称可用于匹配确切名称example.org和通配符名称*.example.org

正则表达式

nginx 使用的正则表达式与 Perl 编程语言 (PCRE) 使用的正则表达式兼容。要使用正则表达式,服务器名称必须以波浪号字符开头:

1
server_name  ~^www\d+\.example\.net$;

否则,它将被视为精确匹配名称,或者如果表达式包含星号,则将其视为通配符名称(并且很可能被视为无效名称)。不要忘记设置“^”和“$”锚点。它们在语法上不是必需的,但在逻辑上是必需的。另请注意,域名点应使用反斜杠进行转义。应引用包含字符“{”和“}”的正则表达式:

1
server_name  "~^(?<name>\w\d{1,3}+)\.example\.net$";

否则 nginx 将无法启动并显示错误消息:

1
directive "server_name" is not terminated by ";" in ...

命名的正则表达式捕获可以稍后用作变量:

1
2
3
4
5
6
7
server {
server_name ~^(www\.)?(?<domain>.+)$;

location / {
root /sites/$domain;
}
}

PCRE 库支持使用以下语法的命名捕获:

1
2
3
?<name>	Perl 5.10 compatible syntax, supported since PCRE-7.0
?'name' Perl 5.10 compatible syntax, supported since PCRE-7.0
?P<name>Python compatible syntax, supported since PCRE-4.0

如果 nginx 无法启动并显示错误消息:

1
pcre_compile() failed: unrecognized character after (?< in ...

这意味着 PCRE 库很旧,应该尝试使用语法“?P”。捕获也可以以数字形式使用:

1
2
3
4
5
6
7
server {
server_name ~^(www\.)?(.+)$;

location / {
root /sites/$2;
}
}

但是,这种用法应仅限于简单的情况(如上述),因为数字参考很容易被覆盖。

其他

有一些名称会被特殊处理。

如果需要在非默认的server块中处理没有host头的请求,可以指定一个空字符串的名称,如下:

1
2
3
4
5
server {
listen 80;
server_name example.org www.example.org "";
...
}

如果server_name指令未在server块中指定,则使用空字符串作为server_name。如果server_name 定义为$hostname,则使用hostname。

如果有人使用 IP 地址而不是服务器名称发出请求,“Host”请求头字段将包含 IP 地址,并且可以使用 IP 地址作为服务器名称来处理请求:

1
2
3
4
5
6
7
8
9
server {
listen 80;
server_name example.org
www.example.org
""
192.168.1.1
;
...
}

在包罗万象的服务器示例中,可以看到奇怪的名称“_”:

1
2
3
4
5
server {
listen 80 default_server;
server_name _;
return 444;
}

上面没有任何特别之处,只是一个无效域名。还有其他诸如--!@# 之类。

国际化名称

国际化域名应该在server_name中只用ASCII表示,如

1
2
3
4
5
server {
listen 80;
server_name xn--e1afmkfd.xn--80akhbyknj4f; # пример.испытание
...
}

优化建议

精确匹配名称、以*开头的通配符和以*结尾的通配符是和端口绑定,保存在三张哈希表里中。哈希表的大小在配置阶段进行了优化,以便可以找到具有最少 CPU 缓存未命中的名称。

匹配过程顺序是 精确名称>以*开头的通配符>以*结尾的通配符>正则,所以建议是对于server_name的配置尽量是精确更好。

如果定义了大量服务器名称,或者定义了异常长的服务器名称,则可能需要在 http 级别调整 server_names_hash_max_size 和 server_names_hash_bucket_size 指令。 server_names_hash_bucket_size 指令的默认值可能等于 32、64 或其他值,具体取决于 CPU 缓存行大小。

nginx作为负载均衡

nginx 支持以下负载均衡机制:

  • 轮询:对请求以循环的方式进行分发
  • 最少连接:下一个请求分配给活动连接数最少的服务器
  • IP哈希:使用散列函数确定应该为下一个请求选择哪个服务器(基于客户端的 IP 地址)

默认是使用轮询方式,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    upstream myapp1 {
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}

server {
listen 80;

location / {
proxy_pass http://myapp1;
}
}
}

如果服务器的性能不同,也可以根据不同的配置性能来进行权重分配,这样在性能高的服务器能接受更多的请求,配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    upstream myapp1 {
server srv1.example.com weight=3;
server srv2.example.com;
server srv3.example.com;
}

server {
listen 80;

location / {
proxy_pass http://myapp1;
}
}
}

使用最少连接数如下:

```nginx
upstream myapp1 {
least_conn;
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}

server {
listen 80;

location / {
proxy_pass http://myapp1;
}
}
}

需要注意的是,轮询和最少连接方法两次请求可能会分发到不同的服务器,所以需要对session 做些处理,能保证即便分发不同的服务器,能正常的处理会话。

如果需要保证同个客户端的多次请求被分发到同个服务器,可以使用IP哈希的方式,配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    upstream myapp1 {
ip_hash;
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}

server {
listen 80;

location / {
proxy_pass http://myapp1;
}
}
}

作为负载均衡,nginx支持服务器健康检查。如果来自特定服务器的响应失败并出现错误,nginx 会将此服务器标记为失败,并在一段时间内尝试避免为后续入站请求选择此服务器。

max_fails 指令设置在 fail_timeout 期间应该发生的与服务器通信的连续不成功尝试次数。默认情况下,max_fails 设置为 1。设置为 0 时,将禁用此服务器的健康检查。 fail_timeout 参数还定义了服务器将被标记为失败的时间。在服务器故障后的 fail_timeout 间隔之后,nginx 将开始使用实时客户端的请求优雅地探测服务器。如果探测成功,则服务器被标记为活动服务器。

参考文档

[1].nginx documentation