楼层: 首页/ 软件技术/ 中间件全景/ Web 服务器中间件:Nginx 与 Apache
02

Web 服务器中间件:Nginx 与 Apache

Web Server Middleware · Nginx / Apache

Nginx 就是前台接待员——所有用户请求先到它这,它笑盈盈地接过请求,能自己搞定的(静态图片、HTML)直接给你,搞不定的(要跑 Java 代码的动态请求)转手递给后面的 Tomcat。C 语言写的、事件驱动、单线程非阻塞,单机扛几万并发轻轻松松。Apache 是老牌接待员,模块多、配置灵活,但每个请求一个线程,并发高了就喘。

1.1 Nginx 是什么

论Nginx 的脾气

高性能 HTTP 和反向代理服务器,C 语言写的,事件驱动,高并发之王。它和 Apache 的本质区别:Apache 是"一个请求一个线程",来 1 万个请求就开 1 万个线程,内存和上下文切换开销大;Nginx 是"事件驱动、非阻塞",一个 master 进程管几个 worker 进程,每个 worker 用 epoll 同时监听几千个连接,谁来了谁服务,不傻等。

为什么这么快:① 事件驱动模型(epoll/kqueue),没有线程切换开销;② 全异步非阻塞,一个 worker 同时处理几千连接;③ 静态文件直接 sendfile 零拷贝,不走用户态。

1.2 安装:四种姿势

Ubuntu / Debian(apt)

# 更新包列表,装 Nginx sudo apt update sudo apt install -y nginx # 验证 nginx -v # nginx version: nginx/1.26.x

CentOS / RHEL(yum/dnf)

sudo dnf install -y nginx sudo systemctl enable --now nginx

macOS(brew)

brew install nginx # brew 装的 Nginx 默认监听 8080,因为 80 端口要 root

Docker(最省事,开发推荐)

docker run -d --name nginx \ -p 80:80 \ -v /your/html:/usr/share/nginx/html:ro \ nginx:1.26
$ curl http://localhost <!DOCTYPE html> <html> <head><title>Welcome to nginx!</title>...</html>

1.3 核心配置 nginx.conf:四大块

Nginx 的配置文件就是一棵嵌套的"块"树,从外到内依次是 events(连接级别)、http(HTTP 级别)、server(虚拟主机)、location(路由匹配)。改完配置用 nginx -t 检查语法,再 nginx -s reload 热加载。

nginx.conf —— 最小可用配置(带注释)

# === 全局块 === worker_processes auto; # 通常设成 CPU 核数,auto 让 Nginx 自己算 pid /run/nginx.pid; # === events 块:连接级别 === events { worker_connections 1024; # 每个 worker 最多同时开 1024 个连接 multi_accept on; # 一次接受多个新连接 use epoll; # Linux 上用 epoll,高性能事件模型 } # === http 块:HTTP 级别 === 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"'; access_log /var/log/nginx/access.log main; sendfile on; keepalive_timeout 65; # === server 块:一个虚拟主机 === server { listen 80; server_name localhost; # === location 块:路由匹配 === location / { root /usr/share/nginx/html; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } } }
改完配置别忘了这两步

第一步:nginx -t 检查语法。打错一个分号 Nginx 就起不来,热加载也会失败。第二步:nginx -s reload 热加载。不用重启进程,新 worker 慢慢接管新请求,老 worker 处理完旧请求再退。这是 Nginx 最优雅的设计之一——改配置零停机。

1.4 静态文件服务:Nginx 的老本行

把一堆 HTML、CSS、JS、图片扔到一个目录,Nginx 直接给。比让 Tomcat 去返回静态文件快十倍——因为 Tomcat 要走一遍 Servlet 容器的流水线,Nginx 直接 sendfile 从磁盘到网卡。

