ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Nginx核心功能实战:从反向代理到高并发架构的完整指南

Nginx核心功能实战:从反向代理到高并发架构的完整指南 如果你在一台线上服务器的终端敲下ps -ef | grep nginx大概率会看到这样的画面一个 master 进程后面跟着好几个 worker 进程。这个画面我看了快十年Nginx 早已是我所有线上项目的入口标配。刚开始接触它纯粹是因为一个社区网站流量突然上涨Apache 的 CPU 占用一路飙到 95%页面打开像蜗牛。我把静态文件和反向代理切到 Nginx 之后半天时间压力就降下来了CPU 直接回落到 20% 以下。那时候我就明白很多所谓高并发的问题很多时候换个合理的接入层就解决了一半。Nginx 核心功能真的不只是做个反向代理这么简单。这些年我带过的不少新人甚至一些工作两三年的后端说起 Nginx 就是配个 server、配个 proxy_pass 完事。但实际生产里静态资源服务、Gzip 压缩、负载均衡、健康检查、SSL 终止、平滑升级、限流防刷每一个都是能直接影响系统稳定性的大项。这篇文章我想从自己实际使用的角度把 Nginx 的几个核心功能掰开揉碎讲清楚重点是讲清楚为什么要这样配以及我踩过哪些坑给正在入门或者想系统补盲区的朋友一个完整的参考。1. Nginx 在技术架构里到底扮演什么角色1.1 先建立一张功能地图很多人一提到 Nginx 就想到反向代理这没有错但只看到了冰山一角。我习惯把 Nginx 的能力分成几个层级来理解第一层流量入口层。所有外部请求先进 Nginx由它决定转发到哪个后端服务。这是反向代理和负载均衡的职责。第二层静态资源层。图片、CSS、JS、字体这类文件根本不需要打到后端应用Nginx 自己就能以非常高的效率吐出去。第三层数据处理层。包括 Gzip 压缩、HTTP 缓存控制、URL 重写、限流、鉴权校验等在请求到达业务代码之前就把能处理的事情处理掉。第四层传输安全层。SSL 证书终止、强制 HTTPS 跳转、安全响应头注入这层是现代的标配。第五层运维保障层。日志切割、优雅重载配置、平滑升级二进制、灰度发布时的流量切换。你把这五层串起来看就会发现 Nginx 实际上是一个位于应用之前的全能网关。后端服务只要专注于业务逻辑就行其余那些横切面的脏活累活全都可以在 Nginx 这一层消化掉。1.2 不同规模场景下的角色差异Nginx 在不同体量的项目里扮演的角色的权重其实是不一样的个人项目或小站点Nginx 主要做静态文件服务加一层反代一个 server 块就搞定配置不超过二十行。中型业务开始需要多个 server 块区分域名upstream 后面挂着两三个后端节点做负载均衡还要考虑 SSL 证书和 HTTP/2。大型系统Nginx 往往变成多层架构里的接入层有的甚至用 OpenResty 在 Nginx 上写 Lua 做复杂路由、限流和灰度逻辑。但这并不意味着大项目才有必要学 Nginx。恰恰相反正因为 Nginx 从小到大的扩展路径非常平滑你才越早掌握它越划算。一个刚起步的小项目用 Nginx 做反代 静态缓存架构就已经比直接用应用服务器对外暴露要合理一大截。2. 高并发的底层秘密Master-Worker 架构与事件驱动模型2.1 一个连接进来之后发生了什么聊 Nginx 核心功能永远绕不开它为什么能扛高并发。Apache 传统上是一个连接对应一个进程来了 1000 个请求就要开 1000 个进程去处理每一个进程都有独立的内存空间和上下文开销大得可怕。Nginx 走的是另一条路事件驱动 异步非阻塞 I/O。这个设计用大白话讲就是Nginx 的 worker 进程就像是餐厅里一个非常高效的服务员他不会死等某一个客人慢慢点菜而是同时招呼几十桌客人——谁这边菜好了就去端谁那边要加水就去倒所有动作都是有空就干没有一个动作是干等着。Linux 下实现这种效果的核心机制叫 epoll它可以同时监控成千上万个连接上的事件哪条连接有数据可读、可写内核会主动通知 Nginx不用 Nginx 轮询去问。所以在 Nginx 里一个 worker 进程能够服务的并发连接数不是靠开线程堆出来的而是靠事件循环转出来的。这也是为什么 Nginx 可以做到单机轻松撑起几万并发而很多传统模式在几千连接时 CPU 就已经烧起来了。2.2 worker 进程数量的确定方式Master 进程的主要职责是管理 worker读取配置、绑定端口、fork 出一堆 worker、监控 worker 状态发生了异常还能自动重启它。真正的读写、转发、压缩这些活儿全是 worker 干的。那 worker 开多少合适我见过不少服务器上写着worker_processes 2但机器是 32 核的也有见过 4 核机器上配了 16 个 worker 的。这两种都是不对的。生产环境里直接把worker_processes设为auto是最省心的。Nginx 会自动探测 CPU 核心数并创建对应数量的 worker。如果你喜欢显式配置一般规则是核心数不超过 8 的机器worker 数量和 CPU 核心数保持一致核心数很多的机器可以设置为核心数的一半或稍少避免频繁的上下文切换如果 worker 里还要跑 Lua 做复杂逻辑那要额外留一些 CPU 余量不要全部占满。我之前在一台 16 核机器上做过测试设成 16 个 worker 时请求耗时反而比设成 8 个稍微高了一点原因是 CPU 在内核态和用户态之间切换的开销上去了。所以worker 越多越强是新手最容易踩的直觉陷阱。2.3 worker_connections 和系统句柄的联动events块里的worker_connections决定每个 worker 能同时打开的最大连接数。Nginx 官方的公式是最大并发连接数 ≈ worker_processes × worker_connections假设worker_processes 8worker_connections 10240理论上这个 Nginx 能接受的并发连接就是 8 万多。但这里有个隐蔽的前提每个连接都需要一个文件描述符而文件描述符上限不是你配了就有还受系统层 ulimit 限制。我在机器上部署 Nginx 时有个固定动作先执行ulimit -n看系统允许的进程文件句柄数如果只有 1024那 Nginx 的 worker 连接数配得再高也没用到 1024 就捅破天花板了。需要改/etc/security/limits.conf把 nofile 调大同时别忘了在 nginx.conf 里加上worker_rlimit_nofile 102400;这句的意义是让 worker 进程突破 shell 层限制直接继承更高的文件句柄上限。顺序上先调系统层面再调 Nginx 层面两头都到位了高并发才算真正有底子。3. 反向代理与负载均衡线上最常碰到的两块3.1 反代配置没那么神秘反向代理的本质是把客户端的请求原样转交给内网的后端服务。用 Nginx 做反向代理的时候一个最小可用配置大概是这样的upstream backend_api { server 192.168.1.10:8080; server 192.168.1.11:8080; } server { listen 80; server_name api.example.com; location / { proxy_pass http://backend_api; 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 X-Forwarded-Proto $scheme; } }注意看proxy_set_header那几行很多人配反代就只写一个proxy_pass结果后端拿到的客户 IP 全是 Nginx 的内网地址导致日志分析、风控、限流全部失真。X-Real-IP把客户端真实 IP 发给后端X-Forwarded-For在链路经过多层代理时会把每一层 IP 都追加进去X-Forwarded-Proto告诉后端客户端原来是用 HTTP 还是 HTTPS避免后端生成回调链接时协议判断错误。3.2 proxy_pass 末尾斜杠的经典坑这个坑我踩过一次之后就再也没忘过。同样是location /api/ { proxy_pass http://backend_api; }和location /api/ { proxy_pass http://backend_api/; }两者的转发效果差别非常大。第一种不带斜杠请求/api/user转发到后端时 URI 保持原样还是/api/user。第二种带斜杠proxy_pass后面的 URI 会替换掉location匹配到的部分所以/api/user会被转成/user发给后端。这本身没有对错取决于后端接口是不是带着/api前缀。但如果你的后端是 Spring Boot 这类自带 context-path 的应用或者网关层和 Nginx 各自路由的语义不一致就会出现本地 curl 后端通、走 Nginx 就 404的情况。排查的时候先看一眼proxy_pass末尾有没有斜杠。3.3 upstream 负载均衡策略怎么选Nginx 的 upstream 模块提供了几种负载均衡策略策略说明适用场景默认轮询请求按顺序轮流分发到各个后端后端配置均衡、接口无状态时最省事weight 权重给不同后端指定占比比如 5:3:2机器性能不一或者要按比例切流量时ip_hash按客户端 IP 做哈希同一 IP 固定打到同一台后端需要会话保持但又不方便引入 Redis 时least_conn把请求发给当前活跃连接数最少的后端后端长连接较多或请求耗时差异大时我个人最常用的还是weight 权重和least_conn。ip_hash虽然能解决会话保持但一旦后端节点扩缩容哈希重新分布会导致大量用户的会话失效现在已经在一般项目里很少用了。如果你有两台机器A 机器配置高一些想让约三分之二的流量过去可以这样配upstream backend_api { server 192.168.1.10:8080 weight2; server 192.168.1.11:8080 weight1; }3.4 健康检查与故障剔除upstream 里的服务器如果挂了Nginx 会不会自动把流量切走这个要分情况。默认情况下Nginx 用的是被动健康检查。也就是说请求转发到某台后端如果失败Nginx 才会把该节点标记为不可用。相关参数是upstream backend_api { server 192.168.1.10:8080 max_fails3 fail_timeout30s; }解释一下max_fails3表示在fail_timeout指定的 30 秒内如果这台后端出现 3 次转发失败Nginx 就认为它挂了之后的 30 秒内不会再把请求分给它。30 秒后 Nginx 会尝试放一个请求过去试探如果恢复就重新拉回节点池。需要说明的是这个失败默认指的是连接失败、超时这类 TCP 层问题。如果后端返回 HTTP 500Nginx 默认是不会把节点摘掉的因为从传输层的角度看连接是成功的。要让 5xx 也触发摘除需要开启proxy_next_upstream http_500 http_502 http_503;但要注意proxy_next_upstream的本意是当前后端返回这些状态码时重试下一个后端。它确实能达到快速容错的效果但也意味着同一个请求可能被后端执行了两次。如果下游接口不是幂等的比如下单、扣款这会带来重复操作风险生产环境务必想清楚再开。4. 静态资源服务与缓存最容易被忽略的看家本领4.1 root 与 alias 的区别很多人配静态文件时搞不清root和alias返回 404 的时候一头雾水。两者的核心区别是root会把location的 URI 拼到路径后面alias则是直接替换掉匹配的那段。location /static/ { root /data/www; }请求/static/img/logo.png时Nginx 查找的文件路径是/data/www/static/img/logo.png。注意 root 后面不需要配/static因为 URI 里已经带了。location /static/ { alias /data/files/; }请求/static/img/logo.png时Nginx 查找的文件路径是/data/files/img/logo.png。alias是把/static/这段剥掉再拼上后面的路径。新手最容易错的是在root下再多写了一层目录或者在alias时漏了末尾斜杠导致路径拼接出问题。调试的时候可以通过nginx -T查看最终生效的配置也可以临时开 access_log 看实际请求的文件路径。4.2 Gzip 压缩未压缩体量大、压缩后网络开销小静态资源和接口响应都建议开 Gzip。配置如下gzip on; gzip_comp_level 5; gzip_min_length 1k; gzip_types text/plain text/css application/json application/javascript application/xml image/svgxml; gzip_vary on;几个参数的取值经验gzip_comp_level不是越高越好。1 到 3 就能获得很高的压缩率收益9 反而会让 CPU 消耗明显增大、收益却很有限。我一般用 5 左右压缩率和 CPU 开销比较均衡。gzip_min_length 1k的意思是小于 1KB 的资源不压缩。因为压缩算法本身有固定开销小文件压缩完可能比原文件还大得不偿失。不需要把图片也塞进gzip_typesJPEG、PNG 本身就是压缩格式再压一遍既费 CPU 又没什么收益纯属盲区里的白费劲。SVG 是文本格式可以压缩。4.3 expires 与浏览器缓存策略静态资源服务还有一个大杀器是浏览器缓存。配置起来其实就一行location /static/ { alias /data/www/static/; expires 7d; add_header Cache-Control public; }expires 7d会让 Nginx 自动在响应头里加上Expires和Cache-Control: max-age604800浏览器在 7 天内请求同一资源时不再向服务器发出实际请求直接读本地缓存。这对图片、CSS、JS 这类变了才需要重新拉取的资源非常友好。但这里有个常识性提醒缓存策略要配合文件名指纹来用。如果你的 JS 文件更新后文件名还是app.js浏览器一直用旧缓存那问题大了。正确做法是构建工具生成带哈希的文件名比如app-8f3k2d.js然后再配合expires做长期缓存。这个配合是静态资源服务能不能真正提速的关键缺一不可。4.4 open_file_cache让热点文件访问更快Nginx 有一个容易被忽略的加速机制叫 open file cache。它缓存的是打开文件后的句柄、文件大小和修改时间等元信息连打开文件这个动作都省了open_file_cache max10000 inactive30s; open_file_cache_valid 60s; open_file_cache_min_uses 2; open_file_cache_errors on;含义稍微解释一下max10000表示最多缓存 10000 个文件句柄超过后按 LRU 策略淘汰inactive30s表示如果某个文件在 30 秒内没有被访问缓存项会被清理min_uses2的意思是文件至少被访问 2 次才值得缓存。这个配置对图片站、静态资源量大的系统效果很明显因为文件句柄的打开和关闭在高频访问下也是个不小的系统调用开销。5. SSL 终止与安全加固从 HTTP 到 HTTPS 的完整落地5.1 证书配置与协议版本现在的线上系统没有 HTTPS 基本说不过去。Nginx 通常作为 SSL 终止节点对外是 HTTPS对内转发给后端则是明文 HTTP这样后端服务不用自己处理证书证书的管理和续期集中到 Nginx 一层。配置示例server { listen 443 ssl; server_name example.com; ssl_certificate /etc/nginx/certs/example.com.pem; ssl_certificate_key /etc/nginx/certs/example.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; }几个点值得展开ssl_protocols里我强烈建议只保留 TLSv1.2 和 TLSv1.3。老旧的 TLSv1.0、TLSv1.1 已经有大量已知漏洞很多安全扫描工具还会直接报高危。ssl_session_cache shared:SSL:10m开了共享会话缓存1MB 大概能缓存约 4000 个会话。它的意义在于客户端在同一个会话里多次建立 TLS 连接时不需要重新走完整握手能显著降低 HTTPS 的握手开销。如果你用的是云厂商的证书下载时一般会给你 PEM 格式如果是自己用 OpenSSL 生成的只需要把公钥和私钥按上面指定的路径放好。收到证书后记得顺手跑一下nginx -t证书路径写错是最常见的启动失败原因。5.2 HTTP 全量跳转 HTTPS配好 443 之后还有个必不可少的路由所有 80 端口的请求都要 301 跳到 HTTPS。我的习惯是单独写一个 server 块server { listen 80; server_name example.com www.example.com; return 301 https://$host$request_uri; }$host$request_uri保留了原始域名和路径用户访问http://example.com/abc时会被重定向到https://example.com/abc路径不会丢。一个非常容易踩的坑是如果你这边刚从 HTTP 切到 HTTPS而某个老接口还在被第三方回调 HTTP控制台看到的日志全是各种301/302甚至提示回调失败。这时候要检查第三方那边的回调地址是否同步更新了Nginx 侧别瞎改 return 规则。5.3 安全响应头注入加了证书之后我一般还会顺手加上几个安全响应头成本极低收益很高add_header X-Frame-Options SAMEORIGIN always; add_header X-Content-Type-Options nosniff always; add_header Strict-Transport-Security max-age31536000; includeSubDomains always; add_header Referrer-Policy strict-origin-when-cross-origin always;X-Frame-Options: SAMEORIGIN防止页面被恶意站点用 iframe 嵌入降低点击劫持风险。X-Content-Type-Options: nosniff阻止浏览器对响应类型做 MIME 猜测减少 XSS 类攻击面。Strict-Transport-Security就是 HSTS告诉浏览器这个域名只能走 HTTPS有效期一年。Referrer-Policy控制 Referrer 信息在跨域请求时怎么带避免 URL 里的敏感参数泄漏给第三方站点。需要留个心眼add_header在 Nginx 里是有继承陷阱的。如果你在 server 块里用了add_header而某个 location 里也用了add_header那么 server 层其他没在 location 里重复声明的 header 会被吞掉。所以要么统一只用一段要么在 location 里把该继承的 header 全部重新声明一遍。5.4 基础访问控制和防恶意请求SSL 之外接入层还应承担一部分基础安全职责。最常用的两块IP 白名单/黑名单和请求限流。白名单通常用于管理后台或内网接口location /admin/ { allow 192.168.1.0/24; allow 114.114.114.114; deny all; proxy_pass http://backend_admin; }限流则用limit_req模块。下面这个配置允许每个 IP 平均每秒最多 5 个请求超过之后直接返回 503limit_req_zone $binary_remote_addr zoneapi_limit:10m rate5r/s; server { location /api/ { limit_req zoneapi_limit burst10 nodelay; proxy_pass http://backend_api; } }burst10表示允许最多积压 10 个超过速率的请求相当于临时缓冲池nodelay表示缓冲池里的请求不需要人为延迟直接发给后端。当你发现某个接口被脚本疯狂刷的时候这一套组合能够在不改业务代码的前提下先把压力挡在网关层。6. 平滑升级与热更新线上零中断的秘密6.1 信号机制是理解升级流程的前提Nginx 的运维操作本质上是在给 master 进程发信号。掌握这一套你对平滑的理解会彻底不一样。HUP重载配置相当于nginx -s reloadUSR1重新打开日志文件做日志切割时的标准操作USR2启动新的 master 进程用于平滑升级二进制WINCH让旧 master 优雅关闭它的 worker 进程QUIT优雅退出TERM/INT快速退出为什么要发信号而不是直接停进程因为 master 收到信号后第一步永远是先让旧的 worker 优雅地处理完当前正在进行的连接然后再退出。整个过程中新老 worker 会短暂并存对用户来说几乎无感知。6.2 平滑升级一只脚本搞定升级 Nginx 的常规流程是替换二进制文件。比如从 1.24 升级到 1.26直接覆盖/usr/sbin/nginx后重启可能会有极短暂的连接断开。用信号机制可以做到完全无感知备份旧的二进制文件cp /usr/sbin/nginx /usr/sbin/nginx.old上传并覆盖新版本二进制然后测试配置nginx -t如果测试通过给当前的 master 进程发送 USR2 信号kill -USR2 $(cat /var/run/nginx.pid)这个信号执行后Nginx 会读取到新二进制并且启动一个新的 master 及对应的一组 worker。此时新旧 master 同时在跑旧 master 的 PID 会被写到/var/run/nginx.pid.oldbin。确认新 master 正常后把这个信号发给旧 master让它优雅关闭旧 workerkill -WINCH $(cat /var/run/nginx.pid.oldbin)旧 worker 会在处理完手头连接后陆续退出新 worker 继续接管全部流量。过一会儿再验证新版本无误最后让旧 master 也退出kill -QUIT $(cat /var/run/nginx.pid.oldbin)如果升级后发现有问题回滚也很简单重新放回nginx.old文件杀掉新 master然后对旧 master 发送 HUP 信号让它重新拉起 worker 即可。整个过程只要信号顺序没搞错线上连接是不会断的。6.3 reload 和真正升级的本质区别很多人会把nginx -s reload和升级混为一谈。实际上reload只是重新读取配置文件二进制的代码没有变处理的是配置变更场景。任何配置修改之后我都会养成先nginx -t再nginx -s reload的习惯而且强烈建议你也这么做。另外有个细节reload之后旧的 worker 不会立刻全部退出它会等正在处理的连接结束才退出。如果你改了配置想看效果但发现有些连接还是旧行为不用紧张那是旧 worker 还在收尾过几秒再看就正常了。如果碰到死活不退的说明有长连接一直占着 worker这时候nginx -s quit有时候比 reload 更彻底。7. 一套生产可用的配置基线与最终经验沉淀7.1 全局配置基线最后分享一套我常用的生产配置基线不是让你无脑抄而是给你一个可对比的参照系。每个参数我都会顺带说明为什么要这样设。user nginx; worker_processes auto; worker_rlimit_nofile 102400; events { worker_connections 10240; use epoll; multi_accept on; } http { include /etc/nginx/mime.types; default_type application/octet-stream; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; keepalive_requests 1000; server_tokens off; client_max_body_size 10m; client_body_buffer_size 16k; gzip on; gzip_comp_level 5; gzip_min_length 1k; gzip_types text/plain text/css application/json application/javascript application/xml image/svgxml; open_file_cache max10000 inactive30s; open_file_cache_valid 60s; include /etc/nginx/conf.d/*.conf; }注意sendfile这个参数。打开它之后静态文件从磁盘到网卡的传输不再经过用户态缓冲区拷贝而是由内核直接完成吞吐量会有非常明显的提升。前提是你主要用它服务静态内容如果做反代sendfile对动态请求影响不大。7.2 调优参数踩坑记录参数排坑的顺序我基本遵循一个原则从系统层到应用层一层层排除。最典型的例子是很多同事改了worker_connections 10240然后压测发现连接数到 1024 就上不去了死活找不到原因。这不怪 Nginx是系统 ulimit 没调。你需要在/etc/security/limits.conf里设置nginx soft nofile 102400 nginx hard nofile 102400然后再配worker_rlimit_nofile两头都放开连接数才上得去。另一个容易忽略的是keepalive_requests。这个参数含义是单个 keepalive 连接最多能复用多少次。默认值比较小如果你有大量请求是复用连接发出去的连接次数很快耗尽Nginx 会频繁关闭旧连接、建立新连接握手开销一下子就上来了。我一般调到 1000 或者更大对高 QPS 的接口效果比较明显。7.3 最后再分享几个实战心得这套配置我用了好几年整体稳定。最后的经验是三条第一不要上来就抄一堆调优参数。先跑默认配置压测看瓶颈在哪再针对性地调每调一个参数都要能说出理由。盲目抄配置只会把你的问题雪上加霜。第二Nginx 的日志是你的第一排查工具。很多 502、504、流量异常access log 里的upstream_addr、upstream_status、request_time几个字段会直接告诉你问题出在 Nginx 还是后端。我几乎每个项目的 log_format 都会把 upstream 耗时打出来log_format main $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $http_x_forwarded_for upstream_addr$upstream_addr upstream_status$upstream_status request_time$request_time upstream_response_time$upstream_response_time;第三Nginx 的安装方式决定了你后续的管理方式。Linux 下我推荐直接用发行版包管理器安装这样 systemd 管理、日志切割、配置目录都是现成的。Windows 上虽然也有官方二进制包解压就能跑但它更多是开发调试用生产环境请务必用 Linux。环境差异会导致很多表现不一致别在生产环境给自己挖坑。Nginx 核心功能就讲到这里。从事件驱动模型到反向代理、负载均衡从静态资源缓存到 SSL 终止再到平滑升级这一套组合拳打下来你已经具备了自己动手搭建一个高可用接入层的能力。下次再遇到系统扛不住的问题别急着加机器先回头看看 Nginx 这层有没有已经帮你做完一半。
返回列表