Web接入层的“铁三角”---防盗链、反向代理,负载均衡(nginx)
第一部分:防盗链(防别人白嫖你的资源)
场景
你的网站有商品图、视频、JS/CSS,结果被别人直接 <img src="https://你的域名/1.jpg">
嵌到他网站上,流量全算你的,钱白花。
方案1:基础防盗链(靠 Referer 判断来源)
# 文件位置:/etc/nginx/conf.d/anti-leech.conf# 匹配所有图片、视频、静态资源
location ~* \.(jpg|jpeg|png|gif|bmp|mp4|avi|css|js|woff2)$ {# 【关键】定义哪些 Referer 是合法的# - none:用户直接在浏览器输入 URL(无 Referer)# - blocked:Referer 被代理/防火墙删了(比如公司内网)# - *.zjx521.com:你的主站和子站# - ~\.google\.:正则,允许 Google 爬虫(SEO 必须!)# - ~\.baidu\.:同理,允许百度valid_referers none blocked*.zjx521.com zjx521.com~\.google\. ~\.baidu\. ~\.bing\.# 【关键】如果 Referer 不在上面列表里,就认为是盗链if ($invalid_referer) {# 方式1:直接返回 403 禁止访问(最省资源)return 403;# 方式2(可选):返回一张“禁止盗链”图片(用户体验好点)# rewrite ^/.*$ /static/forbidden.png break;}# 如果是合法请求,正常返回文件root /data/www/static;expires 30d; # 浏览器缓存30天,减少回源
}
重要提醒:
- 这个方案能防小白,防不住高手(黑产用 Python requests 加个 fake Referer 就绕过);
- 所以重要资源(比如付费视频)必须用方案2:Token 签名。
方案2:高级防盗链(URL 带时效签名,也叫“URL 签名”)
原理
生成一个带过期时间的 URL,比如:
https://cdn.yourdomain.com/video.mp4?expire=1712345678&sign=a1b2c3d4...
expire
:Unix 时间戳,比如 5 分钟后过期;sign
:用密钥对路径 + expire
做 MD5,别人算不出来。
配置(需 OpenResty,即 Nginx + Lua)
# /usr/local/openresty/nginx/conf/conf.d/secure.conflocation /secure/ {access_by_lua_block {-- 引入 MD5 库(OpenResty 默认带)local md5 = require "resty.md5"local secret = "my_secret_2025"local uri = ngx.var.urilocal expire = ngx.var.arg_expirelocal sign = ngx.var.arg_signif not expire or not sign thenngx.exit(403)endif tonumber(expire) < ngx.time() thenngx.exit(403)end-- 正确计算 MD5local hasher = md5:new()hasher:update(secret .. uri .. expire)local expected_sign = hasher:final()-- 转成十六进制字符串(sign 通常是 hex)expected_sign = require("resty.string").to_hex(expected_sign)if sign ~= expected_sign thenngx.log(ngx.ERR, "Invalid sign")ngx.exit(403)end}alias /data/secure/;
}
实战经验:
- 密钥每天自动轮换,通过内部配置中心推送;
- 签名链接生成逻辑放在业务代码里(Java/Go),前端只拿最终 URL;
- CDN 也必须支持同样的签名逻辑(阿里云 CDN 支持,自建可用 Nginx 边缘节点)。
第二部分:反向代理(Nginx 当“中间人”)
场景
用户访问 https://api.yourdomain.com
,Nginx 把请求转给后端 Java 服务(比如 10.0.1.10:8080),但要加安全头、控超时、防雪崩。
# 文件位置:/etc/nginx/conf.d/api-proxy.conf# 定义后端服务集群(多个实例)
upstream backend_api {# 【关键】健康检查 + 故障隔离server 10.0.1.10:8080 max_fails=2 fail_timeout=10s;server 10.0.1.11:8080 max_fails=2 fail_timeout=10s;# 【生产优化点】慢启动:新机器上线,先少分点流量,30秒后全量# server 10.0.1.12:8080 slow_start=30s;
}server {listen 443 ssl http2;server_name api.yourdomain.com;# SSL 配置(略,参考前文)location / {# 【关键】透传用户真实 IP 给后端proxy_set_header Host $host; # 原始 Hostproxy_set_header X-Real-IP $remote_addr; # 用户真实 IPproxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 链式代理 IP# 【关键】超时控制(防后端卡死拖垮 Nginx)proxy_connect_timeout 1s; # 连接后端最多等1秒(阿里标准)proxy_send_timeout 3s; # 发请求数据最多3秒proxy_read_timeout 5s; # 等响应最多5秒(P99 要求)# 【关键】熔断重试:如果这个节点挂了,自动试下一个proxy_next_upstream error timeout http_502 http_503;proxy_next_upstream_timeout 2s; # 重试总时间不超过2秒proxy_next_upstream_tries 3; # 最多重试3次# 转发到 upstreamproxy_pass http://backend_api;}
}
血泪教训:一个后端 Full GC 30秒,Nginx 没设
proxy_read_timeout
,连接池占满,整个 API 瘫痪。从此超时是强制基线!
第三部分:负载均衡(四层 + 七层配合)
为什么不能只用 Nginx 做 LB?
- Nginx 是七层(HTTP),每连接都要解析 HTTP 头,CPU 消耗大;
- LVS 是四层(TCP),内核态转发,单机扛 100 万+ 连接。
三大负载均衡器对比
能力 | LVS(四层) | HAProxy(四/七层) | Nginx(七层) |
---|---|---|---|
工作层级 | 传输层(TCP/UDP) | 四层(TCP) + 七层(HTTP) | 应用层(HTTP/HTTPS) |
性能 | ⭐⭐⭐⭐⭐(内核态,百万并发) | ⭐⭐⭐⭐(用户态,10万级) | ⭐⭐⭐(解析 HTTP,5万级) |
配置难度 | ❌ 极高(需调内核、ARP、VIP) | ✅ 中等(纯配置文件) | ✅ 简单 |
健康检查 | 简单 TCP | 丰富(HTTP/SSL/TCP/Lua) | 基础 HTTP |
高可用 | 需 Keepalived | 自带 peers 或 Keepalived | 需外部 LB |
适用场景 | 超大流量入口 | 中小网站、API 网关、内部服务 | Web 服务器、反向代理 |
中小厂推荐度 | ❌ 不推荐 | ✅✅✅ 强烈推荐 | ✅ 作为后端 |
一、大型/超大型网站架构
架构图
用户 → [LVS(四层,IP: 10.0.0.100)] → [Nginx1, Nginx2, Nginx3] → [Java 服务]
步骤1:配置 LVS(用 Keepalived 实现高可用)
# 文件:/etc/keepalived/keepalived.conf# 虚拟 IP(VIP),用户访问这个 IP
vrrp_instance VI_API {state MASTER # 主节点(备机写 BACKUP)interface eth0 # 绑定网卡virtual_router_id 51 # 同一组必须相同priority 100 # 主机优先级高advert_int 1 # 心跳间隔1秒authentication {auth_type PASSauth_pass 1111 # 心跳认证密码}virtual_ipaddress {10.0.0.100/24 # VIP,用户访问的 IP}
}# 四层负载均衡规则
virtual_server 10.0.0.100 80 {delay_loop 6 # 健康检查间隔6秒lb_algo wrr # 加权轮询(weight 越大流量越多)lb_kind DR # DR 模式:回包不经过 LVS,性能最高!# 后端真实服务器(即 Nginx 节点)real_server 10.0.1.20 80 {weight 10 # 权重10TCP_CHECK {connect_timeout 3nb_get_retry 3delay_before_retry 3}}real_server 10.0.1.21 80 {weight 10TCP_CHECK { ... }}
}
步骤2:在 Nginx 节点绑定 VIP(DR 模式必需)
# 在每台 Nginx 机器上执行(用 Ansible 自动化!)
ip addr add 10.0.0.100/32 dev lo # 绑定 VIP 到 loopback
echo 1 > /proc/sys/net/ipv4/conf/lo/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/lo/arp_announce
echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce
为什么这么麻烦?
DR 模式下,LVS 只改 MAC 地址,IP 不变。Nginx 必须“认领”这个 VIP,否则会丢包。
LVS、keepalived 待后续补充详解
传送门:。。。。。。。。
二、中小网站推荐架构:HAProxy + Nginx(无需 LVS)
[用户]↓ HTTPS
[HAProxy(四层 + 七层 LB,带 SSL 终止)]↓ HTTP
[Nginx 集群(反向代理 + 静态资源)]↓
[Java/Go 应用]
优势:
- HAProxy 一台机器就能做高可用(配 Keepalived);
- 配置全是文本,Ansible 一键下发;
- 支持 四层(TCP)透传 HTTPS 或 七层(HTTP)终止 SSL,灵活选择。
三、HAProxy 详细配置(生产级,带注释)
场景:HTTPS 终止在 HAProxy,后端走 HTTP(减轻 Nginx CPU)
# 文件:/etc/haproxy/haproxy.cfggloballog /dev/log local0 infomaxconn 20000 # 最大连接数(根据机器调)tune.ssl.default-dh-param 2048 # 安全 DH 参数stats socket /run/haproxy/admin.sock mode 660 level admindefaultslog globalmode http # 默认七层模式option httplog # 记录详细 HTTP 日志option dontlognulltimeout connect 5s # 连接后端超时timeout client 30s # 客户端空闲超时timeout server 30s # 后端响应超时timeout http-request 10s # 读取 HTTP 请求头超时(防 Slowloris)retries 3 # 失败重试次数# 【关键】健康检查模板
backend templateoption httpchk GET /health HTTP/1.1\r\nHost:\ api.yourdomain.comhttp-check expect status 200# 前端:接收 HTTPS 流量
frontend https-inbind *:443 ssl crt /etc/ssl/yourdomain.pem # 证书文件(含私钥+证书链)# ACL 判断 Hostacl host_api hdr(host) -i api.yourdomain.comuse_backend api-servers if host_api# 后端:转发给 Nginx 集群
backend api-serversbalance roundrobin # 轮询(也可用 leastconn)server nginx1 10.0.1.20:80 check inter 2s fall 3 rise 2server nginx2 10.0.1.21:80 check inter 2s fall 3 rise 2# check:开启健康检查# inter 2s:每2秒检查一次# fall 3:连续3次失败才标记 down# rise 2:连续2次成功才标记 up# 【可选】监控页面(生产环境加 ACL 限制 IP!)
listen statsbind *:8404stats enablestats uri /statsstats refresh 10sstats admin if TRUE
证书文件格式:
yourdomain.pem
必须是 私钥 + 证书 + 中间 CA 拼在一起:cat yourdomain.key yourdomain.crt ca-bundle.crt > /etc/ssl/yourdomain.pem chmod 600 /etc/ssl/yourdomain.pem
场景2:四层透传 HTTPS(SSL 终止在 Nginx)
如果你不想在 HAProxy 解密 HTTPS(比如合规要求),就用 TCP 模式:
frontend https-tcpbind *:443mode tcp # 四层模式default_backend nginx-tlsbackend nginx-tlsmode tcpbalance leastconn # TCP 连接用 leastconn 更公平server nginx1 10.0.1.20:443 check-ssl verify none # verify none:跳过证书验证server nginx2 10.0.1.21:443 check-ssl verify none
适用场景:
- Nginx 需要拿到原始 TLS 信息(如 client cert);
- 不想管理 HAProxy 的证书。
四、HAProxy + Nginx 协同要点
1. 透传真实 IP
HAProxy 默认不传 X-Real-IP
,需手动加:
frontend https-in...http-request set-header X-Real-IP %[src]http-request set-header X-Forwarded-For %[src]http-request set-header X-Forwarded-Proto https
Nginx 侧无需改,直接用:
proxy_set_header X-Real-IP $http_x_real_ip;
2. 高可用(Keepalived + HAProxy)
# /etc/keepalived/keepalived.conf
vrrp_script chk_haproxy {script "killall -0 haproxy" # 检查进程是否存在interval 2fall 2rise 2
}vrrp_instance VI_1 {state MASTERinterface eth0virtual_router_id 51priority 100advert_int 1authentication {auth_type PASSauth_pass 1111}virtual_ipaddress {192.168.1.100/24 # 对外 VIP}track_script {chk_haproxy}
}
两台 HAProxy + Keepalived,成本低,配置简单,中小厂标配。
haproxy 待后续补充详解
传送门:。。。。。。。。
五、为什么不推荐中小厂用 LVS?
问题 | LVS | HAProxy |
---|---|---|
配置复杂度 | 需调内核参数、ARP、VIP 绑定 | 纯文本配置 |
故障排查 | ipvsadm + tcpdump + 内核日志 | 日志清晰,/stats 页面可视化 |
功能扩展 | 几乎不能动态扩展 | 支持 Lua、ACL、自定义日志 |
运维成本 | 需专职网络工程师 | 普通 SRE 就能维护 |
教训:配错
arp_ignore
,导致整个 AZ 流量中断 20 分钟。从此非核心业务禁用 LVS。
行动建议
装 HAProxy(CentOS):
sudo yum install -y haproxy keepalived
用上面的配置替换
/etc/haproxy/haproxy.cfg
,记得:- 改
yourdomain.pem
路径; - 改后端 Nginx IP;
- 加
/health
健康检查接口(返回 200 即可)。
- 改
启动并验证:
sudo systemctl start haproxy curl -H "Host: api.yourdomain.com" http://localhost:8404/stats # 看状态
压测:
wrk -t12 -c1000 -d30s https://your-vip/api/test # 观察 HAProxy stats 页面的 sess_rate 和 error
现在立刻做这三件事:
防盗链测试:
curl -I -H "Referer: https://hacker.com" https://你的域名/logo.png # 正常应返回 HTTP/1.1 403 Forbidden
反向代理检查:
在 Nginx 日志格式加$upstream_response_time
,然后:awk '$NF > 5.0 {print $7, $NF}' /var/log/nginx/access.log # 找出响应时间 >5秒 的慢接口
负载均衡验证:
在 LVS 机器上执行:ipvsadm -ln # 看 Real Server 状态是不是 Active ping 10.0.0.100 # 确认 VIP 能通
记住:生产环境没有“差不多”。防盗链少配一行,可能被薅秃;反向代理没设超时,一次 Full GC 就雪崩;负载均衡没做健康检查,一台机器挂了全站瘫痪。