تسرب ذاكرة جانب الخادم Nuxt باستخدام SSR (التقديم من جانب الخادم)

مرحبا يا هابر! هذه المقالة يجب قراءتها لأي شخص يعمل مع Vue SSR ، خاصة مع Nuxt . هذا عن تسرب للذاكرة عند استخدام المحاور .

خلفية


منذ نصف عام ، دخلت في مشروع مع مكدس VueJS + Nuxt ، وكانت خصوصيته هي أن خوادم Nod (Nuxt) كانت تموت باستمرار في حث المنتج وكانت جديدة ترتفع في مكانها. وفقًا للرسومات البيانية والسجلات ، كان من الواضح أن عملية معالجة العقدة وصلت إلى 100 ٪ وانخفضت مع وجود خطأ في نفاد الذاكرة. في هذا الوقت ، كانت هناك واحدة جديدة ترتفع إلى مكان العملية القتلى ، والتي استغرقت حوالي 30 ثانية ، وكان هذا كافياً للمستخدمين للحصول على خطأ 502. من الواضح أنه في مكان ما من الكود كان هناك تسرب للذاكرة يجب العثور عليه.

أريد أن أسلط الضوء على النقاط الرئيسية على الفور ، لأن قراءة جزء فقط من هذه المقالة قد لا يجيب على جميع أسئلتك:

  1. أهمية الموضوع
  2. اعتراضات Axios
  3. runInNewContext

1. ملاءمة الموضوع


أول شيء، وكثير منا أن تفعل، وبدأت تبحث عن حل على شبكة الإنترنت، بدت الاستفسارات بلدي شيئا من هذا القبيل: NodeJS تسرب الذاكرة ، تسرب الذاكرة nuxt ، تسرب الذاكرة nuxt في الإنتاج ، الخ

بالطبع ، لم يساعدني أي من العدد العشرين من stackoverflow ، لكنني تعلمت كيفية تتبع استخدام الذاكرة من خلال chrome: // Insp. لخيبة أملي ، وجدت أن 90 ٪ من الذاكرة التي لم يتم تنظيفها لسبب ما كانت بعض وظائف Vue مثل تجعل الأمر مكونًا ، وميزة العرض ، وغيرها.



1. اعتراضات 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;
  }
}

  • بعد بحث طويل عن التسريبات ، وجدت أنه إذا قمت بتعطيل جميع المحاور ، فإن الذاكرة تبدأ في التنظيف.
  • ما المشكلة؟
  • المعترض هو وكيل يتقبل كل الاستجابة أو الطلب ويسمح لك بتنفيذ أي رمز بإجابة (على سبيل المثال ، للتعامل مع الأخطاء) أو إضافة شيء قبل إرسال طلب عالمي لجميع الطلبات وفي مكان واحد ، أليس كذلك؟ فيما يلي مثال لكيفية ظهوره (ملف 'plugins / axios / interceptor.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 أو في جلب نقوم بالطلبات ، ويعمل المعترض كما ينبغي ، ثم يأتي المستخدم الثاني وننشئ اعتراضًا ثانيًا وستعمل الشفرة داخل الوظيفة مرتين!

لفهم كلماتي بشكل أفضل ، سوف أرسم عدادًا يتزايد مع كل استدعاء للوظيفة ويقرع الفهرس 5 مرات.



يمكننا ملاحظة حدوث 15 مكالمة ، وهذا هو 1 + 2 + 3 + 4 + 5 ، بالإضافة إلى ذلك ، خصصت بعض الوقت لإنشاء المعترض التالي للتأكد أن هناك تحديات لتلك التي تم إنشاؤها في وقت سابق.

من المدرسة ، نتذكر جميعًا صيغة التقدم الحسابي جيدًا ، ويمكن كتابة المجموع من 1 إلى n على شكل n * (n + 1) / 2. وتبين أنه عندما يأتي المستخدم رقم 1000 ، سيتم استدعاء وظيفتنا 1000 مرة ، وفي المجموع هذا بالفعل نصف مليون مكالمة ، لذلك إذا كان الحمل متوسطًا أو مرتفعًا ، فلا تفاجأ إذا تعطل الخادم الخاص بك.

حل للمشكلة


UPD. الحل # 0 - تصف التعليقات الحلول الجيدة لهذه المشكلة.

الحل # 1 - لا تستخدم مفاصل اعتراضية.

الحل رقم 2 - كل شيء بسيط للغاية ، تحتاج إلى تنظيف المعترض بنفسك ، مسترشدًا بوثائق المحاور

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


هذا خيار مضحك للغاية ، لأنه لا يمكن إعادة إنتاج هذا الخطأ محليًا ، ولكن يمكن إعادة إنتاجه بسهولة في البناء. اقرأ عنها هنا . عندما كنت أستعد لكتابة هذه المقالة ، قمت بإنشاء مشروع nuxt لقالب المبتدئين لإعادة إنتاج هذه المشكلة ، وكيف فوجئت أنه بالنسبة لكل مستخدم عادي - تم اعتراض المعترض مرة واحدة ، وليس n. الشيء هو ، عندما نكتب npm run dev - يكون هذا الخيار صحيحًا افتراضيًا ، وفي كل مرة نقوم فيها بوظائف من المكونات الإضافية على جانب الخادم ، يكون السياق جديدًا في كل مرة (من الواضح من اسم العلم) ، ويتم تلقائيًا في الإصدار خطأ للحصول على أداء أفضل في المنتج ، لذلك اضطررت إلى تعطيل هذا الخيار في nuxt.config.js


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

استنتاج


أما بالنسبة لي ، فهذه المشكلة خطيرة للغاية ، وتستحق الاهتمام بها بشكل خاص. ربما لا تتعلق هذه المشكلة بـ Vue ssr فحسب ، بل أيضًا بالآخرين ، وليس فقط المحاور ، ولكن أيضًا أي عملاء HTTP آخرين لديهم وكلاء مشابهين للمعترض. إذا كان لديك أسئلة، يمكنك أن تكتب لي في برقية alexander_proydenko . يمكن عرض جميع التعليمات البرمجية المستخدمة في المقالة على github هنا .

All Articles