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


في المقال الأخير ، درسنا الأسس النظرية للهندسة التفاعلية. حان الوقت للحديث عن تدفقات البيانات وطرق تطبيق أنظمة Erlang / Elixir التفاعلية وأنماط المراسلة فيها:


  • طلب الرد
  • استجابة مكتنزة طلب
  • الرد مع الطلب
  • نشر الإشتراك
  • مقلوب نشر الاشتراك
  • توزيع المهمة

الخدمية ، MSA والرسائل


SOA ، MSA - بنيات النظام التي تحدد قواعد أنظمة البناء ، بينما توفر المراسلة بدائل لتنفيذها.


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


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


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

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


تتفاعل مكونات النظام مباشرة أو من خلال وسيط. من منظور المراسلة ، لكل خدمة عدة مراحل حياة:


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

يبدو الأمر معقدًا جدًا ، لكن ليس كل شيء مخيفًا جدًا في الكود. سيتم تقديم أمثلة على الكود مع التعليقات في تحليل القوالب في وقت لاحق.


التبادل


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


أنماط تبادل الرسائل (MEPs)


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


طلب - استجابة أو RPC


يتم استخدام RPC عندما نحتاج إلى الحصول على استجابة من عملية أخرى. يمكن إطلاق هذه العملية في نفس الموقع أو في قارة مختلفة. يوجد أدناه مخطط لتفاعل العميل والخادم من خلال المراسلة.



نظرًا لأن المراسلة غير متزامنة تمامًا ، فإن العميل ينقسم إلى مرحلتين:


  1. طلب التقديم


    messaging:request(Exchange, ResponseMatchingTag, RequestDefinition, HandlerProcess). 

    الصرف - اسم فريد لنقطة التبادل
    ResponseMatchingTag - التسمية المحلية لمعالجة الاستجابة. على سبيل المثال ، في حالة إرسال عدة طلبات مماثلة تنتمي إلى مستخدمين مختلفين.
    RequestDefinition - نص الطلب
    HandlerProcess - معالج PID. ستتلقى هذه العملية استجابة من الخادم.


  2. معالجة الاستجابة


     handle_info(#'$msg'{exchange = EXCHANGE, tag = ResponseMatchingTag,message = ResponsePayload}, State) 

    ResponsePayload - استجابة الخادم.



بالنسبة للخادم ، تتكون العملية أيضًا من مرحلتين:


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

دعونا توضيح هذا القالب مع رمز. لنفترض أننا نحتاج إلى تنفيذ خدمة بسيطة توفر طريقة الوقت الدقيق فقط.


كود الخادم


احصل على تعريف واجهة برمجة التطبيقات للخدمة في api.hrl:


 %% ===================================================== %% entities %% ===================================================== -record(time, { unixtime :: non_neg_integer(), datetime :: binary() }). -record(time_error, { code :: non_neg_integer(), error :: term() }). %% ===================================================== %% methods %% ===================================================== -record(time_req, { opts :: term() }). -record(time_resp, { result :: #time{} | #time_error{} }). 

تحديد وحدة تحكم الخدمة في time_controller.erl


 %%      .     gen_server    . %%  gen_server init(Args) -> %%     messaging:monitor_exchange(req_resp, ?EXCHANGE, default, self()) {ok, #{}}. %%       .    ,      . handle_info(#exchange_die{exchange = ?EXCHANGE}, State) -> erlang:send(self(), monitor_exchange), {noreply, State}; %%  API handle_info(#time_req{opts = _Opts}, State) -> messaging:response_once(Client, #time_resp{ result = #time{ unixtime = time_utils:unixtime(now()), datetime = time_utils:iso8601_fmt(now())} }); {noreply, State}; %%   gen_server terminate(_Reason, _State) -> messaging:demonitor_exchange(req_resp, ?EXCHANGE, default, self()), ok. 

كود العميل


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


 case messaging:request(?EXCHANGE, tag, #time_req{opts = #{}}, self()) of ok -> ok; _ -> %% repeat or fail logic end 

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


 handle_info(#'$msg'{exchange = ?EXCHANGE, tag = tag, message = #time_resp{result = #time{unixtime = Utime}}}, State) -> ?debugVal(Utime), {noreply, State}; handle_info(#'$msg'{exchange = ?EXCHANGE, tag = tag, message = #time_resp{result = #time_error{code = ErrorCode}}}, State) -> ?debugVal({error, ErrorCode}), {noreply, State}; 

استجابة مكتنزة طلب


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



سأقدم بعض الأمثلة على مثل هذه الحالات:


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

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


في نظام Erlang ، نحصل على مكسب إضافي - تقليل الحمل على نقطة التبادل والشبكة ، حيث يتم إرسال الإجابات على الفور إلى المستلم ، متجاوزة نقطة التبادل.


الرد مع الطلب


هذا هو تعديل نادر إلى حد ما لنمط RPC لبناء أنظمة تفاعلية.



النشر-الاشتراك (شجرة توزيع البيانات)


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



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


النظر في رمز المشترك:


 init(_Args) -> %%   ,  = key messaging:subscribe(?SUBSCRIPTION, key, tag, self()), {ok, #{}}. handle_info(#exchange_die{exchange = ?SUBSCRIPTION}, State) -> %%    ,    messaging:subscribe(?SUBSCRIPTION, key, tag, self()), {noreply, State}; %%    handle_info(#'$msg'{exchange = ?SUBSCRIPTION, message = Msg}, State) -> ?debugVal(Msg), {noreply, State}; %%    -     terminate(_Reason, _State) -> messaging:unsubscribe(?SUBSCRIPTION, key, tag, self()), ok. 

يمكن للمصدر استدعاء وظيفة نشر المنشور في أي مكان مناسب:


 messaging:publish_message(Exchange, Key, Message). 

الصرف - اسم نقطة التبادل ،
مفتاح التوجيه
رسالة - حمولة


مقلوب نشر الاشتراك



من خلال توسيع pub-sub ، يمكنك الحصول على نمط مناسب للتسجيل. يمكن أن تكون مجموعة المصادر والمستهلكين مختلفة تمامًا. يوضح الشكل حالة مع مستهلك واحد والعديد من المصادر.


نمط توزيع المهمة


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


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


المراسلة تدير قوائم الانتظار ومعالجة الأولوية. يتلقى معالجات المهام عندما تصبح متوفرة. المهمة قد تنجح أو تفشل:


  • messaging:ack(Tack) - تسمى في حالة نجاح معالجة الرسائل
  • messaging:nack(Tack) - يسمى في جميع حالات الطوارئ. بعد عودة المهمة ، ستنقلها الرسائل إلى معالج آخر.


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


النتيجة الأولية


لقد قمنا بفك اللبنات الأساسية للأنظمة الموزعة وحصلنا على فهم أساسي لتطبيقها في Erlang / Elixir.


من خلال الجمع بين الأنماط الأساسية ، يمكنك بناء نماذج معقدة لحل المشكلات الناشئة.


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


نهاية الجزء الثاني.


صورة ماريوس كريستنسن
الرسوم التوضيحية التي أعدتها websequencediagrams.com

Source: https://habr.com/ru/post/ar446108/


All Articles