مضادات العمارة الموجهة نحو الحدث

مرحبا مجددا! تحسبًا لبدء الدورة "مهندس برامج" ، قمنا بإعداد ترجمة لمواد أخرى مثيرة للاهتمام.




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

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

  • : , . ( ..) . ( ) Users.
  • : , . . , , , , . .

-


ليست كل معماريات الخدمات الصغيرة مدفوعة بالحدث. بعض الناس ينادون بالاتصال المتزامن بين الخدمات في هذه البنية باستخدام HTTP (gRPC ، REST ، وما إلى ذلك). في letgo ، نحاول عدم اتباع هذا النمط وربط خدماتنا بأحداث النطاق بشكل غير متزامن . فيما يلي أسباب قيامنا بذلك:

  • : . , DDoS . , DDoS . , . .



  • (bulkheads) – , , .
  • : . , , , , . , , API, , , , . Users , Chat.

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

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



العمارة مدفوعة بالحدث في letgo

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

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


عرض قائمة مربعات الحوار في محادثتنا. يعرض أي خدمة محددة على الواجهة الخلفية توفر معلومات.

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


عرض مبسط للهندسة المعمارية ليجو

مضادات


غالبًا ما أصبحت بعض الحلول البديهية أخطاء. فيما يلي قائمة بأهم المضادات التي واجهناها في بنيتنا المتعلقة بالنطاق.

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

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

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

3. عدم وجود تسلسل أو ضغط مستقل:
يجب ألا تعتمد أنظمة التسلسل وضغط الأحداث في مجال تخصصنا على لغة البرمجة. لا تحتاج حتى إلى معرفة اللغة التي تتم كتابة خدمات المستهلكين بها. هذا هو السبب في أنه يمكننا استخدام مُسلسل Java أو PHP ، على سبيل المثال. دع فريقك يقضي وقتًا في مناقشة واختيار مُسلسل ، لأن تغييره في المستقبل سيكون صعبًا ويستغرق وقتًا طويلاً. نحن في letgo نستخدم JSON ، ولكن هناك العديد من تنسيقات التسلسل الأخرى ذات الأداء الجيد.

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

{
  “data”: {
    “id”: [uuid], // event id.type”: “user_registered”,
    “attributes”: {
      “id”: [uuid], // aggregate/entity id, in this case user_id
      “user_name”: “John Doe”,
    }
  },
  “meta” : {
    “created_at”: timestamp, // when was the event created?
    “host”: “users-service” // where was the event created?
  }
}

يسمح لنا وجود هيكل مشترك لأحداث المجال الخاصة بنا بدمج الخدمات بسرعة وتنفيذ بعض المكتبات مع الملخصات.

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


{
  “null_value_one”: null, // thank god
  “null_value_two”: “null”,
  “null_value_three”: “”,
}

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

6. أحداث فقر الدم
كما قلت من قبل ، وكما يوحي الاسم ، يجب أن يكون لأحداث المجال قيمة على مستوى النطاق. مثلما نحاول تجنب تناقض الدول في كياناتنا ، يجب علينا تجنب ذلك في أحداث المجال. دعنا نوضح ذلك بالمثال التالي: المنتج في نظامنا له تحديد الموقع الجغرافي مع خطوط الطول والعرض ، والتي يتم تخزينها في مجالين مختلفين من جدول المنتجات في خدمة المنتجات. يمكن "نقل" جميع المنتجات ، لذلك سيكون لدينا أحداث المجال لتقديم هذا التحديث. في السابق ، كان لدينا حدثان: product_latitude_updated و product_longitude_updated ، وهو ما لم يكن له معنى كبير إذا لم تكن غرابًا على رقعة الشطرنج. في هذه الحالة ، ستكون الأحداث product_location_updated أكثر منطقية.أو product_moved .


الرخ قطعة شطرنج. كان يطلق عليه جولة. يمكن أن يتحرك الرخ عموديًا أو أفقيًا فقط من خلال أي عدد من الحقول غير المشغولة.

7. نقص أدوات التصحيح
نحن في letgo ننتج الآلاف من أحداث النطاق في الدقيقة. تصبح كل هذه الأحداث موردًا مفيدًا للغاية لفهم ما يحدث في نظامنا ، أو تسجيل نشاط المستخدم ، أو حتى إعادة بناء حالة النظام في وقت محدد باستخدام البحث عن الأحداث. نحن بحاجة إلى استخدام هذا المورد بمهارة ، ولهذا نحتاج إلى أدوات لفحص وتصحيح أحداثنا. يمكن أن تكون الطلبات مثل "عرض جميع الأحداث التي أنشأها John Doe في آخر 3 ساعات" مفيدة أيضًا في الكشف عن الاحتيال. لهذه الأغراض ، قمنا بتطوير بعض الأدوات على ElasticSearch و Kibana و S3.

8. قلة مراقبة الأحداث
يمكننا استخدام أحداث المجال لاختبار صحة النظام. عندما نقوم بنشر شيء ما (والذي يحدث عدة مرات في اليوم حسب الخدمة) ، نحتاج إلى أدوات للتحقق بسرعة من العملية الصحيحة. على سبيل المثال ، إذا نشرنا إصدارًا جديدًا من خدمة المنتجات عند الإنتاج ورأينا انخفاضًا في عدد الأحداث التي تم نشرها على product_pub20٪ ، من الآمن القول أننا كسرنا شيئًا ما. نحن نستخدم حاليًا InfluxDB و Grafana و Prometheus لتحقيق ذلك من خلال الوظائف المشتقة. إذا تذكرت مسار الرياضيات ، فسوف تفهم أن مشتق الدالة f (x) عند النقطة x يساوي ظل الزاوية المماس المرسومة على الرسم البياني للدالة عند هذه النقطة. إذا كان لديك وظيفة لنشر سرعة حدث معين في منطقة موضوعية وأخذت مشتقًا منه ، فسوف ترى قمم هذه الوظيفة ويمكنك تعيين الإشعارات بناءً عليها. باستخدام هذه الإشعارات ، يمكنك تجنب عبارات مثل "تحذيرني إذا قمنا بنشر أقل من 200 حدث في الثانية لمدة 5 دقائق" والتركيز على تغيير كبير في سرعة النشر.


حدث شيء غريب هنا ... أو ربما أنها مجرد حملة تسويقية

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

  • : , « , », - , . letgo Data, Backend.
  • : - . , , , message bus . – , , . , user_registered Users, , MySQL, user_id . user_registered, , . , , - MySQL ( , 30 ). -, DynamoDB. , , , . , , , , .

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

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

نراكم على بالطبع!

All Articles