دليل لنماذج خادم الشبكة متعدد المعالجة

نشرت: 2022-03-11

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

ما هو طراز خادم الشبكة الذي يجب أن أختاره

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

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

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

تقسيم التطبيق (إلى عمليات أو سلاسل محادثات متعددة)

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

مهام تطبيقات الشبكة المشتركة ونماذج خادم الشبكة

تتناول هذه المقالة على وجه التحديد رمز خادم الشبكة ، والذي يؤدي بالضرورة إلى تنفيذ المهام الثلاث التالية:

  • المهمة رقم 1: إنشاء (وتفكيك) اتصالات الشبكة
  • المهمة رقم 2: اتصالات الشبكة (IO)
  • المهمة رقم 3: عمل مفيد ؛ على سبيل المثال ، الحمولة أو سبب وجود التطبيق

هناك العديد من نماذج خادم الشبكة العامة لتقسيم هذه المهام عبر العمليات ؛ يسمى:

  • MP: عمليات متعددة
  • السرعة: عملية فردية ، مدفوعة بالحدث
  • سيدا: هندسة معمارية مرحلية مدفوعة بالحدث
  • AMPED: غير متماثل متعدد العمليات يحركها الحدث
  • SYMPED: نموذجي متعدد العمليات يحركه الحدث

هذه هي أسماء نماذج خادم الشبكة المستخدمة في المجتمع الأكاديمي ، وأتذكر أنني وجدت مرادفات "في البرية" لبعضها على الأقل. (الأسماء نفسها ، بالطبع ، أقل أهمية - القيمة الحقيقية تكمن في كيفية التفكير فيما يحدث في الكود.)

يتم وصف كل من نماذج خادم الشبكة هذه بمزيد من التفصيل في الأقسام التالية.

نموذج العمليات المتعددة (MP)

نموذج خادم شبكة MP هو النموذج الذي اعتاد الجميع تعلمه أولاً ، خاصةً عند التعرف على تعدد مؤشرات الترابط. في نموذج MP ، توجد عملية "رئيسية" تقبل التوصيلات (المهمة رقم 1). بمجرد إنشاء الاتصال ، تنشئ العملية الرئيسية عملية جديدة وتمرر مقبس الاتصال إليها ، لذلك هناك عملية واحدة لكل اتصال. تعمل هذه العملية الجديدة عادةً مع الاتصال بطريقة بسيطة ومتسلسلة وقفل خطوة: تقرأ شيئًا منها (المهمة رقم 2) ، ثم تقوم ببعض العمليات الحسابية (المهمة رقم 3) ، ثم تكتب شيئًا إليها (المهمة رقم 2 تكرارا).

نموذج MP سهل التنفيذ للغاية ، وهو في الواقع يعمل بشكل جيد للغاية طالما أن العدد الإجمالي للعمليات لا يزال منخفضًا إلى حد ما. كم قليل؟ تعتمد الإجابة حقًا على ما تستلزمه المهمتان رقم 2 و 3. كقاعدة عامة ، لنفترض أن عدد العمليات أو الخيوط يجب ألا يتجاوز ضعف عدد نوى وحدة المعالجة المركزية. بمجرد أن يكون هناك عدد كبير جدًا من العمليات النشطة في نفس الوقت ، يميل نظام التشغيل إلى قضاء الكثير من الوقت في التحطم (على سبيل المثال ، التوفيق بين العمليات أو الخيوط حول نوى وحدة المعالجة المركزية المتاحة) وينتهي الأمر بهذه التطبيقات عمومًا بإنفاق كل وحدة المعالجة المركزية الخاصة بها تقريبًا الوقت في كود "sys" (أو kernel) ، يؤدي القليل من العمل المفيد في الواقع.

الإيجابيات: سهل التنفيذ للغاية ، يعمل بشكل جيد جدًا طالما أن عدد التوصيلات صغير.

السلبيات: يميل إلى إثقال كاهل نظام التشغيل إذا زاد عدد العمليات بشكل كبير جدًا ، وقد يكون هناك ارتعاش في زمن الانتقال حيث تنتظر شبكة الإدخال / الإخراج (IO) حتى تنتهي مرحلة الحمولة (الحساب).

