大数跨境
0
0

Nginx | 核心知识150讲,百万并发下性能优化之HTTP协议中反向代理实践笔记

Nginx | 核心知识150讲,百万并发下性能优化之HTTP协议中反向代理实践笔记 全栈工程师修炼指南
2025-12-12
2
导读:本文讲解 Nginx 中 HTTP 反向代理流程,并根据其流程介绍了不同阶段的相关指令参数,实践一个简单的HTTP反向代理的配置示例,以及浅析了 HTTP 反向代理中接收客户端请求处理的香港指令模块,

[ 知识是人生的灯塔,只有不断学习,才能照亮前行的道路 ]


📢 大家好,我是 WeiyiGeek一名深耕安全运维开发(SecOpsDev)领域的技术从业者,致力于探索DevOps与安全的融合(DevSecOps),自动化运维工具开发与实践,企业网络安全防护,欢迎各位道友一起学习交流、一起进步 🚀,若此文对你有帮助,一定记得点个关注⭐与小红星❤️或加入到作者知识星球『 全栈工程师修炼指南,转发收藏学习不迷路 😋 。

0x00 前言简述

描述:上一章《Nginx | 核心知识150讲,百万并发下性能优化之反向代理流程与负载均衡模块介绍笔记》作者介绍了 Nginx 负载均衡以及反向代理的基础知识,通过参考 Nginx 官方文档,并结合实际案例,详细讲解实践了 Nginx 中常用的负载均衡算法,例如轮询、加权轮询、IP 哈希(一致性)、以及最小连接等。

本章将在此基础上,继续讲解 Nginx 反向代理模块相关指令的配置,以及反向代理不同阶段的缓存配置,并针对不同的应用场景做优化配置,提高访问性能,最后通过一些小小案例,让各位看友可快速掌握如何在Nginx配置反向代理,不过在此之前,我们先来复习一下 Nginx 中 HTTP 协议反向代理请求处理流程,以便于对 Nginx 反向代理有更深层次的理解,在后续实践时能与此相互印证。

温馨提示:若文章代码块中存在乱码或不能复制,请联系作者,也可通过文末的阅读原文链接,加入知识星球中阅读,原文链接:https://articles.zsxq.com/id_fkims9wlo8ww.html


Nginx 中 HTTP 反向代理流程

