《NGINX金典教程》读书笔记
书名 | 备注 |
---|---|
《NGINX金典教程》 | 人民邮电出版社 |
1、nginx调整 worker 进程的数量
为了充分利用 NGINX的性能特点,在不同的使用场景下,应该适当调整 worker 进程的数量。
基本规则是 worker 进程的数量应该和 CPU的核数相同。
但也有一些场景略有不同:
-
如果负载是CPU密集型,例如处理大量的 TCP/P 请求、执行 SSL或压缩),那么 worker进程的数量应该和CPU的核数相匹配
-
如果负载大多和磁盘IO操作相关,比如代理静态瓦片地图;(从磁盘或大量代理中获取不同内容集),那么 worker进程的数量可能是 CPU核数的 1.5倍~2倍。还有一些场景,需要根据内存大选择 worker 进程的数量,但效率取决于磁盘存储的内容类型和配置
2、什么是HTTP 请求组成部分?
HTTP 请求由 请求行、请求头 和 请求体 三部分组成,以下是它们的详细说明:
(1)、请求行 (Request Line)
描述:请求行位于 HTTP 请求的第一行,用于描述请求的基本信息。
结构:
<方法> <URL> <协议/版本>
- 方法:指定请求的动作类型,比如
GET
(获取资源)、POST
(提交数据)、PUT
(更新资源)、DELETE
(删除资源)。 - URL:请求的目标资源路径,比如
/index.html
或/api/data
。 - 协议/版本:使用的 HTTP 协议及其版本,比如
HTTP/1.1
或HTTP/2.0
。
示例:
GET /index.html HTTP/1.1
(2)、请求头 (Request Headers)
描述:请求头包含以键值对形式提供的附加信息,用于描述客户端的环境或传递额外参数。
结构:
键: 值
- 常见请求头:
Host
:服务器的域名或 IP 地址(必须的)。User-Agent
:描述客户端(浏览器/应用)的信息。Accept
:客户端可接受的响应类型,比如text/html
。Authorization
:用于身份验证。Content-Type
:当有请求体时,描述数据的格式,如application/json
。
示例:
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html
Content-Type: application/json
(3)、请求体 (Request Body)
描述:请求体是 HTTP 请求中可选的部分,包含需要发送给服务器的数据,通常在 POST
或 PUT
请求中使用。
特点:
- GET 请求通常没有请求体,参数附加在 URL 中。
- POST/PUT 请求中,数据通常通过请求体发送。
示例:
对于一个登录请求:
{
"username": "user123",
"password": "mypassword"
}
请求体会直接放在请求的最后部分。
完整 HTTP 请求示例
以下是一个完整的 HTTP 请求示例:
POST /login HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Content-Type: application/json
Content-Length: 50
{
"username": "user123",
"password": "mypassword"
}
总结
- 请求行:描述请求的基础信息(方法、URL、协议版本)。
- 请求头:包含辅助信息(客户端环境、内容类型等)。
- 请求体:在需要上传数据时,包含具体的内容(如 JSON、文件等)
3、server name 指令 问题
该指令一般用来区分相同 IP地址和端口组合下的不同服务。NGINX在接收到HTTP 请求后会根据 listen 指令的参数在文件中查找相匹配的 server 块,然后在查找到的所有 server 块中根据 server_name 继续选择。如果找不到对应的 server 块,那么默认使用得到的所有server 块中的第一个。
在使用 server_name 指令时,会匹配请求头中的 Host 字段,通常是一个 FODN 域名或地址。可以使用精确匹配、泛域名匹配和正则表达式匹配这些匹配规则,具体的匹配顺序依次如下:
- 精确匹配,如 www.example.co
- 以通配符开始的字符串匹配,如*.example.com*
- *以通配符结束的字符串匹配,如 www.example
- 正则表达式匹配,如^www.example.com$
- 配置了 default_server 参数的 server 块【如果没有匹配到其他server块的监听端口为80的server_name,那么将会使用当前server块作为默认服务器来处理请求。这通常用于设置默认的虚拟主机或者处理未知主机名的请求。】
案例:
我有一个 nginx 服务器,做为公司的 WEB 服务器,监听在 80 端口上
监听在下面不同的 server_name 下面, 且我的 DNS 解析是 把 *.srebro.cn 都 A 记录到 172.22.33.1 这个ip 上;这样就会有一个问题,当我访问任意 xx.srebro.cn 的时候,他就是随机跳转我下面的 server_name 上; 这意味着当请求到达nginx服务器时,如果没有匹配到其他server块的监听端口为80的server_name,那么将会使用当前server块作为默认服务器来处理请求。
-
wiki.srebro.cn
-
code.srebro.cn
-
map.srebro.cn
**处理方式:设置默认的虚拟主机或者处理未知主机名的请求 **
server {
listen 80 default_server;
listen 443 ssl default_server;
ssl_certificate /home/application/nginx/cert/srebro.cn/srebro.cn.crt;
ssl_certificate_key /home/application/nginx/cert/srebro.cn/srebro.cn.key;
server_name _;
root /home/application/nginx/html;
}
4、nginx 中默认使用 http1 还是http2?
在 Nginx 中,默认情况下使用的是 HTTP/1.1 协议。如果希望启用 HTTP/2,需要显式地在配置文件中进行设置。
如果nginx 是编译部署,在编译 Nginx 时没有添加 --with-http_v2_module
参数,Nginx 就不会包含 HTTP/2 模块,因此你无法在配置中启用 HTTP/2 功能
编译 Nginx在安装或重新编译 Nginx 时,加入以下参数:
./configure --with-http_v2_module ......
配置文件中启用 http2示例:
server {
listen 443 ssl http2; # 启用 HTTP/2
server_name www.example.com;
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private.key;
# 其他配置
}
在这个配置中,http2
参数使得 Nginx 启用 HTTP/2,并且这个设置仅对 SSL/TLS(即 HTTPS)连接有效。
默认 HTTP 协议:
-
HTTP/1.1 是 Nginx 的默认协议,除非显式启用 HTTP/2。
-
HTTP/2 需要 Nginx 版本 1.9.5 或更高,并且仅能在启用了 SSL/TLS 的情况下使用。
HTTP/1.1 对比 HTTP/2 协议
5、nginx 的ssl 配置中ssl_prefer_server_ciphers on;参数用途
在 Nginx 的 SSL 配置中,ssl_prefer_server_ciphers on;
这个指令的作用是控制在客户端和服务器之间协商加密套件时,是否优先使用服务器指定的加密算法(cipher suite)
具体解释:
- 加密套件(Cipher Suite)是用于 SSL/TLS 连接的加密算法集合,包括加密算法、消息认证码算法(MAC)、密钥交换算法等。
- 客户端和服务器协商:当客户端和服务器建立 SSL/TLS 连接时,会互相协商一个共同支持的加密套件。这个过程通常是客户端先发送自己支持的加密套件列表,然后服务器从中选择一个双方都支持的加密套件。
ssl_prefer_server_ciphers on;
的作用:
- 开启:如果设置为
on
,服务器将优先选择自己配置的加密套件,而不是根据客户端的首选顺序来选择。这意味着,服务器可以强制使用更强的加密套件,尽管客户端可能更倾向于使用其他套件。 - 关闭(默认值是
off
):如果设置为off
,则服务器会遵循客户端提供的加密套件优先顺序,选择客户端列出的最优加密套件。
为什么使用 ssl_prefer_server_ciphers on;
?
- 提高安全性:通过优先选择服务器配置的加密套件,服务器可以确保使用强加密算法,避免客户端可能使用的弱加密算法(即使客户端支持它们)。这种配置有助于提高 SSL/TLS 连接的安全性。
配置文件示例:
server {
listen 8208;
listen 18208 ssl http2;
ssl_certificate /home/application/nginx/conf/server.crt;
ssl_certificate_key /home/application/nginx/conf/server.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;
在这个例子中,ssl_ciphers
指定了服务器允许的加密套件,而 ssl_prefer_server_ciphers on;
确保即使客户端提供了其他加密套件,服务器也会优先选择这些高强度的加密算法。 ssl_ciphers
指定算法为 ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE
6、split_clients 模块用途
Nginx 的 split_clients
模块 是一个非常实用的模块,主要用于实现流量分流或请求随机分配,常被用在 A/B 测试、灰度发布或负载均衡场景中。
split_clients
模块根据预定义的权重,按一定比例将请求划分到不同的组。通过这种方式,可以将用户分配到不同的实验组、版本或后端服务器,便于测试新功能或分配流量。- 模块通过对某些用户属性(如 Cookie、IP 地址、客户端类型,请求参数等)的哈希值进行计算,来保证请求的分配是稳定的(即同一个用户总是会被分配到同一组)。
基本语法
split_clients "${variable}" $result_variable {
range1 condition1;
range2 condition2;
...
* default_condition;
}
${variable}
:用于哈希的变量,可以是$remote_addr
、$cookie_*
、$arg_*
等。$result_variable
:结果存储变量,用于判断用户属于哪一组。range
:划分的范围(权重)。condition
:条件的值,表示用户所属的组。default_condition
:默认条件,当范围未覆盖到某些请求时的兜底值。
(1)、实现 A/B 测试的示例
假设需要将 50% 的用户分配到版本 A,另 50% 分配到版本 B:
nginx
复制代码
split_clients "${remote_addr}" $variant {
50% "A";
50% "B";
}
location / {
if ($variant = "A") {
proxy_pass http://backend_A;
}
if ($variant = "B") {
proxy_pass http://backend_B;
}
}
- 根据用户的 IP 地址(
$remote_addr
),以 50% 的比例将请求分流到backend_A
和backend_B
。 - 每个用户的分配是固定的,因为基于哈希计算。
(2)、实现灰度发布的示例
在灰度发布中,你可能希望将 20% 的流量发送到新版本,其余 80% 发送到旧版本:
nginx
复制代码
split_clients "${remote_addr}" $variant {
20% "new";
* "old";
}
location / {
if ($variant = "new") {
proxy_pass http://new_backend;
}
if ($variant = "old") {
proxy_pass http://old_backend;
}
}
20%
表示将 20% 的用户分配到new_backend
。*
表示剩余的用户,分配到old_backend
。
(3)、实现多版本分流
如果有多个版本需要测试,可以设置多个分流比例:
nginx
复制代码
split_clients "${remote_addr}" $variant {
10% "A";
20% "B";
30% "C";
* "D";
}
location / {
proxy_pass http://backend_$variant;
}
此配置中:
- 10% 的用户分配到版本 A;
- 20% 的用户分配到版本 B;
- 30% 的用户分配到版本 C;
- 其余用户分配到版本 D。
实践: 使用 User-Agent 区分移动端和 PC 端
Nginx 的 $http_user_agent
变量可以获取客户端的 User-Agent 字段,通过检测其中的关键标识词来区分设备类型。
# 根据 User-Agent 设置变量
map $http_user_agent $device_type {
~*Mobile "mobile"; # 包含 "Mobile" 的 User-Agent,通常为移动设备
~*Android|iPhone "mobile"; # 常见移动设备关键字
default "pc"; # 其他情况默认为 PC
}
# 使用 split_clients 进行分流
split_clients "${remote_addr}" $variant {
50% "A"; # 分流到 A 组
50% "B"; # 分流到 B 组
}
# 不同设备访问不同服务
location / {
if ($device_type = "mobile") {
proxy_pass http://mobile_backend_$variant; # 移动端服务
}
if ($device_type = "pc") {
proxy_pass http://pc_backend_$variant; # PC 端服务
}
}
7、nginx http 反向代理 传递请求头
NGINX默认会重新定义所代理请求中的两个请求头字段— Host和 Connection,将 Host设置为$proxy_host 变量,将 connection 设置为 off,并去掉值为空字符串的请求头字段。当然,也可以修改其他请求头字段。
要实现这些设置,请使用proxy_set_header 指令。可以继续在上一步的 location 块内或层级更高的 location 块内调用这个伪指令,也可以在特定的 server 块或 http 块中调用它
location / {
proxy_pass http://127.0.0.1:8080;
# 将客户端的 Host 头部传递给后端;如果一个 Nginx 实例代理多个域名(如 example.com 和 test.com),后端需要根据 Host 来区分不同的请求。可以确保后端服务器接收到正确的域名信息
proxy_set_header Host $host;
# 传递客户端真实 IP
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 传递客户端协议
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket 支持
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 自定义请求头
proxy_set_header X-Custom-Header "my-custom-value";
}
8、配置缓冲区
在默认情况下,NGIX 缓冲区中的内容来自后端服务器。当后端服务器返回响应内容时NGINX 先将它存储在内部缓冲区中,直到接收到整个响应内容后才发送给客户端。缓冲区有于优化慢速客户端的性能,如果 NGINX把响应内容同步传递到客户端,那么有可能会浪费代理服务器的时间。而启用缓冲区后,NGINX允许代理服务器快速处理响应,NGINX把响应内容在储下来的时间和客户端下载响应内容所需的时间一样长。
负责启用和禁用缓冲区的指令是 proxy_buffering,其默认值为 on,表示启用缓冲区proxy_buffers 指令用于设置为请求分配的缓冲区大小及数量。缓冲区的大小由 proxybuffer_size 指令设置。来自代理服务器的响应内容的第一部分会被存储在单独的缓冲区中这部分通常包含一个相对较小的响应头。
缓冲区默认大小
默认缓冲区大小
以下是一些缓冲区的默认值(不同版本可能略有不同):
proxy_buffers:默认是 8 4k 或 8 8k(8 个缓冲区,每个缓冲区大小为 4k 或 8k)。
proxy_buffer_size:默认值通常为一个内存页大小(4k 或 8k,取决于系统架构)。
client_body_buffer_size:默认通常是 8k 或 16k。
自定义缓冲区大小
location / {
proxy_buffers 16 4k; # 响应体缓冲区
proxy_buffer_size 2k; # 响应头缓冲区
proxy_pass http://localhost:8080;
}
如果禁用缓冲区,则将从服务器接收到的响应同步发送到客户端。对于需要尽快开始接收响应的快速交互客户端,这样的配置是可行的。
例如在 websock 的场景下,为了时效性,快速交互客户端,就不会开启缓冲区配置
location ^~/socket {
proxy_buffering off; #禁用缓冲区
rewrite ^/accidentsocket/(.*)$ /$1 break;
proxy_pass http://192.168.1.1:9005;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#升级http1.1到 websocket协议
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}