在服务器运维中,硬件配置往往不是瓶颈,配置失衡才是。最近我对运行在 2核2G VPS(20M带宽) 上的 Typecho 博客进行了一次深度性能调优。通过对 PHP 8.4 环境的层层优化,见证了网站承载能力从“高延迟抗拒”到“稳如泰山”的转变。
本文将分享完整的优化逻辑、使用 Locust 进行的压测数据对比,以及最关键的 Nginx FastCGI Cache 配置方案。
1. 测试方法:Locust 阶梯压力测试
为了获得最真实的性能曲线,我弃用了简单的命令行工具wrk,改用现代化的分布式压测工具 Locust。
- 测试策略:阶梯式增加并发。分别测试 50、100、150 名虚拟用户。
- 用户行为:模拟真实用户持续随机访问首页及文章页。
- 孵化速率 (Spawn Rate):5 users/s(模拟用户平滑涌入)。
- 环境基准:所有测试均已开启 PHP 8.4 的 Zend OPcache,对比项仅为“是否开启 Nginx FastCGI Cache”。
2. 初始状态:OPcache 开启但无页面缓存
在仅有 OPcache 的情况下,每一条请求仍需经过 PHP-FPM 解析并查询 MySQL 数据库。
压测数据分析(优化前):
- Run #1 (50用户):RPS 约为 11.3,95%ile 延迟为 1700ms。
- Run #2 (100用户):响应延迟剧增,95%ile 飙升至 6000ms (6秒)。
- Run #3 (150用户):性能雪崩。RPS 依然卡在 11 左右无法增长,95%ile 延迟达到了恐怖的 11000ms (11秒)。

