前几天,我们的网站经历了一次惊心动魄的故障,Core Web Vitals指标突然暴跌,LCP从2秒涨到了8秒,CLS从0.05涨到了0.3,搜索引擎排名也受到了影响。整个排查和修复的过程,惊心动魄,也让我对Web Vitals有了更深入的理解。今天,就来复盘这次故障,从故障发现、排查、定位、修复,到事后总结,一步步讲清楚,希望能帮助大家避免类似的问题。
先说说背景。我们的网站,是一个内容型的网站,每天有几十万的访问量,SEO对我们来说非常重要。Google在2020年宣布,Core Web Vitals会成为搜索排名的因素,所以我们一直很重视Web Vitals的优化,经过一段时间的优化,我们的Core Web Vitals指标一直保持得不错,LCP(最大内容绘制)在2秒左右,FID(首次输入延迟)在50毫秒以内,CLS(累积布局偏移)在0.05左右,大部分页面都达到了Google的"良好"标准,搜索排名也很稳定。
但是,前几天,我们突然发现,网站的Core Web Vitals指标暴跌,LCP从2秒涨到了8秒,FID也涨到了200多毫秒,CLS从0.05涨到了0.3,大部分页面都变成了"需要改进"甚至"差"的级别。同时,我们发现,网站的搜索排名也开始下降,流量减少了20%多,情况非常紧急。
于是,我们紧急成立了排查小组,开始排查问题。整个排查和修复的过程,花了两天多的时间,惊心动魄,也踩了很多坑。今天,就来完整复盘这次故障。
一、故障发现:指标突然异常
故障的发现,其实有点偶然。那天早上,我像往常一样,打开Google Search Console,看看网站的搜索表现,突然发现,Core Web Vitals报告里,"良好"的页面比例,从90%多,突然降到了20%多,"需要改进"和"差"的页面比例,大幅上升。
我心里咯噔一下,知道出问题了。赶紧点开详细数据,看到:
- LCP(最大内容绘制):从平均2.1秒,涨到了平均8.3秒,很多页面甚至超过了10秒。
- FID(首次输入延迟):从平均40毫秒,涨到了平均230毫秒,很多页面超过了300毫秒。
- CLS(累积布局偏移):从平均0.05,涨到了平均0.32,很多页面超过了0.5。
三个指标,全部恶化,尤其是LCP和CLS,恶化得非常严重。
同时,我看了一下搜索流量,发现从三天前开始,搜索流量就开始下降,到今天,已经下降了20%多,而且还在继续下降。这说明,Core Web Vitals的恶化,已经影响到了搜索排名。
情况非常紧急,我马上通知了团队,紧急成立了排查小组,开始排查问题。
二、初步排查:排除常见原因
排查的第一步,是先排除一些常见的原因,看看是不是这些常见问题导致的。
1. 是不是服务器出问题了?
首先,我们检查了服务器的状态,包括CPU、内存、磁盘、网络、响应时间等。结果发现,服务器的状态一切正常,CPU使用率在30%左右,内存使用率在50%左右,磁盘IO正常,网络带宽也正常,服务器的响应时间,也在正常范围内,200毫秒左右。
而且,我们用Postman直接请求服务器的接口,响应速度也很快,没有问题。所以,服务器出问题的可能性,基本排除了。
2. 是不是网络出问题了?
然后,我们检查了网络,包括CDN、DNS、带宽等。我们的网站用了CDN,检查了CDN的状态,节点都正常,缓存命中率也正常,在90%以上。DNS解析也正常,没有解析错误。带宽也够用,没有打满。
我们还在不同的地区、不同的网络环境下(电信、联通、移动、4G、5G、WiFi),测试了网站的访问速度,发现都很慢,不是某个地区或者某个网络的问题。所以,网络出问题的可能性,也基本排除了。
3. 是不是最近有代码发布?
然后,我们查了最近的代码发布记录,发现一周前,我们发布了一个版本,主要是改版了首页和文章详情页的UI,同时优化了一些代码。发布之后,我们做了简单的测试,功能都正常,当时也没发现性能问题。
我们怀疑,是不是这次发布引入的问题?但发布是一周前,而指标恶化是三天前才开始的,中间隔了四天,时间上对不上。不过,也有可能是发布之后,慢慢积累的问题,或者是某个条件触发的。所以,这个版本,还是有嫌疑,我们先记下来,作为重点排查对象。
4. 是不是第三方脚本的问题?
然后,我们检查了网站上的第三方脚本,比如广告、统计、社交分享、评论、客服等。第三方脚本,经常是导致Web Vitals恶化的原因,因为第三方脚本加载慢、执行慢,还可能阻塞页面渲染。
我们检查了一下,发现网站上的第三方脚本,和之前一样,没有新增,也没有修改。不过,第三方脚本的服务端,可能出问题了,比如某个第三方脚本的服务器变慢了,或者被墙了,导致加载慢。
我们用Chrome DevTools的Network面板,看了一下页面加载的过程,发现有一个广告脚本,加载时间特别长,要5秒多,而且是同步加载的,阻塞了页面的渲染。我们怀疑,是不是这个广告脚本的问题?
但是,这个广告脚本,我们一直都在用,之前一直没问题,为什么突然变慢了?我们联系了广告平台,对方说他们的服务正常,没有问题。而且,我们测试了一下,即使去掉这个广告脚本,页面的LCP还是很慢,有6秒多,说明广告脚本只是其中一个原因,不是根本原因。
所以,第三方脚本的问题,虽然有影响,但不是根本原因,还需要继续排查。
三、深入排查:用工具分析
初步排查,没有找到根本原因,我们开始用更专业的工具,深入分析。
1. 用Lighthouse分析
首先,我们用Lighthouse,对首页和几个典型的文章详情页,做了全面的性能分析。Lighthouse的报告显示:
- 性能得分:只有30多分(满分100),之前是80多分。
- First Contentful Paint(首次内容绘制):3.5秒,之前是1秒左右。
- Largest Contentful Paint(最大内容绘制):8.2秒,之前是2秒左右。
- Time to Interactive(可交互时间):10秒,之前是3秒左右。
- Cumulative Layout Shift(累积布局偏移):0.35,之前是0.05左右。
Lighthouse还给出了一些优化建议:
- 消除阻塞渲染的资源:有几个CSS和JS文件,阻塞了渲染。
- 减少未使用的JavaScript:有很大的JS文件,里面很多代码没用到。
- 正确设置图片的尺寸:很多图片没有设置宽高,导致布局偏移。
- 延迟加载离屏图片:很多首屏外的图片,没有懒加载。
- 减少服务器响应时间:服务器响应时间有点长。
这些建议,看起来都很有道理,但我们之前都做过这些优化,为什么现在又出现了?我们怀疑,是一周前的那个版本,把之前的优化给破坏了。
2. 用Chrome DevTools的Performance面板分析
然后,我们用Chrome DevTools的Performance面板,录制了页面加载的全过程,详细分析每一步的耗时。
分析结果显示:
- HTML加载:很快,200毫秒。
- CSS加载:有一个CSS文件,加载了2秒,而且是阻塞渲染的。
- JS加载和执行:有一个很大的JS文件,3MB多,加载了3秒,执行了2秒,而且是同步执行的,阻塞了主线程。
- 图片加载:首屏的主图,加载了3秒,而且没有设置宽高,加载完成后,导致了很大的布局偏移。
- 第三方脚本:广告脚本,加载了5秒,阻塞了渲染。
看到这个结果,我们很惊讶,因为我们之前做了很多优化,比如CSS和JS的压缩、合并、懒加载,图片的懒加载和尺寸设置等,为什么现在都失效了?
我们赶紧去看了一下一周前发布的那个版本的代码,这才发现了问题。
四、根本原因:一次失败的重构
原来,一周前的那个版本,我们对前端代码做了一次重构,把原来的多页应用,改成了单页应用(SPA),用了一个新的前端框架。但是,这次重构,做得很仓促,很多优化都没有跟上,导致了严重的性能问题。
具体来说,有以下几个问题:
1. JS包太大,没有做代码分割
新的单页应用,把所有的页面代码,都打包到了一个JS文件里,这个JS文件有3MB多(压缩后也有1MB多),而且是同步加载的,在页面的head里,阻塞了页面的渲染。
用户打开页面,首先要下载这个3MB的JS文件,然后解析执行,执行完了,才能渲染页面内容。这就导致了LCP(最大内容绘制)非常慢,因为页面内容是靠JS渲染出来的,JS没加载完,页面就没有内容。
而之前的多页应用,每个页面的JS都很小,只有几十KB,而且是异步加载的,不阻塞渲染,所以LCP很快。
2. 服务端渲染(SSR)没有做好
改成单页应用之后,我们本来计划做服务端渲染(SSR),让服务器直接返回渲染好的HTML,这样首屏加载快,也有利于SEO。但是,因为时间紧,任务重,SSR没有做好,只是做了一个简单的壳,内容还是靠客户端JS渲染的。
这就导致,服务器返回的HTML,几乎是空的,只有一个div容器,内容都要等JS加载完,才能渲染出来。这不仅导致LCP慢,还导致FID(首次输入延迟)慢,因为JS在执行的时候,阻塞了主线程,用户的点击、输入等操作,都要等JS执行完才能响应。
而且,这对SEO也很不友好,因为搜索引擎爬虫,虽然能执行JS,但执行JS需要时间,而且很多爬虫执行JS的能力有限,可能抓不到完整的内容。这也是搜索排名下降的原因之一。
3. 图片没有设置宽高,也没有懒加载
重构之后,图片组件也重写了,但是,新的图片组件,没有设置图片的宽高属性,也没有做懒加载。
没有设置宽高,就导致图片加载之前,浏览器不知道图片要占多大空间,图片加载完成后,会把后面的内容挤下去,导致布局偏移,这就是CLS(累积布局偏移)飙升的原因。
没有懒加载,就导致页面上所有的图片,不管是首屏的还是首屏外的,都一起加载,这就导致网络拥塞,首屏的主图,反而加载慢了,进一步拖慢了LCP。
而之前的多页应用,图片都设置了宽高,也做了懒加载,所以CLS很小,LCP也快。
4. CSS没有优化,阻塞渲染
重构之后,CSS也重写了,用了一个新的CSS框架,但是,CSS没有做优化,所有的CSS都打包到了一个文件里,而且是同步加载的,在head里,阻塞了渲染。
而且,这个CSS文件很大,有500多KB,里面很多样式,当前页面根本用不到,但是也一起加载了,浪费了带宽,也拖慢了渲染速度。
5. 第三方脚本没有优化
重构之后,第三方脚本的加载方式也变了,之前是异步加载的,不阻塞渲染,但是重构之后,变成了同步加载,而且放在了head里,阻塞了渲染。
尤其是那个广告脚本,加载要5秒多,同步加载的话,页面要等它加载完,才能继续渲染,这就严重拖慢了首屏速度。
五、紧急修复:先恢复指标
找到根本原因之后,我们开始紧急修复。因为情况紧急,搜索流量还在下降,我们决定先做紧急修复,把指标恢复到之前的水平,然后再做长期的优化。
紧急修复的措施:
1. 回滚到之前的多页应用版本
最直接、最快的修复方式,就是回滚到之前的多页应用版本,因为之前的版本,性能是好的,指标是正常的。
但是,回滚也有问题,因为新的版本,已经上线一周了,有一些新功能,回滚之后,新功能就没了,而且,数据库也有一些变化,回滚可能会有兼容性问题。
我们讨论了一下,决定先回滚首页和文章详情页这两个流量最大的页面,其他页面暂时用新版本,这样,既能快速恢复主要的性能指标,又能保留大部分新功能。
说干就干,我们花了几个小时,把首页和文章详情页,回滚到了之前的多页应用版本,然后部署上线。
上线之后,我们马上测试,发现这两个页面的性能,马上就恢复了,LCP回到了2秒左右,CLS回到了0.05左右,FID也回到了50毫秒以内。
然后,我们观察了一天,发现Search Console里的Core Web Vitals指标,也开始慢慢恢复,搜索流量也不再下降了,开始企稳回升。这说明,回滚的措施,是有效的。
2. 优化第三方脚本
虽然回滚了主要页面,但其他页面还是用的新版本,我们也对新版本做了一些紧急优化,首先是优化第三方脚本。
我们把所有的第三方脚本,都改成了异步加载(async或者defer),并且移到了页面的底部,不阻塞首屏渲染。
对于广告脚本,我们改成了异步加载,并且延迟加载,等页面首屏渲染完成之后,再加载广告脚本。这样,广告脚本就不会阻塞首屏渲染了。
优化之后,新版本页面的首屏速度,也提升了不少。
3. 给图片设置宽高,加上懒加载
然后,我们给新版本的图片组件,加上了宽高属性,根据图片的原始尺寸,设置width和height,这样,浏览器在图片加载之前,就知道图片要占多大空间,不会导致布局偏移了。
同时,我们给图片加上了懒加载,首屏外的图片,不立即加载,等滚动到可视区域的时候,再加载。这样,就减少了首屏的网络请求,让首屏的主图,能更快加载。
优化之后,新版本页面的CLS,也降到了0.1左右,LCP也提升了不少。
4. 拆分JS包,做代码分割
然后,我们对新版本的JS包,做了代码分割,把一个大的JS包,拆分成多个小的包,每个页面对应一个小的JS包,并且,把公共的依赖,单独打包,利用浏览器缓存。
同时,我们把JS改成了异步加载,不阻塞首屏渲染。并且,做了路由级别的代码分割,访问哪个页面,就加载哪个页面的JS,不需要加载所有页面的JS。
优化之后,首屏需要加载的JS,从3MB降到了200KB左右,加载和执行的时间,都大大缩短了。
5. 优化CSS
最后,我们对CSS也做了优化,把一个大的CSS文件,拆分成多个小的CSS文件,每个页面对应一个CSS文件,并且,只加载当前页面需要的CSS,不需要加载所有的CSS。
同时,我们对CSS做了压缩,去掉了无用的CSS,CSS文件的体积,从500KB降到了50KB左右。
而且,我们把关键的CSS(首屏需要的CSS),内联到了HTML里,这样,首屏渲染不需要等CSS文件加载,渲染速度更快。
经过这些紧急修复,新版本页面的性能,也有了很大的提升,LCP降到了3秒左右,CLS降到了0.1左右,虽然还不如老版本,但已经达到了"需要改进"的级别,不再是"差"了。
六、长期优化:彻底解决问题
紧急修复之后,指标恢复了,流量也回升了,我们松了一口气。但我们知道,这只是临时的修复,还有很多问题,需要长期优化,彻底解决。
长期优化的计划:
1. 完善服务端渲染(SSR)
单页应用的最大问题,就是首屏渲染慢,SEO不友好。解决这个问题的根本方法,就是做好服务端渲染(SSR),让服务器直接返回渲染好的HTML,首屏内容直接在HTML里,不需要等JS加载完再渲染。
我们花了两周的时间,重新做了服务端渲染,用Next.js框架,把所有页面都改成了服务端渲染。服务端渲染之后,服务器直接返回完整的HTML,首屏内容直接在HTML里,LCP大大提升,而且,对SEO也很友好,搜索引擎爬虫,直接就能抓到完整的内容。
做好SSR之后,新版本页面的LCP,降到了1.5秒左右,比老版本还快。
2. 静态生成(SSG)和增量静态再生(ISR)
对于内容不经常变化的页面,比如文章详情页,我们用了静态生成(SSG),在构建的时候,就把页面生成静态HTML,部署到CDN上,用户访问的时候,直接从CDN返回静态HTML,速度非常快,而且服务器压力小。
对于内容会更新的页面,我们用了增量静态再生(ISR),页面在构建的时候生成静态HTML,之后,每隔一段时间,自动重新生成页面,更新内容。这样,既保证了访问速度,又保证了内容的新鲜度。
用了SSG和ISR之后,文章详情页的LCP,降到了1秒以内,非常快。
3. 图片优化
图片,是影响Web Vitals的重要因素,尤其是LCP。我们对图片做了全面的优化:
- 响应式图片:用srcset和sizes,根据不同的屏幕尺寸,加载不同大小的图片,避免在小屏幕上加载大图,浪费带宽。
- 现代图片格式:用WebP格式,比JPEG和PNG小很多,而且质量差不多。对于不支持WebP的浏览器,降级到JPEG/PNG。
- 图片压缩:对图片进行压缩,在保证质量的前提下,尽量减小文件体积。
- 懒加载:首屏外的图片,懒加载,等滚动到可视区域再加载。
- 预加载首屏主图:对于首屏的主图(LCP元素),用link rel="preload"预加载,让浏览器优先加载这张图片,提升LCP。
- 设置宽高:所有图片都设置宽高,避免布局偏移。
经过这些图片优化,图片的体积减小了60%多,加载速度大大提升,LCP也进一步提升。
4. 字体优化
字体,也是影响首屏渲染和CLS的因素。我们对字体也做了优化:
- 字体子集化:只保留页面用到的字符,减小字体文件的体积。
- 字体预加载:用link rel="preload"预加载字体,让字体更快加载。
- font-display: swap:设置font-display为swap,字体没加载完的时候,先用系统字体显示,字体加载完再替换,避免文字闪烁,也减少CLS。
经过字体优化,字体加载速度提升了,也减少了布局偏移。
5. 建立性能监控和告警
最后,我们建立了完善的性能监控和告警机制,避免类似的问题再次发生。
- 真实用户监控(RUM):用真实用户监控工具,收集真实用户的Web Vitals数据,实时监控LCP、FID、CLS等指标。
- 合成监控:定期用Lighthouse等工具,对关键页面做性能测试,监控性能变化。
- 告警:当性能指标恶化到一定程度时,自动告警,通知相关人员,及时处理。
- 发布前性能测试:每次发布新版本之前,都要做性能测试,确保性能指标不下降,才能发布。
建立了这些机制之后,我们就能及时发现性能问题,在问题影响到用户和搜索排名之前,就解决掉。
七、故障复盘和经验总结
这次故障,从发现到彻底解决,花了两周多的时间,虽然最终解决了,但也给我们造成了不小的损失,搜索流量下降了20%多,花了很长时间才恢复。这次故障,也给我们带来了很多经验和教训。
1. 经验教训
- 重构不能仓促,性能不能丢:这次故障的根本原因,就是一次仓促的重构,把之前的性能优化都丢了。重构的时候,不能只看功能,还要看性能,不能为了重构而重构,把之前的优化都丢掉了。
- 性能要有监控,不能靠感觉:之前,我们只是偶尔看一下Search Console里的Web Vitals,没有实时监控,导致故障发生了三天,我们才发现,错过了最佳处理时间。如果有实时的性能监控和告警,就能在第一时间发现问题,及时处理,损失会小很多。
- 发布前要做性能测试:新版本发布之前,我们只做了功能测试,没有做性能测试,导致性能严重下降的版本,直接发布上线了。如果发布前做了性能测试,就能发现性能问题,不会把有问题的版本发布上线。
- SEO相关的指标,要特别重视:对于依赖SEO的网站,Core Web Vitals、页面内容、可抓取性等,都是非常重要的,任何改动,都要考虑对这些指标的影响,不能掉以轻心。
- 紧急情况下,回滚是最快的修复方式:这次故障,我们能快速恢复指标,最关键的就是回滚了老版本。遇到紧急故障,不要想着一下子就修复好,先回滚到正常的版本,恢复服务,然后再慢慢排查和修复,这是最快、最稳妥的方式。
2. 最佳实践
经过这次故障,我们总结了一些Web Vitals优化的最佳实践:
- LCP优化:
- 确保LCP元素(通常是首屏主图或者大标题)加载快,用preload预加载。 - 优化服务器响应时间,用CDN、缓存等。 - 优化CSS和JS,不要阻塞渲染。 - 优化图片,用现代格式,压缩,响应式。 - 做好服务端渲染或者静态生成,让首屏内容直接在HTML里。
- FID优化:
- 减少JS的体积和执行时间,拆分JS包,代码分割。 - 延迟加载不需要的JS,异步加载第三方脚本。 - 把长任务拆分成小任务,避免阻塞主线程。 - 用Web Worker处理耗时的计算,不阻塞主线程。
- CLS优化:
- 给所有图片和视频设置宽高属性。 - 广告、嵌入内容等,预留空间,避免加载后挤压内容。 - 字体用font-display: swap,避免字体加载导致的布局偏移。 - 不要在现有内容上方插入新内容,除非是用户操作触发的。
3. 后续改进
这次故障之后,我们也做了很多后续改进:
- 建立了完善的性能监控和告警体系,实时监控Web Vitals。
- 建立了发布前的性能测试流程,性能不达标,不能发布。
- 建立了代码评审机制,任何影响性能的改动,都要经过评审。
- 定期做性能审计,持续优化性能。
- 团队做了Web Vitals的培训,每个人都了解性能优化的重要性和方法。
八、写在最后
这次Web Vitals故障,是一次惊心动魄的经历,也是一次宝贵的学习机会。它让我们深刻认识到,性能不是小事,尤其是对于依赖SEO的网站,Core Web Vitals直接影响搜索排名和流量,任何时候都不能掉以轻心。
故障的发生,虽然给我们造成了损失,但也让我们发现了自己的问题,比如性能监控缺失、发布流程不完善、团队性能意识不足等。通过这次故障,我们完善了监控、流程和规范,团队的性能意识也大大提升,从长远来看,这是一件好事。
今天,把这次故障的完整复盘,分享出来,从故障发现、初步排查、深入排查、根本原因、紧急修复、长期优化,到经验总结,一步步讲清楚,希望能帮助大家避免类似的问题。
记住,性能优化不是一次性的工作,而是一个持续的过程。要建立完善的监控和告警,要在发布前做性能测试,要在团队中培养性能意识,持续优化,才能让网站一直保持良好的性能,避免类似的故障再次发生。
如果有什么问题或者不同的看法,欢迎在评论区交流。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录