نموذج العملية المنفردة المستند إلى الحدث (السرعة)

اشتهر نموذج خادم شبكة SPED من خلال بعض تطبيقات خادم الشبكة رفيعة المستوى نسبيًا ، مثل Nginx. في الأساس ، تقوم بجميع المهام الثلاث في نفس العملية ، وتعدد الإرسال بينها. لكي تكون فعالة ، فإنها تتطلب بعض وظائف النواة المتقدمة إلى حد ما مثل epoll و kqueue. في هذا النموذج ، تكون الشفرة مدفوعة بالاتصالات الواردة و "أحداث" البيانات ، وتنفذ "حلقة الحدث" التي تبدو كالتالي:

  • اسأل نظام التشغيل عما إذا كانت هناك أي "أحداث" جديدة للشبكة (مثل الاتصالات الجديدة أو البيانات الواردة)
  • إذا كانت هناك اتصالات جديدة متاحة ، فقم بتأسيسها (المهمة رقم 1)
  • إذا كانت هناك بيانات متاحة ، فاقرأها (المهمة رقم 2) وتصرف وفقًا لها (المهمة رقم 3)
  • كرر حتى خروج الخادم

كل هذا يتم في عملية واحدة ، ويمكن القيام به بكفاءة عالية لأنه يتجنب تمامًا تبديل السياق بين العمليات ، والذي عادة ما يقتل الأداء في نموذج MP. تأتي مفاتيح تبديل السياق الوحيدة هنا من مكالمات النظام ، ويتم تصغيرها من خلال العمل فقط على الاتصالات المحددة التي لها بعض الأحداث المرتبطة بها. يمكن لهذا النموذج التعامل مع عشرات الآلاف من الاتصالات في نفس الوقت ، طالما أن عمل الحمولة (المهمة رقم 3) ليس شديد التعقيد أو كثيف الموارد.

على الرغم من ذلك ، هناك جانبان رئيسيان لهذا النهج:

  1. نظرًا لأن المهام الثلاث تتم بشكل متسلسل في تكرار حلقة واحدة ، فإن عمل الحمولة (المهمة رقم 3) يتم بشكل متزامن مع كل شيء آخر ، مما يعني أنه إذا استغرق الأمر وقتًا طويلاً لحساب استجابة للبيانات التي يتلقاها العميل ، فكل شيء آخر يتوقف أثناء القيام بذلك ، مما يؤدي إلى حدوث تقلبات كبيرة محتملة في وقت الاستجابة.
  2. يتم استخدام نواة واحدة فقط لوحدة المعالجة المركزية. هذا له فائدة ، مرة أخرى ، في الحد تمامًا من عدد محولات السياق المطلوبة من نظام التشغيل ، مما يزيد من الأداء العام ، ولكن له جانب سلبي كبير يتمثل في أن أي نوى وحدة معالجة مركزية أخرى متوفرة لا تفعل شيئًا على الإطلاق.

ولهذه الأسباب ، هناك حاجة إلى نماذج أكثر تقدمًا.

الإيجابيات: يمكن أن يكون عالي الأداء وسهلًا على نظام التشغيل (أي يتطلب الحد الأدنى من تدخل نظام التشغيل). لا يتطلب سوى نواة واحدة لوحدة المعالجة المركزية.

السلبيات: يستخدم فقط وحدة معالجة مركزية واحدة (بغض النظر عن العدد المتاح). إذا كان عمل الحمولة غير موحد ، ينتج عنه زمن انتقال غير منتظم للاستجابات.

نموذج العمارة المبنية على الأحداث (SEDA)

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

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

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

الإيجابيات: الحلم النهائي لمهندس البرمجيات: يتم فصل كل شيء إلى وحدات مستقلة مرتبة.

السلبيات: يمكن أن ينفجر التعقيد فقط من عدد الوحدات النمطية ، ولا تزال قائمة انتظار الرسائل أبطأ بكثير من مشاركة الذاكرة المباشرة.

نموذج غير متماثل متعدد العمليات يحركه الحدث (AMPED)