server { listen 80; server_name static.example.com; # /static/ 下的请求直接给文件 location /static/ { root /data/www; # 实际路径 /data/www/static/... expires 7d; # 浏览器缓存 7 天 add_header Cache-Control "public"; } # 图片单独放,长缓存 location ~* \.(jpg|jpeg|png|gif|ico|woff2)$ { root /data/www/images; expires 30d; } }

1.5 反向代理:把请求转给后端

反向代理就是"Nginx 站在前面,用户以为自己在跟 Nginx 说话,其实 Nginx 把请求转发给了后面的 Tomcat"。用户根本不知道 Tomcat 的存在。这是 Nginx 最常用的功能。

# 定义后端服务器组 upstream tomcat_backend { server 127.0.0.1:8080; server 127.0.0.1:8081; } server { listen 80; server_name api.example.com; location / { proxy_pass http://tomcat_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 X-Forwarded-Proto $scheme; # 超时设置 proxy_connect_timeout 5s; proxy_read_timeout 60s; } }
$ curl http://api.example.com/users # Nginx 收到请求,转给 tomcat_backend 里的某台 Tomcat {"code":0,"data":[...]} # Tomcat 日志里能看到真实客户端 IP(通过 X-Real-IP)
忘了 proxy_set_header 的后果

如果只写 proxy_pass http://tomcat_backend 不传 header,Tomcat 看到的 RemoteAddr 是 Nginx 的 IP(127.0.0.1),所有日志里的访客 IP 都变成了 Nginx,你想按真实 IP 统计访问量、做 IP 限流,全废。所以 proxy_set_header Host / X-Real-IP / X-Forwarded-For 是反向代理的标配三件套,别省。

1.6 负载均衡:把请求分摊到多台后端

单机扛不住了,你搞了 3 台 Tomcat。Nginx 的 upstream 就是负载均衡器——它把进来的请求按策略分给 3 台后端。Nginx 内置四种策略,足够应付绝大多数场景。

Nginx upstream 四种负载均衡策略
策略怎么分什么时候用
轮询(默认)按顺序一个一个来,1 号 2 号 3 号 1 号 2 号...后端机器性能差不多时
加权轮询server 10.0.0.1:8080 weight=3; 性能好的机器多分点后端机器性能不一样
ip_hash同一个 IP 的请求永远打到同一台后端(哈希)需要会话粘滞(Session 不共享时)
least_conn分给当前连接数最少的后端请求耗时长、长短不一

upstream 配置:加权轮询 + 健康检查

upstream backend { # 主后端,性能好,权重 3 server 10.0.0.11:8080 weight=3 max_fails=2 fail_timeout=10s; # 备后端,性能一般,权重 1 server 10.0.0.12:8080 weight=1 max_fails=2 fail_timeout=10s; # 备用机,主后端全挂了才顶上 server 10.0.0.13:8080 backup; # least_conn 策略(默认轮询,取消注释即可) # least_conn; }
# max_fails=2 表示失败 2 次就把这台后端标记为宕机 # fail_timeout=10s 表示 10 秒后重新试一下这台后端活了没 # backup 表示备胎:平时不参与,其他全挂了才上

1.7 动静分离:静态 Nginx 给,动态转给后端

这是 Nginx + Tomcat 的经典玩法:CSS/JS/图片这些"静态文件"Nginx 自己给,跑得快;/api/ 这种动态请求才转给 Tomcat。别让 Tomcat 干它不擅长的事。

server { listen 80; server_name www.example.com; # 静态资源:Nginx 直接返回 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2)$ { root /data/www/static; expires 7d; access_log off; # 静态文件不记日志,省 IO } # 动态请求:转给 Tomcat location /api/ { proxy_pass http://tomcat_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; } # 剩下的走默认首页 location / { root /data/www/html; index index.html; } }

1.8 HTTPS 配置:上 TLS

现在不上 HTTPS 浏览器直接给你标"不安全"。Nginx 配 HTTPS 就是加几行 SSL 配置 + 证书文件。证书去 Let's Encrypt 免费申请(后面 TLS 章节细讲)。

server { listen 443 ssl http2; # http2 让一个连接并行传多个资源 server_name www.example.com; # 证书文件(Let's Encrypt 申请的) ssl_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem; # 只允许 TLS 1.2+,禁掉老旧的 SSLv3/TLSv1.0 ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; # HSTS:浏览器记住"以后只走 HTTPS" add_header Strict-Transport-Security "max-age=31536000" always; location / { proxy_pass http://tomcat_backend; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; } } # HTTP 自动跳 HTTPS server { listen 80; server_name www.example.com; return 301 https://$host$request_uri; }

1.9 代理缓存:Nginx 帮后端扛流量

后端接口返回的 JSON 可以缓存在 Nginx 上,下次同样的请求直接给缓存,不用再敲 Tomcat。这就是 CDN 的原理。

# http 块里定义缓存路径 proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m # 缓存区名字 + 大小(10m 能存几万个 key) max_size=10g # 缓存磁盘上限 inactive=60m # 60 分钟没访问就删掉 use_temp_path=off; # location 里用缓存 location /api/products/ { proxy_pass http://tomcat_backend; proxy_cache my_cache; # 用哪个缓存区 proxy_cache_valid 200 5m; # 200 响应缓存 5 分钟 proxy_cache_key $uri$is_args$args; # 按 URL+参数做 key add_header X-Cache $upstream_cache_status; # HIT/MISS 看缓存命中 }
$ curl -I http://api.example.com/api/products/1 HTTP/1.1 200 OK X-Cache: MISS # 第一次,没命中,去后端拿了 $ curl -I http://api.example.com/api/products/1 HTTP/1.1 200 OK X-Cache: HIT # 第二次,直接从 Nginx 缓存给

1.10 限流与 gzip:保护后端、省带宽

# http 块里定义限流区 limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; # $binary_remote_addr 按客户端 IP 限流,每 IP 每秒 10 个请求 limit_conn_zone $binary_remote_addr zone=conn_limit:10m; # 限制并发连接数 # location 里用 location /api/ { limit_req zone=api_limit burst=20 nodelay; # 超了就排队最多 20 个 limit_conn conn_limit 20; # 每个 IP 最多 20 个并发连接 proxy_pass http://tomcat_backend; } # gzip 压缩:文本类资源压一下,省带宽 gzip on; gzip_types text/plain text/css application/json application/javascript; gzip_min_length 1k; gzip_comp_level 5;

1.11 常用命令速查

命令干什么
nginx -t检查配置文件语法(改完先跑这个)
nginx -s reload热加载配置,不重启进程,零停机
nginx -s stop快速停止(硬杀,不管请求处理完没)
nginx -s quit优雅停止(等当前请求处理完再退)
nginx -s reopen重新打开日志文件(日志切割后用)
nginx -T打印完整配置(含 include 进来的),排错用

1.12 Nginx 集群:主备 + Keepalived

单台 Nginx 挂了整个网站就没了。生产上至少两台 Nginx 做主备,用 Keepalived 搞一个 VIP(虚拟 IP):主 Nginx 活着时 VIP 飘在主上,主挂了 VIP 自动飘到备 Nginx。用户无感知。

VIP 192.168.1.100
用户只知道这个 IP
Nginx 主 (192.168.1.11)
活着时 VIP 在这
⇅
Nginx 备 (192.168.1.12)
主挂了 VIP 飘过来

1.13 完整案例:Nginx + Tomcat 动静分离 + 负载均衡 + HTTPS

把前面学的拼起来:两台 Tomcat 做后端,Nginx 在前面做负载均衡,静态文件直接给,动态请求转发,HTTPS 加密。这就是一个标准的生产配置。

# nginx.conf —— 生产级完整配置 worker_processes auto; events { worker_connections 4096; use epoll; } http { include mime.types; default_type application/octet-stream; # 日志 log_format main '$remote_addr - [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"'; access_log /var/log/nginx/access.log main; sendfile on; keepalive_timeout 65; gzip on; gzip_types text/css application/json application/javascript; # 后端 Tomcat 集群 upstream tomcat_pool { server 10.0.0.21:8080 weight=2; server 10.0.0.22:8080 weight=1; } # HTTP → HTTPS 跳转 server { listen 80; server_name www.example.com; return 301 https://$host$request_uri; } # HTTPS 主服务 server { listen 443 ssl http2; server_name www.example.com; ssl_certificate /etc/nginx/ssl/example.crt; ssl_certificate_key /etc/nginx/ssl/example.key; ssl_protocols TLSv1.2 TLSv1.3; # 静态文件直接给 location ~* \.(js|css|png|jpg|gif|ico)$ { root /data/www/static; expires 7d; access_log off; } # 动态请求转发 Tomcat location /api/ { proxy_pass http://tomcat_pool; 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; } } }
1.14 Nginx 面试重点(点开展开)

Q1:Nginx 为什么高并发性能好?

查看答案

事件驱动 + 非阻塞 I/O(epoll),一个 worker 同时处理几千连接,不像 Apache 一请求一线程。sendfile 零拷贝直接从磁盘到网卡,不走用户态。

Q2:Nginx 的 location 匹配优先级?

查看答案

优先级从高到低:=(精确匹配)> ^~(前缀匹配不再查正则)> ~/~*(正则)> 普通前缀 /static/ > /(兜底)。

Q3:Nginx 和 Apache 怎么选?

查看答案

高并发、静态资源、反向代理、负载均衡选 Nginx。需要 .htaccess 分布式配置、模块极多、跑老式 PHP/Perl 选 Apache。现在 90% 的场景选 Nginx。

1.15 Apache HTTP Server:老牌接待员

Apache 是 Web 服务器的"祖师爷",从 1995 年开始统治互联网十几年。现在被 Nginx 抢了风头,但它有两个独门绝技:.htaccess 分布式配置和极丰富的模块生态。如果你在跑老式 PHP/Perl 老项目,或者需要在每目录下放配置文件而不改主配置,Apache 还是首选。

论Apache 的脾气

老牌 Web 服务器,模块丰富,.htaccess 分布式配置。它的工作模式有三种:prefork(多进程,一进程一请求,稳定但吃内存)、worker(多进程多线程,折中)、event(事件驱动,Apache 2.4+ 新增,接近 Nginx)。默认 prefork,所以并发高了就喘。

安装与核心配置 httpd.conf

# Ubuntu sudo apt install -y apache2 # 核心配置文件 /etc/apache2/apache2.conf # 关键指令: ServerName www.example.com Listen 80 MaxRequestWorkers 150 # 最大并发请求数(prefork 模式) DocumentRoot /var/www/html

1.16 虚拟主机与 .htaccess

# 虚拟主机:一个 IP 跑多个网站 <VirtualHost *:80> ServerName www.example.com DocumentRoot /var/www/site1 </VirtualHost> <VirtualHost *:80> ServerName blog.example.com DocumentRoot /var/www/blog </VirtualHost> # .htaccess:放网站根目录里,Apache 每次请求都读一遍 # 比如把 /old.html 永久重定向到 /new.html RewriteEngine On RewriteRule ^old\.html$ /new.html [R=301,L]
.htaccess 为什么慢

Apache 每来一个请求都要去磁盘读一遍 .htaccess,找配置。如果这个文件很大、目录嵌套很深,性能直线下降。生产环境建议把 .htaccess 的规则写进主 httpd.conf,然后关掉 .htaccess(AllowOverride None)。Nginx 根本没有这个机制,所有配置集中在主文件里,所以快。

Nginx vs Apache 选型对比
对比点NginxApache
并发模型事件驱动,非阻塞,高并发一请求一进程/线程,并发高了吃内存
静态文件极快,sendfile 零拷贝还行,但比 Nginx 慢
动态内容必须反代给后端(Tomcat/PHP-FPM)模块直接跑 PHP/Perl
.htaccess不支持支持,灵活但慢
配置方式集中配置文件,改完 reload主配置 + 分布式 .htaccess
适用场景高并发、反向代理、负载均衡、CDN老式 PHP 项目、需要 .htaccess
1.17 第一章练习(点开展开)

1.(配置题)你要把 https://api.example.com/ 反代到 http://127.0.0.1:8080,写出 Nginx 配置。

查看答案

server { listen 443 ssl; server_name api.example.com; ssl_certificate ...; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }

2.(排错题)Nginx 改完配置 nginx -s reload 报错 unknown directive "proxy_passs",怎么回事?

查看答案

拼写错误:proxy_passs 多了一个 s。先跑 nginx -t,它会告诉你第几行有问题,改掉再 reload。

3.(设计题)3 台 Tomcat,1 台性能好(8核16G)2 台一般(4核8G),怎么配负载均衡?

查看答案

加权轮询:好的那台 weight=4,一般两台 weight=2。这样性能好的机器分到的请求多一倍。

记
第一章小结

① Nginx = 前台接待员,事件驱动 + 非阻塞,高并发之王。核心就四句话:worker_processes 管进程,events 管连接,http/server/location 管路由。

② 反向代理三件套:proxy_pass + proxy_set_header Host/X-Real-IP/X-Forwarded-For,别忘。

③ 动静分离是黄金法则:静态 Nginx 给,动态转后端。改完配置 nginx -t 检查 + nginx -s reload 热加载。