Nginx学习笔记

学习时间:2023年1月19日

学习来源:深入理解Nginx——模块开发与架构解析第二版 陶辉著

1 安装

1.1 准备工作

1.1.1 Linux系统内核版本

首先我们需要一个内核为Linux 2.6及以上版本的操作系统,因为Linux 2.6及以上内核才支持epoll,而在Linux上使用select或poll来解决事件的多路复用,是无法解决高并发压力问题的。

使用命令查看电脑以及操作系统的相关信息。

1
uname [-amnrsv][--help][--version]
  • -a或--all:显示全部的信息。
  • -m或--machine:显示电脑类型。
  • -n或--nodename:显示在网络上的主机名称。
  • -r或--release:显示操作系统的发行编号。
  • -s或--sysname:显示操作系统名称。
  • -v:显示操作系统的版本。
  • --version:显示版本信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@HongyiZeng local]# uname -m
x86_64
[root@HongyiZeng local]# uname -n
HongyiZeng
[root@HongyiZeng local]# uname -r
3.10.0-1160.11.1.el7.x86_64
[root@HongyiZeng local]# uname -s
Linux
[root@HongyiZeng local]# uname -v
#1 SMP Fri Dec 18 16:34:56 UTC 2020
[root@HongyiZeng local]# uname --version
uname (GNU coreutils) 8.22
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by David MacKenzie.
[root@HongyiZeng local]#

1.1.2 必备软件

  • GCC编译器
  • PCRE库:PCRE(Perl Compatible Regular Expressions,Perl兼容正则表达式)是由Philip Hazel开发的函数库,目前为很多软件所使用,该库支持正则表达式。Nginx的HTTP模块要靠它来解析正则表达式。
1
yum install -y pcre pcre-devel
  • zlib库:zlib库用于对HTTP包的内容做gzip格式的压缩。
1
yum install -y zlib zlib-devel

1.1.3 磁盘目录

需要在Linux文件系统上准备以下目录:

  • Nginx源代码存放目录:该目录用于放置从官网上下载的Nginx源码文件,以及第三方或我们自己所写的模块源代码文件。
  • Nginx编译阶段产生的中间文件存放目录:该目录用于放置在configure命令执行后所生成的源文件及目录,以及make命令执行后生成的目标文件和最终连接成功的二进制文件。默认情况下,configure命令会将该目录命名为objs,并放在Nginx源代码目录下。
  • 部署目录:该目录存放实际Nginx服务运行期间所需要的二进制文件、配置文件等。默认情况下,该目录为/usr/local/nginx
  • 日志文件存放目录:日志文件通常会比较大,当研究Nginx的底层架构时,需要打开debug级别的日志,这个级别的日志非常详细,会导致日志文件的大小增长得极快,需要预先分配一个拥有更大磁盘空间的目录。

1.2 编译安装

到官网上下载源码包,上传至linux服务器。在usr/local下创建文件夹nginx并进入:

1
2
3
cd /usr/local/
mkdir nginx
cd nginx

将源码包解压缩到这里:

1
2
3
4
[root@HongyiZeng nginx]# tar -zxvf ../nginx-1.22.0.tar.gz -C ./
# ...解压缩过程
[root@HongyiZeng nginx]# ls
nginx-1.22.0

进入解压缩后得到的文件夹,并编译安装:

1
2
3
[root@HongyiZeng nginx]# cd nginx-1.22.0/
[root@HongyiZeng nginx-1.22.0]# ./configure
[root@HongyiZeng nginx-1.22.0]# make && make install

命令作用:

  • configure命令做了大量的幕后工作,包括检测操作系统内核和已经安装的软件,参数的解析,中间目录的生成以及根据各种参数生成一些C源码文件、Makefile文件等。
  • make命令根据configure命令生成的Makefile文件编译Nginx工程,并生成目标文件、最终的二进制文件。
  • make install命令根据configure执行时的参数将Nginx部署到指定的安装目录,包括相关目录的建立和二进制文件、配置文件的复制。

解压缩得到的文件有:

1
2
[root@HongyiZeng nginx-1.22.0]# ls
auto CHANGES CHANGES.ru conf configure contrib html LICENSE Makefile man objs README src

Nginx运行目录为/usr/local/nginx,目录结构为:

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
|---sbin
| |---nginx 可执行文件
|---conf
| |---koi-win
| |---koi-utf
| |---win-utf
| |---mime.types
| |---mime.types.default
| |---fastcgi_params
| |---fastcgi_params.default
| |---fastcgi.conf
| |---fastcgi.conf.default
| |---uwsgi_params
| |---uwsgi_params.default
| |---scgi_params
| |---scgi_params.default
| |---nginx.conf 主配置文件
| |---nginx.conf.default
|---logs
| |---error.log
| |---access.log
| |---nginx.pid 记录nginx主进程的pid
|---html
| |---50x.html
| |---index.html
|---client_body_temp
|---proxy_temp
|---fastcgi_temp
|---uwsgi_temp
|---scgi_temp