下图展示了 Nginx 中 proxy 模块反向代理的处理流程,包括请求接收、缓存检查、上下游请求与响应的缓冲机制、负载均衡选择及连接复用等核心环节。通过这些步骤,Nginx 能够有效地将客户端请求转发给后端服务器,并将服务器的响应返回给客户端,从而实现反向代理的功能,这一流程对于理解 Nginx 的工作原理和优化配置至关重要。

  • 流程01.反向代理在 Nginx 十一阶段之一的 content 阶段生效,此阶段负责生成发往用户的响应内容,当配置使用了 proxy_pass 指令(优先级比 root 指令高)时,反向代理流程在此阶段被触发。

  • 流程02.反向代理流程中,首先会检查是否有缓存可用,如果有则直接返回响应, 无需向上游发起请求。如果没有缓存或缓存过期,则会进入下一步处理。

  • 流程03.未命中缓存时,将进入完整的反向代理流程,首先是首先生成发往上流的HTTP头部和包体(body),而非立即建立TCP连接,因为提前生成请求可避免对上游应用服务器造成不必要的连接压力,延迟建连可优化资源使用。

  • 流程04.判断 proxy_request_buffering 指令控制是否先将用户请求的完整包体缓存后再处理,缺省为 on 表示启用缓冲 Nginx 会先读取完整的请求包体并存储至内存或磁盘。

    • 开启场景(on):上游服务并发处理能力弱于 Nginx  c 时,若边接收边转发,会导致与上游服务器的连接长时间保持,消耗其有限的并发连接资源。
    • 关闭场景(off):上游服务不敏感于并发连接数,可关闭此功能,然后采用流式转发,提升响应实时性,但可能增加内存压力。
  • 流程05.反向代理流程中,Nginx 会根据负载均衡算法(包括哈希(hash)、轮询(round robin)、最少连接(least_conn)等)选择  upstream 块中 server 指令定义的上游服务器,若加入lua模块,还可通过 balance_by_lua 实现 gray 的 LB 策略。

  • 流程06.根据所选服务器参数建立TCP连接,并可通过多种指令配置连接行为,如超时、重试等。

  • 流程07.发送请求到上游服务器,若启用了 proxy_request_buffering ,则在接收完全部请求体后一次性发送,否则采用边读边发的方式进行流式传输,两种发送方式会对服务器内存产生影响,缓冲模式内存可控,流式模式因上下游速率差异导致更高内存占用。

  • 流程08.接收上游响应头部,在接收到完整的响应头部后,进入到下一步处理。

  • 流程09.处理响应头部,接收完成后根据配置指令处理响应头部信息,与此同时 $upstream_header_time 变量记录接收响应头部所耗时间。

  • 流程10.根据 proxy_buffering 指令控制下游响应方式,默认开启(on)表示接收完整的上游响应包体,暂存于临时文件(目的:节约内存)或内存,再统一向客户端发送响应头部和包体。

    • 开启场景(on):快速接收上游响应,释放上游连接,独立控制向客户端的发送,提高系统吞吐。
    • 关闭场景(off):采用流式传输,边接收上游响应边转发给客户端, 受限于下游网速,若都客户端都在内网则影响不大。
  • 流程11.缓存写入判断,响应发送完毕后,判断是否开启缓存(cache),若开启且当前响应可被缓存,则将其加入缓存系统。

  • 流程12.反向代理流程结束,根据配置决定是否关闭或复用与上游的连接,复用连接通过 keepalive 机制实现,提升性能,至此整个请求处理完毕。


weiyigeek.top-Nginx中HTTP 反向代理流程图

由上述可知 proxy 反向代理完整处理流程,涵盖缓存检查 → 请求构造 → 包体接收控制 → 负载均衡选服 → 连接建立 → 请求发送 → 响应头接收 → 响应体缓冲控制 → 响应转发 → 缓存写入 → 连接管理,后续章节将围绕此流程详解各指令的具体用法。


0x01 Nginx 反向代理模块指令浅析

描述:说到反向代理,通过 Nginx 官方文档可知,在 http 和 stream 都有 proxy 模块,即 ngx_http_proxy_module 模块和 ngx_stream_proxy_module 模块,分别用于处理转发 HTTP、gPRC、Websocket,和 TCP、UDP 协议到上游服务器上,接下来我们先来了解下这两个模块的常用指令。

ngx_http_proxy_module 模块指令

HTTP 协议反向代理

描述:在 Nginx 中 Content 阶段通过指令 proxy_pass,决定请求由 ngx_http_proxy_module 模块处理,接下来我们以反向代理最常用的 proxy_pass 指令开始介绍,逐步介绍在 http_proxy 模块中其它常用指令。

官方文档地址:https://nginx.org/en/docs/http/ngx_http_proxy_module.html


指令参数:

proxy_pass 是动作类指令用于对上游服务使用 HTTP/HTTPS 协议的反向代理,在 ngx_http_proxy_module 模块中,该指令是核心配置之一,所以其默认编译进 Nginx 中,可通过 --without-http_proxy_module 编译选项禁用。

# 设置代理服务器的协议和地址以及位置应映射到的可选URI。
Syntax:  proxy_pass URL;
Default:  —
Context:  location, if in location, limit_except

生成发往上游的请求行:

  • proxy_method 指令用于设置代理请求的 HTTP 方法,默认情况下,Nginx 会将客户端发起的原始方法传递给上游服务器。
Syntax:  proxy_method method;
Default:  —
Context:  http, server, location
  • proxy_http_version 指令用于设置代理请求的 HTTP 版本,默认情况下,HTTP 版本 1.0 传递给上游服务器,特别注意在长连接( keepalive )的场景,需要与服务端协商HTTP/1.1版本。
