一张图加载 124 秒:我给自己网站治了次「慢病」
今天周末休息,闲着没事就想瞅瞅自己的网站,正好想看看我图库里放的壁纸。
第一页打开的挺顺,翻到第二页,图开始一张一张地卡。我上了个厕所,再翻一张,还在等。
翻到《废墟之窗》的时候,它干脆不出来了。
我盯着那块白看了半天,越等越精神,干脆坐起来掐了个表。
124 秒。
什么概念?够我把牙刷完,或者等一碗泡面泡开一半,而它只是一张图。
查了下:9.92MB,PNG。再看看这个站的归属人——我自己。
行,自己的病,自己治。
一、先怀疑
然后就开始左思右想问题出在哪里,我第一反应是怀疑EO——前几天刚刚折腾过它,属于重点怀疑对象。
测了两轮,结果有点意外:缓存命中的时候,EdgeOne 其实挺快。冤枉人家了。这下懵了,那慢在哪?顺着请求链路往下摸,摸到了两个字:回源。
给不了解的朋友解释一句:CDN 不会存下每一张图,手上没有的时候,就得回我的服务器现取,这就叫回源。命中缓存,拿的是 CDN 自己家的存货;回源,拼的就是我服务器交货的速度。
二、稳得像散步
我测了下自己这条回源链路。
rclone 把对象存储挂载成本地盘,CDN 回源,就从这条路里拉文件。一张 9.92MB 的 PNG,124 秒,速度 80KB/s。
最气人的是它特别稳,不是一会儿快一会儿慢,就是一条直线——像楼下大爷散步,不快,但你永远不用怀疑他会停。
这下药费功夫了。
三、翻开代码,我笑了
接了杯水,开干!!
翻开图库代码,开始一场大工程。
然后我笑了。
网格里加载的,就是原图
不是缩略图,不是压缩版,是 9.92MB 的原图本人,一张一张排在列表里。
这就好比你去便利店买瓶水,老板把整个冰柜搬出来让你挑。
但怪不了代码。壁纸就是要原图才看得爽,压了还叫什么壁纸?"压原图"这条路走不通,剩下的路只有一条:在原图旁边另做一份小的,列表看小的,点开才上大的。
四、拯救吧那就
方案
原图一个字节不动,我在服务器本地磁盘上另生成一份:宽 1100px 的 WebP,质量 82,存在 /opt/cosbed/thumbs/。nginx 加一个 location 直接吐本地文件,rclone 不碰,对象存储也不碰:
location /qwthumb/ {
alias /opt/cosbed/thumbs/;
expires 30d;
}
生成脚本,Ai 几秒的事:
im = Image.open(src).convert('RGB')
w, h = im.size
if w > 1100:
im = im.resize((1100, int(h * 1100 / w)), Image.LANCZOS)
im.save(out, 'WEBP', quality=82, method=4)
整个方案的核心就一句话:把回源换掉。以前 CDN 没命中,回源要跑 rclone → 对象存储,一路倒好几趟车;现在读的是本地磁盘,没缓存也能秒回,缓存命中就是纯白送。
服务端弄完的时候,我心想:剩下前端换个图源,收个尾,搞定。
后来证明,我想多了。
五、那些还没"出生"的图
图库里的图有两拨,得分开收拾。
JS 动态渲染的那拨好办:创建 img 的时候把 src 换成缩略图地址,两分钟收工。
服务端直出的那拨,让我在电脑前多坐了一个小时。瀑布流是异步渲染的,我的改写脚本跑起来时,那些图还没"出生"。执行一遍改写函数,扑空;以为姿势不对,又执行一遍,还是扑空。
当然活都是Ai干的,我主要是坐着看着它别偷懒。
对着控制台发了会儿呆才反应过来:不是函数没生效,是压根没有图给它改。它跑得再快,也快不过图的出生。
没辙,请出 MutationObserver 守着:图一出现,立刻改写。看着新冒出来的图一张张被自动换掉,才有种"活了"的感觉。
顺手留了条后路:缩略图万一 404,自动退回原图;点开大图走的 Fancybox,加载的还是原图,画质无损。缩略图管"快",原图管"清",各干各的活。
六、三个坑
第一个坑,刚换完就踩上了。
第一批缩略图生成完,我刷新页面准备欣赏成果——一张都没命中。愣了半天,逐字对比文件名,才搞明白:模板里 img 的 src 属性跨了行,我抠 URL 的正则一碰到换行就收手,文件名短了一截。能对得上才怪。
按完整文件名重新生成,全部命中。
第二个坑,最让我难为情。
图库一共 112 张图,第 1-2 页在 cos.yuyt.cn,第 3-6 页在 pic.yuyt.cn。我有两个图床,我忘了。
改写逻辑只写了 cos,后面 72 张全漏了。上线那天我翻到第三页,熟悉的干等又回来了,我盯着屏幕想了半天:哪一步漏了?想来想去,才想起来去数页码。
自己搭的图库,自己没摸清。认了呗,那还咋整。
第三个坑,只能归给运气。
有张原图本身就是 .webp,按规则又补一个后缀,文件名成了 xxx.webp.webp。发现的时候我盯着看了半天,气笑了。好在生成和改写用的是同一套规则,歪打正着,对上了。
七、验收
验收的时候,我特意清了缓存,重新量了一次《废墟之窗》。
| 体积 | 冷加载(第一次看) | 热加载(第二次看) | |
|---|---|---|---|
| 治之前 | 9.92MB | 124.6s | 1.96s |
| 治之后 | 114KB | 0.19s | 0.02s |
从 124.6 秒到 0.19 秒,快了 600 多倍;体积从 9.92MB 到 114KB,小了 87 倍。110 多张缩略图加起来 10MB 出头,原库 231MB。
当晚我把定时任务从一天一次改成 5 分钟一轮,加了个 flock 防重叠,空跑一轮 2-6 秒。装备页结构一样,一并治了。
然后我躺回床上,把图库从第一页翻到最后一页。图一张一张蹦出来,没有一块空白让我等。
那种感觉很奇妙——像住了好几年的房子,电梯突然修好了。明明还是同一栋楼,上下楼却成了另一回事。
尾声
原图,一张没压。防盗刷的源站壕沟也还在——谁想搬走原图,请便,还是那个 80KB/s,稳得像大爷散步。
这次快的,只是「看」。
但「看」这件事,本来就是一个图库的全部。
最后,爽了!