美国VPS打开慢怎么优化
来源: 飞翔小编 2026/11/14 10:27:10 浏览量:0
一、先把「慢」拆成六段,别一上来就换机器

大部分人排查「打开慢」,第一反应是换一台更贵的机器,或者换个面板、换个数据库版本。这两种做法通常无效,因为慢的原因可能根本不在服务器上。
一次 HTTP 请求从用户敲回车到看到页面,要经过六段。任何一段慢,整体就慢。
| 阶段 | 耗时典型区间 | 是什么造成的 | 你的可控度 |
|---|---|---|---|
| DNS 解析 | 20 到 150 毫秒 | 域名解析到 IP 的往返 | 高,换解析服务商、加预解析 |
| TCP 握手 | 1 到 3 个 RTT | 跨洋往返约 150 到 300 毫秒 | 低,靠线路和 CDN |
| TLS 握手 | 1 到 2 个 RTT | 证书协商,TLS 1.3 已优化 | 中,靠会话复用、HTTP/2/3 |
| 首字节 TTFB | 100 到 800 毫秒 | 服务器收到请求到吐出第一个字节 | 高,代码、数据库、缓存 |
| 首屏渲染 | 300 到 3000 毫秒 | HTML 到达后渲染出可见内容 | 中,CSS、JS、字体阻塞 |
| 后续资源 | 视资源量 | 图片、CSS、JS、字体并发加载 | 高,压缩、合并、CDN |
这张表给出第一个判断规则:如果 TTFB 已经小于 300 毫秒,那问题大概率不在服务器,继续优化 PHP 代码的收益很小,应该去看首屏渲染和资源加载。 反过来,如果 TTFB 有 800 毫秒,而首屏只多花了 500 毫秒,那服务器才是瓶颈。
分段测量的具体命令。 这条命令把总耗时拆成了五个数字:
curl -s -o /dev/null -w "dns=%{time_namelookup}s tcp=%{time_connect}s tls=%{time_appconnect}s ttfb=%{time_starttransfer}s total=%{time_total}s size=%{size_download}\n" https://your-site.com
各字段的读法:
time_namelookup 是 DNS 解析耗时。这个值大说明要么本地 DNS 服务慢,要么你的域名记录分散在多个服务商,解析要走多次查询。
time_connect 减去 time_namelookup 是 TCP 握手耗时。跨太平洋的典型值是 150 到 250 毫秒,这个数字基本由物理距离和线路决定,服务器本身配置再高也压不下去。
time_appconnect 减去 time_connect 是 TLS 握手耗时。这个值如果超过 200 毫秒,通常是因为没有开启会话复用或者证书链太长。
time_starttransfer 减去 time_appconnect 就是服务器处理时间,也就是纯 TTFB(服务端视角)。这是你能通过代码和数据库优化直接改善的部分。
time_total 减去 time_starttransfer 是内容传输时间。
把数字抄进一张表,两周内每周测一次,你会立刻看出瓶颈在哪一段。 这比凭感觉改配置有效得多。
二、验收阈值:优化必须以数字验收

优化最常见的失败模式是「改了一堆,感觉好像快了一点」。解决办法很简单:改之前先写下数字,改之后验证数字。
下面这些是可以直接拿去用的验收阈值。
| 指标 | 合格线 | 优秀线 | 怎么测 |
|---|---|---|---|
| DNS 解析 | 小于 100 毫秒 | 小于 50 毫秒 | dig your-site.com |
| TCP 握手(跨洋) | 小于 200 毫秒 | 小于 150 毫秒 | curl 的 time_connect 差值 |
| TLS 握手 | 小于 150 毫秒 | 小于 80 毫秒 | curl 的 time_appconnect 差值 |
| 服务端处理时间 TTFB | 小于 300 毫秒 | 小于 150 毫秒 | 本机 curl 的 time_starttransfer |
| 端到端 TTFB | 小于 800 毫秒 | 小于 500 毫秒 | 异地 curl 的 time_starttransfer |
| 1 MB 文件下载 | 小于 2 秒 | 小于 1 秒 | 本机 curl 下载 1 MB 静态文件 |
| 首屏可见时间 | 小于 2.5 秒 | 小于 1.8 秒 | 浏览器 Performance 面板 |
| 移动端首屏可见时间 | 小于 4 秒 | 小于 2.5 秒 | 4 倍 CPU 降速下测 |
| 缓存命中率 | 大于 60% | 大于 80% | 访问日志里的 X-Cache 状态 |
| 压缩后首页体积 | 小于 150 KB | 小于 80 KB | curl -w "%{size_download}" |
| Lighthouse 性能分 | 大于 70 | 大于 90 | Chrome Lighthouse |
阈值要分「服务端视角」和「用户视角」两套,这一点很多人搞混。 服务端视角是「服务器从收到请求到吐出第一个字节用了多久」,这一段你能优化。用户视角是「从用户敲回车到看到内容」,这一段包含物理距离。你不可能把跨洋的 150 毫秒握手变成 10 毫秒,但你能保证服务端那一段不超过 300 毫秒。
本机测量的意义。 在服务器本机跑 curl 测出的 TTFB 不含网络往返,这是你唯一能压到的下限。如果本机 TTFB 就是 600 毫秒,那不管换多快的线路,用户端至少要 600 毫秒加网络时间。
三、按收益排序:先做这三件事