خادم شبكة AMPED هو نسخة تامر وأسهل نموذجًا من SEDA. لا توجد العديد من الوحدات والعمليات المختلفة ، وليس العديد من قوائم انتظار الرسائل. وإليك كيف يعمل:

  • قم بتنفيذ المهمتين رقم 1 و 2 في عملية "رئيسية" واحدة ، بأسلوب السرعة. هذه هي العملية الوحيدة التي تقوم بها شبكة IO.
  • قم بتنفيذ المهمة رقم 3 في عملية "عاملة" منفصلة (ربما بدأت في حالات متعددة) ، متصلة بالعملية الرئيسية بقائمة انتظار (قائمة انتظار واحدة لكل عملية).
  • عندما يتم تلقي البيانات في العملية "الرئيسية" ، ابحث عن عملية عاملة غير مستغلة (أو خاملة) وقم بتمرير البيانات إلى قائمة انتظار الرسائل الخاصة بها. يتم إرسال العملية الرئيسية برسالة بواسطة العملية عندما تكون الاستجابة جاهزة ، وعند هذه النقطة تمرر الاستجابة عبر الاتصال.

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

الإيجابيات: فصل نظيف جدًا لشبكة IO وعمل الحمولة.

السلبيات: يستخدم قائمة انتظار الرسائل لتمرير البيانات ذهابًا وإيابًا بين العمليات ، والتي ، اعتمادًا على طبيعة البروتوكول ، قد تصبح عنق الزجاجة.

النموذج التجميعي متعدد العمليات المستند إلى الحدث (SYMPED)

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

  • يتم تحميل وحدات المعالجة المركزية (CPU) للعدد المحدد من العمليات التي تم إنتاجها ، والتي في كل نقطة زمنية إما تقوم بمعالجة IO للشبكة أو معالجة الحمولة. لا توجد طريقة لتصعيد استخدام وحدة المعالجة المركزية بشكل أكبر.
  • إذا كانت الاتصالات مستقلة (مثل HTTP) ، فلا يوجد اتصال بين العمليات بين العمليات العاملة.

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

الإيجابيات: سقف أعلى صارم لاستخدام وحدة المعالجة المركزية ، مع عدد يمكن التحكم فيه من الحلقات الشبيهة بالسرعة.

السلبيات: نظرًا لأن كل عملية من العمليات تحتوي على حلقة تشبه السرعة ، إذا كان عمل الحمولة غير منتظم ، يمكن أن يختلف زمن الانتقال مرة أخرى ، تمامًا كما هو الحال مع نموذج SPED العادي.

بعض الحيل منخفضة المستوى

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

  1. تجنب تخصيص الذاكرة الديناميكي. كتفسير ، انظر ببساطة إلى الكود الخاص بمخصصات الذاكرة المشهورة - فهم يستخدمون هياكل بيانات معقدة ، وكائنات متعددة ، وهناك ببساطة الكثير من الأكواد بداخلهم (jemalloc ، على سبيل المثال ، حوالي 450 كيلو بايت من كود سي!). يمكن تنفيذ معظم النماذج المذكورة أعلاه بشبكة ثابتة (أو مخصصة مسبقًا) و / أو مخازن مؤقتة تقوم فقط بتغيير الملكية بين سلاسل العمليات عند الحاجة.
  2. استخدم الحد الأقصى الذي يمكن أن يوفره نظام التشغيل. تسمح معظم أنظمة التشغيل لعمليات متعددة للاستماع على مقبس واحد ، وتنفيذ ميزات حيث لن يتم قبول الاتصال حتى يتم استلام البايت الأول (أو حتى أول طلب كامل!) على المقبس. استخدم sendfile () إذا استطعت.
  3. افهم بروتوكول الشبكة الذي تستخدمه! على سبيل المثال ، عادةً ما يكون من المنطقي تعطيل خوارزمية Nagle ، ويمكن أن يكون من المنطقي تعطيل الاستمرار إذا كان معدل الاتصال (إعادة الاتصال) مرتفعًا. تعرف على خوارزميات التحكم في الازدحام في بروتوكول TCP ومعرفة ما إذا كان من المنطقي تجربة إحدى الخوارزميات الأحدث.

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