Nginx-enginex
索引
1. installation
on arch linux1
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$ sudo pacman -Syu
$ sudo pacman -S nginx
$ sudo systemctl start nginx.service
$ sudo systemctl enable nginx.service
# $ sudo systemctl status nginx.service
s3loy@archlinux ~]$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host noprefixroute
valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 00:0c:29:4f:fa:7f brd ff:ff:ff:ff:ff:ff
altname enp2s1
altname enx000c294ffa7f
inet 192.168.209.128/24 brd 192.168.209.255 scope global dynamic noprefixroute ens33
valid_lft 1592sec preferred_lft 1367sec
inet6 fe80::98ef:2b0d:cbd3:648c/64 scope link
valid_lft forever preferred_lft forever
# inet 192.168.209.128
主机直接访问http://192.168.209.128/ 就可以看到Welcome to 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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122$ cat /etc/nginx/nginx.conf
#user http;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
# Load all installed modules
include modules.d/*.conf;
events {
worker_connections 1024;
}
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;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
}
blockhttp { ... }: 配置HTTP相关功能的顶级块。几乎所有的 Web 服务配置都在这里面。server { ... }: 定义一个“虚拟主机”,可以在一个Nginx里配置多个server块,来托管不同的网站location / { ... }: 定义当请求的 URL 匹配某个路径时,应该如何处理。location /表示匹配所有请求。
1 | $ mkdir -p ~/my-website/images |
访问http://192.168.209.128/about.html 发现403 Forbidden
此时考虑到文件夹为新建且处于whoami用户下,所以1
2
3
4
5
6
7$ ps aux | grep "nginx: worker process"
http 2179 0.0 0.1 15388 5140 ? S 16:13 0:00 nginx: worker process
$ chmod o+x /home/s3loy
$ ls -ld ~/my-website
drwxr-xr-x 3 s3loy s3loy 4096 Jul 23 16:10 /home/s3loy/my-website
直接重返链接就发现页面可以刷新出来了
当然这也很明显,请不要把网站放在自己的目录当中。出于隐私和安全考虑,默认权限非常严格
在arch linux当中,http推荐放在/srv/http1
2$ ls -ld /srv/http
drwxr-xr-x 2 root root 4096 May 3 19:26 /srv/http
在其他很多 Linux 系统上,常用目录是 /var/www/html
2. feature
2.1. Nginx 的灵魂:事件驱动的异步非阻塞架构
我们以这个简单的静态网页为例,当你请求静态文件时,Nginx 的 Worker 进程接收到请求,告诉操作系统内核:“把 ~/my-website/index.html 这个文件的数据发给这个用户”。然后 Worker 进程就去处理其他请求了。当内核把文件数据准备好后,会通知 Worker 进程,Worker 进程再把数据发回给浏览器。这个过程极其高效,因为它利用了操作系统的 sendfile 机制,避免了数据在内核和用户空间之间的多次复制,CPU 占用极低。
sendfile机制
用户空间 (User Space) 和 内核空间 (Kernel Space)。
- 用户空间: 你的应用程序(如
Nginx进程)运行的地方。 - 内核空间: 操作系统内核运行的地方,它能直接操作硬件(如磁盘、网卡)。
2.1.1. 传统 read() + write() 方式
当 Nginx 需要发送一个静态文件给客户端时,传统的 I/O 流程如下:
read()系统调用:Nginx进程发起read()系统调用,请求读取文件。这会导致一次从用户空间到内核空间的上下文切换。- 第一次数据复制 (
DMA):CPU指示DMA控制器将文件内容从磁盘读取到内核空间的缓冲区。 - 第二次数据复制 (
CPU):CPU将数据从内核空间缓冲区复制到用户空间的Nginx缓冲区。read()调用返回,发生一次从内核空间到用户空间的上下文切换。 write()系统调用:Nginx进程拿到数据后,发起write()系统调用,请求发送数据。再次发生一次从用户空间到内核空间的上下文切换。- 第三次数据复制 (
CPU):CPU将数据从用户空间的Nginx缓冲区复制到内核空间的Socket缓冲区(与网卡关联)。 - 第四次数据复制 (
DMA):DMA控制器将数据从内核空间的Socket缓冲区复制到网卡硬件中,然后发送出去。write()调用返回,再次发生一次从内核空间到用户空间的上下文切换。
2.1.2. sendfile() 方式
sendfile() 是一个系统调用,它把上述过程大大简化了。
sendfile()系统调用:Nginx进程发起sendfile()系统调用,它包含了输入(文件描述符)和输出(Socket描述符)的信息。发生一次从用户空间到内核空间的上下文切换。- 第一次数据复制 (
DMA):DMA控制器将文件内容从磁盘读取到内核空间的缓冲区。 - 第二次数据复制 (
CPU):CPU将数据直接从内核空间的读缓冲区复制到内核空间的Socket缓冲区。数据完全没有进入过用户空间! - 第三次数据复制 (
DMA):DMA控制器将数据从内核空间的Socket缓冲区复制到网卡硬件中。sendfile()调用返回,发生一次从内核空间到用户空间的上下文切换。
总结一下高效之处:
- 2 次上下文切换:减少了一半。
- 3 次数据复制:更重要的是,CPU 驱动的数据复制只有 1 次,且完全在内核空间内完成,避免了数据在内核和用户空间之间的拷贝。
真正的“零拷贝” (Zero-Copy):
在更现代的硬件上,sendfile() 还能做到更极致的优化。如果网卡支持 “Scatter-gather DMA” 功能,那么第 3 步的 CPU 复制也可以省掉。内核只需要把数据在缓冲区的位置和长度等描述信息传递给网卡,网卡就可以自己去内核缓冲区里拉取数据。此时,数据复制就只剩两次 DMA 操作,CPU 完全不参与数据拷贝,这就是真正的“零拷贝”。
sendfile在http块中,可自行寻找。/etc/nginx/nginx.conf
2.2. Master-Worker
虽然我们用 sudo 来启动和管理 Nginx,但为了安全,Nginx 的 Master 进程在启动后,会创建一些低权限的 Worker 进程。真正去处理用户请求、读取网站文件的,正是这些 Worker 进程。
在 Arch Linux 上,这个低权限用户通常是 http。在其他系统(如 Debian/Ubuntu)上可能是 www-data,可以把 Nginx 的 Worker 进程想象成一个名叫 http 的普通访客。
403 Forbidden 就是因为这个访客在某个环节被拦住了
2.2.1. Master Process
Master 进程是 Nginx 的入口和管理者,它以 root 用户权限启动,因为它需要执行一些特权操作。它的核心职责包括:
- 读取和验证配置: 启动时,它会读取
nginx.conf并检查语法是否正确。 - 绑定特权端口: 监听低于
1024的端口(如80,443)需要root权限。Master进程负责绑定这些端口。 - 创建和管理 Worker 进程: 根据配置 (
worker_processes) 创建指定数量的Worker进程。这些Worker进程会继承已经打开的监听端口。 - 以低权限运行 Worker: 出于安全考虑,
Master进程会以一个普通用户(如http或www-data)的身份来启动Worker进程。这样,即使Worker进程被嘿壳利用,其破坏能力也被限制在该用户的权限范围内。🤔 - 处理控制信号:
Master进程负责响应来自管理员的指令,例如:nginx -s reload: 平滑重载配置。nginx -s quit: 优雅地关闭。nginx -s stop: 快速关闭。
- 监控 Worker 状态: 持续监控
Worker进程的健康状况,如果某个Worker异常退出,Master会立即重新启动一个新的Worker来替代它。 - 日志文件管理: 负责打开日志文件。
关键点: Master 进程不处理任何客户端的请求。它只做管理工作。
2.2.2. Worker Processes
Worker 进程是真正处理业务的“劳动力”。它们由 Master 进程创建,并以低权限用户运行。
核心工作机制:事件驱动 + 异步非阻塞 I/O
- 共享监听套接字 (
Shared Listening Sockets):
所有Worker进程都从Master进程继承了同一个监听套接字(listening socket)。当一个新的客户端连接请求到达时,操作系统会唤醒这些正在等待的Worker进程。 - 争抢连接 (
accept_mutex):
为了避免多个Worker同时去处理同一个新连接(即“惊群效应”Thundering Herd),Nginx内部有一个accept_mutex锁。只有一个Worker能成功获取到这个锁,然后调用accept()来建立连接。这保证了连接处理的负载在多个Worker之间是相对均衡的。 - 事件循环和
epoll:
每个Worker进程内部都有一个高效的事件循环。它利用操作系统的epoll(在Linux上) 或kqueue(在BSD/macOS上) 这样的I/O多路复用技术。- 注册事件: 当一个
Worker接受一个新连接后,它不会傻等客户端发数据。它只是把这个连接(socket)注册到epoll实例上,并告诉epoll:“当这个连接上有数据可读时,请通知我。” - 等待事件: 然后,
Worker进程就调用epoll_wait(),把自己“挂起”,等待epoll的通知。它不消耗CPU。 - 处理就绪事件: 当一个或多个连接上的事件就绪时(比如,客户端发来了数据,或者发送缓冲区变空可以继续写入了),
epoll_wait()会被唤醒,并返回一个“就绪事件”的列表。 - 处理与再注册:
Worker进程遍历这个列表,对每个就绪的连接执行相应的非阻塞操作(如read()读取数据,write()或sendfile()发送数据)。处理完一个连接后,它会再次向epoll 更新这个连接需要监听的下一个事件。
- 注册事件: 当一个
流程的关键优势:
- 单线程高效处理: 每个 Worker 进程是单线程的,避免了线程切换的开销和锁的复杂性。
- 永不阻塞
- 连接数与内存消耗解耦: 一个 Worker 可以轻松维护数万个连接
- [ ] to be continued