第二轮优化记录:HTTP/3、WebP、MySQL 8,还有一堆小修补

可爱的站长 发布于 1 小时前 17 次阅读 1878 字 web


上一篇记录了怎么把首页从 2.6 秒救回 0.13 秒。这一轮做的不是「救火」,而是把上次留下的那些半成品和隐患挨个收拾掉:HTTP/3 一直没真正生效、分享卡片一张都没有、评论谁来都能直接发、数据库还停在 2023 年就停止支持的 5.7。

下面按「配置 → 内容 → 运维」三类记一遍,坑都写在里面了。

一、HTTP/3:配了不等于开了

宝塔里明明有 listen 443 quic;,也写了 Alt-Svc,但用 curl -I 一看,响应头里根本没有 alt-svc——浏览器压根不知道这台服务器会说 h3。

原因还是上次踩过的那个 nginx 规则:

location 里只要出现一个 add_header,server 层的所有 add_header 就全部失效。

上次把安全头从 server 层挪进 location 修 HSTS,这次 Alt-Svc 就被同一个坑顶掉了。把它也挪进 location 之后:

alt-svc: h3=":443"; ma=86400

顺带确认了 nginx 确实在听 UDP 443。唯一还需要手动确认的是云安全组有没有放行 UDP 443——不放行的话浏览器会静默退回 HTTP/2,页面照样能开,只是吃不到 h3 的好处。

二、几个不起眼但确实错的响应头

  • X-Frame-Options 发了两遍:主题的 header.php 发一次,nginx 又发一次,响应头变成 SAMEORIGIN, SAMEORIGIN。重复值在部分浏览器里会被判为无效,等于没防。用 proxy_hide_header 去掉上游那份,只留一个。
  • PHP 版本泄露:x-powered-by: PHP/8.3.31 明晃晃告诉扫描器你跑什么版本。除了 expose_php=Off,还在 nginx 侧 proxy_hide_header X-Powered-By,双保险。

三、分享卡片:以前分享出去是光秃秃一条链接

查了一下,og:title、og:image、twitter:card、canonical、结构化数据,一个都没有。也就是说文章发到 QQ 群或者 Telegram,就是一行没有配图的链接。

不想为这个装 Yoast(那玩意儿一套下来比主题还重),直接写了个三十来行的 mu-plugin:

wp-content/mu-plugins/seo-meta.php
  ├─ canonical / description
  ├─ og:title / og:description / og:image / og:type
  ├─ twitter:card(有图就 summary_large_image)
  └─ JSON-LD(文章用 BlogPosting,页面用 WebPage)

配图优先级:特色图 → 正文第一张图 → 默认分享卡。顺手把站点的 favicon 和这张 1200×630 的默认分享图也用 GD 现画了一张——绿色渐变加一条像素草地,和这个主题的衬线字还挺配。

四、图片:209 张转 WebP,省了 59%

上次把大图压到了 1920px,这次换格式:

项目 之前 之后
图片总量(209 张) 50.8 MB 20.9 MB
单图示例(768 宽) 49 KB 33 KB

做法不是把原图删掉换 WebP,而是并排生成 .jpg.webp,输出时包一层 <picture>:

<picture>
  <source type="image/webp" srcset="xxx.jpg.webp 768w, ...">
  <img src="xxx.jpg">
</picture>

支持 WebP 的浏览器走小图,不支持的自动回落原图,零风险。以后新上传的图也会自动生成 WebP 副本。

五、那些「一点点」的性能

  • 看板娘只在桌面加载:以前手机上也要把 pixi.js、cubism SDK、pio 一整套拉下来,光脚本就七个请求。现在手机 UA 直接不加载。
  • 外链图标本地化:主题的播放器控件、波形图、社交图标原本都从 s.nmxc.ltd 拉。镜像到本地后,首页外部引用从 11 个降到 1 个(剩下那个在源站已经 404,镜像不了,保留外链)。

六、内容体验:目录、相关文章、访问统计

  • 目录终于能用了:主题的目录功能其实一直开着,但它只在正文里出现
    标记时才生成。谁写文章会记得手打这个。现在改成自动判断:正文有 2 个以上 h2 就自动补上。
  • 相关文章:同分类取 3 篇,带缩略图,纯 CSS 没有 JS。
  • 访问统计:主题本来就有每篇的「N 次阅读」,这次补的是今日 / 累计,页脚一行小字。不存 IP、不引第三方脚本,同一访客半小时只算一次。

七、运维兜底:证书和备份

这两件事平时看不见,出事就是要命的那种。

  • 证书:之前那张 11 月 4 日就到期了,而续签任务一直跑在「文件验证」上。手动触发一次确认链路是通的,新证书到 2027-01-18。另外加了个每日检查,剩余不足 10 天直接发邮件。
  • 数据库备份:之前 /www/backup/database 里最新的一份停在 8 月 19 日——等于三个月没有可用的库备份。现在每天凌晨 3:15 自动 dump,保留 14 份,并且带体积校验(防止出现「备份成功但内容为空」这种更坑的情况)。

异地备份的脚本也写好了,但需要对象存储的密钥,这块还空着。本地盘满那类故障里,备份和站点是会一起死的。

八、MySQL 5.7 → 8.0

5.7 已经停止支持三年了。数据量不大(236 MB),但升级要做对:5.7 的数据目录不能直接给 8.0 用,只能 dump 出来、初始化新库、再导回去。最终从 5.7.44 升到了 8.0.46。

dump 全库 → 停 db → 打包旧数据目录 → compose 换 mysql:8.0
→ 旧目录改名保留 → 起新库 → 导入 → 重启站点 → 校验

停机约两分钟,迁移前后数据一条没变:文章 20、页面 8、评论 36、用户 19、附件 41。

踩到的坑:mysqladmin ping 会骗人

脚本在「等待数据库就绪」那一步用了 mysqladmin ping,结果它在认证失败时也返回 0——它把 "access denied" 也当成 "server alive"。于是脚本以为库好了,冲进去导数据,直接被拒:

ERROR 1045 (28000): Access denied for user 'root'@'localhost'

看清楚是认证问题而不是密码问题时,等它真正初始化完再导一次就好了。脚本现在改成「必须能用密码真跑一条 SELECT 才继续」。

现在的资源占用:db 容器 418 MB(5.7 时 302 MB),整机已用 941 MB、可用 929 MB。够用,但内存迟早要升到 2 G。

九、还剩什么没做

  1. 异地备份——脚本在,缺密钥。
  2. 内存扩容——控制台的事,不急但建议排上。
  3. 首页缩略图还没吃到 WebP——那些图是主题模板直接拼的 <img>,不走正文过滤器;要覆盖得改模板或者上 nginx 协商,风险大于收益,先放着。

最后

这一轮下来,站点在「看得见的性能」上其实没多少提升——首页 TTFB 还是 130~150 ms 那个量级。真正变好的是那些看不见的地方:分享出去有卡片了、评论不会被人刷了、证书和数据库备份都有兜底了、图片少了六成体积。

折腾服务器最有意思的地方大概就在这:最好的状态是没人注意到你干了什么。