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 选型对比
| 对比点 | Nginx | Apache |
| 并发模型 | 事件驱动,非阻塞,高并发 | 一请求一进程/线程,并发高了吃内存 |
| 静态文件 | 极快,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 热加载。