Antipattern مشغول الجبهة الأمامية

يُمكن أن يؤدي تنفيذ عمل غير متزامن على عدد كبير من مؤشرات الترابط الخلفية إلى التقليل من المهام الأمامية المتزامنة الأخرى للموارد، وتقليل أوقات الاستجابة إلى مستويات غير مقبولة.

وصف المشكلة

يُمكن أن تزيد المهام كثيفة الموارد من أوقات الاستجابة لطلبات المُستخدم وتتسبب في زمن انتقال عال. تتمثل إحدى طرق تحسين أوقات الاستجابة في إلغاء تحميل مهمة كثيفة الموارد إلى مؤشر ترابط مُنفصل. يتيح هذا الأسلوب للتطبيق الاستمرار فـي الاستجابة أثناء حدوث المعالجة في الخلفية. ومع ذلك، لا تزال المهام التي تعمل عـلى مؤشر ترابط الخلفية تستهلك الموارد. إذا كان هناك عدد كبير جدا منهم، يُمكنهم تقليل مؤشرات الترابط التي تعالج الطلبات.

إشعار

يُمكن أن يشمل مصطلح المورد العديد من الأشياء، مثل استخدام وحدة المعالجة المركزية، وإشغال الذاكرة، والإدخار/الإخراج للشبكة أو القرص.

تحدث هذه المشكلة عادة عند تطوير تطبيق كقطعة متجانسة من التعليمات البرمجية، مـع دمج كل منطق العمل في طبقة واحدة مشتركة مع طبقة العرض التقديمي.

إليك الرمز الزائف الذي يوضح المشكلة.

public class WorkInFrontEndController : ApiController
{
    [HttpPost]
    [Route("api/workinfrontend")]
    public HttpResponseMessage Post()
    {
        new Thread(() =>
        {
            //Simulate processing
            Thread.SpinWait(Int32.MaxValue / 100);
        }).Start();

        return Request.CreateResponse(HttpStatusCode.Accepted);
    }
}

public class UserProfileController : ApiController
{
    [HttpGet]
    [Route("api/userprofile/{id}")]
    public UserProfile Get(int id)
    {
        //Simulate processing
        return new UserProfile() { FirstName = "Alton", LastName = "Hudgens" };
    }
}
  • يقوم Postالأسلوب في WorkInFrontEnd وحـدة التحكم بتنفيذ عملية HTTP POST. تحاكي هذه العملية مُهمة طويلة الأمد ومكثفة لوحدة المعالجة المركزية. يتم تنفيذ العمل على مؤشر ترابط مُنفصل، في محاولة لتمكين عملية النشر لإكمالها بسرعة.

  • Get يقوم الأسلوب في UserProfile وحـدة التحكم بتنفيذ عملية HTTP POST. هذه الطريقة أقل كثافة فـي وحدة المعالجة المركزية.

الشاغل الأساسي هو متطلبات الموارد للأسلوب Post. على الرغم من أنه يضع العمل على مؤشر ترابط الخلفية، إلا أن العمل لا يزال يستهلك موارد كبيرة مـن وحدة المعالجة المركزية. تتم مشاركة هذه الموارد مع العمليات الأخرى التي يتم تنفيذها من قبل مُستخدمين متزامنين آخرين. إذا أرسل عدد معتدل من المستخدمين هذا الطلب في نفس الوقت، فمن المُحتمل أن يعاني الأداء العام، مـما يؤدي إلى إبطاء جميع العمليات. قد يواجه المستخدمون زمن انتقال كبير فـي Get الأسلوب، على سبيل المثال.

كيفية حل المشكلة

انقل العمليات التي تستهلك موارد كبيرة إلى نهاية خلفية منفصلة.

باستخدام هذا الأسلوب، تضع الواجهة الأمامية المهام كثيفة الموارد فـي قائمة انتظار الرسائل. تلتقط النهاية الخلفية المهام للمعالجة غـير المتزامنة. تعمل قائمة الانتظار أيضًا كمستوي تحميل، طلبات التخزين المؤقت للنهاية الخلفية. إذا أصبح طول قائمة الانتظار طويلًا جدًا، يمكنك تكوين التحجيم التلقائي لتوسيع الواجهة الخلفية.

