问题出现
项目上线后,用户反馈页面加载很慢。我看了下监控,发现服务端渲染时间(SSR Time)平均 1.8 秒,最慢的页面要 3 秒多。这肯定有问题。
更奇怪的是,开发环境很快,生产环境就慢。一开始以为是服务器配置问题,但换了更好的服务器,还是慢。
排查过程
第一步:看日志
先看服务端日志,发现每个请求都要等很久。用 console.time 打点,发现 getServerSideProps 执行时间特别长。
第二步:分析数据获取
我们的 getServerSideProps 里做了好几件事:
- 调用用户 API 获取用户信息
- 调用内容 API 获取页面内容
- 调用推荐 API 获取推荐内容
- 调用统计 API 获取统计数据
这些 API 调用都是串行的,每个要等 200-500ms,加起来就 1-2 秒了。
第三步:发现重复请求
更严重的是,我们发现同一个 API 在同一个请求里被调用了多次。比如用户信息,在 header 组件里调一次,在 sidebar 组件里又调一次。
虽然 Next.js 有请求去重(request deduplication),但只对 fetch 有效。我们用的是 axios,就没这个优化。
优化方案
方案一:并行请求
把串行的 API 调用改成并行的:
// 之前 const user = await getUser(); const content = await getContent(); const recommendations = await getRecommendations(); // 之后 const [user, content, recommendations] = await Promise.all([ getUser(), getContent(), getRecommendations() ]);这一改,数据获取时间从 1.5 秒降到了 600ms,效果很明显。
方案二:请求去重
统一用 Next.js 的 fetch,利用它的请求去重功能。但有些第三方库只能用 axios,我们就自己实现了一个简单的去重机制。
在请求层面加了个缓存,同一个请求在 100ms 内的重复调用,直接返回缓存结果。
方案三:ISR + 增量更新
对于不经常变的内容,用 ISR(Incremental Static Regeneration)。比如文章详情页,设置 revalidate: 3600,每小时更新一次。
这样大部分请求直接返回静态页面,只有第一次或者更新时才需要服务端渲染。
方案四:数据库查询优化
有些慢是因为数据库查询慢。我们加了索引,优化了查询语句,还用了 Redis 做缓存。
对于热点数据,比如首页推荐,直接缓存 5 分钟,减少数据库压力。
遇到的坑
坑一:ISR 的缓存失效
ISR 用起来很爽,但缓存失效是个问题。我们有个需求是文章发布后立即更新,但 ISR 的缓存还在,用户看不到新内容。
后来用了 On-Demand Revalidation,文章发布时调用 API 手动清除缓存,才解决了这个问题。
坑二:并行请求的依赖
有些请求之间有依赖关系,不能完全并行。比如获取推荐内容,需要先知道用户信息。
我们做了个依赖图,先并行获取独立的数据,再串行获取有依赖的数据。
坑三:内存泄漏
优化过程中,发现服务端内存一直在涨。排查后发现是请求去重的缓存没有清理,时间长了就内存泄漏了。
加了 LRU 缓存,限制缓存大小,才解决了这个问题。
优化效果
优化完成后:
- SSR 时间从 1.8 秒降到 400ms
- TTFB 从 2 秒降到 500ms
- 服务器 CPU 使用率从 80% 降到 40%
- 数据库查询次数减少 60%
经验总结
SSR 的性能问题,大部分都是数据获取的问题:
- API 调用要并行,不要串行
- 重复请求要去重
- 能静态化的就静态化,用 ISR
- 数据库查询要优化,加缓存
另外,开发环境和生产环境的性能差异,往往是因为数据量不同。开发环境数据少,看不出问题。生产环境数据多,问题就暴露了。
如果重新开始
如果重新做一个 SSR 项目,我会:
- 一开始就考虑数据获取的性能
- 统一用 Next.js 的 fetch,利用请求去重
- 能静态化的页面就用静态生成
- 做好监控,及时发现问题
总结
SSR 不是银弹,性能问题要提前考虑。数据获取、缓存策略、数据库优化,这些都是关键。
如果你们项目也有 SSR 性能问题,建议先看数据获取,再看缓存,最后看数据库。大部分问题都能在这几个地方找到。
评论区