رسلان أروماتوف ، كبير المطورين ، ICD
مرحبا يا هبر! أعمل كمطور خلفية في بنك موسكو الائتماني ، وخلال عملي اكتسبت بعض الخبرة التي أود مشاركتها مع المجتمع. سأخبرك اليوم كيف كتبنا خدمة التخزين المؤقت الخاصة بنا للخوادم الأمامية لعملائنا باستخدام تطبيق الهاتف المحمول MKB Online. قد تكون هذه المقالة مفيدة لأولئك الذين يشاركون في تصميم الخدمة وعلى دراية بهندسة الخدمات الصغيرة وقاعدة بيانات Tarantool في الذاكرة ومكتبة ZeroMQ. في المقالة ، لن يكون هناك عملياً أمثلة على الكود وشرح الأساسيات ، ولكن فقط وصف لمنطق الخدمات وتفاعلها مع مثال محدد يعمل على معركتنا منذ أكثر من عامين.
كيف بدأ كل شيء
منذ حوالي 6 سنوات ، كان المخطط بسيطًا. كإرث من شركة الاستعانة بمصادر خارجية ، حصلنا على اثنين من عملاء الخدمات المصرفية عبر الهاتف المحمول لنظامي التشغيل iOS و android ، بالإضافة إلى خادم أمامي يخدمهم. تمت كتابة الخادم نفسه بلغة java ، ثم انتقل إلى الواجهة الخلفية بطرق مختلفة (بشكل أساسي الصابون) ، وتم التواصل مع العملاء من خلال إرسال XML عبر https.
تمكنت تطبيقات العملاء من المصادقة بطريقة ما ، وعرض قائمة بالمنتجات و ... يبدو أنها كانت قادرة على إجراء بعض التحويلات والمدفوعات ، ولكن في الواقع لم يفعلوا ذلك جيدًا وليس دائمًا. لذلك ، لم يواجه الخادم الأمامي عددًا كبيرًا من المستخدمين أو أي حملات جدية (والتي ، مع ذلك ، لم تمنعه من الانخفاض مرة واحدة كل يومين).
من الواضح أننا (وفي ذلك الوقت كان فريقنا مكونًا من أربعة أشخاص) ، بوصفنا المسؤولين عن بنك الهاتف المحمول ، لم نلائم هذا الموقف ، وللبداية وضعنا ترتيب التطبيقات الحالية ، ولكن تبين أن الخادم الأمامي كان سيئًا للغاية ، لذلك كان يجب أن يكون أعد كتابة الكل بسرعة ، واستبدل xml في وقت واحد بـ json والانتقال إلى خادم تطبيق
WildFly . تمتد عملية إعادة البناء على مدار عامين ، ولا تعتمد على وظيفة منفصلة ، حيث تم كل شيء بشكل أساسي لضمان عمل النظام بثبات.
تدريجيا ، بدأت التطبيقات والخوادم المطورة ، تعمل بشكل أكثر استقرارا ، وتوسعت وظائفها باستمرار ، مما أدى إلى ثمارها - كان هناك المزيد والمزيد من المستخدمين.
في الوقت نفسه ، بدأت قضايا مثل التسامح مع الخطأ والتكرار وتكرار التفكير - الحمل الثقيل في الظهور.
كان الحل السريع للمشكلة هو إضافة خادم WildFly ثانٍ ، وتعلمت التطبيقات التبديل بينها. تم حل مشكلة العمل المتزامن مع جلسات العميل من خلال وحدة
Infinispan المدمجة في WildFly.
يبدو أن الحياة تتحسن ...
لا يمكنك العيش هكذا
ومع ذلك ، فإن هذا الخيار للعمل مع جلسات في الواقع لم يكن دون عيوب. سأذكر تلك التي لم تناسبنا.
- فقدان الجلسات. أهم ناقص. على سبيل المثال ، يرسل أحد التطبيقات طلبين إلى server-1: الطلب الأول هو المصادقة ، والثاني هو طلب للحصول على قائمة بالحسابات. المصادقة ناجحة ، يتم إنشاء جلسة على الخادم 1. في هذا الوقت ، ينقطع طلب العميل الثاني فجأة بسبب ضعف التواصل ، ويتحول التطبيق إلى server-2 ، ويعيد إرسال الطلب الثاني. ولكن في عبء عمل معين ، قد لا يتوفر لدى Infinispan وقت لمزامنة البيانات بين العقد. نتيجة لذلك ، يتعذر على server-2 التحقق من جلسة العميل ، ويرسل استجابة غاضبة إلى العميل ، ويكون العميل حزينًا وينتهي دورته. يجب على المستخدم تسجيل الدخول مرة أخرى. حزين.
- يمكن أن تؤدي إعادة تشغيل الخادم أيضًا إلى فقد الجلسات. على سبيل المثال ، بعد التحديث (وهذا يحدث في كثير من الأحيان). عند بدء تشغيل server-2 ، لا يمكن أن يعمل حتى تتم مزامنة البيانات مع server-1. يبدو أن الخادم بدأ ، ولكن في الواقع لا ينبغي قبول الطلبات. هذا غير مريح.
- هذه وحدة WildFly مدمجة تمنعنا من الابتعاد عن خادم التطبيقات هذا نحو الخدمات الصغيرة.
من هنا ، تم تشكيل قائمة ما نود لوحده بطريقة ما.
- نريد تخزين جلسات عمل العميل حتى يتمكن أي خادم (بغض النظر عن عددها) من الوصول مباشرة بعد الإطلاق.
- نريد تخزين أي بيانات عميل بين الطلبات (على سبيل المثال ، معلمات الدفع وكل ذلك).
- نريد حفظ أي بيانات تعسفية على مفتاح تعسفي بشكل عام.
- ونريد أيضًا استلام بيانات العميل قبل مرور المصادقة. على سبيل المثال ، تتم المصادقة على المستخدم ، وجميع منتجاته هناك ، جديدة ودافئة.
- ونحن نريد أن حجم وفقا للحمل.
- ثم قم بالتشغيل في عامل الميناء ، واكتب السجلات على مكدس واحد ، وقم بحساب المقاييس ، وما إلى ذلك ...
- أوه نعم ، وهكذا كل شيء يعمل بسرعة.
دقيق الاختيار
في السابق ، لم نقم بتطبيق بنية الخدمات المصغرة ، لذلك كنا نجلس بداية لقراءة الخيارات المختلفة ومشاهدتها وتجربتها. كان من الواضح على الفور أننا نحتاج إلى مستودع سريع ونوع من الإضافات عليه يتعامل مع منطق العمل وهو واجهة الوصول إلى المستودع. بالإضافة إلى ذلك ، سيكون من الجيد ربط النقل السريع بين الخدمات.
اختاروا لفترة طويلة ، جادلوا كثيرا وجربوا. لن أصف الآن إيجابيات وسلبيات جميع المرشحين ، وهذا لا ينطبق على موضوع هذه المقالة ، أنا فقط أقول إن التخزين سيكون
tarantool ، وسوف نكتب خدمتنا في جافا ،
وسوف تعمل
ZeroMQ كوسيلة نقل. أنا حتى لا أزعم أن الخيار غامض للغاية ، لكن تأثر إلى حد كبير بحقيقة أننا لا نحب الأطر الكبيرة والثقيلة المختلفة (بسبب وزنها وبطئها) ، والحلول المعبأة في صناديق (بسبب تعدد استخداماتها وعدم وجود التخصيص) ، ولكن في نفس الوقت نحن نحب السيطرة على جميع أجزاء نظامنا قدر الإمكان. وللتحكم في عمل الخدمات ، اخترنا خادم جمع
بروميثيوس للقياسات مع الوكلاء المريحين الذين يمكن دمجهم في أي كود تقريبًا. سجلات كل هذا سوف تذهب إلى مكدس ELK.
حسنًا ، يبدو لي أن هناك الكثير من النظريات بالفعل.
البدء والانتهاء
كانت نتيجة التصميم مثل هذا المخطط تقريبًا.
مستودعيجب أن يكون الأمر غبيًا قدر الإمكان ، فقط لتخزين البيانات وحالتها الحالية ، ولكن العمل دائمًا دون إعادة التشغيل. مصممة لخدمة إصدارات مختلفة من الخوادم الأمامية. نبقي جميع البيانات في الذاكرة ، والانتعاش في حالة إعادة التشغيل من خلال ملفات .snap و .xlog.
جدول (مساحة) لجلسات العميل:
- معرف الجلسة
- هوية العميل
- نسخة (خدمة)
- وقت التحديث (الطابع الزمني) ؛
- وقت الحياة (ttl) ؛
- بيانات الجلسة المتسلسلة.
كل شيء بسيط هنا: العميل مصادق عليه ، ويقوم الخادم الأمامي بإنشاء جلسة وحفظها في التخزين ، مع تذكر الوقت. مع كل طلب بيانات ، يتم تحديث الوقت ، وبالتالي تظل الجلسة حية. إذا تبين أن البيانات قديمة (أو لن يكون هناك أي منها على الإطلاق) ، فسنقوم بإرجاع رمز إرجاع خاص ، وبعد ذلك سينهي العميل جلسته.
جدول ذاكرة التخزين المؤقت البسيط (لأي بيانات الجلسة):
- مفتاح.
- معرف الجلسة
- نوع البيانات المخزنة (رقم تعسفي) ؛
- وقت التحديث (الطابع الزمني) ؛
- وقت الحياة (ttl) ؛
- البيانات المتسلسلة.
جدول بيانات العميل الذي يجب أن يتم تسخينه قبل تسجيل الدخول:
- هوية العميل
- معرف الجلسة
- نسخة (خدمة)
- نوع البيانات المخزنة (رقم تعسفي) ؛
- وقت التحديث (الطابع الزمني) ؛
- الدولة،
- البيانات المتسلسلة.
مجال مهم هنا هو الشرط. في الواقع ، لا يوجد سوى اثنين منهم - الخمول والتحديث. يتم وضعها بواسطة خدمة شاملة تنتقل إلى الواجهة الخلفية لبيانات العميل بحيث لا يقوم مثيل آخر من هذه الخدمة بنفس العمل (عديم الفائدة بالفعل) ولا يقوم بتحميل الواجهة الخلفية.
جدول الجهاز:
- هوية العميل
- معرف الجهاز
- وقت التحديث (الطابع الزمني) ؛
يعد جدول الجهاز ضروريًا حتى قبل المصادقة على العميل في النظام ، اكتشف هويته وابدأ في استلام منتجاته (تسخين ذاكرة التخزين المؤقت). المنطق هو هذا: يكون المدخل الأول باردًا دائمًا ، لأنه قبل المصادقة لا نعرف نوع العميل الذي يأتي من جهاز غير مألوف (عملاء الأجهزة المحمولة ينقلون دائمًا معرفات الجهاز في أي طلبات). سيتم إرفاق جميع الإدخالات اللاحقة من هذا الجهاز بذاكرة تخزين مؤقت للإحماء للعميل المرتبط بها.
العمل مع البيانات معزول عن خدمة java بواسطة إجراءات الخادم. نعم ، كان علي أن أتعلم لوا ، لكن الأمر لم يستغرق الكثير من الوقت. بالإضافة إلى إدارة البيانات نفسها ، تعد lua-procedures أيضًا مسؤولة عن إعادة الحالات الحالية ، واختيار الفهرسة ، وتنظيف السجلات القديمة في عمليات الخلفية (الألياف) وتشغيل خادم الويب المدمج الذي يتم من خلاله الوصول المباشر إلى البيانات. ومن هنا - سحر كتابة كل شيء بيديك - إمكانية التحكم غير المحدود. لكن ناقص هو نفسه - تحتاج إلى كتابة كل شيء بنفسك.
يعمل Tarantool نفسه في حاوية الإرساء ، حيث يتم وضع جميع ملفات lua اللازمة في مرحلة تجميع الصور. التجميع كله من خلال البرامج النصية الدرجات.
تكرار السيد والعبد. على المضيف الآخر ، يتم تشغيل الحاوية نفسها بالضبط كنسخة متماثلة للتخزين الرئيسي. هناك حاجة إليها في حالة حدوث عطل طارئ للماجستير - ثم تتحول خدمات جافا إلى العبد ، ويصبح المعلم. هناك العبد الثالث فقط في القضية. ومع ذلك ، حتى فقدان البيانات بالكامل في حالتنا أمر محزن ، ولكن ليس قاتلاً. وفقًا لأسوأ سيناريو ، سيتعين على المستخدمين تسجيل الدخول واسترداد جميع البيانات التي تنتقل مرة أخرى إلى ذاكرة التخزين المؤقت.
خدمة جافاصممت لتكون خدمة نموذجية بدون جنسية. لا يحتوي على تهيئة ، يتم تمرير جميع المعلمات الضرورية (وهناك 6 منها) من خلال متغيرات البيئة عند إنشاء حاوية عامل الميناء. إنه يعمل مع الخادم الأمامي من خلال النقل ZeroMQ (org.zeromq.jzmq - واجهة java إلى libzmq.so.5.1.1 الأصلي ، والتي بنيناها نحن) باستخدام بروتوكولنا الخاص. وهو يعمل مع الرتيلاء من خلال موصل جافا (org.tarantool.connector).
تهيئة الخدمة بسيطة للغاية:
- نبدأ المسجل (log4j2) ؛
- من متغيرات البيئة (نحن في عامل الميناء) نقرأ المعلمات اللازمة للعمل ؛
- نبدأ خادم المقاييس (رصيف) ؛
- الاتصال الرتيلاء (بشكل غير متزامن) ؛
- نبدأ العدد المطلوب من مؤشرات الترابط (العمال) ؛
- نبدأ وسيط (zmq) - دورة معالجة الرسائل التي لا نهاية لها.
من بين كل ما سبق ، فإن محرك معالجة الرسائل فقط هو المثير للاهتمام. أدناه هو مخطط لل microservice.
لنبدأ مع بداية الوسيط. الوسيط لدينا عبارة عن مجموعة من المقابس zmq من النوع ROUTER ، والتي تقبل الاتصالات من عملاء مختلفين وتكون مسؤولة عن إرسال الرسائل الواردة منهم.
في حالتنا ، لدينا مأخذ استماع واحد على الواجهة الخارجية يستقبل رسائل من العملاء باستخدام بروتوكول tcp والآخر يقبل رسائل من مؤشرات ترابط العاملين باستخدام بروتوكول inproc (أسرع بكثير من tcp).
بعد تهيئة المقابس ، نبدأ حلقة حدث لا نهاية لها.
public int run() { int status; try { ZMQ.Poller poller = new ZMQ.Poller(2); poller.register(workerServicePoint, ZMQ.Poller.POLLIN); poller.register(clientServicePoint, ZMQ.Poller.POLLIN); int rc; while (true) {
منطق العمل بسيط للغاية: نتلقى رسائل من أماكن مختلفة ونفعل شيئًا معهم. إذا حدث شيء ما بشكل خطير معنا ، فإننا نخرج من الحلقة ، مما يؤدي إلى تعطل العملية ، والتي ستتم إعادة تشغيلها تلقائيًا بواسطة البرنامج الخفي للموصل.
الفكرة الرئيسية هي أن الوسيط لا يتعامل مع أي منطق تجاري ، فهو يحلل فقط رأس الرسالة ويوزع المهام على مؤشرات ترابط العامل التي تم إطلاقها في وقت مبكر عندما بدأت الخدمة. في هذا ، يساعده قائمة انتظار رسالة واحدة مع إعطاء الأولوية لطول ثابت.
دعنا نحلل الخوارزمية باستخدام مثال المخطط والكود أعلاه.
بعد البدء ، تتم تهيئة عمال الخيط الذين بدأوا بعد السمسار وإرسال رسالة استعداد إلى الوسيط. يقبلها الوسيط ويحللها ويضيف كل عامل إلى القائمة.
حدث يحدث على مأخذ توصيل العميل - تلقينا message1. يستدعي الوسيط معالج الرسائل الواردة ، وتتمثل مهمته في:
- تحليل رأس الرسالة ؛
- وضع رسالة في كائن حامل مع أولوية معينة (بناءً على تحليل الرأس) وعمرها ؛
- وضع الحامل في قائمة انتظار الرسائل ؛
- إذا لم تكن قائمة الانتظار ممتلئة ، فإن مهمة المعالج قد انتهت ؛
- إذا كانت قائمة الانتظار ممتلئة ، فإننا ندعو الطريقة لإرسال رسالة خطأ إلى العميل.
في نفس تكرار الحلقة ، نسميها معالج قائمة انتظار الرسائل:
- نطلب أحدث رسالة من قائمة الانتظار (قائمة الانتظار تقرر ذلك من تلقاء نفسها بناءً على الأولوية وترتيب إضافة الرسالة) ؛
- تحقق من عمر الرسالة (إذا انتهت صلاحيتها ، فاتصل بالطريقة لإرسال رسالة خطأ إلى العميل) ؛
- إذا كانت رسالة المعالجة ذات صلة ، فحاول إعداد أول عامل حر جاهز للعمل ؛
- إذا لم يكن هناك أي شيء ، ضع الرسالة في قائمة الانتظار (بتعبير أدق ، لا تحذفها من هناك ، فسوف تتوقف هناك حتى تنتهي مدة صلاحيتها) ؛
- إذا كان لدينا عامل جاهز للعمل ، فإننا نحتفل به على أنه مشغول ونرسل إليه رسالة للمعالجة ؛
- حذف الرسالة من قائمة الانتظار.
نحن نفعل الشيء نفسه مع جميع الرسائل اللاحقة. تم تصميم عامل مؤشر الترابط نفسه بنفس طريقة وسيط - لديه نفس دورة معالجة الرسائل التي لا نهاية لها. ولكن لم نعد بحاجة إلى المعالجة الفورية ، فقد صُمم لأداء مهام طويلة.
بعد أن أنهى العامل مهمته (على سبيل المثال ، ذهب إلى الواجهة الخلفية لمنتجات العميل أو في الرتيلاء للدورة) ، يرسل رسالة إلى الوسيط ، والتي يرسلها الوسيط إلى العميل. يتم تذكر عنوان العميل الذي يجب إرسال الإجابة إليه منذ اللحظة التي تصل فيها الرسالة من العميل في كائن حامل ، والتي يتم إرسالها إلى العامل كرسالة بتنسيق مختلف قليلاً ، ثم يعود مرة أخرى.
تنسيق الرسائل التي أذكرها باستمرار هو إنتاجنا الخاص. من خارج الصندوق ، يزودنا ZeroMQ بفئات ZMsg - الرسالة نفسها ، و ZFrame - جزء من هذه الرسالة ، في الأساس مجرد صفيف من البايتات ، وأنا حر في استخدامه إذا كانت هناك مثل هذه الرغبة. تتكون رسالتنا من جزأين (جزئين ZFrames) ، أولهما عبارة عن عنوان ثنائي ، والثاني هو البيانات (نص الطلب ، على سبيل المثال ، في شكل سلسلة json ممثلة بمجموعة من البايتات). رأس الرسالة عالمي وينتقل من عميل إلى خادم ومن خادم إلى آخر.
في الواقع ، ليس لدينا مفهوم "طلب" أو "استجابة" ، فقط الرسائل. يحتوي الرأس على: إصدار البروتوكول ، ونوع النظام (أي النظام الذي يتم تناوله) ، ونوع الرسالة ، ورمز خطأ مستوى النقل (إذا لم يكن 0 ، حدث شيء في مشغل نقل الرسائل) ، معرف الطلب (معرف التمريري قادم من العميل - ضرورية للتتبع) ، معرف جلسة العميل (اختياري) ، وكذلك علامة على وجود خطأ في مستوى البيانات (على سبيل المثال ، إذا تعذر تحليل استجابة الواجهة الخلفية ، فسنقوم بتعيين هذه العلامة بحيث لا يقوم المحلل على جانب العميل بإلغاء تسلسل الاستجابة ، ولكنه يتلقى بيانات الخطأ بطريقة أخرى).
بفضل بروتوكول واحد بين جميع الخدمات الصغيرة ومثل هذا العنوان ، يمكننا ببساطة التعامل مع مكونات خدماتنا. على سبيل المثال ، يمكنك نقل الوسيط إلى عملية منفصلة وجعله وسيطًا للرسائل على مستوى نظام الخدمات الجزئية بأكمله. أو ، على سبيل المثال ، قم بتشغيل العمال ليس في شكل مؤشرات ترابط داخل العملية ، ولكن كعمليات مستقلة منفصلة. وبينما لا يتغير رمز داخلها. بشكل عام ، هناك مجال للإبداع.
قليلا عن الأداء والموارد
الوسيط نفسه سريع ، والنطاق الترددي الكلي للخدمة محدود بسبب سرعة الواجهة الخلفية وعدد العمال. ملائمًا ، يتم تخصيص كل المساحة اللازمة من الذاكرة فورًا في بداية الخدمة ، كما يتم تشغيل كافة مؤشرات الترابط على الفور. حجم قائمة الانتظار ثابت أيضًا. في وقت التشغيل ، تتم معالجة الرسائل فقط.
كمثال: بالإضافة إلى مؤشر الترابط الرئيسي ، تطلق خدمة مكافحة التخزين المؤقت الحالية لدينا 100 مؤشر ترابط عامل آخر ، وحجم قائمة الانتظار يقتصر على ثلاثة آلاف رسالة. في التشغيل العادي ، يعالج كل مثيل ما يصل إلى 200 رسالة في الثانية ويستهلك حوالي 250 ميغابايت من الذاكرة وحوالي 2-3٪ من وحدة المعالجة المركزية. في بعض الأحيان في ذروة الأحمال يقفز إلى 7-8 ٪. كل شيء يعمل على نوع من زيون الظاهري ثنائي النواة.
يتضمن العمل المنتظم للخدمة الاستخدام المتزامن لـ 3-5 عمال (من أصل 100) مع عدد الرسائل في قائمة الانتظار 0 (أي ، يذهبون إلى المعالجة على الفور). إذا بدأت الواجهة الخلفية في التباطؤ ، يزداد عدد العمال المشغولين بما يتناسب مع وقت استجابتها. في الحالات التي يحدث فيها حادث وترتفع الواجهة الخلفية ، ينتهي كل العمال أولاً ، وبعد ذلك تبدأ قائمة انتظار الرسائل بالانسداد. عندما تسد تمامًا ، نبدأ في الاستجابة للعملاء برفضهم للمعالجة. في الوقت نفسه ، لا نبدأ في تناول موارد الذاكرة أو وحدة المعالجة المركزية ، حيث نعطي المقاييس بشكل ثابت ونستجيب للعملاء بوضوح لما يحدث.
تُظهر لقطة الشاشة الأولى التشغيل المنتظم للخدمة.
وفي الحالة الثانية ، وقع حادث - لم تستجب الواجهة الخلفية لسبب ما في 30 ثانية. في البداية ، نفد جميع العمال ، وبعد ذلك بدأت قائمة انتظار الرسائل تسد.
اختبارات الأداء
الاختبارات الاصطناعية على جهاز العمل الخاص بي (CentOS 7 ، Core i5 ، 16GB RAM) أظهرت ما يلي.
العمل مع المستودع (الكتابة إلى الرتيلاء وقراءة هذا السجل على الفور من 100 بايت - محاكاة العمل مع الدورة) - 12000 دورة في الدقيقة.
نفس الشيء ، تم قياس السرعة فقط ليس بين الخدمة - نقاط الرتيلاء ، ولكن بين العميل والخدمة. بالطبع ، اضطررت إلى كتابة عميل لاختبار الضغط نفسي. ضمن جهاز واحد ، كان من الممكن الحصول على 7000 روبية في الثانية. على شبكة محلية (ولدينا العديد من الأجهزة الافتراضية المختلفة غير الواضحة حول مدى الاتصال المادي) ، تختلف النتائج ، ولكن يصل إلى 5000 rps لمثيل واحد ممكن تمامًا. الله يعلم أي نوع من الأداء ، لكنه يغطي أكثر من عشر مرات أحمالنا القصوى. وهذا فقط إذا تم تشغيل مثيل واحد من الخدمة ، ولكن لدينا العديد منها ، وفي أي وقت يمكنك تشغيل ما تشاء. عندما تمنع الخدمات سرعة التخزين ، سيكون من الممكن توسيع الرتيلاء أفقياً (قشرة على أساس معرف العميل ، على سبيل المثال).
خدمة المخابرات
ربما يطرح القارئ اليقظ السؤال بالفعل - ما هي "ذكاء" هذه الخدمة ، المذكورة في العنوان. لقد سبق أن ذكرت ذلك مرارًا ، لكن الآن سأخبرك المزيد.
كانت إحدى المهام الرئيسية للخدمة هي تقليل الوقت المستغرق لإصدار منتجاتها للمستخدمين (قوائم الحسابات ، والبطاقات ، والودائع ، والقروض ، وحزم الخدمات ، وما إلى ذلك) مع تقليل التحميل على الواجهة الخلفية (تقليل عدد الطلبات في Oracle الكبيرة والثقيلة) بسبب التخزين المؤقت في الرتيلاء.
وقد فعلها جيدًا. منطق تسخين ذاكرة التخزين المؤقت العميل كما يلي:
- يقوم المستخدم بتشغيل تطبيق الهاتف المحمول ؛
- يتم إرسال طلب AppStart يحتوي على معرف الجهاز إلى الخادم الأمامي ؛
- يرسل الخادم الأمامي رسالة مع هذا المعرف إلى خدمة ذاكرة التخزين المؤقت ؛
- تبحث الخدمة في جدول الجهاز عن معرف العميل لهذا الجهاز ؛
- إذا لم يكن هناك ، لم يحدث شيء (لم يتم إرسال الاستجابة ، لا ينتظر الخادم ذلك) ؛
- إذا تم تحديد هوية العميل ، يقوم العامل بإنشاء مجموعة من الرسائل لتلقي قوائم منتجات المستخدم التي يتم معالجتها فورًا بواسطة الوسيط ويتم توزيعها على العمال في الوضع العادي ؛
- يرسل كل عامل طلبًا لنوع معين من البيانات إلى المستخدم ، مع وضع "التحديث" في قاعدة البيانات (هذه الحالة تحمي الواجهة الخلفية من تكرار نفس الطلبات إذا كانت تأتي من مثيلات أخرى من الخدمة) ؛
- بعد تلقي البيانات ، يتم تسجيلها في الرتيلاء.
- يقوم المستخدم بتسجيل الدخول إلى النظام ، ويرسل التطبيق طلبات لتلقي منتجاته ، ويرسل الخادم هذه الطلبات في شكل رسائل إلى خدمة ذاكرة التخزين المؤقت ؛
- إذا تم استلام بيانات المستخدم بالفعل ، فنحن ببساطة نرسلها من ذاكرة التخزين المؤقت ؛
- إذا كانت البيانات في طور استلامها (حالة "التحديث") ، عندئذٍ تبدأ دورة انتظار البيانات داخل العامل (تساوي مهلة الطلب في الواجهة الخلفية) ؛
- بمجرد استلام البيانات (أي ، حالة هذا السجل (tuple) في الجدول تذهب إلى "خاملاً" ، ستقدم الخدمة للعميل ؛
- إذا لم يتم استلام البيانات خلال فترة زمنية معينة ، فسيتم إرجاع خطأ إلى العميل.
وبالتالي ، في الممارسة العملية ، تمكنا من تقليل متوسط الوقت لاستلام المنتجات للخادم الأمامي من 200 مللي ثانية إلى 20 مللي ثانية ، أي حوالي 10 مرات ، وعدد الطلبات إلى الواجهة الخلفية بنحو 4 مرات.
المشاكل
تعمل خدمة ذاكرة التخزين المؤقت في المعركة منذ حوالي عامين وتلبي احتياجاتنا حاليًا.
بالطبع ، لا تزال هناك مشاكل لم يتم حلها ، وأحيانًا تحدث مشكلات. خدمات Java في المعركة لم تسقط بعد. سقط الرتيلاء عدة مرات على SIGSEGV ، لكنه كان بعض الإصدارات القديمة ، وبعد التحديث لم يحدث مرة أخرى. أثناء اختبار الإجهاد ، يسقط التكرار ، حدث أنبوب مكسور على السيد ، وبعد ذلك سقط العبد ، على الرغم من استمرار السيد في العمل. تقرر إعادة تشغيل الرقيق.
بمجرد حدوث نوع من الحوادث في مركز البيانات ، واتضح أن نظام التشغيل (CentOS 7) توقف عن رؤية محركات الأقراص الصلبة. ذهب نظام الملفات في وضع القراءة فقط. الشيء الأكثر إثارة للدهشة هو أن الخدمات استمرت في العمل ، حيث نحتفظ بجميع البيانات الموجودة في الذاكرة. لم يتمكن الرتيلاء من كتابة ملفات .xlog ، ولم يقم أي شخص بتسجيل أي شيء ، ولكن بطريقة ما كان كل شيء يعمل. لكن محاولة إعادة التشغيل لم تكلل بالنجاح - لم يتمكن أحد من البدء.
هناك مشكلة كبيرة لم يتم حلها ، وأود أن أستمع إلى رأي المجتمع بشأن هذا الموضوع. عندما تعطل الرتيلاء الرئيسي ، يمكن أن تتحول خدمات جافا إلى الرقيق ، والتي تستمر في العمل كسيد. ومع ذلك ، يحدث هذا فقط إذا تعطل الرئيسي ولم يتمكن من العمل.
افترض أن لدينا 3 حالات من الخدمة التي تعمل مع البيانات على الرتيلاء الرئيسي. الخدمات نفسها لا تسقط ، النسخ المتماثل لقاعدة البيانات يجري ، كل شيء على ما يرام. ولكن فجأة لدينا شبكة تتفكك بين العقدة 1 والعقدة 4 ، حيث يعمل المعالج. Service-1 بعد عدد من المحاولات الفاشلة تقرر التبديل إلى قاعدة بيانات النسخ الاحتياطي ويبدأ إرسال الطلبات هناك.
مباشرة بعد ذلك ، يبدأ عبقر الرتيلاء في قبول طلبات تعديل البيانات ، ونتيجة لذلك ينهار النسخ المتماثل من الرئيسي ، ونحصل على بيانات غير متسقة. في الوقت نفسه ، تعمل الخدمتان 2 و 3 بشكل مثالي مع السيد ، وتتصل الخدمة 1 بشكل جيد مع العبد السابق. من الواضح أننا في هذه الحالة نبدأ في فقدان جلسات العميل وأي بيانات أخرى ، على الرغم من أن كل شيء يعمل من الجانب التقني. لم نحل بعد مثل هذه المشكلة المحتملة. لحسن الحظ ، لم يحدث هذا منذ عامين ، لكن الوضع حقيقي تمامًا. الآن كل خدمة تعرف عدد المتجر الذي تذهب إليه ، ولدينا تنبيه لهذا المقياس ، والذي سيعمل عند التبديل من السيد إلى العبد. وعليك إصلاح كل شيء بيديك. كيف يمكنك حل هذه المشاكل؟
خطط
نحن نخطط للعمل على حل المشكلة الموضحة أعلاه ، والحد من عدد العمال المشغولين في وقت واحد بنوع واحد من الطلب ، وإيقاف خدمة آمنة (دون فقدان الطلبات الحالية) ، وتلميع إضافي.
استنتاج
ربما كان هذا هو كل شيء ، على الرغم من أنني مررت بالموضوع بشكل سطحي إلى حد ما ، ولكن يجب أن يكون المنطق العام للعمل واضحًا. لذلك ، إن أمكن ، أنا مستعد للرد في التعليقات. لقد وصفت بإيجاز كيف يعمل نظام فرعي صغير واحد لخوادم البنك الأمامية لخدمة عملاء الأجهزة المحمولة.
إذا كان الموضوع يهم المجتمع ، فعندئذ يمكنني إخبارك بالعديد من حلولنا التي تسهم في تحسين جودة خدمة العملاء للبنك.