فيما يلي إصدار تمت مراجعته مـن التعليمات البرمجية السابقة. في هذا الإصدار، Post يضع الأسلوب رسالة عـلى قائمة انتظار ناقل خدمة Microsoft Azure.

public class WorkInBackgroundController : ApiController
{
    private static readonly QueueClient QueueClient;
    private static readonly string QueueName;
    private static readonly ServiceBusQueueHandler ServiceBusQueueHandler;

    public WorkInBackgroundController()
    {
        string serviceBusNamespace = ...;
        QueueName = ...;
        ServiceBusQueueHandler = new ServiceBusQueueHandler(serviceBusNamespace);
        QueueClient = ServiceBusQueueHandler.GetQueueClientAsync(QueueName).Result;
    }

    [HttpPost]
    [Route("api/workinbackground")]
    public async Task<long> Post()
    {
        return await ServiceBusQueueHandler.AddWorkLoadToQueueAsync(QueueClient, QueueName, 0);
    }
}

تسحب الواجهة الخلفية الرسائل مـن قائمة انتظار ناقل خدمة Microsoft Azure وتعالجها.

public async Task RunAsync(CancellationToken cancellationToken)
{
    this._queueClient.OnMessageAsync(
        // This lambda is invoked for each message received.
        async (receivedMessage) =>
        {
            try
            {
                // Simulate processing of message
                Thread.SpinWait(Int32.MaxValue / 1000);

                await receivedMessage.CompleteAsync();
            }
            catch
            {
                receivedMessage.Abandon();
            }
        });
}

الاعتبارات

  • يضيف هذا الأسلوب بعض التعقيد الإضافي إلـى التطبيق. يجب عليك التعامل مع الانتظار وإلغاء الانتظار بأمان لتجنب فقدان الطلبات فـي حالة حدوث فشل.
  • يأخذ التطبيق تبعية عـلى خدمة إضافية لقائمة انتظار الرسائل.
  • يجب أن تكون بيئة المعالجة قابلة للتطوير بشكل كاف للتعامل مع حمل العمل المتوقع وتلبية أهداف مُعدل النقل المطلوبة.
  • في حين أن هذا النهج يجب أن يحسن الاستجابة الشاملة، قد تستغرق المهام التي يتم نقلها إلى النهاية الخلفية وقتًا أطول لإكمالها.

كيف تكتشف المشكلة

تتضمن أعراض الواجهة الأمامية المزدحمة زمن انتقال عال عند تنفيذ مـهام كثيفة الموارد. من المُحتمل أن يقوم المستخدمون النهائيون بالإبلاغ عن أوقات الاستجابة الموسعة أو حالات الفشل الناجمة عن انتهاء مهلة الخدمات. قد ترجع حالات الفشل هذه أيضا أخطاء HTTP 500 (الخادم الداخلي) أو أخطاء HTTP 503 (الخدمة غير متوفرة). فحص سجلات الأحداث لخادم الويب، والتي من المحتمل أن تحتوي على معلومات أكثر تفصيلاً حول أسباب وظروف الأخطاء.

يمكنك متابعة الخطوات التالية للمساعدة في تحديد هذه المشكلة:

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

مثال التشخيص

تطبق الأقسام التالية هذه الخطوات على نموذج التطبيق الموصوف سابقاً.

تحديد نقـاط التباطؤ

وضع علامة على كل أسلوب لتتبع المدة والموارد التي يستهلكها كـل طلب. ثم مراقبة التطبيق فـي الإنتاج. يُمكن أن يوفر هذا عرضًا شاملًا لكيفية تنافس الطلبات مع بعضها البعض. خلال فترات الإجهاد، من المحتمل أن تؤثر الطلبات المتعطشة للموارد التي تعمل ببطء على العمليات الأخرى، ويمكن ملاحظة هذا السلوك من خلال مُراقبة النظام والرجوع إلـى الانخفاض في الأداء.