1.3 configure详解

1.3.1 命令参数

使用help命令可以查看configure包含的参数。

1
./configure --help

configure的参数分为了四大类型,只列出重要的。

① 路径相关
参数名称 含义 默认值
--prefix=PATH Nginx安装后的根目录 /usr/local/nginx
--sbin-path=PATH 可执行文件的存放路径 <prefix>/sbin/nginx
--conf-path=PATH 配置文件的存放路径 <prefix>/conf/nginx.conf
--error-log-path=PATH error日志文件的存放路径 <prefix>/logs/error.log
--pid-path=PATH pid文件的存放路径。这个文件里仅以ASCII码存放着Nginx master的进程ID,有了这个进程ID,在使用命令行(例如nginx -s reload)通过读取master进程ID 向master进程发送信号时,才能对运行中的Nginx服务产生作用 <prefix>/logs/nginx.pid
② 编译相关

③ 依赖软件

④ 模块相关

除了少量核心代码外,Nginx完全是由各种功能模块组成的。这些模块会根据配置参数决定自己的行为,因此,正确地使用各个模块非常关键。在configure的参数中,我们把它们分为五大类。

  • 事件模块
  • 默认编译进入Nginx的HTTP模块
  • 默认不会编译进入Nginx的HTTP模块
  • 邮件代理服务器相关的mail模块
  • 其他模块

1.3.2 执行流程

configure由Shell脚本编写,中间会调用<nginx-source>/auto/目录下的脚本。

1.3.3 生成的文件

当configure执行成功时会生成objs目录,并在该目录下产生以下目录和文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|---ngx_auto_headers.h
|---autoconf.err
|---ngx_auto_config.h
|---ngx_modules.c
|---src
| |---core
| |---event
| | |---modules
| |---os
| | |---unix
| | |---win32
| |---http
| | |---modules
| | | |---perl
| |---mail
| |---misc
|---Makefile

1.4 命令行控制

在Linux中,需要使用命令行来控制Nginx服务器的启动与停止、重载配置文件、回滚日志文件、平滑升级等行为。

配置环境变量

默认情况下,二进制可执行文件路径为usr/local/nginx/sbin/nginx。在执行nginx命令时,为简化输入,可以将其加入到环境变量中。这里直接加入到所有用户的环境变量中:

1
vim /etc/profile

在文件最后加入:

1
2
export NGINX_HOME=/usr/local/nginx # nginx的安装目录
export PATH=$PATH:$NGINX_HOME/sbin # 将可执行文件所在的目录sbin添加到PATH环境变量中

使其生效:

1
source /etc/profile

常用命令

  • 默认方式启动

直接执行Nginx二进制程序:

1
nginx

这时,会读取默认路径下的配置文件:usr/local/nginx/conf/nginx.conf

  • 另行指定配置文件的启动方式

使用-c参数指定配置文件。例如:

1
nginx -c tmpnginx.conf
  • 测试配置信息是否有错误

在不启动Nginx的情况下,使用-t参数仅测试配置文件是否有错误。例如:

1
2
3
[root@HongyiZeng nginx]# nginx -t
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
  • 版本信息
1
nginx -v
  • 快速停止服务

使用-s stop可以强制停止Nginx服务。-s参数其实是告诉Nginx程序向正在运行的Nginx服务发送信号量,Nginx程序通过nginx.pid文件中得到master进程的进程ID,再向运行中的master进程发送TERM信号来快速地关闭Nginx服务。

1
nginx -s stop
  • 优雅停止服务

如果希望Nginx服务可以正常地处理完当前所有请求再停止服务,那么可以使用-s quit参数来停止服务。

1
nginx -s quit

该命令与快速停止Nginx服务是有区别的。当快速停止服务时,worker进程与master进程在收到信号后会立刻跳出循环,退出进程。而优雅地停止服务时,首先会关闭监听端口,停止接收新的连接,然后把当前正在处理的连接全部处理完,最后再退出进程。

  • 使运行中的Nginx重读配置项并生效

使用-s reload参数可以使运行中的Nginx服务重新加载nginx.conf文件

1
nginx -s reolad

事实上,Nginx会先检查新的配置项是否有误,如果全部正确就以优雅的方式关闭,再重新启动Nginx来实现这个目的。类似的,-s是发送信号,仍然可以用kill命令发送HUP信号来达到相同的效果。

  • 查看nginx的进程号
