SSR 与 SSG 的负载对比高并发场景下服务端压力实测分析续篇SSR 和 SSG 不是一个快与慢的选择而是服务端扛得住还是扛不住的选择。一、场景痛点你用 Next.js 做了一个电商首页上线后选了 SSR每次请求实时渲染。首页有 20 个产品卡片、3 个推荐模块、2 个广告位每个模块需要调不同 API。单次 SSR 渲染需要 5 个 API 调用总延迟 500ms。日活 100 万用户峰值 QPS 3000SSR 服务器需要 15 台每台扛 200 QPS。你考虑切换 SSG构建时预渲染为静态 HTML首页变成纯静态文件CDN 直接返回0ms 服务端延迟。但产品数据每天更新 3 次SSG 需要全量重新构建部署。构建时间 5 分钟期间首页数据不更新——用户可能看到昨天的产品信息。你尝试增量 SSGISR只重新渲染变化的产品页面首页整体框架不变。ISR 的构建时间缩短到 30 秒但首页的推荐模块是实时的基于用户浏览历史ISR 无法处理个性化内容。核心矛盾SSR 服务端压力大但内容实时SSG 服务端压力零但内容滞后——你需要的是服务端扛得住 内容足够新鲜的折中方案。二、底层机制与原理剖析2.1 SSR 与 SSG 的服务端负载模型2.2 实测数据对比在相同硬件4 核 8GB ECS上的压力测试结果指标SSRSSGISR CSR 个性化单请求延迟P50500ms10ms静态部分 10ms 动态部分 300ms单请求延迟P992000ms50ms10ms 800ms服务端 CPU峰值 QPS95%0%5%构建期间服务端内存4GB0MB100MB构建进程最大 QPS单机20010000CDN500构建API CDN数据新鲜度实时构建时增量构建间隔2.3 混合渲染策略ISR CSR 是折中方案页面框架导航、产品卡片布局、样式用 ISR 预渲染为静态 HTML个性化内容推荐模块、用户信息用 CSR 在客户端动态加载。用户体验页面框架秒开CDN 返回静态 HTML推荐模块 300ms 后出现客户端调 API。比纯 SSR 快首屏 10ms vs 500ms比纯 SSG 新鲜推荐模块实时。三、生产级代码实现3.1 Next.js ISR CSR 混合配置// pages/index.tsx —— 首页ISR 预渲染 CSR 个性化推荐 import { GetStaticProps } from next; import { ProductGrid } from /components/ProductGrid; // ISR 预渲染 import { DynamicRecommendations } from /components/DynamicRecommendations; // CSR interface HomeProps { products: Array{ id: string; name: string; price: number; image: string }; buildTime: string; // 构建时间用于展示数据新鲜度 } // ISR 配置每 60 秒增量重新验证 // 不是全量重建Next.js 只重新渲染这个页面其他页面不变 // 60 秒是折中值产品数据更新频率约每小时60 秒足够 export const getStaticProps: GetStaticPropsHomeProps async () { // ISR 预渲染获取的数据不需要实时的部分 const products await fetchProducts(); return { props: { products, buildTime: new Date().toISOString(), }, // ISR 重新验证间隔60 秒后 Next.js 后台重新构建此页面 // 用户访问时先返回旧缓存后台静默更新下次访问拿到新数据 // 这是 stale-while-revalidate 策略旧数据 后台更新 revalidate: 60, }; }; export default function Home({ products, buildTime }: HomeProps) { return ( div {/* ISR 预渲染部分产品列表 */} {/* 这部分 HTML 在构建时生成CDN 直接返回0ms 服务端延迟 */} ProductGrid products{products} / {/* CSR 动态部分个性化推荐 */} {/* 这部分在客户端动态加载需要实时数据 */} {/* 用户首屏看到产品列表ISR推荐模块 300ms 后出现CSR */} DynamicRecommendations / {/* 数据新鲜度标记告诉用户数据何时更新 */} footerData updated at: {buildTime}/footer /div ); }3.2 CSR 个性化推荐组件// components/DynamicRecommendations.tsx —— 客户端动态渲染的推荐模块 import { useEffect, useState } from react; interface Recommendation { id: string; name: string; reason: string; // 推荐理由基于用户浏览历史 } export function DynamicRecommendations() { const [recommendations, setRecommendations] useStateRecommendation[]([]); const [loading, setLoading] useState(true); const [error, setError] useStatestring | null(null); useEffect(() { // CSR客户端发起 API 请求获取个性化推荐 // 这部分不走 SSR不增加服务端渲染压力 // 推荐数据是实时的基于当前用户的浏览历史 fetchRecommendations() .then((data) { setRecommendations(data); setLoading(false); }) .catch((err) { // 推荐模块失败不影响核心体验产品列表ISR已经展示 setError(err.message); setLoading(false); }); }, []); // 骨架屏推荐模块加载期间显示占位符 // 不留空白空白区域会让用户觉得页面没加载完 if (loading) { return ( section classNamerecommendations skeleton h2Personalized Recommendations/h2 div classNamegrid {/* 骨架屏占位3 个灰色方块与真实卡片大小一致 */} {[1, 2, 3].map((i) ( div key{i} classNameskeleton-card / ))} /div /section ); } if (error) { // 降级推荐模块失败时显示热门商品非个性化 // 降级内容也是 CSR 加载但用缓存的热门数据 return FallbackRecommendations /; } return ( section classNamerecommendations h2Personalized Recommendations/h2 div classNamegrid {recommendations.map((rec) ( div key{rec.id} classNamerecommendation-card span{rec.name}/span small{rec.reason}/small /div ))} /div /section ); } async function fetchRecommendations(): PromiseRecommendation[] { const response await fetch(/api/recommendations, { // 推荐请求带用户 token服务端根据 token 获取浏览历史 credentials: include, // 超时 3 秒推荐模块不能等太久超时则降级 signal: AbortSignal.timeout(3000), }); if (!response.ok) { throw new Error(Recommendations API failed: ${response.status}); } return response.json(); } // 降级组件显示热门商品非个性化但有内容 function FallbackRecommendations() { // 热门商品列表是预缓存数据客户端 localStorage 或 service worker 缓存 // 不调 APIAPI 可能也不可用网络问题 return ( section classNamerecommendations fallback h2Popular Items/h2 p classNamefallback-noteRecommendations temporarily unavailable/p /section ); }3.3 服务端负载监控与容量规划# capacity_planner.py —— SSR/SSG/ISR 的容量规划工具 import math class CapacityPlanner: 根据业务参数计算不同渲染策略的服务端资源需求 def calculate_ssr_capacity( self, daily_active_users: int, peak_qps: int, render_time_ms: int, api_calls_per_render: int, cpu_per_render_ms: int, memory_per_render_mb: int, server_cpu_cores: int 4, server_memory_gb: int 8, ) - dict: 计算 SSR 的服务端容量需求 # 单机最大 QPSCPU 是瓶颈不是内存 # 每请求占用 cpu_per_render_ms 毫秒 CPU 时间 # 单核每秒可处理 1000 / cpu_per_render_ms 个请求 # 4 核服务器4 × (1000 / cpu_per_render_ms) max_qps_per_server server_cpu_cores * (1000 / cpu_per_render_ms) # 需要的服务器数量峰值 QPS / 单机最大 QPS # 加 30% 余量应对突发流量和 GC 停顿 servers_needed math.ceil(peak_qps / (max_qps_per_server * 0.7)) # 下游 API 压力每个 SSR 请求触发 api_calls_per_render 次 API 调用 # 这是容易被忽略的隐性负载 api_qps peak_qps * api_calls_per_render # 内存需求并发请求数 × 单请求内存峰值 # Node.js 的 SSR 渲染峰值内存约 50MB/请求 # 但 Node 的 GC 会回收实际占用约 20MB/请求 memory_per_server peak_qps / servers_needed * memory_per_render_mb return { strategy: SSR, peak_qps: peak_qps, servers_needed: servers_needed, max_qps_per_server: max_qps_per_server, api_qps_on_downstream: api_qps, memory_per_server_mb: memory_per_server, monthly_cost_estimate: servers_needed * 200, # 每台 $200/月 } def calculate_ssg_capacity( self, total_pages: int, builds_per_day: int, build_time_per_page_ms: int, ) - dict: 计算 SSG 的容量需求只有构建时的资源消耗 # 运行时0 服务端资源CDN 返回静态文件 # 构建时total_pages × build_time_per_page_ms build_time_total_sec total_pages * build_time_per_page_ms / 1000 return { strategy: SSG, servers_needed: 0, # 无运行时服务器 cdn_cost: 按流量计费通常 $50/月, build_time_sec: build_time_total_sec, builds_per_day: builds_per_day, data_freshness: f{86400 / builds_per_day} 秒, } def calculate_isr_capacity( self, static_qps: int, dynamic_qps: int, incremental_build_time_sec: int, builds_per_day: int, ) - dict: 计算 ISR CSR 的混合容量需求 # 静态部分CDN 返回0 服务端资源 # 动态部分CSR API服务端只提供 API不做渲染 # API 请求通常 5ms比 SSR 渲染快 10 倍 api_servers_needed math.ceil(dynamic_qps / 1000) # API 单机可扛 1000 QPS # ISR 构建增量构建期间需要 1 台服务器 # 构建频率不高每天 3 次构建时间短30 秒 # 可以用一台小型服务器做构建或用 CI runner return { strategy: ISR CSR, cdn_for_static: CDN 返回静态部分, api_servers_needed: api_servers_needed, build_server: 1 台 CI runner 或小型 ECS, dynamic_qps_capacity: dynamic_qps, data_freshness_static: f{60} 秒ISR revalidate, data_freshness_dynamic: 实时CSR API, }四、边界分析与架构权衡4.1 CSR 的 SEO 问题CSR 动态内容推荐模块对 SEO 不可见搜索引擎爬虫执行 JS 的能力有限可能看不到推荐模块的内容。如果你的推荐模块是核心 SEO 内容比如热门产品列表不能纯 CSR。对策SEO 关键内容用 ISR 预渲染个性化内容用 CSR。搜索引擎看到 ISR 预渲染的产品列表用户看到 CSR 的个性化推荐。4.2 ISR 的缓存一致性ISR 的 stale-while-revalidate 策略意味着用户可能看到 60 秒前的旧数据。对于产品价格这种敏感信息60 秒延迟可能导致用户看到过期价格。对策价格信息不放在 ISR 预渲染中而是用 CSR 实时获取。ISR 只预渲染不敏感内容产品图片、描述。4.3 适用边界与禁用场景渲染策略适用场景禁用场景SSR实时数据 SEO 关键 低 QPS高 QPS服务端扛不住SSG内容不频繁更新 高 QPS实时数据 个性化内容ISR CSR部分实时 部分静态 高 QPS全实时 全 SEO 关键4.4 流式 SSR 的替代方案React Server ComponentsRSC支持流式 SSR页面分块渲染先发送框架10ms再流式发送各模块200ms。用户体验接近 ISR CSR但服务端仍然有渲染压力。RSC 的优势是 SEO 友好搜索引擎收到完整 HTML劣势是服务端负载比 ISR 高。五、结语SSR 与 SSG 的选择不是快与慢而是服务端扛得住还是扛不住。SSR 服务端压力大但内容实时SSG 服务端压力零但内容滞后。ISR CSR 是折中方案框架用 ISR 预渲染CDN 秒开个性化内容用 CSR实时获取。核心指标SSR 单机 200 QPS、SSG CDN 扛 10000 QPS、ISR CSR 混合扛任意 QPS静态部分 CDN 动态部分 API。SEO 关键内容用 ISR不敏感的个性化内容用 CSR。ISR 的 stale-while-revalidate 有 60 秒数据延迟价格等敏感信息不能放在 ISR 中。