使用SSR(服务器端渲染)的Nuxt服务器端内存泄漏

哈Ha!对于使用Vue SSR(尤其是Nuxt)的任何人来说,本文都是必读的这是关于使用axios时的内存泄漏

背景


半年前,我进入了一个带有VueJS + Nuxt堆栈的项目,其独特之处在于Nod服务器(Nuxt)一直死在产品中,而新的服务器正在取代它们。根据图形和日志,很明显,节点进程可操作性达到100%,并且由于内存不足错误而失败。这时,一个新的上升到了终止进程的位置,大约花费了30秒,这足以使用户收到502错误。显然,在代码中的某处需要发现内存泄漏。

我想立即强调要点,因为仅阅读本文的一部分可能无法回答您的所有问题:

  1. 主题的相关性
  2. Axios拦截器
  3. runInNewContext

1.主题的相关性


首先,我们许多人会做,我开始寻找互联网上的解决方案,我的查询看起来是这样的:的NodeJS内存泄漏nuxt内存泄漏在生产nuxt内存泄漏等。

当然,关于stackoverflow的二十个问题都没有帮助我,但是我学会了如何通过chrome跟踪内存使用情况://检查。令我失望的是,我发现由于某种原因而未清除的所有内存中有90%是某些Vue的函数,例如renderComponent,renderElement等。



1. Axios拦截器


我们迅速经历了折磨,寻找问题,并立即发现axios.interceptor应当为所有事情负责(对不起,Habr,发现有罪)。

立即预订创建axios的方法,如下所示:

import baseAxios from 'axios';

const axios = baseAxios.create({
  timeout: 10000,
});


export default axios;

并附加到应用程序上下文中,如下所示:

import axios from './index';

export default function(context) {

  if(!context.axios) {
    context.axios = axios;
  }
}

  • 长时间搜索泄漏后,我发现如果禁用所有axios.interceptors,则内存将开始被清理。
  • 有什么事?
  • 拦截器是一种代理,可以拦截所有响应或请求,并允许您执行带有答案的任何代码(例如,处理错误)或在对所有请求全局发送请求之前添加某些内容,并且方便吗?这是外观的示例(文件“ plugins / axios /拦截器.js”)

export default function({ axios }) {

  const interceptor = axios.interceptors.response.use( (response) => {
    return response;
  }, function (error) {
    //-   ,  
    return Promise.reject(error);
  });

}

从这里开始乐趣。我们在nuxt.config.js中添加了通过插件添加拦截器的功能

  plugins: [
    { src: '~/plugins/axios/bindContext' },
    { src: '~/plugins/axios/interceptor' },
  ]

然后,对于每个新请求,nuxt自动执行所有插件功能,然后执行nuxtServerInit,然后一切正常。也就是说,对于第一个用户,我们在服务器端创建一个拦截器,在asyncData或获取的组件中的某个位置发出请求,然后拦截器按预期工作,然后第二个用户进入并创建第二个拦截器,并且函数内的代码将工作2次!

为了更好地理解我的话,我将绘制一个计数器,该计数器在每次调用该函数时递增,并在索引上敲5次。



我们可以注意到发生了15次调用,这是1 + 2 + 3 + 4 + 5,我还花了一些时间来创建下一个拦截器以确保挑战是以前创造的。

从学校开始,我们都很好地记住了算术级数的公式,从1到n的总和可以写成n *(n +1)/2。事实证明,当第1000个用户进入时,我们的函数将被调用1000次,总共这已经是500万次呼叫,因此,如果负载是中等或很高,那么如果服务器崩溃,也不要感到惊讶。

解决问题


UPD。解决方案#0-注释描述了此问题的好的解决方案。

解决方案1-不要使用axios.interceptors。

解决方案2-一切都很简单,您需要在axios文档的指导下自行清理拦截器

export default function({ axios }) {

  const interceptor = axios.interceptors.response.use( (response) => {
    
    if(process.server) {
      axios.interceptors.response.eject(interceptor);
    }
    
    return response;
  }, function (error) {
    if(process.server) {
      axios.interceptors.response.eject(interceptor);
    }
    
    return Promise.reject(error);
  });

}

这仅需要在服务器端完成,因为否则,在客户端,成功完成任何第一个请求后,此拦截器将停止执行。在我们仍在服务器上并处理下一个用户的请求时,还有一个细微差别:在这种情况下,可能有几个(但几个)请求,然后弹出此拦截器,除第一个拦截器之外的所有请求都不会通过要独立考虑需要执行弹出操作的那一刻,最简单的方法是通过setTimeout,例如,在10秒后,然后我们可以假定在服务器端,我们将设法完成对当前用户的所有请求,并且所有这些请求将在此期间执行。拦截器仍将处于活动状态。

runInNewContext


这是一个非常有趣的选项,因此无法在本地复制该错误,但是很容易在构建中复制该错误。在这里阅读当我准备撰写本文时,我创建了starter-template nuxt项目来重现此问题,以及让我为每个普通用户感到惊讶的原因-拦截器执行了1次,而不是n次。问题是,当我们编写npm run dev时-默认情况下此选项为true,并且每次我们从服务器端的插件执行功能时,上下文都是新的(显然来自标志名),并且在构建时自动完成为在产品中获得更好的性能,返回false,因此我不得不在nuxt.config.js中禁用此选项


render: {
    bundleRenderer: {
      runInNewContext: false,
    },
  },

结论


对我来说,这个问题非常严重,值得特别注意。也许这个问题不仅涉及Vue ssr,还涉及其他问题,不仅涉及axios,还涉及具有类似于拦截器的代理的任何其他HTTP客户端。如果您有任何疑问,可以通过Telegram @alexander_proydenko给我写信本文中使用的所有代码都可以在github上查看

All Articles