先说一个我自己的变化:

刚接触项目部署时,我对 Nginx 的理解很简单,就是把 dist 文件夹丢上去跑起来;项目做多之后才慢慢意识到,这种理解是很片面的。Nginx 从来不只是一个静态资源服务器,它更像是整个系统的入口层,前端页面访问、接口转发、HTTPS、负载均衡、缓存策略,很多线上能力最终都会落在它身上。


一、Nginx 到底是什么

一开始我也会把 Nginx 简单理解为 Web 服务器,但实际用下来会发现,它的能力要更全面一些。它既可以作为 Web Server 提供网页访问,也可以作为 Reverse Proxy 做反向代理,同时还支持 Load Balancer 负载均衡、Static File Server 静态资源服务、Gateway 统一网关入口,甚至还能做 TCP / UDP 流量代理。


二、为什么 Nginx 性能高

在对比一些传统 Web 服务(例如早期 Apache)时,这一点会比较明显。传统模型通常采用多线程或多进程,一个请求对应一个线程,当连接数上来之后,很容易出现内存占用升高、CPU 上下文切换频繁、吞吐量下降的问题。

Nginx 选择了完全不同的一条路径,它的核心是:事件驱动 + 非阻塞 IO + 多 Worker 进程,这三者组合在一起,决定了它在高并发场景下的表现。


三、Nginx 的核心模型

我自己在理解这一块时,是把它拆成三个点来看。

  • 首先是事件驱动。传统模型是一连接一线程,即使连接处于等待状态也会占用资源,而事件驱动的思路是只关注“发生了什么事件”,比如某个连接收到请求头、某个连接可以写响应、某个连接断开,系统把这些事件通知给 Nginx,它再去处理对应的连接。

  • 其次是非阻塞 IO。阻塞 IO 在读取 socket 时,如果没有数据就会一直等待,而非阻塞 IO 在没有数据时会立刻返回,让程序可以先处理其他连接,等内核通知“数据到了”再继续处理。

这两个机制结合起来,实际运行流程会变成:所有连接设为非阻塞,然后通过 epoll(Linux)或 kqueue(BSD)统一监听事件,哪个连接就绪就处理哪个。比如有 10000 个连接在线,真正活跃的只有几十个,Nginx 只处理这些活跃连接,其余连接几乎不消耗额外资源。

  • 最后是多 Worker 进程。Nginx 会根据 CPU 核数启动多个 Worker,每个 Worker 都是独立进程,可以并行处理请求;Master 进程负责读取配置、管理 Worker、处理 reload 等控制逻辑,而真正对外提供服务的是 Worker。

常见配置就是:

worker_processes auto;

四、Nginx 的进程结构

从进程模型上看,Nginx 启动后主要有两类进程。

Master 进程负责读取配置文件、管理 Worker、支持平滑重载(如 nginx -s reload)、接收系统信号等,但它本身并不处理具体请求。

Worker 进程才是真正处理 HTTP 请求的部分,每个 Worker 内部基于事件循环处理大量连接,这也是高并发能力的核心来源。


五、前端为什么离不开 Nginx

对于前端项目来说,这一点其实非常直接。Vue 或 React 项目打包后通常会生成 dist 目录,例如包含 index.html 和 assets 资源,这些文件需要一个服务器对外提供访问,而 Nginx 是最常见的选择。

一个最基础的配置大致是这样:

server {
    listen 80;

    location / {
        root /www/project/dist;
        index index.html;
    }
}

浏览器访问域名时,本质上就是在访问这套静态资源。


六、反向代理是更核心的能力

在实际线上项目中,我逐渐发现 Nginx 更重要的作用其实是反向代理。比如用户访问一个接口地址:

https://api.demo.com/api/user/list

请求会先到 Nginx,再由它转发到后端服务:

location /api/ {
    proxy_pass http://127.0.0.1:8080/;
}

这样做的好处是比较明显的,可以隐藏真实服务地址、统一对外入口、方便做权限控制和限流、防止直接暴露内部服务,同时也为后续扩容和负载均衡提供基础。


七、前端刷新 404 的问题

在使用 Vue Router 或 React Router 的 history 模式时,一个很常见的问题是:页面内跳转正常,但刷新就 404。

原因在于浏览器刷新时会直接请求当前路径,比如 /user/info,而服务器并没有这个真实文件。

通常的解决方式是:

location / {
    try_files $uri $uri/ /index.html;
}

也就是优先查找真实文件或目录,如果不存在就返回 index.html,再交由前端路由接管,这是 SPA 项目部署中几乎必备的一段配置。


八、静态资源缓存与压缩

在性能优化上,我一般会同时做两件事:缓存和压缩。

对于带 hash 的静态资源(如 JS、CSS、图片),可以设置较长缓存时间:

location /assets/ {
    root /www/project/dist;
    expires 30d;
}

同时开启 gzip 压缩:

gzip on;
gzip_types text/css application/javascript application/json;

这样可以显著减少资源体积和请求次数,例如一个几百 KB 的 JS 文件,压缩后体积会明显下降,对首屏加载是有帮助的。


九、负载均衡与 HTTPS

当后端服务不止一个实例时,可以通过 upstream 做负载均衡:

upstream backend {
    server 127.0.0.1:8080;
    server 127.0.0.1:8081;
    server 127.0.0.1:8082;
}

server {
    location /api/ {
        proxy_pass http://backend;
    }
}

默认是轮询策略,也可以根据需要调整权重、使用 ip_hash 或最少连接数策略。

HTTPS 一般也会由 Nginx 统一处理:

server {
    listen 443 ssl;
    ssl_certificate xxx.pem;
    ssl_certificate_key xxx.key;
}

浏览器只与 Nginx 建立 HTTPS 连接,内部服务仍然可以使用 HTTP,这样可以简化后端配置。


十、跨域与常见问题

线上环境中,通常会通过统一域名来避免跨域问题,例如把前端和接口统一在同一域下,通过路径区分:

location /api/ {
    proxy_pass http://127.0.0.1:8080;
}

这样浏览器视角下是同源请求,不会触发跨域限制。

另外一些比较常见的问题也基本都和配置有关,比如 502 通常是后端服务不可用或地址配置错误,刷新 404 基本是缺少 try_files,上传失败通常需要调整 client_max_body_size 100m;,路径访问异常则多半是 root 和 alias 使用不当。


十一、root 与 alias 的区别

这一点我一开始踩过坑。

location /img/ {
    root /data;
}

访问 /img/a.png 时,实际路径是 /data/img/a.png

而如果使用:

location /img/ {
    alias /data/;
}

同样的访问路径,对应的是 /data/a.png

简单理解就是:root 是路径拼接,alias 是路径替换。


最后总结

事件驱动的本质是:不为每个连接分配线程,而是监听事件,谁就绪处理谁;

非阻塞 IO 的特点是:当 IO 未准备好时立即返回,不阻塞线程;

多 Worker 进程的作用是:利用多核 CPU 提高整体吞吐能力,同时增强系统稳定性,这些机制共同构成了 Nginx 在高并发场景下的基础能力。

最开始我把 Nginx 当作一个“部署工具”,后来逐渐意识到,它其实是整个系统的入口层,它既能处理高并发连接,也能承担网关职责;既能托管前端资源,也能代理后端服务;既能做缓存优化,也能处理 HTTPS。它的核心本质是通过少量进程配合事件驱动模型,高效管理海量连接。