اللبنات الأساسية للتطبيقات الموزعة. التقريب الثاني

إعلان

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

اللبنات الأساسية للتطبيقات الموزعة. التقريب الثاني

هذه هي المقالة الأخيرة في سلسلة التطبيقات التفاعلية الموزعة في Erlang/Elixir. في المادة الأولى يمكنك العثور على الأسس النظرية للهندسة التفاعلية. المادة الثانية ويوضح الأنماط والآليات الأساسية لبناء مثل هذه الأنظمة.

سنطرح اليوم قضايا تطوير قاعدة الكود والمشاريع بشكل عام.

تنظيم الخدمات

في الحياة الواقعية، عند تطوير خدمة ما، غالبًا ما يتعين عليك الجمع بين عدة أنماط تفاعل في وحدة تحكم واحدة. على سبيل المثال، يجب على خدمة المستخدمين، التي تحل مشكلة إدارة ملفات تعريف مستخدمي المشروع، الاستجابة لطلبات req-resp والإبلاغ عن تحديثات الملفات الشخصية عبر pub-sub. هذه الحالة بسيطة للغاية: خلف المراسلة توجد وحدة تحكم واحدة تنفذ منطق الخدمة وتنشر التحديثات.

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

  1. الآن يجب على الخدمة معالجة الطلبات على 5 عقد عنقودية،
  2. تكون قادرة على أداء مهام معالجة الخلفية،
  3. وتكون أيضًا قادرًا على إدارة قوائم الاشتراك ديناميكيًا لتحديثات الملف الشخصي.

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

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

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

تتطلب النقطة 3 امتداد قالب pub-sub. وللتنفيذ، بعد إنشاء نقطة تبادل Pub-Sub، نحتاج أيضًا إلى تشغيل وحدة التحكم في هذه النقطة داخل خدمتنا. وبالتالي، يبدو الأمر كما لو أننا ننقل منطق معالجة الاشتراكات وإلغاء الاشتراكات من طبقة الرسائل إلى تنفيذ المستخدمين.

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

اختيار القائد

في الأنظمة الموزعة، يعد انتخاب القائد بمثابة إجراء لتعيين عملية واحدة مسؤولة عن جدولة المعالجة الموزعة لبعض الأحمال.

في الأنظمة التي لا تميل إلى المركزية، يتم استخدام الخوارزميات العالمية والمبنية على الإجماع، مثل paxos أو raft.
نظرًا لأن المراسلة هي وسيط وعنصر مركزي، فهي تعرف جميع وحدات التحكم في الخدمة - القادة المرشحين. يمكن للمراسلة تعيين قائد دون التصويت.

بعد البدء والاتصال بنقطة التبادل، تتلقى كافة الخدمات رسالة النظام #'$leader'{exchange = ?EXCHANGE, pid = LeaderPid, servers = Servers}. لو LeaderPid يتطابق مع pid العملية الحالية، يتم تعيينه كزعيم، والقائمة Servers يشمل جميع العقد ومعلماتها.
في الوقت الحالي تظهر واحدة جديدة ويتم قطع اتصال عقدة كتلة العمل، تتلقى كافة وحدات تحكم الخدمة #'$slave_up'{exchange = ?EXCHANGE, pid = SlavePid, options = SlaveOpts} и #'$slave_down'{exchange = ?EXCHANGE, pid = SlavePid, options = SlaveOpts} على التوالي.

بهذه الطريقة، تكون جميع المكونات على علم بجميع التغييرات، ويضمن للمجموعة أن يكون لها قائد واحد في أي وقت محدد.

وسطاء

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

المثال الكلاسيكي لتحسين Pub-Sub هو تطبيق موزع ذو نواة أعمال يقوم بإنشاء أحداث التحديث، مثل تغيرات الأسعار في السوق، وطبقة الوصول - خوادم N التي توفر واجهة برمجة تطبيقات websocket لعملاء الويب.
إذا قررت ذلك بشكل مباشر، فستبدو خدمة العملاء كما يلي:

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

