动态网站的服务器负载往往比静态站高得多,原因很简单:每次访问都要在后端执行代码、查数据库、拼装页面。用户量一上来,同一段 PHP 或 Java 代码被重复执行成千上万次,数据库被同样的查询反复打,CPU 和数据库连接很快就被吃满。结果就是响应变慢、TTFB 升高,严重时直接 5xx。
降负载的核心思路是"让重复的计算只做一次":能缓存的缓存、能静态化的静态化、能交给前端的交给前端。这篇文章按优先级梳理页面缓存(Nginx 的 proxy_cache / fastcgi_cache)、对象缓存(Redis / Memcached)、数据库优化(索引与慢查询)、PHP opcache、页面静态化和 CDN 加速这几条主线,给出每种的适用场景和一套可落地的组合方案。读完你能判断自己的站点该先从哪一招下手。
一、先分清负载花在哪
降负载之前要先测量,否则容易优化错地方。先回答三个问题:CPU 高还是数据库高?是读多还是写多?是登录用户多还是游客多?
# 系统层面看资源占用
top -o %CPU # 按 CPU 排序
vmstat 1 5 # 看 CPU、内存、IO 等待
iostat -x 1 5 # 看磁盘 IO(需 sysstat:RHEL 系 yum install sysstat,Debian/Ubuntu apt install sysstat)
# 数据库层面看活跃连接和慢查询
mysqladmin status
mysqladmin processlist
经验判断:如果 CPU 打满而数据库不忙,多半是代码或模板渲染重;如果数据库 CPU 高、慢查询多,问题在 SQL 和索引;如果磁盘 IO 高,可能是缓存策略差导致频繁回源或日志写太多。定位清楚再动手,收益最大。
二、第一优先级:页面缓存
页面缓存是把整个渲染好的 HTML 存下来,后续相同请求直接返回缓存,后端完全不用参与。命中缓存时 TTFB 常能降到个位数毫秒,是性价比最高的一招。
Nginx 有两套常用方案:反向代理场景用 proxy_cache,PHP-FPM 场景用 fastcgi_cache。
# http 段:定义缓存区
proxy_cache_path /var/cache/nginx/proxy levels=1:2 keys_zone=pcache:100m
max_size=4g inactive=60m use_temp_path=off;
fastcgi_cache_path /var/cache/nginx/fcgi levels=1:2 keys_zone=fcache:100m
max_size=2g inactive=60m;
server {
# 反向代理缓存
location / {
proxy_cache pcache;
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://backend;
}
# 或者 PHP 用 fastcgi_cache
location ~ \.php$ {
fastcgi_cache fcache;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_valid 200 10m;
fastcgi_cache_bypass $cookie_logged_in; # 登录用户绕过缓存
fastcgi_no_cache $cookie_logged_in;
fastcgi_pass unix:/run/php-fpm/www.sock;
}
}
关键点:
X-Cache-Status是排查利器,返回HIT命中、MISS未命中、BYPASS绕过、EXPIRED过期。命中率低就要查缓存键和绕过条件。- 个性化页面绝不能整页缓存。带登录态、购物车、用户名的页面,整页缓存会把 A 用户的内容发给 B 用户,这是严重的安全事故。用
fastcgi_cache_bypass/fastcgi_no_cache让登录用户绕过缓存。 - 设置合理过期时间。新闻列表可以几分钟,商品详情可以更久;频繁更新的页面过期时间要短,或做主动清除。
三、第二优先级:对象缓存(Redis / Memcached)
页面缓存解决"整页复用",对象缓存解决"局部数据复用"。数据库查询结果、配置、会话、热门列表这类反复读写的数据,放进内存缓存,能把大量数据库查询挡在内存层。
Redis 和 Memcached 的取舍:
| 维度 | Redis | Memcached |
|---|---|---|
| 数据结构 | String/List/Hash/Set/ZSet 等丰富 | 只有 key-value |
| 持久化 | 支持(RDB/AOF) | 不支持 |
| 适用 | 会话、排行、队列、复杂缓存 | 纯缓存、简单 KV、多线程高并发读 |
| 内存效率 | 略低(结构开销) | 高(适合纯缓存) |
| 运维复杂度 | 略高 | 低 |
安装和基本使用(两套发行版命令):
# RHEL/CentOS/Rocky
sudo dnf install redis # 或 yum
sudo systemctl enable --now redis
# Debian/Ubuntu
sudo apt update && sudo apt install redis-server
sudo systemctl enable --now redis-server
# 连接测试
redis-cli ping # 期望返回 PONG
redis-cli info memory | grep used_memory_human
应用里把"查数据库"改造成"先查缓存、未命中再查库并回写缓存"。缓存要注意两个经典问题:缓存穿透(查不存在的数据,每次都打到库)用空值缓存或布隆过滤器缓解;缓存雪崩(大量缓存同时过期,瞬间全打到库)给过期时间加随机抖动。
# 观察 Redis 命中情况和内存
redis-cli info stats | grep -E 'keyspace_hits|keyspace_misses'
redis-cli info memory | grep maxmemory
四、第三优先级:数据库优化
数据库往往是动态站的真正瓶颈。优化三条主线:索引、慢查询、连接。
# 开启并查看慢查询日志(MySQL,配置文件 /etc/my.cnf 或 /etc/mysql/my.cnf)
mysql -e "SHOW VARIABLES LIKE 'slow_query_log%';"
mysql -e "SET GLOBAL slow_query_log=ON; SET GLOBAL long_query_time=1;"
# 用 EXPLAIN 分析查询,重点看 type(是否 ALL 全表扫描)和 rows(扫描行数)
mysql -e "EXPLAIN SELECT u.name, o.total FROM users u
JOIN orders o ON o.user_id=u.id WHERE o.status=1 ORDER BY o.created_at DESC LIMIT 20;"
优化要点:
- 给高频查询的过滤、排序、关联字段建索引;索引不是越多越好,会拖慢写入。
- 避免
SELECT *,只取需要的列,减少 IO。 - 大分页(
LIMIT 100000, 20)用游标或延迟关联优化,避免扫描大量行。 - 合理设置连接池和
max_connections,避免连接耗尽。
# 看当前连接数和最大连接数
mysql -e "SHOW STATUS LIKE 'Threads_connected'; SHOW VARIABLES LIKE 'max_connections';"
# 看慢查询数量
mysql -e "SHOW GLOBAL STATUS LIKE 'Slow_queries';"
五、第四优先级:PHP opcache 与静态化
如果站点是 PHP,opcache 几乎是必选项。它把 PHP 脚本编译后的字节码缓存在内存里,避免每次请求都重新解析编译,能显著降低 CPU 占用。
# php.ini 配置
opcache.enable=1
opcache.memory_consumption=128 # 共享内存大小,视项目规模调整
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000 # 缓存的脚本数量上限
opcache.validate_timestamps=1 # 开发环境开,方便改代码即生效
opcache.revalidate_freq=60 # 生产环境可设较大或配合部署时清理
生产环境常见做法是 validate_timestamps=0(不检查文件时间戳,性能最好),但这样改代码后必须重载 PHP-FPM 才生效,部署流程要相应调整。
# 看 opcache 命中情况(写个 phpinfo 或探针页面查看)
php -i | grep -i opcache
# 部署后清 opcache(如果开了 validate_timestamps=0)
sudo systemctl reload php-fpm
页面静态化是更彻底的方案:把不常变的动态页在发布时或首次访问时生成为静态 HTML 文件,后续直接由 Nginx 发静态文件。新闻、博客这类更新不频繁的站点收益最大。
六、第五优先级:CDN 与动态加速
把静态资源交给 CDN,源站只处理回源请求,能卸掉大量带宽和连接压力。对于有跨地域用户的站点,CDN 还能显著降低延迟。
# 源站按回源与否设置不同的缓存策略
# 静态资源长缓存
location ~* \.(css|js|jpg|png|webp|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
对动态接口,可以评估"动态加速"类产品(主流云厂商都提供,节点和计费各不相同,选型以实际测试为准)。它的思路是通过优化回源链路和就近接入降低动态请求的延迟,但真正降源站负载还得靠缓存。
七、组合方案与优先级
单招往往不够,实际项目是组合拳。推荐的落地顺序:
| 优先级 | 措施 | 主要收益 | 适用场景 |
|---|---|---|---|
| 1 | 页面缓存(proxy/fastcgi_cache) | 命中即秒回,源站零负载 | 游客为主、内容更新不频繁 |
| 2 | 对象缓存(Redis/Memcached) | 挡掉大量数据库查询 | 有重复数据读取的站点 |
| 3 | 数据库优化(索引/慢查询) | 根治数据库瓶颈 | 所有动态站 |
| 4 | PHP opcache / 应用层缓存 | 降低 CPU | PHP 等解释型语言站点 |
| 5 | 静态化 | 彻底不执行后端 | 内容型、更新少的站点 |
| 6 | CDN | 卸载带宽与连接 | 有外部访问、跨地域 |
落地建议:先测量(第一节),确认瓶颈在 CPU 还是数据库;游客多就先上页面缓存,数据库忙就先优化索引和加对象缓存;每加一层都用 X-Cache-Status、Redis 命中率、慢查询数量等指标验证效果,别一次全上,否则出问题不好定位。
风险提示:缓存配置涉及数据正确性,个性化内容整页缓存会串号;清缓存、改索引、调数据库参数等操作在生产环境有风险,建议低峰期执行、先备份、准备好回滚(比如缓存可以先 proxy_cache_bypass 临时关闭验证)。
常见问题
Q:先做页面缓存还是先做对象缓存?
看场景。游客为主、内容更新不频繁的站点,页面缓存收益最大,先做;有明显重复数据库查询的站点,对象缓存更直接。两者不冲突,可以叠加。
Q:整页缓存后用户看到别人的登录信息,怎么办?
这是把个性化页面缓存了导致的,属于严重问题。要让带登录态的请求绕过页面缓存(fastcgi_cache_bypass/proxy_no_cache),或只缓存游客版本。
Q:缓存命中率一直很低?
检查缓存键是否带了随机参数、Cookie 或时间戳,导致同一内容被视为不同请求;检查是否设置了 Cache-Control: no-cache 或频繁过期;用 X-Cache-Status 确认到底是 MISS 还是 BYPASS。
Q:加了 Redis 数据库还是很忙?
可能缓存键设计不合理、命中率低,或大量查询是写操作(缓存帮不上)。看 keyspace_hits/keyspace_misses 比率和慢查询,确认是读写哪一类问题。
Q:opcache 开了之后改了代码不生效?
如果 validate_timestamps=0,改代码后需要重载 PHP-FPM(systemctl reload php-fpm)才会更新缓存。开发环境建议设 validate_timestamps=1。
总结
降负载先测量:分清瓶颈在 CPU、数据库还是磁盘 IO,再决定优化方向,避免瞎优化。
页面缓存(Nginx proxy_cache/fastcgi_cache)是性价比最高的一招,命中即秒回;但个性化页面绝不能整页缓存,否则会串号,属安全事故。
对象缓存(Redis / Memcached)挡数据库查询,注意缓存穿透和缓存雪崩;Redis 功能丰富、Memcached 更纯粹,按需选择。
数据库优化靠索引、慢查询分析和连接管理,是根治手段;PHP 站点别忘开 opcache,内容型站点可做页面静态化。
组合方案按"页面缓存 → 对象缓存 → 数据库优化 → opcache/静态化 → CDN"的优先级落地,每加一层用指标验证效果,缓存和数据库改动在生产环境操作要备份、低峰期执行、可回滚。
企业QQ咨询




