![Search icon](https://hackernoon.imgix.net/search-new.png?w=19&h=19)
يتطلب بناء نظام موزع موثوق به وعالي التوافر وقابل للتطوير الالتزام بتقنيات ومبادئ وأنماط محددة. يتضمن تصميم مثل هذه الأنظمة معالجة عدد لا يحصى من التحديات. ومن بين أكثر المشكلات شيوعًا وأساسية مشكلة الكتابة المزدوجة .
"مشكلة الكتابة المزدوجة" هي تحدٍ ينشأ في الأنظمة الموزعة، وخاصة عند التعامل مع مصادر بيانات متعددة أو قواعد بيانات تحتاج إلى المزامنة. وهي تشير إلى صعوبة ضمان كتابة تغييرات البيانات بشكل متسق في مخازن بيانات مختلفة، مثل قواعد البيانات أو ذاكرات التخزين المؤقت، دون إدخال مشكلات مثل عدم تناسق البيانات أو التعارضات أو الاختناقات في الأداء.
توفر لك بنية الخدمات المصغرة وقاعدة بيانات الأنماط لكل خدمة العديد من الفوائد، مثل النشر والتوسع المستقلين، والأعطال المنعزلة، وزيادة محتملة في سرعة التطوير. ومع ذلك، تتطلب العمليات تغييرات بين الخدمات المصغرة المتعددة، مما يجبرك على التفكير في حل موثوق به لمعالجة هذه المشكلة.
لنأخذ سيناريوًا يتضمن فيه مجالنا قبول طلبات القروض وتقييمها ثم إرسال تنبيهات إعلامية إلى العملاء.
وبروح مبدأ المسؤولية الفردية، وقانون كونواي، ونهج التصميم الموجه بالمجال، وبعد عدة جلسات من العصف الذهني بالأحداث، تم تقسيم المجال بأكمله إلى ثلاثة مجالات فرعية مع سياقات محددة محدودة لها حدود واضحة، ونماذج مجال، ولغة في كل مكان.
تتمثل مهمة النظام الأول في استقبال وتجميع طلبات القروض الجديدة. ويقوم النظام الثاني بتقييم هذه الطلبات واتخاذ القرارات بناءً على البيانات المقدمة. وقد تستغرق عملية التقييم هذه، بما في ذلك التحقق من هوية العميل/البنك ومكافحة الاحتيال ومخاطر الائتمان، وقتًا طويلاً، مما يستلزم القدرة على التعامل مع آلاف الطلبات في وقت واحد. وبالتالي، تم تفويض هذه الوظيفة إلى خدمة صغيرة مخصصة بقاعدة بيانات خاصة بها، مما يسمح بالتوسع بشكل مستقل.
علاوة على ذلك، تتم إدارة هذه الأنظمة الفرعية بواسطة فريقين مختلفين، ولكل منهما دورات إصدار خاصة به، واتفاقيات مستوى الخدمة (SLA)، ومتطلبات قابلية التوسع.
وأخيرًا ، تم توفير خدمة إشعارات متخصصة لإرسال التنبيهات إلى العملاء.
فيما يلي وصف مفصل لحالة الاستخدام الأساسية للنظام:
يبدو هذا النظام بسيطًا وبدائيًا إلى حد ما للوهلة الأولى، ولكن دعنا نتعمق في كيفية معالجة خدمة طلب القرض لأمر إرسال طلب القرض.
يمكننا أن نأخذ في الاعتبار نهجين للتفاعلات الخدمية:
أول التزام محلي ثم النشر: في هذا النهج، تقوم الخدمة بتحديث قاعدة البيانات المحلية الخاصة بها (الالتزامات) ثم تنشر حدثًا أو رسالة إلى خدمات أخرى.
النشر أولاً ثم الالتزام محليًا: على العكس من ذلك، تتضمن هذه الطريقة نشر حدث أو رسالة قبل الالتزام بالتغييرات في قاعدة البيانات المحلية.
كلا الطريقتين لها عيوبها وليست آمنة إلا جزئيًا للاتصالات في الأنظمة الموزعة.
وهذا رسم تخطيطي لتسلسل تطبيق النهج الأول.
في هذا السيناريو، تستخدم خدمة طلب القرض نهج First-Local-Commit-Then-Publish (أولاً-التأكيد-ثم-النشر )، حيث تقوم أولاً بتأكيد المعاملة ثم تحاول إرسال إشعار إلى نظام آخر. ومع ذلك، فإن هذه العملية معرضة للفشل إذا كانت هناك مشكلات في الشبكة، على سبيل المثال، أو كانت خدمة التقييم غير متاحة، أو واجهت خدمة طلب القرض خطأ نفاد الذاكرة (OOM) وتعطلت. في مثل هذه الحالات، ستضيع الرسالة، مما يترك خدمة التقييم بدون إشعار بطلب القرض الجديد، ما لم يتم تنفيذ تدابير إضافية.
والثانية
في سيناريو النشر أولاً ثم الالتزام محليًا ، تواجه خدمة طلبات القروض مخاطر أكبر. فقد تبلغ خدمة التقييم بطلب جديد ولكنها تفشل في حفظ هذا التحديث محليًا بسبب مشكلات مثل مشكلات قاعدة البيانات أو أخطاء الذاكرة أو أخطاء التعليمات البرمجية. وقد يؤدي هذا النهج إلى تناقضات كبيرة في البيانات، مما قد يتسبب في حدوث مشكلات خطيرة، اعتمادًا على كيفية تعامل خدمة مراجعة القروض مع الطلبات الواردة.
لذلك، يتعين علينا تحديد حل يوفر آلية قوية لنشر الأحداث للمستهلكين الخارجيين. ولكن قبل الخوض في الحلول المحتملة، يتعين علينا أولاً توضيح أنواع ضمانات تسليم الرسائل التي يمكن تحقيقها في الأنظمة الموزعة.
هناك أربعة أنواع من الضمانات التي يمكننا تحقيقها.
لا يوجد ضمانات
لا يوجد ضمان بأن الرسالة ستصل إلى الوجهة. إن النهج First-Local-Commit-Then-Publish هو على وجه التحديد يتعلق بهذا الأمر. قد يتلقى المستهلكون الرسائل مرة واحدة أو عدة مرات أو لا يتلقونها على الإطلاق.
التسليم مرة واحدة على الأكثر
تعني عبارة "التسليم مرة واحدة على الأكثر" أن الرسالة سيتم تسليمها إلى الوجهة مرة واحدة على الأكثر. ويمكن تنفيذ نهج First-Local-Commit-Then-Publish بهذه الطريقة أيضًا باستخدام سياسة إعادة المحاولة للمحاولات بقيمة واحد.
مرة واحدة على الأقل للتسليم\سيتلقى المستهلكون كل رسالة ويقومون بمعالجتها ولكن قد يتلقون نفس الرسالة أكثر من مرة.
التسليم مرة واحدة بالضبط\يعني التسليم مرة واحدة بالضبط أن المستهلك سوف يتلقى الرسالة مرة واحدة بشكل فعال.
من الناحية الفنية، من الممكن تحقيق ذلك من خلال معاملات كافكا والتنفيذ الأيدموبوتني المحدد للمنتج والمستهلك.
في أغلب الأحوال، تعالج ضمانات التسليم "مرة واحدة على الأقل" العديد من القضايا من خلال ضمان تسليم الرسائل مرة واحدة على الأقل، ولكن يجب أن يكون المستهلكون قادرين على التعامل مع الأمر بشكل مثالي. ومع ذلك، نظرًا لفشل الشبكة الذي لا مفر منه، يجب أن يكون كل منطق المستهلك مثاليًا لتجنب معالجة الرسائل المكررة، بغض النظر عن ضمانات المنتج. وبالتالي، فإن هذا الشرط ليس عيبًا بقدر ما يعكس الواقع.
هناك الكثير من الحلول لهذه المشكلة، ولكل منها مزاياها وعيوبها.
وفقًا لويكيبيديا، فإن Two-Phase Commit (2PC) هو بروتوكول معاملات موزعة يستخدم في علوم الكمبيوتر وأنظمة إدارة قواعد البيانات لضمان اتساق وموثوقية المعاملات الموزعة. وهو مصمم للمواقف التي تحتاج فيها موارد متعددة (مثل قواعد البيانات) إلى المشاركة في معاملة واحدة، ويضمن إما أن تقوم جميعها بتنفيذ المعاملة أو إلغائها، وبالتالي الحفاظ على اتساق البيانات. يبدو الأمر كما نحتاجه تمامًا، لكن Two-Phase Commit له عدة عيوب:
الحل الأكثر وضوحًا لهندسة الخدمات المصغرة هو تطبيق نمط (أو حتى نمط مضاد في بعض الأحيان) - قاعدة بيانات مشتركة. هذا النهج بديهي للغاية إذا كنت بحاجة إلى اتساق المعاملات عبر جداول متعددة في قواعد بيانات مختلفة، فما عليك سوى استخدام قاعدة بيانات مشتركة واحدة لهذه الخدمات المصغرة.
تتضمن عيوب هذا النهج إدخال نقطة فشل واحدة، ومنع التوسع المستقل لقاعدة البيانات، وتقييد القدرة على استخدام حلول قواعد بيانات مختلفة تناسب المتطلبات وحالات الاستخدام المحددة. بالإضافة إلى ذلك، ستكون التعديلات على قواعد بيانات الخدمات المصغرة ضرورية لدعم هذا الشكل من المعاملات الموزعة.
" صندوق البريد الصادر المعاملاتي " هو نمط تصميم يستخدم في الأنظمة الموزعة لضمان انتشار الرسائل بشكل موثوق، حتى في مواجهة أنظمة المراسلة غير الموثوقة. وهو يتضمن تخزين الأحداث في جدول "OutboxEvents" المخصص ضمن نفس المعاملة مثل العملية نفسها. يتوافق هذا النهج جيدًا مع خصائص ACID لقواعد البيانات العلائقية. وعلى النقيض من ذلك، لا تدعم العديد من قواعد البيانات No-SQL خصائص ACID بشكل كامل، وتختار بدلاً من ذلك مبادئ نظرية CAP وفلسفة BASE، والتي تعطي الأولوية للتوافر والاتساق النهائي على الاتساق الصارم.
يوفر صندوق الصادر المعاملاتي ضمانًا واحدًا على الأقل ويمكن تنفيذه من خلال عدة طرق:
سجل المعاملات
ناشر استطلاعات الرأي
يتضمن نهج تتبع سجل المعاملات استخدام حلول خاصة بقاعدة البيانات مثل CDC (التقاط بيانات التغيير). العيوب الرئيسية لهذا النهج هي:
حلول خاصة بقواعد البيانات
زيادة زمن الوصول بسبب تفاصيل تنفيذات CDC
هناك طريقة أخرى وهي Polling Publisher ، والتي تسهل تفريغ البريد الصادر من خلال استطلاع جدول البريد الصادر. العيب الأساسي لهذا النهج هو إمكانية زيادة تحميل قاعدة البيانات، مما قد يؤدي إلى تكاليف أعلى. علاوة على ذلك، لا تدعم جميع قواعد بيانات No-SQL الاستعلام الفعّال لأجزاء معينة من المستندات. وبالتالي، قد يؤدي استخراج المستندات بالكامل إلى تدهور الأداء.
فيما يلي رسم تخطيطي صغير يوضح كيفية عمل ذلك.
يتمثل التحدي الأساسي في نمط صندوق الصادر المعاملي في اعتماده على خصائص ACID لقاعدة البيانات. قد يكون الأمر بسيطًا في قواعد بيانات OLTP النموذجية ولكنه يفرض تحديات في عالم NoSQL. لمعالجة هذا، فإن الحل المحتمل هو الاستفادة من سجل الإلحاق (على سبيل المثال، Kafka) مباشرة من بدء معالجة الطلب.
بدلاً من معالجة أمر "إرسال طلب القرض" مباشرةً، نرسله على الفور إلى موضوع Kafka داخلي ثم نعيد نتيجة "مقبولة" إلى العميل. ومع ذلك، نظرًا لأنه من المحتمل جدًا أن الأمر لا يزال بحاجة إلى المعالجة، فلا يمكننا إبلاغ العميل بالنتيجة على الفور. لإدارة هذا الاتساق النهائي، يمكننا استخدام تقنيات مثل الاستطلاع الطويل، أو الاستطلاع الذي بدأه العميل، أو تحديثات واجهة المستخدم المتفائلة، أو استخدام WebSockets أو Server-Sent Events للإشعارات. ومع ذلك، فهذا موضوع منفصل تمامًا، لذا دعنا نعود إلى موضوعنا الأولي.
لقد أرسلنا الرسالة حول موضوع داخلي في Kafka. ثم تستهلك خدمة طلب القرض هذه الرسالة — نفس الأمر الذي تلقته من العميل — وتبدأ في المعالجة. أولاً، تقوم بتنفيذ بعض منطق العمل؛ فقط بعد تنفيذ هذا المنطق بنجاح واستمرار النتائج، تقوم بنشر رسائل جديدة حول موضوع عام في Kafka.
دعونا نلقي نظرة على القليل من الكود الزائف.
public async Task HandleAsync(SubmitLoanApplicationCommand command, ...) { //First, process business logic var loanApplication = await _loanApplicationService.HandleCommandAsync(command, ...); //Then, send new events to public Kafka topic producer.Send(new LoanApplicationSubmittedEvent(loanApplication.Id)); //Then, commit offset consumer.Commit(); }
ماذا لو فشلت معالجة منطق العمل؟ لا داعي للقلق، نظرًا لأنه لم يتم تأكيد الإزاحة بعد، فستتم إعادة محاولة إرسال الرسالة.
ماذا لو فشل إرسال أحداث جديدة إلى Kafka؟ لا داعي للقلق، نظرًا لأن منطق العمل غير فعال، فلن يقوم بإنشاء طلب قرض مكرر. بدلاً من ذلك، سيحاول إعادة إرسال الرسائل إلى موضوع Kafka العام.
ماذا لو تم إرسال الرسائل إلى Kafka، ولكن فشل تأكيد الإزاحة؟ لا داعي للقلق، نظرًا لأن منطق العمل هو منطق إيديولوجي، فلن ينشئ طلب قرض مكررًا. بدلاً من ذلك، سيعيد إرسال الرسائل إلى موضوع Kafka العام ويأمل أن ينجح تأكيد الإزاحة هذه المرة.
تتضمن العيوب الرئيسية لهذا النهج التعقيد الإضافي المرتبط بأسلوب البرمجة الجديد، والاتساق النهائي (نظرًا لأن العميل لن يعرف النتيجة على الفور)، والمتطلبات اللازمة لأن يكون منطق الأعمال بأكمله أيديولوجيا.
ما هو مصدر الحدث، وكيف يمكن تطبيقه هنا؟ مصدر الحدث هو نمط معماري للبرمجيات يستخدم لنمذجة حالة النظام من خلال التقاط جميع التغييرات التي تطرأ على بياناته كسلسلة من الأحداث الثابتة. تمثل هذه الأحداث حقائق أو انتقالات حالة وتعمل كمصدر وحيد للحقيقة للحالة الحالية للنظام. لذا، من الناحية الفنية، من خلال تنفيذ نظام مصدر الحدث، لدينا بالفعل جميع الأحداث في EventStore، ويمكن للمستهلكين استخدام EventStore هذا كمصدر وحيد للحقيقة حول ما حدث. ليست هناك حاجة إلى حل قاعدة بيانات محدد لتتبع جميع التغييرات أو المخاوف بشأن الطلب، والمشكلة الوحيدة هي الجلوس على جانب القراءة حيث أن القدرة على الحصول على الحالة الفعلية للكيان تتطلب إعادة تشغيل جميع الأحداث.
في هذه المقالة، قمنا بمراجعة العديد من الأساليب لبناء رسائل موثوقة في الأنظمة الموزعة. هناك العديد من التوصيات التي قد نضعها في الاعتبار أثناء بناء أنظمة تتمتع بهذه الخصائص
في المرة القادمة، سننظر إلى مثال أكثر عملية لتنفيذ صندوق بريد معاملاتي. راجع
أنت!