لنتخيل أن لدينا 50000 ألف مشترك في موضوع "الأخبار". يتم توزيع المشتركين بالتساوي عبر 5 خوادم. ونتيجة لذلك، سيتم تكرار كل تحديث يصل إلى نقطة التبادل 50000 مرة: 10000 مرة على كل خادم، وفقًا لعدد المشتركين عليه. ليس مخططا فعالا للغاية، أليس كذلك؟
لتحسين الوضع، دعنا نقدم وكيلًا يحمل نفس اسم نقطة التبادل. يجب أن يكون مسجل الاسم العام قادرًا على إرجاع أقرب عملية بالاسم، وهذا أمر مهم.

لنقم بتشغيل هذا الوكيل على خوادم طبقة الوصول، وستشترك جميع عملياتنا التي تخدم واجهة برمجة تطبيقات websocket فيه، وليس في نقطة تبادل Pub-Sub الأصلية في النواة. يشترك الوكيل في المركز فقط في حالة الاشتراك الفريد ويقوم بتكرار الرسالة الواردة إلى جميع المشتركين فيه.
ونتيجة لذلك، سيتم إرسال 5 رسائل بين النواة وخوادم الوصول، بدلاً من 50000.

التوجيه والموازنة

متطلبات الاستجابة

في تطبيق المراسلة الحالي، هناك 7 إستراتيجيات لتوزيع الطلب:

  • default. يتم إرسال الطلب إلى كافة وحدات التحكم.
  • round-robin. يتم تعداد الطلبات وتوزيعها دوريًا بين وحدات التحكم.
  • consensus. وينقسم المتحكمون الذين يخدمون الخدمة إلى قادة وعبيد. يتم إرسال الطلبات فقط إلى القائد.
  • consensus & round-robin. المجموعة لديها قائد، ولكن يتم توزيع الطلبات بين جميع الأعضاء.
  • sticky. يتم حساب وظيفة التجزئة وتعيينها لمعالج محدد. الطلبات اللاحقة مع هذا التوقيع تذهب إلى نفس المعالج.
  • sticky-fun. عند تهيئة نقطة التبادل، يتم تشغيل وظيفة حساب التجزئة لـ sticky موازنة.
  • fun. على غرار Sticky-fun، يمكنك أنت وحدك إعادة توجيهه أو رفضه أو معالجته مسبقًا.

يتم تعيين استراتيجية التوزيع عند تهيئة نقطة التبادل.

بالإضافة إلى الموازنة، تسمح لك المراسلة بوضع علامة على الكيانات. دعونا نلقي نظرة على أنواع العلامات في النظام:

  • علامة الاتصال. يتيح لك أن تفهم من خلاله الاتصال الذي جاءت به الأحداث. يُستخدم عندما تتصل عملية وحدة التحكم بنفس نقطة التبادل، ولكن باستخدام مفاتيح توجيه مختلفة.
  • علامة الخدمة. يسمح لك بدمج المعالجات في مجموعات لخدمة واحدة وتوسيع إمكانيات التوجيه والموازنة. بالنسبة لنمط req-resp، يكون التوجيه خطيًا. نقوم بإرسال طلب إلى نقطة الصرف، ثم نقوم بتحويله إلى الخدمة. لكن إذا أردنا تقسيم المعالجات إلى مجموعات منطقية، فسيتم التقسيم باستخدام العلامات. عند تحديد علامة، سيتم إرسال الطلب إلى مجموعة محددة من وحدات التحكم.
  • علامة الطلب. يسمح لك بالتمييز بين الإجابات. نظرًا لأن نظامنا غير متزامن، لمعالجة استجابات الخدمة، نحتاج إلى أن نكون قادرين على تحديد RequestTag عند إرسال الطلب. ومنه سنتمكن من فهم الإجابة على الطلب الذي جاء إلينا.

حانة فرعية

بالنسبة إلى pub-sub، كل شيء أبسط قليلاً. لدينا نقطة تبادل يتم نشر الرسائل عليها. تقوم نقطة التبادل بتوزيع الرسائل بين المشتركين الذين اشتركوا في مفاتيح التوجيه التي يحتاجونها (يمكننا القول أن هذا مشابه للموضوعات).

قابلية التوسع والتسامح مع الخطأ

تعتمد قابلية التوسع للنظام ككل على درجة قابلية التوسع لطبقات النظام ومكوناته:

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

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

حجز

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

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

أداء

دعونا نحاول على الأقل مقارنة أداء Rabbitmq ورسائلنا المخصصة.
وجدت النتائج الرسمية اختبار Rabbitmq من فريق openstack.

