You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

带服务端渲染(SSR)的React应用高负载时崩溃求助

看来你在高负载场景下遇到了SSR服务崩溃的问题,结合你用的react-boilerplate + React Router + Sagas + Express技术栈,我之前处理过类似的场景,咱们一步步拆解可能的问题和解决办法:

一、先排查资源耗尽与阻塞问题

SSR基于Express单线程模型,高负载下最容易因为资源占满或事件循环阻塞导致崩溃:

  • API调用的连接池耗尽:你提到每个SSR请求最多调用5个API端点,而Node默认的http.Agent最大连接数是5,如果每个请求都占满连接,后续请求会排队超时甚至直接崩溃。
    解决办法:给API请求工具(比如axios)配置自定义Agent,调高最大连接数:
    import http from 'http';
    const customHttpAgent = new http.Agent({ maxSockets: 20 }); // 根据服务器CPU/带宽调整
    const customHttpsAgent = new https.Agent({ maxSockets: 20 });
    
    // 在axios请求中使用
    axios.get('/api/user', { httpAgent: customHttpAgent, httpsAgent: customHttpsAgent });
    
  • 未做超时限制的慢API:如果某个API响应超时,会导致当前请求的Sagas任务一直挂起,占用资源且阻塞事件循环。
    解决办法:给所有API调用添加超时配置:
    axios.get('/api/data', { timeout: 5000 }); // 5秒超时,超时直接抛出错误
    
    同时在Sagas里捕获超时错误,避免任务无限挂起。
二、内存泄漏排查与修复

高负载下内存泄漏会快速耗尽进程内存,导致崩溃,SSR场景常见泄漏点:

  • 未清理的Sagas任务:如果每个SSR请求都创建新的Saga Middleware实例,或者请求结束后没有取消未完成的Sagas任务,残留的任务会持续占用内存。
    解决办法:在每个请求的生命周期内管理Sagas任务,请求结束后强制取消:
    // Express路由处理函数示例
    app.get('*', async (req, res) => {
      const sagaTask = sagaMiddleware.run(rootSaga);
      try {
        // 等待Sagas完成数据获取
        await sagaTask.toPromise();
        // 渲染React组件为HTML字符串
        const html = renderToString(<App />);
        res.send(html);
      } catch (err) {
        console.error('SSR数据获取失败:', err);
        res.status(500).send('服务器错误');
      } finally {
        // 无论成功失败,取消当前请求的Sagas任务
        sagaTask.cancel();
      }
    });
    
  • React组件的副作用泄漏:SSR渲染时,部分组件的useEffect/useLayoutEffect可能会在服务端意外执行(比如订阅、定时器),导致内存泄漏。
    解决办法:在组件中判断环境,仅在客户端执行副作用:
    useEffect(() => {
      if (typeof window === 'undefined') return; // 服务端直接跳过
      // 客户端才执行的副作用逻辑,比如定时器、事件监听
      const timer = setInterval(() => {}, 1000);
      return () => clearInterval(timer); // 清理副作用
    }, []);
    
三、进程崩溃的兜底方案

即使优化了代码,高负载下仍可能出现意外,需要兜底机制:

  • 用PM2做进程管理:PM2可以自动重启崩溃的进程,还能开启Cluster模式,把请求分散到多个Node进程,避免单进程崩溃导致整个服务挂掉。
    启动命令:
    pm2 start server.js -i max # 根据CPU核心数自动创建进程数
    
  • 全局错误捕获与日志:在Express和Node进程层面添加错误监听,记录详细日志方便排查:
    // Express全局错误中间件
    app.use((err, req, res, next) => {
      console.error(`SSR崩溃请求URL: ${req.url},错误信息:`, err.stack);
      res.status(500).send('服务器内部错误');
    });
    
    // Node进程层面捕获未处理异常
    process.on('uncaughtException', (err) => {
      console.error('未捕获异常导致崩溃:', err.stack);
      process.exit(1); // 退出进程,PM2会自动重启
    });
    
    process.on('unhandledRejection', (reason, promise) => {
      console.error('未处理Promise拒绝:', reason.stack);
      process.exit(1);
    });
    
四、SSR性能优化缓解负载压力

减少每个请求的处理时间,能有效降低高负载下的崩溃概率:

  • 缓存SSR结果:对于静态页面或更新频率低的页面,缓存渲染后的HTML字符串,避免重复渲染和API调用。比如用内存缓存或Redis:
    const cache = new Map(); // 简单内存缓存,生产环境建议用Redis
    
    app.get('*', async (req, res) => {
      const cacheKey = `ssr_${req.url}`;
      if (cache.has(cacheKey)) {
        return res.send(cache.get(cacheKey));
      }
      // 正常渲染流程...
      const html = renderToString(<App />);
      cache.set(cacheKey, html);
      setTimeout(() => cache.delete(cacheKey), 3600000); // 1小时后过期
      res.send(html);
    });
    
  • 并行发起API请求:确保Sagas中无依赖的API调用是并行执行的,减少数据获取的总时间。用Sagas的all effect:
    import { all, call } from 'redux-saga/effects';
    
    function* fetchPageData() {
      // 并行发起多个API请求
      const [userData, productData, articleData] = yield all([
        call(fetchUserApi),
        call(fetchProductApi),
        call(fetchArticleApi),
      ]);
      // 处理数据并更新state
    }
    

内容的提问来源于stack exchange,提问作者Yura Borunov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 04:00:39