الذهاب إلى الذهاب! كيف استغرق فريق PHP لكتابة الخدمات الصغيرة

مرحبا بالجميع! اسمي أليكسي Skorobogaty ، أنا مهندس نظام في Lamoda. في فبراير 2019 ، تحدثت في Go Meetup بينما كنت لا أزال في منصب قيادة الفريق للفريق الأساسي. أود اليوم تقديم نسخة من تقريري ، والتي يمكنك أن ترى أيضًا .


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


صورة


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


صورة


داخل كل هذا هو منطق multicriteria المعقدة. كتل تتفاعل مع بعضها البعض وتؤثر على بعضها البعض. التغييرات المستمرة والمستمرة من العمل تزيد من تعقيد المعايير. بالإضافة إلى ذلك ، لدينا منصات مختلفة يمكن للعملاء من خلالها إنشاء أوامر: موقع الويب والتطبيقات ومركز الاتصال ومنصة B2B. بالإضافة إلى معايير SLA / MTTI / MTTR الصارمة (مقاييس التسجيل وحل الحوادث). كل هذا يتطلب مرونة عالية والاستقرار من الخدمة.


التراث المعماري


كما سبق أن قلت ، في وقت تشكيل فريقنا ، كان نظام معالجة الطلبات متراصة - ما يقرب من 100 ألف سطر من الكود الذي يصف منطق العمل مباشرة. تم كتابة الجزء الرئيسي في عام 2011 باستخدام بنية MVC الكلاسيكية متعددة الطبقات. كان يعتمد على PHP (إطار عمل ZF1) ، الذي امتلأ تدريجياً باستخدام المحولات ومكونات symfony للتفاعل مع مختلف الخدمات. خلال وجوده ، كان لدى النظام أكثر من 50 مساهمًا ، وعلى الرغم من أننا تمكنا من الحفاظ على نمط موحد لكود الكتابة ، فقد فرض هذا أيضًا قيوده. بالإضافة إلى ذلك ، نشأ عدد كبير من السياقات المختلطة - لأسباب مختلفة ، تم تنفيذ بعض الآليات في النظام التي لم تكن مرتبطة مباشرة بمعالجة الطلبات. كل هذا أدى إلى حقيقة أنه في الوقت الحالي لدينا قاعدة بيانات MySQL أكبر من 1 تيرابايت.


من الناحية التخطيطية ، يمكن تمثيل البنية الأولية على النحو التالي:


صورة


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


لذلك ، قررنا نقل سياق "طلب العميل" من نظام "معالجة الطلبات" إلى خدمة ميكروية منفصلة ، والتي كانت تسمى "إدارة الطلبات".


صورة


المتطلبات والأدوات


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


  • إنتاجية
  • اتساق البيانات
  • استقرار
  • القدرة على التنبؤ
  • شفافية
  • زيادة التغيير

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


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


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


البنية التحتية والأدوات الحالية. لسنا أول فريق في لامودا يبدأ تنفيذ تطبيق Go ؛ قبلنا كان هناك رواد - فريق Go نفسه ، الذي أعد البنية التحتية والأدوات:


  1. Gogi (swagger) هو مولد مواصفات swagger.
  2. Gonkey (اختبار) - للاختبارات الوظيفية.
  3. نحن نستخدم Json-rpc وإنشاء عميل / خادم ملزم عن طريق التباهي. نقوم أيضًا بنشر كل هذا على Kubernetes ، وجمع المقاييس في Prometheus ، واستخدام ELK / Jaeger للبحث عن المفقودين - يتم تضمين كل هذا في الحزمة التي ينشئها Gogi لكل خدمة ميكروية جديدة بالمواصفات.

هذا ما تبدو عليه الخدمة المصغرة لإدارة الطلبات الجديدة:


صورة


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


تحول نموذجي


اختيار الذهاب ، حصلنا على الفور العديد من المزايا:


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

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


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

نتيجة لذلك ، توصلنا إلى أن Go هي لغة برمجة إجرائية.
صورة


البيانات أولا


كنت أفكر في كيفية تصور المشكلة التي نواجهها ووجدت هذه الصورة:


صورة


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


لقد تخلينا عن هذا النهج ، ووضعنا الكيانات في المقام الأول ، دون أن نغفلها التجريد.


الاستدلال بهذه الطريقة ، نضع البيانات في المقام الأول ، وحصلنا على مثل هذه الأنابيب في الخدمة:


صورة


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


العودة إلى المستقبل


فجأة ، تطوير الخدمات الصغيرة ، توصلنا إلى نموذج البرمجة من 70s. بعد سبعينيات القرن العشرين ، نشأت تكتلات كبيرة للمؤسسات ، حيث ظهرت البرمجة الموجهة للكائنات ، والبرمجة الوظيفية - التجريدات الكبيرة التي مكنت من الاحتفاظ بالكود في هذه التجمعات. في الخدمات المصغرة ، لا نحتاج إلى كل هذا ، ويمكننا استخدام النموذج الممتاز لـ CSP ( التواصل بالعمليات المتسلسلة ) ، التي طرحها تشارلز جوير في سبعينيات القرن العشرين.


نستخدم أيضًا التسلسل / التحديد / التفاعل - نموذج البرمجة الهيكلية الذي يمكن من خلاله أن يتكون كل رمز البرنامج من هياكل التحكم المقابلة.


حسنا ، البرمجة الإجرائية ، والتي كانت السائدة في 70s :)


هيكل المشروع


صورة


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


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


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


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


صورة


وبالتالي ، بالنسبة لواجهة برمجة التطبيقات الداخلية في معالجات الخدمة ، فإن التفاعل لا يتغير بأي طريقة.


استقرار


قررنا عدم أخذ أي مكتبات تابعة لجهات خارجية مقدمًا ، لأن البيانات التي نتعامل معها حساسة للغاية. لذا ، قمنا بتدوير القليل :) على سبيل المثال ، قمنا بتنفيذ بعض الآليات الكلاسيكية - من أجل Idempotency و Queue-worker و Fault Tolerance ومعاملات التعويض. خطوتنا التالية هي محاولة إعادة استخدامها. لف في المكتبات ، وربما في حاويات السيارات الجانبية في Kubernetes Pods. ولكن الآن يمكننا تطبيق هذه الأنماط.


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


الذهاب إلى الذهاب!


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


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


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


هل نحن بصدد إعادة كتابة كل شيء على الذهاب والتخلي عن PHP؟


لا ، نظرًا لأننا نتحرك بعيدًا عن احتياجات العمل ، وهناك بعض السياقات التي يناسبها PHP جيدًا - لا يحتاج إلى هذه السرعة ومجموعة أدوات Go-go بأكملها. تتم جميع عمليات التشغيل الآلي لتسليم الطلبات وإدارة استوديو الصور في PHP. ولكن ، على سبيل المثال ، في منصة التجارة الإلكترونية في جانب العميل ، كادنا نعيد كتابة كل شيء على Go ، لأن هناك ما يبرره.

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


All Articles