网站动态页面如何降低服务器负载?页面缓存、对象缓存与数据库优化

动态网站的服务器负载往往比静态站高得多,原因很简单:每次访问都要在后端执行代码、查数据库、拼装页面。用户量一上来,同一段 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"的优先级落地,每加一层用指标验证效果,缓存和数据库改动在生产环境操作要备份、低峰期执行、可回滚。