تعرض الصورة التالية لوحة معلومات مُراقبة. (استخدمنا AppDynamics لاختباراتنا.) في البداية، يحتوي النظام علـى حمل خفيف. ثم يبدأ المستخدمون فـي UserProfile طلب أسلوب GET. الأداء جيد بشكل معقول حتى يبدأ المُستخدمون الآخرون في إصدار الطلبات إلى WorkInFrontEnd أسلوب POST. عند هذه النقطة، تزداد أوقات الاستجابة بشكل كبـير (السهم الأول). تتحسن أوقات الاستجابة فقط بـعد تقليل حجم الطلبات إلى WorkInFrontEnd وحدة التحكم (السهم الثاني).

يعرض جزء AppDynamics Business Transactions تأثيرات أوقات الاستجابة لجميع الطلبات عند استخدام وحدة تحكم WorkInFrontEnd

فحص بيانات تتبع الاستخدام والبحث عـن الارتباطات

تعرض الصورة التالية بعض المقاييس التي تـم جمعها لمُراقبة استخدام الموارد خلال نفس الفاصل الزمني. في البداية، يقوم عدد قليل مـن المستخدمين بالوصول إلى النظام. مع اتصال المزيد من المستخدمين، يصبح استخدام وحدة المعالجة المركزية مرتفعًا جدًا (100٪). لاحظ أيضًا أن معدل الإدخال/الإخراج للشبكة يرتفع في البداية مع ارتفاع استخدام وحدة المعالجة المركزية. ولكن بمجرد ذروة استخدام وحدة المعالجة المركزية، يتعطل إدخال/إخراج الشبكة فعليًا. وذلك لأن النظام لا يُمكنه معالجة سوى عدد صغير نسبيًا من الطلبات بمجرد أن تكون وحدة المعالجة المركزية في السعة. أثناء قطع اتصال المُستخدمين، يتم إيقاف تشغيل تحميل وحدة المعالجة المركزية.

مقاييس AppDynamics التي تعرض استخدام وحدة المعالجة المركزية والشبكة

عند هذه النقطة، يبدو Post أن الأسلوب في WorkInFrontEnd وحدة التحكم هو المُرشح الرئيسي للفحص الدقيق. وهناك حاجة إلى مَزيد من العمل في بيئة خاضعة للرقابة لتأكيد الفرضية.

إجـراء اختبار التحميل

الخطوة التالية هي إجراء الاختبارات فـي بيئة خاضعة للرقابة. على سبيل المثال، قم بتشغيل سلسلة من اختبارات التحميل التي تتضمن ثم قم بحذف كل طلب بدوره لمشاهدة التأثيرات.

يوضح الرسم البياني أدناه نتائج اختبار التحميل الذي تم إجراؤه مقابل توزيع متطابق للخدمة السحابية المستخدمة في الاختبارات السابقة. استخدم الاختبار تحميلاً ثابتًا من 500 مستخدم يقومون Get بتنفيذ العملية في UserProfile وحدة التحكم، جنبًا إلى جنب مع تحميل خطوة للمستخدمين الذين يقومون بالعملية PostWorkInFrontEnd في وحدة التحكم.

نتائج اختبار التحميل الأولية لوحدة تحكم WorkInFrontEnd

في البداية، تحميل الخطوة هو 0، لذلك يقوم المستخدمون النشطـون الوحيدون بتنفيذ UserProfile الطلبات. النظام قادر على الاستجابة لما يقرب مـن 500 طلب في الثانية. بعد 60 ثانية، يبدأ تحميل 100 مُستخدم إضافي في إرسال طلبات POST إلى WorkInFrontEnd وحدة التحكم. على الفور تقريبًا، ينخفض حمل العمل المرسل إلى UserProfile وحدة التحكم إلى حوالي 150 طلبًا في الثانية. ويرجع ذلك إلى الطريقة التي يعمل بـها مشغل اختبار التحميل. ينتظر استجابة قبل إرسال الطلب التالي، لذلك كلما استغرق تلقي استجابة وقتًا أطول، انخفض معدل الطلب.