把所有优化手段按「收益 ÷ 成本」排序,排在前面的应该先做。
第一档:静态资源加缓存头和压缩。这通常是收益最大、成本最低的一步。
Nginx 配置示例:
server {
listen 443 ssl http2;
server_name your-site.com;
# 静态资源:长缓存 + 强缓存 + Gzip
location ~* \.(jpg|jpeg|png|gif|webp|svg|css|js|woff2?)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000, immutable";
gzip_static on; # 优先直接发 .gz 预压缩文件
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
gzip_min_length 1024;
try_files $uri @backend;
}
# HTML:不缓存,保证内容更新能立刻生效
location / {
add_header Cache-Control "no-cache, must-revalidate";
try_files $uri $uri/ @backend;
}
}
location @backend {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_read_timeout 60s;
# 后端处理超过 10 秒就说明该查慢查询了
fastcgi_read_timeout 10s;
}
三个要点:静态资源用 immutable 加长缓存,因为文件名带哈希,内容变了文件名就变,浏览器不需要重新验证。HTML 不缓存,否则改了内容访客还看旧版。gzip_min_length 设为 1024,小于 1 KB 的文件压缩收益抵不上 CPU 开销。
第二档:数据库查询优化。 TTFB 超标十有八九是数据库拖的。
-- 看哪些查询慢
SELECT query_time, lock_time, rows_sent, rows_examined,
LEFT(query_text, 200) AS q
FROM performance_schema.events_statements_summary_by_digest
ORDER BY query_time DESC LIMIT 10;
关键指标是 rows_examined 和 rows_sent 的比值。这个比值如果超过 100,说明扫描了大量行只返回了很少的行,索引有问题。
具体做法:
-- 给最常见的查询加索引(先用 EXPLAIN 确认)
EXPLAIN SELECT * FROM orders WHERE user_id = 1234 AND status = 'paid' ORDER BY created_at DESC LIMIT 20;
-- 看到 type=ALL 且 rows 很大,就说明要走索引
ALTER TABLE orders ADD INDEX idx_user_status_time (user_id, status, created_at DESC);
复合索引的列顺序原则是:等值条件在前、范围条件在后、排序字段最后。 上面的索引顺序就是按这个原则排的。
另外要处理连接数。PHP-FPM 的进程数直接决定并发能力,每个进程都要连数据库,连接数超限会排队:
; /etc/php/8.2/fpm/pool.d/www.conf
pm = dynamic
pm.max_children = 20 ; 1GB 内存机器不超过 10,2GB 不超过 20
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 5
pm.max_requests = 500 ; 处理 500 个请求后重启,回收内存泄漏
第三档:把不查数据库的页面全部静态化。 这是 WordPress 类站点最大的单一收益点。首页、列表页、归档页本质上都是同样的内容按时间排序,访客访问的绝大多数页面都是缓存命中。
收益排序的完整清单(从高到低):
| 优化手段 | 典型收益 | 实施成本 | 风险 |
|---|---|---|---|
| 静态资源缓存头加压缩 | 首屏减 30% 到 60% | 一小时 | 无 |
| 数据库加索引 | TTFB 减 50% 以上(慢查询场景) | 半天 | 低,注意写入变慢 |
| 页面缓存或静态化 | TTFB 降到 10 毫秒级 | 两天 | 中,缓存失效逻辑要写对 |
| 引入 CDN | 静态资源延迟降到 50 毫秒内 | 一天 | 低 |
| 换 HTTP/2 或 HTTP/3 | 资源请求数减半 | 两小时 | 低 |
| 合并 CSS 和 JS | 减少请求数 | 半天 | 中,可能引入顺序问题 |
| 换更快的线路或机房 | 跨洋握手减少 50 到 100 毫秒 | 迁机成本 | 中,要重配证书和 DNS |
| 升级 CPU 核数 | 只在 CPU 确实是瓶颈时有效 | 一小时 | 无,但收益有限 |
注意最后两行的顺序:换线路和升级 CPU 都排在最后。升级 CPU 对 I/O 密集型(WordPress、数据库)几乎没有帮助,这是不争的事实——瓶颈在磁盘 IO 和数据库查询时,加核不解决问题。
四、数据库和 PHP 层的深挖:慢查询到底慢在哪