1
ps -ef grep | nginx

-e:表示列出全部的进程

-f:显示全部的列(显示全字段)

2 配置

2.1 进程间关系

在正式提供服务的产品环境下,部署Nginx时都是使用一个master进程来管理多个worker进程,一般情况下,worker进程的数量与服务器上的CPU核心数相等。每一个worker进程都是繁忙的,它们在真正地提供互联网服务,master进程则很“清闲”,只负责监控管理worker进程。

worker进程之间通过共享内存、原子操作等一些进程间通信机制来实现负载均衡等功能。

image-20230121111259838

2.2 配置通用语法

Nginx的配置文件nginx.conf其实是一个普通的文本文件。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
user nobody;
worker_processes 8;
error_log varlog/nginx/error.log error;
#pid logs/nginx.pid;
events {
use epoll;
worker_connections 50000;
}
http {
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr [$time_local] "$request" '
'$status $bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log logs/access.log main buffer=32k;
# ...

2.2.1 块配置项

块配置项由一个块配置项名和一对大括号组成。具体示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
events { # 块配置项

}
http { # 块配置项
upstream backend { # 块配置项
server 127.0.0.1:8080; # 配置项
}
gzip on; # 配置项
server { # 块配置项
location /webstatic { # 块配置项
gzip off; # 配置项
}
}
}

块配置项可以嵌套。内层块直接继承外层块,例如,上例中,server块里的任意配置都是基于http块里的已有配置的。

当内外层块中的配置发生冲突时,究竟是以内层块还是外层块的配置为准,取决于解析这个配置项的模块。例如,上例在http模块中已经打开了gzip on;,但其下的location/webstatic又把gzip关闭了:gzip off;,最终,在/webstatic的处理模块中,gzip模块是按照gzip off来处理请求的。

2.2.2 配置项语法格式

1
配置项名 配置项值 配置项值 配置项值 ...;

配置项值之间由空格符来分隔,每行配置的结尾需要加上分号。

2.2.3 配置项的单位

当指定空间大小时,可以使用的单位包括:

  • K或者k千字节(KB)
  • M或者m兆字节(MB)

例如:

1
2
gzip_buffers 48k;
client_max_body_size 64M;

2.3 基本配置

Nginx在运行时,至少必须加载几个核心模块和一个事件类模块。这些模块运行时所支持的配置项称为基本配置,即所有其他模块执行时都依赖的配置项。

2.3.1 调试进程和定位问题的配置项

  • 是否以守护进程方式运行Nginx

语法:daemon on|off;

默认:daemon on;

守护进程(daemon)是脱离终端并且在后台运行的进程。它脱离终端是为了避免进程执行过程中的信息在任何终端上显示,这样一来,进程也不会被任何终端所产生的信息所打断。Nginx毫无疑问是一个需要以守护进程方式运行的服务,因此,默认都是以这种方式运行的。

  • 是否以主从方式工作

语法:master_process on|off;

默认:master_process on;

  • error日志的设置

语法: error_log pathfile level;

默认: error_log logs/error.log error;

2.3.2 正常运行的配置项

  • 嵌入其他配置文件

语法: include pathfile;

include配置项可以将其他配置文件嵌入到当前的nginx.conf文件中,它的参数既可以是绝对路径,也可以是相对路径(相对于Nginx的配置目录,即nginx.conf所在的目录),例如:

1
2
include mime.types;
include vhost/*.conf
  • pid文件的路径

语法: pid path/file;

默认: pid logs/nginx.pid;

保存master进程ID的pid文件存放路径。默认与configure执行时的参数--pid-path所指定的路径是相同的,也可以随时修改,但应确保Nginx有权在相应的目标中创建pid文件,该文件直接影响Nginx是否可以运行。

  • Nginx worker进程运行的用户及用户组

语法:user username[groupname];

默认:user nobody nobody;

user用于设置master进程启动后,fork出的worker进程运行在哪个用户和用户组下。当按照user username;设置时,用户组名与用户名相同。

若用户在configure命令执行时使用了参数--user=username--group=groupname,此时nginx.conf将使用参数中指定的用户和用户组。

2.3.3 优化性能的配置项

  • Nginx worker进程个数

语法:worker_processes number;

默认:worker_processes 1;

在master/worker运行方式下,定义worker进程的个数。

worker进程的数量会直接影响性能。

每个worker进程都是单线程的进程,它们会调用各个模块以实现多种多样的功能。如果这些模块确认不会出现阻塞式的调用,那么,有多少CPU内核就应该配置多少个进程;反之,如果有可能出现阻塞式调用,那么需要配置稍多一些的worker进程。

例如,如果业务方面会致使用户请求大量读取本地磁盘上的静态资源文件,而且服务器上的内存较小,以至于大部分的请求访问静态资源文件时都必须读取磁盘(磁头的寻址是缓慢的),而不是内存中的磁盘缓存,那么磁盘I/O调用可能会阻塞住worker进程少量时间,进而导致服务整体性能下降。

多worker进程可以充分利用多核系统架构,但若worker进程的数量多于CPU内核数,那么会增大进程间切换带来的消耗(Linux是抢占式内核)。一般情况下,用户要配置与CPU内核数相等的worker进程,并且使用下面的worker_cpu_affinity配置来绑定CPU内核。

  • 绑定Nginx worker进程到指定的CPU内核

语法:worker_cpu_affinity cpumask[cpumask...]

假定每一个worker进程都是非常繁忙的,如果多个worker进程都在抢同一个CPU,那么这就会出现同步问题。反之,如果每一个worker进程都独享一个CPU,就在内核的调度策略上实现了完全的并发。

1
2
worker_processes 4;
worker_cpu_affinity 1000 0100 0010 0001;
  • Nginx worker进程优先级设置

语法:worker_priority nice;

默认:worker_priority 0;

在Linux或其他类UNIX操作系统中,当许多进程都处于可执行状态时,将按照所有进程的优先级来决定本次内核选择哪一个进程执行。进程所分配的CPU时间片大小也与进程优先级相关,优先级越高,进程分配到的时间片也就越大(例如,在默认配置下,最小的时间片只有5ms,最大的时间片则有800ms)。这样,优先级高的进程会占有更多的系统资源。

优先级由静态优先级和内核根据进程执行情况所做的动态调整(目前只有±5的调整)共同决定。nice值是进程的静态优先级,它的取值范围是–20~+19,–20是最高优先级,+19是最低优先级。因此,如果用户希望Nginx占有更多的系统资源,那么可以把nice值配置得更小一些,但不建议比内核进程的nice值(通常为–5)还要小。

2.3.4 事件类配置项

暂略

2.4 配置静态Web服务器

静态Web服务器的主要功能由ngx_http_core_module模块(HTTP框架的主要成员)实现。

除了2.3节提到的基本配置项外,一个典型的静态Web服务器还会包含多个server块和location块,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
http {
gzip on;
upstream {
...
}
server {
listen localhost:80;
location /webstatic {
if {
...
}
root optwebresource;
...
}
location ~* .(jpg|jpeg|png|jpe|gif)$ {
...
}
}
server {
...
}
}

所有的HTTP配置项都必须直属于http块、server块、location块、upstream块或if块等,同时,在描述每个配置项的功能时,会说明它可以在上述的哪个块中存在,因为有些配置项可以任意地出现在某一个块中,而有些配置项只能出现在特定的块中。

Nginx为配置一个完整的静态Web服务器提供了非常多的功能,下面会把这些配置项分为以下8类进行详述。

2.4.1 虚拟主机与请求转发

由于IP地址的数量有限,因此经常存在多个主机域名对应着同一个IP地址的情况,这时在nginx.conf中就可以按照server_name(对应用户请求中的主机域名)并通过server块来定义虚拟主机,每个server块就是一个虚拟主机,它只处理与之相对应的主机域名请求。

① listen

设置nginx监听的端口

语法:listen address:port[default_server | [backlog=num | rcvbuf=size | sndbuf=size | accept_filter=filter | deferred | bind | ipv6only=[on|off] | ssl]];

默认:listen 80;

配置块:server

示例:

1
2
3
4
5
6
7
8
9
10
11
listen 127.0.0.1:8000;
listen 8000;
listen *:8000;
listen localhost:8000;

listen [::]:8000;
listen [fe80::1];
listen [:::a8c9:1234]:80;

listen 443 default_server ssl;
listen 127.0.0.1 default_server accept_filter=dataready backlog=1024;

可用参数含义:

  • default:将所在的server块作为整个Web服务的默认server块。如果没有设置这个参数,那么将会以在nginx.conf中找到的第一个server块作为默认server块。已经废弃。
  • default_server:同上。
  • backlog=num:表示TCP中backlog队列的大小。默认为–1,表示不予设置。在TCP建立三次握手过程中,进程还没有开始处理监听句柄,这时backlog队列将会放置这些新连接。可如果backlog队列已满,还有新的客户端试图通过三次握手建立TCP连接,这时客户端将会建立连接失败。
  • deferred:在设置该参数后,若用户发起建立连接请求,并且完成了TCP的三次握手,内核也不会为了这次的连接调度worker进程来处理,只有用户真的发送请求数据时(内核已经在网卡中收到请求数据包),内核才会唤醒worker进程处理这个连接。这个参数适用于大并发的情况下,它减轻了worker进程的负担。当请求数据来临时,worker进程才会开始处理这个连接。只有确认上面所说的应用场景符合自己的业务需求时,才可以使用deferred配置。
  • ssl:在当前监听的端口上建立的连接必须基于SSL协议。
② server_name

设置主机名。

语法: server_name name [...];

默认: server_name "";

配置块: server

server_name后可以跟多个主机名称,如:

1
server_name www.testweb.com download.testweb.com;

在开始处理一个HTTP请求时,Nginx会取出header头中的Host,与每个server中的server_name进行匹配,以此决定到底由哪一个server块来处理这个请求。有可能一个Host与多个server块中的server_name都匹配,这时就会根据匹配优先级来选择实际处理的server块。

Nginx正是使用server_name配置项针对特定Host域名的请求提供不同的服务,以此实现虚拟主机功能。

③ server_name_in_redirect

重定向主机名称的处理

语法: server_name_in_redirect on|off;

默认: server_name_in_redirect on;

配置块: http、server或者location

该配置需要配合server_name使用。在使用on打开时,表示在重定向请求时会使用server_name里配置的第一个主机名代替原先请求中的Host头部,而使用off关闭时,表示在重定向请求时使用请求本身的Host头部。

④ location

语法: location[=|~|~*|^~|@]/uri/{...}

配置块: server

location会尝试根据用户请求中的URI来匹配上面的/uri表达式,如果可以匹配,就选择location块中的配置来处理用户请求。

  • =表示把URI作为字符串,以便与参数中的uri做完全匹配。例如:
1
2
3
location = / {
# ...
} # 只有当用户请求是/时,才会使用该location下的配置
  • ~表示匹配URI时是字母大小写敏感的
  • ~*表示匹配URI时忽略字母大小写问题
  • ^~表示匹配URI时只需要其前半部分与uri参数匹配即可。例如:
1
2
3
location ^~ images { 
# ...
} # 以images开始的请求都会匹配上
  • @表示仅用于Nginx服务内部请求之间的重定向,带有@的location不直接处理用户请求

在uri参数里是可以用正则表达式的,例如:

1
2
3
location ~* \.(gif|jpg|jpeg)$ { 
# ...
} # 匹配以.gif、.jpg、.jpeg结尾的请求,且不区分大小写

在最后一个location中使用/作为参数,它会匹配所有的HTTP请求,这样就可以表示如果不能匹配前面的所有location,则由/这个location处理(默认处理)。例如:

1
2
3
location / {
# ...
}

2.4.2 文件路径的定义

URL:统一资源定位符(Uniform Resource Locator,缩写:URL)。是对资源的引用和访问该资源的方法。俗称网址,就是浏览器地址栏里面的。

组成部分:protocol://hostname:port/path?query#fragment

  • 协议:通常是 https 或 http,一种告诉浏览器或者设备如何访问资源的方法,当然还有其他的协议,如 ftp 、mailto 或者 file
  • 接下来是://
  • 主机名:表示 IP 地址的注册名称(域名) 或 IP 地址,用于识别连接到网络的设备的数字标识符
  • 可选的端口号
  • 路径:可以引用文件系统路径,通常作为一个代码段使用
  • 参数:以问号开头的可选查询参数,其中多个参数用 & 连接
  • 锚定:用于为页面上的标题提供快速链接,如锚点链接。

示例:

1
http://www.aspxfans.com:8080/news/day01/index.asp?boardID=5&pwd=24618&page=1#name
  • 协议 :http:
  • 主机名:www.aspxfans.com
  • 端口::8080
  • 目录:/news/day01/
  • 文件:index.asp
  • 参数:boardID=5&pwd=24618&page=1
  • 锚定:name

URI:统一资源标志符(Uniform Resource Identifier,缩写:URI),提供了一种识别资源的方法。但与 URL 不同的是,URI 不提供定位所述资源的方法。

URI 的最常见的形式是统一资源定位符(URL),经常指定为非正式的网址。由此,可以看出 URI 是 URL 的超集,并且每个 URL 本质上也是一个 URI。

① root

作用:以root方式设置资源路径。

语法:root path;

默认:root html;

配置块:http、server、location、if

例如,定义资源文件相对于HTTP请求的根目录:

1
2
3
location /foo {
root /home/hfy/;
}

以请求http://example.com/foo/bar/hello.html 为例。匹配到/foo,所以实际访问的路径为/home/hfy/foo/bar/hello.html

② index

作用:访问首页。

语法:index file…;`

默认:index index.html;

配置块:http、server、location

有时,访问站点时的URI是/,这时一般是返回网站的首页。index后可以跟多个文件参数,Nginx将会按照顺序来访问这些文件,例如:

1
2
3
4
location / {
root path;
index index.html index.php index.htm;
}

当客户端访问/时,Nginx首先会尝试访问path/index.html文件,如果可以访问,就直接返回文件内容结束请求,否则再试图返回path/index.php文件的内容,依此类推。

③ error_page

作用:根据HTTP返回码重定向页面

语法: error_page code[code...][=|=answer-code]uri|@named_location

配置块:http、server、location、if

当对于某个请求返回错误码时,如果匹配上了error_page中设置的code,则重定向到新的URI中。例如:

1
2
3
4
error_page 404 404.html;
error_page 502 503 504 50x.html;
error_page 403 http://example.com/forbidden.html;
error_page 404 = @fetch;

2.4.3 MIME类型的设置

暂略

2.4.4 配置示例

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
user  root;
worker_processes 1;

events {
worker_connections 1024;
}

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

sendfile on;

keepalive_timeout 65;

server {
# SSL 默认访问端口号为 443
listen 443 ssl;
# 请填写绑定证书的域名
server_name kisugitakumi.net;
#请填写证书文件的相对路径或绝对路径
ssl_certificate ...;
#请填写私钥文件的相对路径或绝对路径
ssl_certificate_key ...;
ssl_session_timeout 5m;
#请按照以下协议配置
ssl_protocols TLSv1.2 TLSv1.3;
#请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;
location / {
#网站主页路径。此路径仅供参考,具体请您按照实际目录操作。
#例如,您的网站主页在 Nginx 服务器的 /etc/www 目录下,则请修改 root 后面的 html 为 /etc/www。
root /home/git/www/hexo;
index index.html index.htm;
}
}

server {
listen 80;
#请填写绑定证书的域名
server_name kisugitakumi.net;
#把http的域名请求转成https
return 301 https://$host$request_uri;
}

}

2.5 配置反向代理服务器

2.5.1 简介

反向代理(reverse proxy)方式是指用代理服务器来接受Internet上的连接请求,然后将请求转发给内部网络中的上游服务器,并将从上游服务器上得到的结果返回给Internet上请求连接的客户端,此时代理服务器对外的表现就是一个Web服务器。充当反向代理服务器也是Nginx的一种常见用法(反向代理服务器必须能够处理大量并发请求)。

image-20230220165516967

Nginx作为反向代理服务器时转发请求的流程:

image-20230220165601095

2.5.2 负载均衡的基本配置

作为代理服务器,一般都需要向上游服务器的集群转发请求。这里的负载均衡是指选择一种策略,尽量把请求平均地分布到每一台上游服务器上。

① upstream块

语法:upstream name{...}

配置块:http

upstream块定义了一个上游服务器的集群,便于反向代理中的proxy_pass使用。例如:

1
2
3
4
5
6
7
8
9
10
11
upstream backend {
server backend1.example.com; # server配置项
server backend2.example.com;
server backend3.example.com;
}

sever {
location / {
proxy_pass http://backend;
}
}
② server

语法:server name[parameters];

配置块:upstream

server配置项指定了一台上游服务器的名字,这个名字可以是域名、IP地址端口、UNIX句柄等,在其后还可以跟下列参数。

  • weight=number:设置向这台上游服务器转发的权重,默认为1。
  • max_fails=number:该选项与fail_timeout配合使用,指在fail_timeout时间段内,如果向当前的上游服务器转发失败次数超过number,则认为在当前的fail_timeout时间段内这台上游服务器不可用。max_fails默认为1,如果设置为0,则表示不检查失败次数。
  • fail_timeout=time:fail_timeout表示该时间段内转发失败多少次后就认为上游服务器暂时不可用,用于优化反向代理功能。它与向上游服务器建立连接的超时时间、读取上游服务器的响应超时时间等完全无关。fail_timeout默认为10秒。
  • down:表示所在的上游服务器永久下线,只在使用ip_hash配置项时才有用。
  • backup:在使用ip_hash配置项时它是无效的。它表示所在的上游服务器只是备份服务器,只有在所有的非备份上游服务器都失效后,才会向所在的上游服务器转发请求。
1
2
3
4
5
upstream backend {
server backend1.example.com weight=5;
server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
server unix:/tmp/backend3;
}
③ ip_hash

语法: ip_hash;

配置块: upstream

作用:在有些场景下,我们可能会希望来自某一个用户的请求始终落到固定的一台上游服务器中。例如,假设上游服务器会缓存一些信息,如果同一个用户的请求任意地转发到集群中的任一台上游服务器中,那么每一台上游服务器都有可能会缓存同一份信息,这既会造成资源的浪费,也会难以有效地管理缓存信息。ip_hash就是用以解决上述问题的,它首先根据客户端的IP地址计算出一个key,将key按照upstream集群里的上游服务器数量进行取模,然后以取模后的结果把请求转发到相应的上游服务器中。这样就确保了同一个客户端的请求只会转发到指定的上游服务器中。

ip_hash与weight(权重)配置不可同时使用。如果upstream集群中有一台上游服务器暂时不可用,不能直接删除该配置,而是要down参数标识,确保转发策略的一贯性。例如:

1
2
3
4
5
6
7
upstream backend {
ip_hash;
server backend1.example.com;
server backend2.example.com;
server backend3.example.com down;
server backend4.example.com;
}

2.5.3 反向代理的基本配置

① proxy_pass

语法: proxy_pass URL;

配置块: location、if

此配置项将当前请求反向代理到URL参数指定的服务器上,URL可以是主机名或IP地址加端口的形式,例如:

1
2
proxy_pass http://localhost:8000/uri/;
proxy_pass http://unix:/path/to/backend.socket:/uri/;

还可以如上节负载均衡中所示,直接使用upstream块,例如:

1
2
3
4
5
6
7
8
9
10
11
upstream backend {
server backend1.example.com; # server配置项
server backend2.example.com;
server backend3.example.com;
}

sever {
location / {
proxy_pass http://backend; # 直接使用upstream块的名字backend
}
}
② proxy_method

语法: proxy_method method;

配置块: http、server、location

此配置项表示转发时的协议方法名。例如设置为:

1
proxy_method POST;

那么客户端发来的GET请求在转发时方法名也会改为POST

3 HTTP模块

3.1 一个简单的HTTP模块

Nginx HTTP模块调用的简化流程:

image-20230221142520810

worker进程会在一个for循环语句里反复调用事件模块检测网络事件。当事件模块检测到某个客户端发起的TCP请求时(接收到SYN包),将会为它建立TCP连接,成功建立连接后根据nginx.conf文件中的配置会交由HTTP框架处理。HTTP框架会试图接收完整的HTTP头部,并在接收到完整的HTTP头部后将请求分发到具体的HTTP模块中处理。这种分发策略是多样化的,其中最常见的是根据请求的URI和nginx.conf里location配置项的匹配度来决定如何分发。HTTP模块在处理请求的结束时,大多会向客户端发送响应,此时会自动地依次调用所有的HTTP过滤模块,每个过滤模块可以根据配置文件决定自己的行为。例如,gzip过滤模块根据配置文件中的gzip on|off来决定是否压缩响应。HTTP处理模块在返回时会将控制权交还给HTTP框架,如果在返回前设置了subrequest,那么HTTP框架还会继续异步地调用适合的HTTP模块处理子请求。开发HTTP模块时,首先要注意的就是HTTP框架到具体的HTTP模块间数据流的传递,以及开发的HTTP模块如何与诸多的过滤模块协同工作。

3.2 准备工作

3.2.1 整型封装

Nginx使用ngx_int_t封装有符号整型,使用ngx_uint_t封装无符号整型。Nginx各模块的变量定义都是如此使用的,建议读者沿用Nginx的习惯,以此替代int和unsinged int。

在Linux平台下,Nginx对ngx_int_t和ngx_uint_t的定义如下:

1
2
typedef intptr_t ngx_int_t;
typedef uintptr_t ngx_uint_t;

3.2.2 ngx_str_t数据结构

在Nginx的领域中,ngx_str_t结构就是字符串,定义如下:

1
2
3
4
typedef struct {
size_t len;
u_char *data;
} ngx_str_t;

注意,ngx_str_t的data成员指向的并不是普通的字符串,因为这段字符串未必会以’\0’作为结尾,所以使用时必须根据长度len来使用data成员。

3.2.3 ngx_list_t数据结构

ngx_list_t是Nginx封装的链表容器,它在Nginx中使用得很频繁,例如HTTP的头部就是用ngx_list_t来存储的。定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct ngx_list_part_s ngx_list_part_tshi ; // ngx_list_part_s ngx_list_part_t 是同义词

struct ngx_list_part_s {
void *elts; // 指向数组首地址的指针,void*没有限制数组元素的类型,因此可以存储任意类型的数据
ngx_uint_t nelts; // 数组中元素的个数
ngx_list_part_t *next; // 指向下一个元素
}; // 链表中的一个个元素,元素的本质是一个数组

typedef struct {
ngx_list_part_t *last; // 指向链表最后一个元素的指针
ngx_list_part_t part; // 链表的第一个元素
size_t size; // size限制每一个数组的元素的占用的空间大小,也就是用户要存储的一个数据所占用的字节数必须小于或等于size。
ngx_uint_t nalloc; // nalloc表示每个ngx_list_part_t数组的容量,即最多可存储多少个数据
ngx_pool_t *pool; // 链表中管理内存分配的内存池对象。用户要存放的数据占用的内存都是由pool分配的
} ngx_list_t; // 链表结构体

ngx_list_t中的所有数据都是由ngx_pool_t类型的pool内存池分配的,它们通常都是连续的内存(在由一个pool内存池分配的情况下)。

image-20230221152643083

提供的操作接口:

1
2
3
4
5
6
7
8
9
10
11
// 用于创建新的链表
// pool参数是内存池对象
// n是每个链表数组可容纳元素的个数(相当于ngx_list_t结构中的nalloc成员)
// size是每个元素的大小
ngx_list_t* ngx_list_create(ngx_pool_t pool, ngx_uint_t n, size_t size);

// 用于初始化一个已有的链表
static ngx_inline ngx_int_t ngx_list_init(ngx_list_t list, ngx_pool_t pool, ngx_uint_t n, size_t size);

// 用于添加新的元素,传入链表,返回新分配的元素首地址
void* ngx_list_push(ngx_list_t list);

代码示例

  • 建立一个链表,它存储的元素是ngx_str_t,其中每个链表数组中存储4个元素,代码如下所示:
1
2
3
4
ngx_list_t* testlist = ngx_list_create(r->pool, 4, sizeof(ngx_str_t));
if(testlist == NULL) {
return NGX_ERROR; // 返回空则创建失败
}
  • 添加元素
1
2
3
4
5
6
ngx_str_t* str = ngx_list_push(testlist);
if (str == NULL) {
return NGX_ERROR;
}
str->len= sizeof("Hello world");
str->data = "Hello world";
  • 遍历链表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// part用于指向链表中的每一个ngx_list_part_t数组
ngx_list_part_t* part = &testlist.part;

// 根据链表中的数据类型,把数组里的elts转化为该类型使用
ngx_str_t* str = part->elts;

// i表示元素在链表的每个ngx_list_part_t数组里的序号

for (i = 0; /* void */; i++) {
if(i >= part->nelts) {
if(part->next == NULL) {
// // 如果某个ngx_list_part_t数组的next指针为空,则表明遍历完毕
break;
}
// 访问下一个ngx_list_part_t
part = part->next;
str = part->elts;
// 将i序号置为0,准备重新访问下一个数组
i = 0;
}
// 这里可以很方便地取到当前遍历到的链表元素
printf("list element: %*s\n",str[i].len, str[i].data);
}