Syntax:  proxy_http_version 1.0 | 1.1;
Default:  proxy_http_version 1.0;
Context:  http, server, location

生成发往上游的请求头部(Header):

  • proxy_set_header 指令用于设置代理请求的头部,值可以包含文本、变量及其组合,默认情况下会设置 Host 头部为 $proxy_host ,并关闭长连接,若头字段为空字符串,则 header 不会传递给代理服务器。
Syntax:  proxy_set_header field value;
Default:  proxy_set_header Host $proxy_host;
          proxy_set_header Connection close;
Context:  http, server, location
  • proxy_pass_request_headers 指令用于控制是否将原始请求头部传递给代理服务器,默认情况下为开启。
Syntax:  proxy_pass_request_headers on | off;
Default:  proxy_pass_request_headers on;
Context:  http, server, location

生成发往上游的请求包体(Body):

  • proxy_set_body 指令定义传递给代理服务器的请求正文。
Syntax:  proxy_set_body value;
Default:  —
Context:  http, server, location
  • proxy_pass_request_body 指令用于控制是否将原始请求正文传递到代理服务器,默认情况下为开启。
Syntax:  proxy_pass_request_body on | off;
Default:  proxy_pass_request_body on;
Context:  http, server, location

温馨提示:上述参数 URL 格式为:域名或IP地址(可选端口),Unix socket地址,和上一章所提到的 upstream 块中指定的服务组名称,以及使用@名称(内部location转发),此外还可以使用变量,例如 $scheme$host 、$request_uri 等。


示例演示

步骤 01.在 214 主机的Nginx 配置文件中,分布定义 8011 和 8012 服务用于后续演示反向代理转发及负载均衡。