في الفقرة 6.14.1.2.1.2.2. يعرض المستند الأصلي نتيجة RPC CAST:
اللبنات الأساسية للتطبيقات الموزعة. التقريب الثاني

لن نقوم بإجراء أي إعدادات إضافية لنظام التشغيل kernel أو erlang VM مسبقًا. شروط الاختبار:

  • خيارات erl: +A1 +sbtu.
  • يتم إجراء الاختبار داخل عقدة erlang واحدة على جهاز كمبيوتر محمول مزود بمعالج i7 القديم في إصدار الهاتف المحمول.
  • يتم إجراء الاختبارات العنقودية على خوادم ذات شبكة 10G.
  • يتم تشغيل الكود في حاويات عامل الإرساء. الشبكة في وضع NAT.

رمز الاختبار:

req_resp_bench(_) ->
  W = perftest:comprehensive(10000,
    fun() ->
      messaging:request(?EXCHANGE, default, ping, self()),
      receive
        #'$msg'{message = pong} -> ok
      after 5000 ->
        throw(timeout)
      end
    end
  ),
  true = lists:any(fun(E) -> E >= 30000 end, W),
  ok.

السيناريو 1: يتم إجراء الاختبار على جهاز كمبيوتر محمول مزود بإصدار الهاتف المحمول القديم i7. يتم تنفيذ الاختبار والمراسلة والخدمة على عقدة واحدة في حاوية Docker واحدة:

Sequential 10000 cycles in ~0 seconds (26987 cycles/s)
Sequential 20000 cycles in ~1 seconds (26915 cycles/s)
Sequential 100000 cycles in ~4 seconds (26957 cycles/s)
Parallel 2 100000 cycles in ~2 seconds (44240 cycles/s)
Parallel 4 100000 cycles in ~2 seconds (53459 cycles/s)
Parallel 10 100000 cycles in ~2 seconds (52283 cycles/s)
Parallel 100 100000 cycles in ~3 seconds (49317 cycles/s)

السيناريو 2: 3 عقد تعمل على أجهزة مختلفة تحت عامل الإرساء (NAT).

Sequential 10000 cycles in ~1 seconds (8684 cycles/s)
Sequential 20000 cycles in ~2 seconds (8424 cycles/s)
Sequential 100000 cycles in ~12 seconds (8655 cycles/s)
Parallel 2 100000 cycles in ~7 seconds (15160 cycles/s)
Parallel 4 100000 cycles in ~5 seconds (19133 cycles/s)
Parallel 10 100000 cycles in ~4 seconds (24399 cycles/s)
Parallel 100 100000 cycles in ~3 seconds (34517 cycles/s)

وفي جميع الأحوال لم تتجاوز نسبة استخدام وحدة المعالجة المركزية 250%

نتائج

آمل ألا تبدو هذه الدورة وكأنها تفريغ للعقل وأن تكون تجربتي ذات فائدة حقيقية لكل من الباحثين في الأنظمة الموزعة والممارسين الذين هم في بداية بناء البنى الموزعة لأنظمة أعمالهم وينظرون إلى Erlang/Elixir باهتمام ولكن لديك شكوك هل يستحق الأمر...

صور @chuttersnap

يمكن للمستخدمين المسجلين فقط المشاركة في الاستطلاع. تسجيل الدخول، من فضلك.

ما هي المواضيع التي يجب أن أغطيها بمزيد من التفصيل كجزء من سلسلة تجارب VTrade؟

  • النظرية: الأسواق والأوامر وتوقيتها: DAY، GTD، GTC، IOC، FOK، MOO، MOC، LOO، LOC

  • كتاب الطلبات. نظرية وممارسة تنفيذ كتاب مع المجموعات

  • تصور التداول: القراد، والحانات، والقرارات. كيفية تخزين وكيفية الغراء

  • مكتب خلفي. التخطيط والتطوير. مراقبة الموظفين والتحقيق في الحوادث

  • واجهة برمجة التطبيقات. دعونا نتعرف على الواجهات المطلوبة وكيفية تنفيذها

  • تخزين المعلومات: PostgreSQL، Timescale، Tarantool في أنظمة التداول

  • التفاعل في أنظمة التداول

  • آخر. سأكتب في التعليقات

صوت 6 مستخدمًا. امتنع 4 مستخدما عن التصويت.

المصدر: www.habr.com

إضافة تعليق