结论:2核2G 的服务器在处理动态 PHP 请求时,单机极限约在 11 RPS。当并发超过 50 后,请求在后台开始疯狂排队,用户感知的响应速度迅速恶化到无法使用的地步。
3. 优化坑点:Redis 插件的“负优化”
为了提速,我曾尝试安装 Typecho 的 Redis 缓存插件,结果却令人意外:
- 无 Redis 插件:102.41 RPS (wrk 单口测试)
- 有 Redis 插件:85.75 RPS (下降约 16%)
避坑指南:对于结构简单的个人博客,MySQL 8.4 的主键查询效率极高。引入 Redis 带来的 PHP 序列化开销和网络通信开销,反而超过了节省的数据库时间。在小型博客场景下,减少链路比增加缓存层更有效。
备注:该项测试采用全新Typecho站点,搭配官方默认主题且仅有两篇纯文本的文章
4. 终极优化:Nginx FastCGI Cache 配置实战
FastCGI Cache 让 Nginx 直接缓存 PHP 输出的 HTML,请求无需再经过 PHP-FPM 和数据库,实现了真正的“静态化”。
第一步:定义全局缓存空间
在 Nginx 全局配置文件(http 块)加入:
# 定义缓存路径、层级、内存占用(100m)、失效时间(60m)
fastcgi_cache_path /tmp/nginx_cache levels=1:2 keys_zone=TYPECHO:100m inactive=60m max_size=1g;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_use_stale error timeout invalid_header http_500;
fastcgi_ignore_headers Cache-Control Expires Set-Cookie;Nginx 全局配置文件完整示例:
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" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
access_log /dev/stdout main;
server_tokens off;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
server_names_hash_bucket_size 512;
client_header_buffer_size 32k;
client_max_body_size 50m;
keepalive_timeout 60;
keepalive_requests 5000;
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.1;
gzip_comp_level 2;
gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml;
gzip_vary on;
gzip_proxied expired no-cache no-store private auth;
gzip_disable "MSIE [1-6]\.";
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
# --- FastCGI Cache 全局定义开始 ---
# 定义缓存目录、层级、内存名称及大小(100m)、有效期(60m)、最大硬盘占用(1g)
fastcgi_cache_path /tmp/nginx_cache levels=1:2 keys_zone=TYPECHO:100m inactive=60m max_size=1g;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
# 遇到后端错误时,允许返回过期的缓存给用户,保证可用性
fastcgi_cache_use_stale error timeout invalid_header http_500;
# 忽略后端传来的不缓存指令,强制缓存
fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
# --- FastCGI Cache 全局定义结束 ---
include /usr/local/openresty/nginx/conf/conf.d/*.conf;
include /usr/local/openresty/nginx/conf/default/*.conf;
include /usr/local/openresty/1pwaf/data/conf/waf.conf;
}
第二步:网站 server 块控制逻辑
set $skip_cache 0;
# POST请求、带参数的搜索、后台页面、登录用户不走缓存
if ($request_method = POST) { set $skip_cache 1; }
if ($query_string != "") { set $skip_cache 1; }
if ($request_uri ~* "/admin/|/action/|config.inc.php") { set $skip_cache 1; }
if ($http_cookie ~* "typecho_") { set $skip_cache 1; }第三步:PHP 块应用缓存
location ~ [^/]\.php(/|$) {
fastcgi_pass 127.0.0.1:9000;
fastcgi_cache TYPECHO;
fastcgi_cache_valid 200 301 302 30m;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
add_header X-Cache "$upstream_cache_status From $host"; # 方便调试
}网站完整的Nginx配置文件示例:
(针对handsome主题PJAX加载文章的方式有单独优化)
server {
listen 80 ;
listen 443 ssl ;
server_name www.dora520.top;
index index.php index.html index.htm default.php default.htm default.html;
access_log /www/sites/www.dora520.top/log/access.log main;
error_log /www/sites/www.dora520.top/log/error.log;
location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md) {
return 404;
}
location ^~ /.well-known/acme-challenge {
allow all;
root /usr/share/nginx/html;
}
if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {
return 403;
}
error_page 404 /404.html;
root /www/sites/www.dora520.top/index;
# --- 缓存控制逻辑开始 ---
set $skip_cache 0;
# 1. POST 请求不缓存
if ($request_method = POST) {
set $skip_cache 1;
}
# 2. 处理查询参数
# 先默认:只要有参数就跳过缓存(如搜索)
if ($query_string != "") {
set $skip_cache 1;
}
# 特例豁免:如果是 PJAX 请求(包含 _pjax=),我们允许缓存它
if ($query_string ~* "_pjax=") {
set $skip_cache 0;
}
# 3. 后台页面、安装页面、配置文件不缓存
if ($request_uri ~* "/admin/|/action/|/install.php|config.inc.php") {
set $skip_cache 1;
}
# 4. 登录用户不缓存
if ($http_cookie ~* "typecho_") {
set $skip_cache 1;
}
# --- 缓存控制逻辑结束 ---
location ~ [^/]\.php(/|$) {
fastcgi_pass 127.0.0.1:9000;
include fastcgi-php.conf;
include fastcgi_params;
# --- 新增缓存配置开始 ---
fastcgi_cache TYPECHO; # 使用全局定义的缓存空间
fastcgi_cache_valid 200 301 302 30m; # 状态码 200、301、302 缓存 30 分钟
fastcgi_cache_bypass $skip_cache; # 满足不缓存条件时跳过查询缓存
fastcgi_no_cache $skip_cache; # 满足不缓存条件时不存入缓存
add_header X-Cache "$upstream_cache_status From $host"; # 方便在浏览器查看命中状态
# --- 新增缓存配置结束 ---
set $real_script_name $fastcgi_script_name;
if ($fastcgi_script_name ~ "^(.+?\.php)(/.+)$" ) {
set $real_script_name $1;
set $path_info $2;
}
fastcgi_param SCRIPT_FILENAME $document_root$real_script_name;
fastcgi_param SCRIPT_NAME $real_script_name;
fastcgi_param PATH_INFO $path_info;
}
http2 on;
ssl_certificate /www/sites/www.dora520.top/ssl/fullchain.pem;
ssl_certificate_key /www/sites/www.dora520.top/ssl/privkey.pem;
ssl_protocols TLSv1.3 TLSv1.2;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:!aNULL:!eNULL:!EXPORT:!DSS:!DES:!RC4:!3DES:!MD5:!PSK:!KRB5:!SRP:!CAMELLIA:!SEED;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
error_page 497 https://$host$request_uri;
proxy_set_header X-Forwarded-Proto https;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
}5. 最终成果:触碰物理带宽极限
开启 FastCGI Cache 后,再次运行 Locust 阶梯压测。
压测数据对比(优化后):
- Run #1 (50用户):RPS 约为 15,95%ile 延迟仅 300ms。
- Run #2 (100用户):RPS 翻倍至 31.2,95%ile 依然稳在 300ms 左右。
- Run #3 (150用户):吞吐量极限。RPS 冲到了 40-45 之间,响应时间开始平稳抬升至 1.5s 左右。

深度对比分析:
- 吞吐量提升 400%:开启缓存后,RPS 从 11 暴涨至 45。
- 延迟大幅下降:在 100 并发下,延迟从 6秒 降至 0.3秒,提升了 20倍 的用户体验。
触碰物理瓶颈:
- 平均页面大小约 105 KB。
- 计算:$45 \text{ RPS} \times 105 \text{ KB} \approx 4725 \text{ KB/s} \approx 37 \text{ Mbps}$。
- 由于我的 VPS 拥有 20M 带宽及一定的峰值(Burst)余量,RPS 卡在 45 并不是因为 CPU 不行了,而是 20M 的带宽被彻底填满了。
6. 总结建议
对于 2核2G 级别的轻量级服务器,优化顺序应为:
- 必做项:开启 PHP OPcache,这是 PHP 环境的基石。
- 杀手锏:配置 Nginx FastCGI Cache。它让你的源站拥有了 CDN 般的吐息能力,是应对高并发的唯一最优解。
- 认知修正:当响应速度达到极限时,请查看网卡监控。如果带宽跑满,优化代码已无意义,此时应考虑开启 Gzip/Brotli 压缩 或接入 CDN 分流。
通过这套方案,我的 Typecho 博客在极低配置下实现了从“50人并发崩溃”到“100人并发秒开”的质变,彻底告别了性能焦虑。