نظرًا لأن المزيد من المستخدمين يرسلون طلبات POST إلى WorkInFrontEnd وحدة التحكم، يستمر معدل استجابة وحدة التحكم في UserProfile الانخفاض. ولكن لاحظ أن حجم الطلبات التي تعالجها WorkInFrontEnd وحدة التحكم يظل ثابتًا نسبيًا. يصبح تشبع النظام واضحًا لأن المعدل الإجمالي لكلا الطلبين يميل إلى حد ثابت ولكنه منخفض.

مُراجعة التعليمة البرمجية للمصدر

الخطوة الأخيرة هي إلقاء نظرة على التعليمات البرمجية المَصدر. كان فريق التطوير على علم بأن Post الأسلوب قد يستغرق قدرًا كبيرًا من الوقت، وهذا هو السبب في أن التنفيذ الأصلي استخدم مؤشر ترابط منفصل. أدى ذلك إلى حل المشكلة الفورية Post، لأن الأسلوب لم يمنع انتظار اكتمال مهمة طويلة الأمد.

ومع ذلك، لا يزال العمل الذي يتم تنفيذه بواسطة هـذا الأسلوب يستهلك وحدة المعالجة المركزية والذاكرة والموارد الأخرى. قد يؤدي تمكين هذه العملية للتشغيل بشكل غير متزامن إلى تلف الأداء بالفعل، حيث يمكن للمستخدمين تشغيل عدد كبير مـن هذه العمليات في وقت واحد، بطريقة غير خاضعة للرقابة. هناك حد لعدد مؤشرات الترابط التي يُمكن للخادم تشغيلها. بعد تجاوز هذا الحد، من المُحتمل أن يحصل التطبيق على استثناء عندما يحاول بدء مؤشر ترابط جديد.

إشعار

هذا لا يعني أنه يجب تجنب العمليات غـير المتزامنة. يعد إجراء انتظار غير متزامن علـى مكالمة شبكة ممارسة موصى بها. (راجع مضادات الإدخال/الإخراج المتزامنة.) المشكلة هنا هي أنه تم إنتاج العمل المكثف لوحدة المعالجة المركزية علـى مؤشر ترابط آخر.

نفذ الحل وتحقق من النتيجة

تظهر الصورة التالية مراقبة الأداء بعـد تنفيذ الحل. كان الحمل مشابهًا لتلك الموضحة سابقًا، ولكن أوقات الاستجابة لوحدة UserProfile التحكم أصبحت الآن أسرع بكثير. وزاد حجم الطلبات خلال المدة نفسها، من 759 2 إلى 565 23 طلبًا.

يعرض جزء AppDynamics Business Transactions تأثيرات أوقات الاستجابة لجميع الطلبات عند استخدام وحدة تحكم WorkInBackground

لاحظ أن WorkInBackground وحدة التحكم تعاملت أيضًا مع حجم أكبر بكثير من الطلبات. ومع ذلك، لا يمكنك إجراء مقارنة مباشرة في هذه الحالة، لأن العمل الذي يتم تنفيذه في وحدة التحكم هذه مختلف جدًا عن التعليمات البرمجية الأصلية. يقوم الإصدار الجديد ببساطة بقوائم انتظار طلب، بدلًا من إجراء عملية حسابية تستغرق وقتًا طويلًا. النقطة الرئيسية هي أن هذا الأسلوب لـم يعد يسحب لأسفل النظام بأكمله تحت التحميل.

كما يظهر استخدام وحدة المعالجة المركزية والشبكة الأداء المٌحسن. لم يصل استخدام وحدة المعالجة المركزية أبدا إلى 100٪، وكان حجم طلبات الشبكة التي تمت معالجتها أكبر بكثير من السابق، ولم يتم إيقاف تشغيله حـتى ينخفض حمل العمل.

مقاييس AppDynamics التي تعرض استخدام وحدة المعالجة المركزية والشبكة لوحدة تحكم WorkInBackground

يعـرض الرسم البياني التالي نتائج اختبار التحميل. تـم تحسين الحجم الإجمالي للطلبات التي تمت صيانتها بشكل كبير مقارنة بالاختبارات السابقة.

نتائج اختبار التحميل لوحدة تحكم BackgroundImageProcessing