3.2.4 ngx_table_elt_t数据结构

3.3 将自定义HTTP模块编译进Nginx

Nginx提供了一种简单的方式将第三方的模块编译到Nginx中。首先把源代码文件全部放到一个目录下,同时在该目录中编写一个文件用于通知Nginx如何编译本模块,这个文件名必须为config

这样,只要在configure脚本执行时加入参数--add-module=PATH(PATH就是上面我们给定的源代码、config文件的保存目录),就可以在执行正常编译安装流程时完成Nginx编译工作。

有时,Nginx提供的这种方式可能无法满足我们的需求,其实,在执行完configure脚本后Nginx会生成objs/Makefileobjs/ngx_modules.c文件,完全可以自己去修改这两个文件,这是一种更强大也复杂得多的方法。

3.3.1 config文件

config文件其实是一个可执行的Shell脚本。如果只想开发一个HTTP模块,那么config文件中需要定义以下3个变量:

  • ngx_addon_name:仅在configure执行时使用,一般设置为模块名称。
  • HTTP_MODULES:保存所有的HTTP模块名称,每个HTTP模块间由空格符相连。在重新设置HTTP_MODULES变量时,不要直接覆盖它,因为configure调用到自定义的config脚本前,已经将各个HTTP模块设置到HTTP_MODULES变量中了,因此,要像如下这样设置:
1
"$HTTP_MODULES ngx_http_mytest_module" # 追加形式
  • NGX_ADDON_SRCS:用于指定新增模块的源代码,多个待编译的源代码间以空格符相连。注意,在设置NGX_ADDON_SRCS时可以使用$ngx_addon_dir变量,它等价于configure执行时–add-module=PATH的PATH参数。

因此,对于mytest模块,可以这样编写config文件:

1
2
3
ngx_addon_name=ngx_http_mytest_module
HTTP_MODULES="$HTTP_MODULES ngx_http_mytest_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_mytest_module.c"

3.3.2 利用configure脚本将定制的模块加入到Nginx中