第三节把数据库优化列进了第二档,这一节把它展开,因为它是 TTFB 超标最常见的根因。
第一步:打开慢查询日志,看真实的慢在哪里。
; /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 0.5
long_query_time 设成 0.5 秒意味着任何超过半秒的查询都会被记录。设成默认的 10 秒会漏掉大量你需要优化的查询。
第二步:看懂 EXPLAIN 的输出。 重点看三列:
| 字段 | 好的表现 | 坏的表现 | 说明 |
|---|---|---|---|
| type | ref / const / range | ALL | ALL 是全表扫描,通常是缺索引 |
| rows | 与实际返回行数接近 | 远大于 rows_sent | 扫描的行数远多于需要的 |
| Extra | Using index | Using filesort / Using temporary | 需要额外排序或建临时表 |
Using filesort 尤其要警惕。 它表示 MySQL 需要额外做一次排序,通常是因为索引顺序和 ORDER BY 不一致。这类查询在大表上会随数据量线性变慢。
第三步:处理三个具体的反模式。
反模式一,没有索引的等值条件:
-- 反模式:WHERE 用到的列都没索引
SELECT * FROM users WHERE email = '[email protected]';
-- 修复
ALTER TABLE users ADD UNIQUE INDEX uk_email (email);
反模式二,索引列上套函数或做类型转换:
-- 反模式:索引失效
SELECT * FROM orders WHERE DATE(created_at) = '2026-11-14';
SELECT * FROM orders WHERE user_id = '1234'; -- user_id 是 int,字符串比较
-- 修复:改成范围查询或类型匹配
SELECT * FROM orders WHERE created_at >= '2026-11-14' AND created_at < '2026-11-15';
SELECT * FROM orders WHERE user_id = 1234;
第三种是隐式类型转换,这是 MySQL 最常见的索引失效原因,而且完全看不出来。 因为字符串和数字比较时 MySQL 会把数字转成字符串再比,索引直接作废。
反模式三,SELECT *。这不只是传输慢,它还会让「覆盖索引」失效——本来可以只查索引就拿到数据,加上 * 之后必须回表。
-- 反模式
SELECT * FROM products WHERE category_id = 5 ORDER BY price DESC LIMIT 20;
-- 修复:覆盖索引 + 只取需要的列
ALTER TABLE products ADD INDEX idx_cat_price (category_id, price DESC);
SELECT id, name, price FROM products WHERE category_id = 5 ORDER BY price DESC LIMIT 20;
第四步:PHP 层的两个常见浪费。
一个是 N+1 查询,这是 ORM 场景的典型写法:
// 反模式:循环里查数据库,20 篇文章就是 21 次查询
foreach ($posts as $post) {
echo $post->author_name; // 每次都去查 authors 表
}
// 修复:用 join 一次拿完
$posts = $db->query("SELECT p.*, a.name AS author_name
FROM posts p JOIN authors a ON a.id = p.author_id
LIMIT 20");
一个是 PHP-FPM 进程数与数据库连接数不匹配。pm.max_children 设为 20 时,理论上会同时有 20 个 PHP 进程,每个都持有一个数据库连接。如果数据库的 max_connections 是默认的 151,那 20 个 PHP 进程加上其他客户端还够用;但如果你把 pm.max_children 调到 100,数据库连接就会成为新瓶颈。
内存也要一起算。 一个 PHP-FPM 进程典型占用 30 到 60 MB。20 个进程就是 600 MB 到 1200 MB。在 1 GB 内存的机器上,这会导致进程被 OOM Killer 杀掉,表现为间歇性 502。判断标准:1 GB 内存机器的 pm.max_children 不超过 10,2 GB 不超过 20,4 GB 不超过 40。
第五步:算一下优化收益。 假设你的站点每个页面有 3 个慢查询,每个慢 0.6 秒,串行执行就是 1.8 秒。加上 PHP 执行和模板渲染 0.2 秒,TTFB 大约 2 秒。加对索引后查询降到 0.01 秒,TTFB 回到 0.25 秒。这一项的收益是 1.75 秒,占整个 TTFB 的 87%,而成本是 4 小时加一次建索引的时间。 这是全文性价比最高的一步。
五、成本算账:优化和换机器哪个划算

这是本文的核心算账部分。假设你的站点现在 TTFB 是 800 毫秒,用户端首屏 4 秒。你有两个选择:花钱优化,或者花钱换配置。
先看优化要花多少钱。
| 优化项 | 时间成本 | 金钱成本 | 预期效果 |
|---|---|---|---|
| 缓存头加 gzip 压缩 | 1 小时 | 0 美元 | 传输时间减少 60% |
| 数据库索引 | 4 小时 | 0 美元 | TTFB 从 800 降到 200 毫秒 |
| 页面缓存插件 | 8 小时 | 0 美元 | TTFB 从 200 降到 30 毫秒 |
| 换 HTTP/2 | 2 小时 | 0 美元 | 资源请求数减半 |
| CDN 接入 | 6 小时 | 每月 5 到 20 美元 | 静态资源延迟减半 |
| 合计 | 约 21 小时 | 每月 0 到 20 美元 | 首屏从 4 秒降到 1.5 秒左右 |
再看换机器要花多少钱。 拿商品库里的真实机型算:
RosCloud-01 是 19.99 美元每月,2 核 1 GB 内存、20 GB 硬盘、500 Mbps 带宽、每月 1024 GB 流量,每 GB 内存成本 19.99 美元、每 GB 硬盘成本约 0.999 美元、每 TB 流量成本约 19.99 美元。
RosCloud-03 是 39.99 美元每月,4 核 4 GB 内存、80 GB 硬盘、500 Mbps 带宽、每月 3072 GB 流量,每 GB 内存成本 10 美元、每 GB 硬盘成本 0.5 美元、每 TB 流量成本约 13.33 美元。
RosCloud-06 是 69.99 美元每月,7 核 24 GB 内存、320 GB 硬盘、500 Mbps 带宽、每月 6144 GB 流量,每 GB 内存成本 2.92 美元、每 GB 硬盘成本约 0.219 美元、每 TB 流量成本约 11.67 美元。
从 RosCloud-01 换到 RosCloud-03,每个月多花 20 美元,换来的是内存从 1 GB 到 4 GB、核心从 2 到 4。 但如果瓶颈是数据库慢查询(常见情况),核数翻倍后 TTFB 可能只从 800 毫秒降到 600 毫秒,因为时间花在磁盘 IO 上而不是 CPU 上。
从 RosCloud-03 换到 RosCloud-06,每个月多花 30 美元(39.99 到 69.99),每 GB 内存成本从 10 美元降到 2.92 美元,降幅 71%。这个降价幅度说明大内存机型在这条产品线上的性价比明显更高。如果你本来就需要 8 GB 以上内存,直接选高档位比在低档位上挣扎更划算。
再看流量计费这一维。 站点慢的时候有人会怀疑「是不是流量被限速了」。用商品库的真实数据算:LAX-STARTER 年付 200 美元,折合每月 16.67 美元,2 核 2 GB 内存、40 GB 硬盘、2000 Mbps 带宽、每月 5000 GB 流量,每 TB 流量成本约 3.41 美元。LAX-LARGE 年付 1600 美元,折合每月 133.33 美元,8 核 16 GB 内存、320 GB 硬盘、10000 Mbps 带宽、每月 40000 GB 流量,每 TB 流量成本同样约 3.41 美元。
这条产品线所有机型的每 TB 流量成本都在 3.41 美元左右,说明流量配额是等比例放大的,没有规模效应。 所以「买大流量配额省单位成本」这条路在这条线上不成立,你应该关注的是「你的月流量到底会不会超」。
可以这样估算你的月流量。 一个页面平均 2 MB,包含 HTML、CSS、JS、图片。首屏 5 个资源算,平均每个页面 3 MB 左右(图片占大头)。1000 PV 的站点,月流量约 3000 GB。
| 月 PV | 估算月流量 | 对应够用的机型 |
|---|---|---|
| 500 | 1500 GB | LAX-STARTER(5000 GB)够 |
| 2000 | 6000 GB | LAX-MEDIUM(20000 GB)够 |
| 8000 | 24000 GB | LAX-LARGE(40000 GB)够 |
| 20000 | 60000 GB | 超出这条产品线,需要多机或不限流量方案 |
结论很清楚:如果你的瓶颈是 TTFB 偏高,21 小时的优化等于每月 0 到 20 美元,能把首屏从 4 秒压到 1.5 秒;而换机器每月多花 20 到 30 美元,可能只换来几百毫秒。优化排在换机器前面。
六、CDN 什么时候有用,什么时候是负优化

CDN 是最容易被滥用的工具。判断要不要上 CDN,只需要问三个问题。
问题一:你的访客在哪儿? 全部在国内而源站在美国,CDN 的境内节点能显著改善访问。访客本来就在美国,CDN 基本无用甚至有害。
问题二:你的页面是静态的还是动态的? 静态资源(图片、CSS、JS、字体)上 CDN 收益明确。动态 HTML 上 CDN 收益有限,而且要处理缓存失效。
问题三:你的带宽是瓶颈吗? 如果源站带宽已经打满(表现为高峰期大量超时),CDN 缓存静态资源能直接缓解。
CDN 配置的关键在缓存规则,而不是接哪家。 规则配错的典型后果是:改完网站内容,访客看到的还是旧页面,排查半天以为是 CDN 有问题。
| 资源类型 | 缓存时长 | 理由 |
|---|---|---|
| 带哈希的 CSS/JS | 30 天 | 文件名变则内容变,可永久缓存 |
| 图片 | 30 天 | 同上 |
| HTML 首页 | 5 分钟 | 平衡新鲜度和源站压力 |
| HTML 文章页 | 30 分钟到 1 小时 | 内容更新不频繁 |
| HTML 购物车/账户页 | 不缓存 | 必须实时 |
| API 接口 | 按需,或不缓存 | 写操作必须回源 |
验证缓存是否生效的方法:查响应头。
curl -sI https://your-site.com/style.css | grep -i -E "cache-control|age|x-cache|via"
有 age 头说明这个响应来自缓存,age 越大说明命中越久。
CDN 的隐藏成本。 第一是配置时间,一般一到两天。第二是缓存失效的复杂度,文章更新后要主动刷新对应 URL,否则内容不生效——很多人在这里踩坑。第三是流量账单,静态资源被 CDN 缓存后回源流量减少,但 CDN 出网流量通常按流量计费,站点规模大时这笔钱比 VPS 本身还贵。
七、什么情况下不该自己优化,该换方案

这一节按公开事实说明不适用的场景——机房位置、线路类型、计费方式、IP 资源,不评价任何商家好坏。
场景一:慢的原因是物理距离,不是配置。 你的源站在美西,访客在华东,TCP 握手本身就占 150 毫秒以上,而且这条路径经过的是标准国际出口,晚高峰抖动是线路类型的固有属性。优化代码改不动这段。 替代方案:把静态资源放 CDN 境内节点,动态部分接受这个延迟;或者如果业务允许,选更靠近访客的机房。这条路的成本是重新做一遍部署,但收益是可预期的。
场景二:计费方式是流量计费,而你还没有把流量用满。 LAX 系列每 TB 流量成本约 3.41 美元这一档,如果你的站点月流量只有 500 GB,而套餐给了 5000 GB,那么带宽根本没有被用满,慢的原因不可能是带宽不足。替代方案:把排查精力放到 TTFB 上,不要去买更大流量的套餐,那解决不了问题。
场景三:瓶颈在共享 CPU 争抢,而且你无法控制宿主机。 很多 VPS 是共享核的,超售情况下你的邻居变忙,你的 CPU 份额就被挤掉,表现为负载不高但延迟变高。替代方案:用 top 看 steal time,持续大于 10% 就说明在被超售;这种情况要么找提供独占核心的机型,要么把负载均衡到多台机器上分散。
场景四:IP 资源限制导致你必须绕路。 有些场景(多账号、爬虫、需要多个独立 IP)会让你不得不维护多台机器,每台机器都要重复一遍优化工作。替代方案:不要为每台机器单独做优化,把优化点收敛到「模板化的部署脚本加统一的缓存策略」,一次写好批量应用。
场景五:你用的是带面板的方案,瓶颈在面板自身。 面板守护进程加它自己的 PHP 进程组会占用几百 MB 内存,1 GB 内存的机器上留给业务的只有几百 MB,容易触发 swap。替代方案:换成裸装,用 systemd 管理服务;或者把机器规格提到 2 GB 以上,让面板的常驻开销和业务栈共存。
场景六:优化收益已经低于运维成本。 如果你的站点月收入低于 20 美元,或者月 PV 不到 500,花 21 小时优化加上之后每个月的缓存维护,其成本远超收益。替代方案:用最低配机型加 CDN,把精力放在内容上。开源工具的默认配置对大多数小站已经足够。
八、给四类人的具体方案

第一类:个人博客,月 PV 500 以内。 用 1 GB 内存机型足够,比如 RosCloud-01(19.99 美元每月,2 核 1 GB 内存、20 GB 硬盘、每月 1024 GB 流量)。优化方案只做两件事:静态资源加缓存头加 gzip;装一个页面缓存插件。不要上 CDN,不要买大内存,不要调数据库参数。验收标准:移动端首屏小于 4 秒,Lighthouse 性能分大于 70。
第二类:中小内容站或小型电商,月 PV 2000 到 8000。 用 4 GB 内存起步,比如 RosCloud-03(39.99 美元每月,4 核 4 GB 内存、80 GB 硬盘、每月 3072 GB 流量,每 GB 内存成本 10 美元)。优化方案:第一节的六项全做,加索引重点做,静态化首页和列表页。CDN 按第三节的规则表接。验收标准:服务端 TTFB 小于 300 毫秒,端到端 TTFB 小于 800 毫秒,移动端首屏小于 2.5 秒。
第三类:跑数据库较重的业务(论坛、商城、后台系统),月 PV 8000 以上。 需要 8 GB 以上内存,这时直接选高档位更划算:RosCloud-06 是 69.99 美元每月,7 核 24 GB 内存、320 GB 硬盘、每月 6144 GB 流量,每 GB 内存成本仅 2.92 美元,比 RosCloud-03 的 10 美元低 71%。优化方案:重点做数据库层——索引、慢查询治理、读写分离、连接池;页面缓存只做读多写少的部分。验收标准:慢查询为零(超过 1 秒的查询数为 0),接口 P95 响应小于 500 毫秒。
第四类:访客以国内为主的跨境业务。 选 LAX-MEDIUM 或 LAX-LARGE 这类年付机型:LAX-MEDIUM 年付 800 美元折合每月 66.67 美元,8 核 8 GB 内存、160 GB 硬盘、10000 Mbps 带宽、每月 20000 GB 流量,每 TB 流量成本约 3.41 美元。优化方案:源站只负责动态内容,所有静态资源必须走 CDN 境内节点,源站不直接对外暴露静态文件;源站本身配置只需保证 TTFB 小于 300 毫秒,因为总延迟由 CDN 决定。验收标准:境内访客首屏小于 1.8 秒,源站 TTFB 小于 300 毫秒。
最后一句通用建议:优化的顺序永远是「先测量、再定位、后修改、再复测」,同一个站点每季度复测一次基线。机房和配置会变,访客分布会变,之前的数字不能一直沿用。
九、图片和字体:占页面体积最多的两块

前面几节优化的是服务器响应时间。这一节处理的是内容体积——对首屏时间影响往往比服务器本身更大。
先量一下你的页面里图片占多少。 用浏览器的 Network 面板按类型排序,或者在服务器上直接统计:
# 统计站点里图片的总大小(假设页面在 /var/www/html)
find /var/www/html -type f \( -name "*.jpg" -o -name "*.png" -o -name "*.webp" -o -name "*.gif" \) \
-printf "%s %p\n" | sort -rn | head -20 | awk '{printf "%.1f KB %s\n", $1/1024, $2}'
图片优化的四个动作,按收益排序。
第一,改用 WebP 或 AVIF 格式。WebP 相比 JPEG 通常能小 25% 到 35%,相比 PNG 小 70% 以上,而且全面支持。
第二,按实际显示尺寸出图。图片按 800 像素宽上传但在页面里显示成 400 像素,是最常见的浪费。用 srcset 让浏览器自己挑:
srcset="/img/hero-400.webp 400w, /img/hero-800.webp 800w, /img/hero-1600.webp 1600w"
sizes="(max-width: 600px) 100vw, 800px"
width="800" height="450"
loading="lazy" decoding="async" alt="产品示意图">
sizes 必须写,否则浏览器会按 100vw 假设去下载最大那张,白白浪费流量。
第三,首屏图片不要 lazy load。loading="lazy" 对首屏下方的图片是对的,但首屏主图 lazy 会让它晚一步开始加载,反而更慢。首屏图片用 fetchpriority="high" 显式提高优先级。
第四,非首屏图片一律 lazy 加明确的宽高。width 和 height 属性让浏览器能提前算出布局尺寸,避免图片加载后页面跳动(CLS)。布局跳动是移动端首屏体验差的一个常见原因。
字体的坑比图片更多。 中文字体文件动辄几 MB,全量引入会让首屏增加好几秒。三种做法:
| 做法 | 做法说明 | 适合场景 |
|---|---|---|
| 用系统字体栈 | 直接写 -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif |
绝大多数中文站,最省事 |
| 子集化 | 用 fonttools 裁剪出页面实际用到的字 | 英文站或字数很少的场景 |
| 字体文件自托管 | 不走第三方 CDN,自己托管 | 第三方字体被墙或合规要求 |
首选是系统字体栈,零加载成本。 只有在设计强依赖特定字体时才值得付出子集化的成本。
常用命令:
# 用 fonttools 做子集化(pip install fonttools brotli)
pyftsubset NotoSansSC-Regular.woff2 \
--text="这里是你页面里实际出现的所有文字" \
--output-file=subset.woff2
数字参考。 一个典型页面:HTML 15 KB、CSS 20 KB、JS 60 KB、图片 1.2 MB。图片占总体积的 90% 以上。所以「优化图片」这一件事的效果,通常远大于「优化十行 PHP 代码」。
改完后的验收标准:首页传输体积(gzip 后)小于 150 KB,首屏图片小于 200 KB,非首屏图片全部带 loading="lazy",Lighthouse 的 CLS 分数小于 0.1。
十、怎么持续盯住速度不退化

优化不是一次性工程,是要长期维护的。内容在增加、插件在更新、依赖在升级,半年后你原来优化过的站点可能又慢回去了。
三个层次的持续监控。
第一层,每次部署后自动测一次。用 curl 脚本在发布流水线末尾跑一遍,把关键数字记下来:
#!/bin/bash
# deploy_speedcheck.sh —— 部署后自动跑一次
URL="https://your-site.com"
OUT=$(curl -s -o /dev/null -w "%{time_namelookup} %{time_connect} %{time_starttransfer} %{time_total} %{size_download}" "$URL")
DNS=$(echo $OUT | awk '{print $1}')
TTFB=$(echo $OUT | awk '{print $3}')
TOTAL=$(echo $OUT | awk '{print $4}')
SIZE=$(echo $OUT | awk '{print $5}')
echo "$(date -Iseconds) dns=$DNS ttfb=$TTFB total=$TOTAL size=$SIZE" >> /var/log/speed.log
# 超过阈值就告警
awk -v t="$TTFB" 'BEGIN{exit !(t>0.3)}' && \
curl -s -X POST "https://your-webhook-url" -d "msg=TTFB 退化到 ${TTFB}s"
awk -v s="$SIZE" 'BEGIN{exit !(s>153600)}' && \
curl -s -X POST "https://your-webhook-url" -d "msg=首页体积 ${SIZE} 字节,超过 150KB"
第二层,每周看一次趋势。每周跑一次同样的测量并存进同一个文件,画出 TTFB 的 30 天曲线。曲线比单点有用:单点是 0.35 秒不代表问题,曲线从 0.15 秒慢慢爬到 0.35 秒说明有一个东西在持续变慢,这才是需要追的。
第三层,每季度重测一次完整基线。用第二节那张表从头测一遍:DNS、TCP、TLS、TTFB、首屏、图片体积、缓存命中率。记录在文档里,和上季度对比。规格变了、访客分布变了、机房线路政策变了,这些都会让旧基线失效。
部署后的性能回归检查清单(每次上线都要过一遍):
| 检查项 | 通过标准 |
|---|---|
| 页面总体积 | 比上线前没有明显增加(增幅超过 20% 要说明原因) |
| 首屏图片大小 | 小于 200 KB |
| 新增依赖是否引入了额外请求 | 资源请求数增加不超过 5 个 |
| 数据库慢查询 | 部署后 10 分钟内慢查询数为 0 |
| 缓存头是否正确 | 静态资源带长缓存,HTML 不缓存 |
| TTFB | 不超过 300 毫秒 |
一个常见的退步来源值得单独提:图片不压缩就直接上传到服务器。很多内容站的图片来自用户投稿,原图动辄 5 MB。这类问题应该在上传环节解决(服务端自动转 WebP 并限制最大尺寸),而不是在事后优化。
按你的情况,下一步该看哪家
上面讲的是判断标准,但多数人最后还是想问一句「那我该买谁」。按你的实际情况对照下面几条,直接去看对应的机型页比继续比价更快:
- 只想搭个站、预算优先:先看价格最低那档够不够用,别为用不上的配置付钱。看 spinservers 当前机型与价格
- 做跨境或店群、需要多 IP:优先看 IP 类型与换 IP 政策,别只看配置。看 zenlayer 当前机型与价格
- 主要访客在国内、在意打开速度:先确认回程线路,再谈配置。看 goodlyclouds 当前机型与价格
价格与库存以商家页面当日显示为准,下单前建议先确认退款窗口与是否支持按月付费。
十一、常见问题

问题一:怎么确定慢在服务器还是网络? 用本机 curl 测一次服务端 TTFB,再用异地机器测一次端到端 TTFB。本机 30 毫秒、异地 800 毫秒,说明慢在网络;本机也是 800 毫秒,说明慢在服务器。
问题二:gzip 和 Brotli 选哪个? Brotli 压缩率更高(比 gzip 高 15% 到 20%),现代浏览器都支持。但服务端要预生成 .br 文件,Nginx 用 brotli_static on 配合。折中做法是静态文件预压缩成 .br,动态内容仍用 gzip。
问题三:为什么加了缓存还是慢? 大概率是 HTML 被缓存了或者反过来。更常见的情况是缓存规则配错导致 HTML 没缓存,每次都要回源查数据库。查响应头确认 Cache-Control 是否符合第三节的表。
问题四:数据库索引加多了会不会变慢? 会。索引不是越多越好。每个索引都会让写入变慢(插入时要维护索引树),并占用磁盘空间。经验值是单表索引不超过 5 个,且要用 EXPLAIN 确认每个索引真的被用上了,没用上的删掉。
问题五:升级 CPU 核数到底有没有用? 只在 CPU 确实是瓶颈时有用。判断方法看 top 里的 %CPU,如果长期低于 50% 但响应还是慢,瓶颈在 IO 或数据库,升级核数收益很小。
问题六:换到离访客更近的机房要多久? 迁移本身包括迁数据、装环境、切 DNS 和等 TTL 生效,通常 2 到 4 小时。但如果 DNS 切换后你还开着旧机器,流量会按 TTL 慢慢迁移,所以保留旧机器 48 小时再下线是稳妥做法。
问题七:CDN 缓存的内容不更新怎么办? 两种做法:给后台发布流程加一个刷新接口调用;或者给文章页设置 30 分钟到 1 小时的短缓存,用「发布后延迟更新」换掉复杂度。
问题八:开了 HTTP/2 反而更慢? 常见原因是服务端虽然支持 HTTP/2 但没有针对多路复用做优化,同时某些老旧客户端对 HTTP/2 的实现有兼容问题。用 curl -sI --http2 https://your-site.com 确认是否真的协商成功,并对比 HTTP/1.1 下的表现。
问题九:首屏时间和 FCP 有什么区别? FCP 是浏览器第一次绘制内容的时刻,通常比首屏可见时间早;首屏可见还要等大图、字体和关键 CSS。优化时两个都要看,只看 FCP 会漏掉图片加载慢的问题。
问题十:为什么我这台机器 CPU 很低但 steal time 很高? 这说明宿主机上的其他租户占用了 CPU,是共享核超售的表现。这种情况下加核无效,需要找提供独占资源或更低超售比的方案。