# 编辑主配置文件,添加 include 指令,引入自定义配置文件目录。
vim /usr/local/nginx/conf/nginx.conf
....
include /usr/local/nginx/conf.d/*.conf;
....

# 创建测试服务器配置文件目录,并写入两个监听服务器的配置。
mkdir /usr/local/nginx/conf.d/
tee /usr/local/nginx/conf.d/server.conf <<'EOF'
server {
  listen 8011;
  server_name localhost _;
  default_type text/plain;
return 200 'From 8011 server response!\nrequest: $request\nhost: $host\nuri: $uri\nrequest_uri: $request_uri\nmethod: $request_method\nhttp_name: $http_name\nrequest_body: $request_body\n';
}

server {
  listen 8012;
  server_name localhost _;
  default_type text/plain;
return 200 'From 8012 server response!\nrequest: $request\nhost: $host\nuri: $uri\nrequest_uri: $request_uri\nmethod: $request_method\nhttp_name: $http_name\nrequest_body: $request_body\n';
}
EOF

步骤 02.在 214 主机的 Nginx 配置目录中创建 backend_server.conf 文件,定义 upstream 服务组。

tee /usr/local/nginx/conf.d/backend_server.conf <<'EOF'
# 创建上游服务器
upstream backend {
# 定义共享内存区,用于在工作进程间同步负载信息,可根据后端服务器数量调整。
  zone backend_zone 64k;

# 缺省使用轮询的负载均衡算法
  server 127.0.0.1:8011 weight=2;  # 权重为2表示,轮询三次中有两次命中此上游服务器
  server 127.0.0.1:8012;

# 设置与上游服务器的长连接,最多保持10个空闲的保活连接。
  keepalive 10;  
  keepalive_timeout 60s; # 设置与上游服务器的长连接,空闲连接的超时时间。
}
EOF

步骤 03.在 214 主机的 Nginx 配置目录中创建 proxy_server.conf 文件,定义反向代理服务,以及上述介绍的相关指令的使用案例。

  • 如果 proxy_pass 指令不带URI,则原始请求的URI将被传递到服务器,请求 URI 以 / 开头
# 创建监听用于反向代理
tee /usr/local/nginx/conf.d/proxy_server.conf <<'EOF'
server {
  listen 80;
  server_name test.weiyigeek.top;
  access_log /var/log/nginx/test.log main;
  error_log test.error.log info;
  location / {
    proxy_pass http://backend;
    # 设置请求方法
    proxy_method POST;

    # 启用头部传递(缺省也是开启的) 
    proxy_pass_request_headers on;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    # 启用长连接
    proxy_set_header Connection "";
    proxy_http_version 1.1;

    # 自定义头部请求
    proxy_set_header name 'weiyigeek';

    # 启用包体传递(缺省也是开启的)
    proxy_pass_request_body off;
    # 自定义body请求
    proxy_set_body 'Hello World! Proxy Server By http://test.weiyigeek.top';
    
  }

# 测试时请将下述三个需求配置的 location 代码块加入到此处。
}
EOF

然后,在 location 块中演示下述其它三个需求配置。

  • 如果 proxy_pass 指令指定了URI,则当请求被传递到服务器时,规范化请求URI中与位置匹配的部分将被指令中指定的URI替换。
location /a {
  proxy_pass http://backend/addurl;
  proxy_set_header Host $host;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header Connection "";
  proxy_set_header name 'weiyigeek';
  proxy_http_version 1.1;
}

如果 proxy_pass 指令中使用了请求URI变量 $request_uri,将会根据请求变量动态构建代理服务器的地址。

location /b {
  proxy_pass http://backend$request_uri;
  proxy_method GET;
  proxy_set_header Host $host;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header Connection "";
  proxy_set_header name 'weiyigeek';
  proxy_http_version 1.1;
}

如果需对于更复杂的URI判断逻辑,可用 rewrite 指令使用 regular expression 匹配元组更改 URI时,将使用相同的配置来处理请求(break),此时假若,请求 /reg/xxx 请求,则转发到后端服务器的为 /xxx

location ^~ /reg/ {
   rewrite ^/reg(/|$)(.*) /$2 break;
  proxy_pass http://backend;
  proxy_set_header Host $host;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header Connection "";
  proxy_set_header name 'weiyigeek';
  proxy_http_version 1.1;
}

温馨提示:在使用 proxy_pass 指令时,务必确保上游服务器能够正确处理传递的请求URI,很多新手朋友在从不带URI 改为带 URI 时,未意识到路径替换机制,上游服务无法正确处理请求路径,返回404或逻辑错误;所以在实践时需根据是否配置 URI 路径,采用不同的URI转发策略,需明确理解其替换行为,并在测试环境中验证配置的正确性,从而避免因路径替换导致服务不可用。


步骤 04.同样,完成配置后使用 nginx -s reload 重载配置,并使用 curl 模拟请求进行验证。

$ curl http://test.weiyigeek.top
From 8011 server response!
request: POST / HTTP/1.1
host: test.weiyigeek.top
uri: /
request_uri: /
method: POST
http_name: weiyigeek
request_body:

$ curl http://test.weiyigeek.top/test/
From 8012 server response!
request: POST /test/ HTTP/1.1
host: test.weiyigeek.top
uri: /test/
request_uri: /test/
method: POST
http_name: weiyigeek
request_body:

温馨提示:$request_body 变量通常不包含由 proxy_set_body 修改后的内容,因为它是在请求被发送到后端之前就已经确定了,可通过 tcpdump -i lo port 8011 -A -s 0 命令抓包进行查看,内容如下所示:


weiyigeek.top-抓包验证转发到上游的头部及包体图

其它三个示例的验证如下所示,其中 8011 端口的上游服务器由于权重设置为2,所以转发到8011端口的请求为2次,转发到 8012 端口的上游服务器为 1 次。

# 示例2.使用 proxy_pass 指令指定了URI目录的示例验证
$ curl http://test.weiyigeek.top/a/bb/cc?uid=1024
From 8011 server response!
request: GET /addurl/bb/cc?uid=1024 HTTP/1.1
host: test.weiyigeek.top
uri: /addurl/bb/cc   # 关键点:此处URI已经被替换为 /addurl/bb/cc
request_uri: /addurl/bb/cc?uid=1024
method: GET
http_name: weiyigeek
request_body:

# 示例3.使用 $request_uri 变量动态构建代理服务器的地址的示例验证
$ curl http://test.weiyigeek.top/b/cc/dd?id=1024
From 8011 server response!
request: GET /b/cc/dd?id=1024 HTTP/1.1
host: test.weiyigeek.top
uri: /b/cc/dd         # 关键点:此处URI没有被替换,request_uri 变量值为 /b/cc/dd
request_uri: /b/cc/dd?id=1024
method: GET
http_name: weiyigeek
request_body:

# 示例4.利用正则以及重写URI的示例验证
$ curl http://test.weiyigeek.top/reg/aa/bb/cc?id=1024
From 8012 server response!
request: GET /aa/bb/cc?id=1024 HTTP/1.1  # 关键点: 请求的URI,使用重写指令将其替换为 /aa/bb/cc
host: test.weiyigeek.top
uri: /aa/bb/cc
request_uri: /aa/bb/cc?id=1024
method: GET
http_name: weiyigeek
request_body:

至此,我们已经完成了 Nginx 中最简单的反向代理与负载均衡服务器的配置与验证,下一小节,将继续学习在 Nginx HTTP 协议的反向代理中接收客户端请求处理相关指令。


HTTP 协议反向代理中接收客户端请求处理

描述:本小节将主要讲解 Nginx 处理用户请求包体的核心机制,包括 proxy_request_buffering 指令在反向代理中接收客户端包体的控制机制,client_body 相关指令配置、临时文件存储路径与层级设计、以及超时控制等关键配置对性能的影响。


指令参数

1.反向代理中接收客户端包体的控制机制

  • proxy_request_buffering 指令控制是否在转发到上游服务器前先接收完整客户端请求包体,其中 on 表示接收完整个 body 再向上游发送,off 表示边接收边转发,若选择关闭,则需注意的是一旦向 upstream 发送body 数据时,proxy_next_upstream 指令失效,无法进行后续的故障转移或重试。

    • 开启: 适用于客户端网速慢,上游服务并非处理能力弱,以及高并发场景,依赖 Nginx 本地处理能力,避免上游服务过载的场景。

    • 关闭:适用于内网客户端,上游能更快接收到数据,无需等待 Nginx 接收完整包体,避免 Nginx 服务器读写磁盘的开销的场景。

Syntax: proxy_request_buffering on | off;
Default: proxy_request_buffering on;
Context: http, server, location

2.客户端包体接收缓冲区控制:https://nginx.org/en/docs/http/ngx_http_core_module.html

  • client_body_buffer_size 设置阅读客户端请求正文的缓冲区大小。如果请求体大于缓冲区,则整个请求体或仅其一部分被写入临时文件。默认情况下,缓冲区大小等于两个内存页,这是x86、其他32位平台和x86-64上的8K,在其他64位平台上通常为16 K,若客户端请求包体超大则分段通过该 buffer 接收。
Syntax: client_body_buffer_size size;
Default: client_body_buffer_size 8k|16k;
Context: http, server, location
  • client_body_in_single_buffer 确定 nginx 是否应该将整个客户端请求主体保存在单个缓冲区中, 建议在使用 $request_body 变量时使用该指令,以节省所涉及的复制操作的数量。
Syntax: client_body_in_single_buffer on | off;
Default: client_body_in_single_buffer off;
Context: http, server, location
  • client_body_in_file_only 确定 nginx 是否应该将整个客户端请求主体保存在磁盘文件中。如果设置为 on,则 nginx 将不会在内存中缓存任何部分的正文,而是在磁盘上保存整个正文。如果设置为 clean,则在读取正文后将其删除,off 表示直接存入内存 buffer 中。
Syntax: client_body_in_file_only on | clean | off;
Default: client_body_in_file_only off;
Context: http, server, location

3.客户端请求体临时文件存储配置:

  • client_body_temp_path: 用于存储保存客户端请求正文的临时文件,在指定的目录下最多可以使用三个级别的XML层次结构,目的是为了防止单一目录下文件过多导致 I/O 性能问题。
Syntax: client_body_temp_path path [level1 [level2 [level3]]];
Default: client_body_temp_path client_body_temp;
Context: http, server, location

# 例如:client_body_temp_path /spool/nginx/client_temp 1 2;
# 示例:/spool/nginx/client_temp/7/45/00000123457

4.Nginx 读取请求体超时控制:

  • client_body_timeout 设置读取客户端请求正文的超时时间,缺省值 60s。如果在此时间内没有完全接收正文,则连接将关闭,并返回 408(Request Time-out)错误终止。
Syntax: client_body_timeout time;
Default: client_body_timeout 60s;
Context: http, server, location

5.客户端最大请求体长度限制:

  • client_max_body_size 设置客户端请求主体的最大允许大小,缺省 1m。如果请求中的主体(Content-Length)大小大于指定的限制,则返回 413(Request Entity Too Large)错误,设置为 0 表示不检查包体的大小,对于上传文件后端服务,通常需要设置更大的值,支持单位 m、g。
Syntax: client_max_body_size size;
Default: client_max_body_size 1m;
Context: http, server, location

上面学习了 Nginx 对于请求包体处理的相关指令参数,由于 Nginx 对客户端请求 body 的处理方式直接影响着 Nginx 服务器的性能表现,特别是在高并发场景下,合理的配置可以显著提升服务器的吞吐量与稳定性。所以在实际的生产环境中通常需要根据实际业务场景进行 proxy_request_buffering、client_body_* 系列指令调整,此外配置时需综合考虑下游网络状况(客户端上传速度)、上游服务处理能力及磁盘 I/O 特性等。


END

加入:作者【全栈工程师修炼指南】知识星球

『 全栈工程师修炼指南』星球,主要涉及全栈工程师(Full Stack Development)实践文章,持续更新包括但不限于企业SecDevOps和网络安全等保合规、安全渗透测试、编程开发、云原生(Cloud Native)、物联网工业控制(IOT)、人工智能Ai,从业书籍笔记,人生职场认识等方面资料或文章。


Q: 加入作者【全栈工程师修炼指南】星球后有啥好处?

✅ 将获得作者最新工作学习实践文章以及网盘资源。

✅ 将获得作者珍藏多年的全栈学习笔记(需连续两年及以上老星球友,也可单次购买)

✅ 将获得作者专门答疑学习交流群,解决在工作学习中的问题。

✅ 将获得作者远程支持(在作者能力范围内且合规)。


获取:作者工作学习全栈笔记

作者整理了10年的工作学习笔记(涉及网络、安全、运维、开发),需要学习实践笔记的看友,可添加作者微信或者回复【工作学习实践笔记】,当前价格¥299,除了获得从业笔记的同时还可进行问题答疑以及每月远程技术支持,希望大家多多支持,收获定大于付出!


 知识推荐 往期文章


若文章对你有帮助,请将它转发给更多的看友,若有疑问的小伙伴,可在评论区留言你想法哟 💬

【声明】内容源于网络
0
0
全栈工程师修炼指南
记录全栈工程师学习之路修炼心得,分享工作实践、网络安全、运维、编程、大数据、云原生、物联网、人工智能以及书籍笔记、人生职场感悟等相关高质量文章。 花开堪折直须折,莫待无花空折枝。 Blog:【https://weiyigeek.top】
内容 589
粉丝 0
全栈工程师修炼指南 记录全栈工程师学习之路修炼心得,分享工作实践、网络安全、运维、编程、大数据、云原生、物联网、人工智能以及书籍笔记、人生职场感悟等相关高质量文章。 花开堪折直须折,莫待无花空折枝。 Blog:【https://weiyigeek.top】
总阅读31
粉丝0
内容589