مرحبا يا هبر! اسمي بافيل ليبسكي. أنا مهندس ، أعمل في Sberbank-Technology. تخصصي هو اختبار التسامح مع الخطأ وأداء الواجهة الخلفية للأنظمة الموزعة الكبيرة. ببساطة ، أنا كسر برامج الآخرين. في هذا المنشور ، سأتحدث عن حقن الأخطاء - وهي طريقة اختبار تتيح لك العثور على مشاكل في النظام عن طريق إنشاء أعطال اصطناعية. سأبدأ بكيفية توصلت إلى هذه الطريقة ، ثم سنتحدث عن الطريقة نفسها وكيف نستخدمها.
سوف يكون المقال أمثلة جافا. إذا كنت لا تستخدم البرمجة بلغة Java - لا بأس ، ففهم فقط الطريقة نفسها والمبادئ الأساسية. يتم استخدام Apache Ignite كقاعدة بيانات ، ولكن نفس الأساليب تنطبق على أي قواعد بيانات أخرى. يمكن تنزيل جميع الأمثلة من
GitHub الخاص بي.
لماذا نحتاج كل هذا؟
سأبدأ بالقصة. في عام 2005 ، عملت لدى Rambler. بحلول ذلك الوقت ، كان عدد مستخدمي Rambler ينمو بسرعة ، وتوقف "خادم - قاعدة بيانات - خادم - تطبيقاتنا" عن الهندسة المعمارية من مستويين. فكرنا في كيفية حل مشاكل الأداء ، ولفتنا الانتباه إلى التكنولوجيا المحفوظة.

ما هو memcached؟ Memcached - جدول التجزئة في ذاكرة الوصول العشوائي مع الوصول إلى الأشياء المخزنة بواسطة مفتاح. على سبيل المثال ، تحتاج إلى الحصول على ملف تعريف المستخدم. يصل التطبيق إلى memcached (2). إذا كان هناك كائن فيه ، فيتم إعادته فورًا إلى المستخدم. إذا لم يكن هناك أي كائن ، فسيتم تقديم نداء إلى قاعدة البيانات (3) ، ويتم تشكيل الكائن ووضعه في memcached (4). ثم ، في المكالمة التالية ، لم نعد بحاجة إلى إجراء مكالمة غالية الثمن إلى قاعدة البيانات - سنحصل على كائن جاهز من ذاكرة الوصول العشوائي - memcached.
نظرًا لخلاصة memcached ، فقد قمنا بإلغاء تحميل قاعدة البيانات بشكل ملحوظ ، وبدأت تطبيقاتنا تعمل بشكل أسرع. ولكن ، كما اتضح ، كان من السابق لأوانه أن نفرح. جنبا إلى جنب مع زيادة الإنتاجية ، واجهنا تحديات جديدة.

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

الجزء الأخضر من العمود هو عدد عمليات السحب النقدي في أجهزة الصراف الآلي ، والجزء الأزرق هو عدد العمليات التي يجب دفعها مقابل السلع والخدمات. نرى أن عدد المعاملات غير النقدية يتزايد من سنة إلى أخرى. في غضون بضع سنوات ، سنحتاج إلى أن نكون قادرين على التعامل مع عبء العمل المتزايد ومواصلة تقديم خدمات جديدة لعملائنا. هذا هو أحد الأسباب لإنشاء نظام Sberbank IT جديد. بالإضافة إلى ذلك ، نود تقليل اعتمادنا على التقنيات الغربية والإطارات الرئيسية باهظة الثمن ، والتي تكلف ملايين الدولارات ، والتحول إلى التقنيات مفتوحة المصدر والخوادم المنخفضة النهاية.
في البداية ، وضعنا أساس تقنية Apache Ignite في قلب بنية سبيربنك الجديدة. بتعبير أدق ، نستخدم البرنامج المساعد Gridgain المدفوع. التكنولوجيا لديها وظيفة غنية إلى حد ما: فهي تجمع بين خصائص قاعدة البيانات العلائقية (هناك دعم لاستعلامات SQL) ، NoSQL ، المعالجة الموزعة وتخزين البيانات في ذاكرة الوصول العشوائي. علاوة على ذلك ، عند إعادة التشغيل ، لن تضيع البيانات الموجودة في ذاكرة الوصول العشوائي في أي مكان. بدءًا من الإصدار 2.1 ، قام Apache Ignite بتوزيع Apache Ignite Persistent Data Store بدعم SQL.
سأذكر بعض ميزات هذه التقنية:
- التخزين ومعالجة البيانات في ذاكرة الوصول العشوائي
- تخزين القرص
- دعم SQL
- تنفيذ المهمة الموزعة
- التحجيم الأفقي
التكنولوجيا جديدة نسبيًا ، لذا فهي تتطلب عناية خاصة.
يتكون نظام تكنولوجيا المعلومات الجديد في Sberbank من العديد من الخوادم الصغيرة نسبيًا التي يتم تجميعها في مجموعة سحابية واحدة. جميع العقد متطابقة في الهيكل ، الند للند ، أداء وظيفة تخزين ومعالجة البيانات.
داخل الكتلة تنقسم إلى ما يسمى الخلايا. خلية واحدة هي 8 العقد. يحتوي كل مركز بيانات على 4 نقاط.

بما أننا نستخدم Apache Ignite ، شبكة بيانات في الذاكرة ، وبالتالي ، يتم تخزين كل هذا في ذاكرة التخزين المؤقت الموزعة على الخادم. علاوة على ذلك ، يتم تقسيم المخابئ ، بدورها ، إلى أجزاء متطابقة - أقسام. على الخوادم ، يتم تمثيلها كملفات. يمكن تخزين أقسام ذاكرة التخزين المؤقت نفسها على خوادم مختلفة. لكل قسم في الكتلة ، هناك العقد الأساسية والعقد الاحتياطية.
تخزن العقد الرئيسية الأقسام الرئيسية وتعالج طلباتها ، وتنسخ البيانات إلى عقد النسخ الاحتياطي (عقدة النسخ الاحتياطي) ، حيث يتم تخزين أقسام النسخ الاحتياطي.
عند تصميم الهيكل الجديد لـ Sberbank ، توصلنا إلى استنتاج مفاده أن مكونات النظام يمكن أن تفشل. لنقل أنه إذا كان لديك مجموعة من 1000 خادم خوادم منخفضة الحديد ، فمن وقت لآخر سوف تواجه أعطال الأجهزة. ستفشل شرائط RAM وبطاقات الشبكة والأقراص الصلبة وما إلى ذلك. سوف نعتبر هذا السلوك سلوكًا طبيعيًا للنظام بالكامل. يجب التعامل مع مثل هذه المواقف بشكل صحيح ويجب ألا يلاحظ عملاؤنا ذلك.
لكن لا يكفي تصميم مقاومة النظام للفشل ؛ من الضروري اختبار الأنظمة أثناء هذه الإخفاقات. كما تقول Caitie McCaffrey من Microsoft Research ، باحثة أنظمة موزعة معروفة: "لن تعرف أبدًا كيف يتصرف النظام أثناء حدوث فشل طارئ حتى تقوم بإعادة إنتاج الفشل".
فقدت التحديثات
لنأخذ مثالاً بسيطًا ، تطبيق مصرفي يحاكي تحويل الأموال. سيتألف التطبيق من جزأين: خادم Apache Ignite وخادم Apache Ignite. جانب الخادم هو مستودع البيانات.
يتصل تطبيق العميل بخادم Apache Ignite. ينشئ ذاكرة تخزين مؤقت حيث يكون المفتاح هو معرف الحساب وتكون القيمة هي كائن الحساب. في المجموع ، سيتم تخزين عشرة من هذه الكائنات في ذاكرة التخزين المؤقت. في هذه الحالة ، سنضع مبدئيًا 100 دولار على كل حساب (بحيث يكون هناك شيء لنقله). وفقًا لذلك ، سيكون إجمالي الرصيد في جميع الحسابات مساويًا 1000 دولار.
CacheConfiguration<Integer, Account> cfg = new CacheConfiguration<>(CACHE_NAME); cfg.setAtomicityMode(CacheAtomicityMode.ATOMIC); try (IgniteCache<Integer, Account> cache = ignite.getOrCreateCache(cfg)) { for (int i = 1; i <= ENTRIES_COUNT; i++) cache.put(i, new Account(i, 100)); System.out.println("Accounts before transfers"); printAccounts(cache); printTotalBalance(cache); for (int i = 1; i <= 100; i++) { int pairOfAccounts[] = getPairOfRandomAccounts(); transferMoney(cache, pairOfAccounts[0], pairOfAccounts[1]); } } ... private static void transferMoney(IgniteCache<Integer, Account> cache, int fromAccountId, int toAccountId) { Account fromAccount = cache.get(fromAccountId); Account toAccount = cache.get(toAccountId); int amount = getRandomAmount(fromAccount.balance); if (amount < 1) { return; } fromAccount.withdraw(amount); toAccount.deposit(amount); cache.put(fromAccountId, fromAccount); cache.put(toAccountId, toAccount); }
ثم نقوم بإجراء 100 تحويل أموال عشوائي بين هذه الحسابات العشرة. على سبيل المثال ، يتم تحويل 50 دولارًا من الحساب أ إلى حساب آخر ب. من الناحية التخطيطية ، يمكن تمثيل هذه العملية على النحو التالي:

النظام مغلق ، تتم عمليات النقل داخليًا فقط ، أي يجب أن يظل الرصيد الكلي مساوي 1000 دولار.

قم بتشغيل التطبيق.
حصلنا على القيمة المتوقعة للرصيد الإجمالي - 1000 دولار. الآن دعنا نعقد طلبنا بعض الشيء - دعنا نجعله متعدد المهام. في الواقع ، يمكن أن تعمل العديد من تطبيقات العميل في نفس الوقت مع نفس الحساب. قم بتشغيل مهمتين من شأنها إجراء تحويلات مالية في وقت واحد بين عشرة حسابات.
CacheConfiguration<Integer, Account> cfg = new CacheConfiguration<>(CACHE_NAME); cfg.setAtomicityMode(CacheAtomicityMode.ATOMIC); cfg.setCacheMode(CacheMode.PARTITIONED); cfg.setIndexedTypes(Integer.class, Account.class); try (IgniteCache<Integer, Account> cache = ignite.getOrCreateCache(cfg)) {
الرصيد الكلي هو 1296 دولار. نبتهج العملاء ، البنك يعاني خسائر. لماذا حدث هذا؟

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

- أ - الذرية ، الذرية. سيتم إجراء كل التغييرات المقترحة على قاعدة البيانات ، أو لن يتم إجراء أي شيء. أي إذا حدث فشل بين الخطوتين 3 و 6 ، فلن تكون التغييرات في قاعدة البيانات
- ج - الاتساق والنزاهة. بعد اكتمال المعاملة ، يجب أن تظل قاعدة البيانات في حالة متسقة. في مثالنا ، هذا يعني أن مجموع A و B يجب أن يكون دائمًا كما هو ، والرصيد الإجمالي هو 1000 دولار.
- أنا - العزلة ، العزلة. يجب ألا تؤثر المعاملات على بعضها البعض. إذا أجرت إحدى المعاملات عملية تحويل ، وحصلت الأخرى على قيمة الحساب A و B بعد الخطوة 3 وحتى الخطوة 6 ، فإنها تعتقد أن النظام لديه أموال أقل من اللازم. هناك فروق دقيقة هنا سأركز عليها لاحقًا.
- د - المتانة بعد أن ارتكبت المعاملة تغييرات في قاعدة البيانات ، لا ينبغي أن تضيع هذه التغييرات كنتيجة للفشل.
لذلك ، في طريقة transferMoney ، سنقوم بتحويل الأموال في المعاملة.
private void transferMoney(int fromAccountId, int toAccountId) { try (Transaction tx = ignite.transactions().txStart()) { Account fromAccount = cache.get(fromAccountId); Account toAccount = cache.get(toAccountId); int amount = getRandomAmount(fromAccount.balance); if (amount < 1) { return; } int fromAccountBalanceBeforeTransfer = fromAccount.balance; int toAccountBalanceBeforeTransfer = toAccount.balance; fromAccount.withdraw(amount); toAccount.deposit(amount); cache.put(fromAccountId, fromAccount); cache.put(toAccountId, toAccount); tx.commit(); } catch (Exception e){ e.printStackTrace(); } }
قم بتشغيل التطبيق.
جلالة الملك لم المعاملات لا تساعد. الرصيد الكلي هو 6951 دولار! ما هي مشكلة هذا السلوك التطبيق؟
أولاً ، اختاروا نوع ذاكرة التخزين المؤقت ATOMIC ، أي بدون دعم المعاملات ACID:
CacheConfiguration<Integer, Account> cfg = new CacheConfiguration<>(CACHE_NAME); cfg.setAtomicityMode(CacheAtomicityMode.TOMIC);
ثانياً ، تحتوي طريقة txStart على معلمتين مهمتين من نوع التعداد سيكون من الجيد تحديدهما: طريقة القفل (وضع التزامن في Apache Ignite) ومستوى العزل. وفقًا لقيم هذه المعلمات ، يمكن للمعاملة قراءة البيانات وكتابتها بطرق مختلفة. في Apache Ignite ، يتم تعيين هذه المعلمات كما يلي:
try (Transaction tx = ignite.transactions().txStart( , )) { Account fromAccount = cache.get(fromAccountId); Account toAccount = cache.get(toAccountId); ... tx.commit(); }
يمكنك استخدام PESSIMISTIC (قفل متشائم) أو OPTIMISTIC (قفل متفائل) كقيمة للمعلمة LOCK METHOD. أنها تختلف في لحظة الحظر. عند استخدام PESSIMISTIC ، يتم فرض القفل في القراءة / الكتابة الأولى ويتم الاحتفاظ به حتى يتم الالتزام بالمعاملة. على سبيل المثال ، عندما تقوم معاملة ذات قفل متشائم بإجراء عملية نقل من الحساب "أ" إلى الحساب "ب" ، فلن تتمكن المعاملات الأخرى من قراءة أو كتابة قيم هذه الحسابات حتى يتم الالتزام بالمعاملة التي تجري عملية النقل. من الواضح أنه إذا كانت المعاملات الأخرى ترغب في الوصول إلى الحسابات A و B ، فإنها تضطر إلى الانتظار حتى تكتمل المعاملة ، مما يؤثر سلبًا على الأداء الكلي للتطبيق. لا يقيد القفل الأمثل الوصول إلى البيانات للمعاملات الأخرى ، ومع ذلك ، أثناء مرحلة إعداد المعاملة للالتزام (مرحلة الإعداد ، يستخدم Apache Ignite بروتوكول 2PC) ، سيتم إجراء فحص - هل تغيرت البيانات مع المعاملات الأخرى؟ وإذا حدثت تغييرات ، فسيتم إلغاء المعاملة. من حيث الأداء ، سيتم تشغيل OPTIMISTIC بشكل أسرع ، ولكنه مناسب أكثر للتطبيقات التي لا يوجد فيها تنافس مع البيانات.
تحدد المعلمة العزل مستوى درجة عزل المعاملات عن بعضها البعض. يحدد معيار SQL ANSI / ISO 4 أنواع من العزل ، ولكل مستوى عزل ، يمكن أن يؤدي سيناريو المعاملة نفسه إلى نتائج مختلفة.
- READ_UNCOMMITED هو أدنى مستوى للعزل. يمكن للمعاملات أن ترى بيانات "غير ملوثة" غير ملتزم بها.
- READ_COMMITTED - عندما ترى المعاملة داخل نفسها فقط البيانات الحساسة
- REPEATABLE_READ - يعني أنه إذا تمت القراءة داخل المعاملة ، فيجب أن تكون هذه القراءة قابلة للتكرار.
- SERIALIZABLE - يفترض هذا المستوى أقصى درجة من عزل المعاملة - كما لو لم يكن هناك مستخدمون آخرون في النظام. ستكون نتيجة المعاملات الموازية كما لو كانت قد نفذت بالترتيب (بالترتيب). ولكن مع درجة عالية من العزلة ، نحصل على انخفاض في الأداء. لذلك ، يجب أن تقترب بعناية من اختيار هذا المستوى من العزلة.
بالنسبة إلى العديد من نظم إدارة قواعد البيانات الحديثة (Microsoft SQL Server و PostgreSQL و Oracle) ، فإن مستوى العزل الافتراضي هو READ_COMMITTED. على سبيل المثال ، سيكون هذا الأمر فادحًا ، لأنه لن يحمينا من التحديثات المفقودة. ستكون النتيجة كما لو أننا لم نستخدم المعاملات على الإطلاق.

من
وثائق معاملات Apache Ignite ، من المناسب لنا استخدام مزيج من طريقة القفل ومستوى العزل:
- PESSIMISTIC REPEATABLE_READ - يتم فرض القفل على القراءة الأولى أو كتابة البيانات ويتم الاحتفاظ به حتى يتم الانتهاء منه.
- PESSIMISTIC SERIALIZABLE - يعمل بشكل مشابه لـ PESSIMISTIC REPEATABLE_READ
- OPTIMISTIC SERIALIZABLE - يتم تذكر إصدار البيانات التي تم الحصول عليها بعد القراءة الأولى ، وإذا كان هذا الإصدار مختلفًا أثناء مرحلة الإعداد للالتزام (تم تغيير البيانات بواسطة معاملة أخرى) ، فسيتم إلغاء المعاملة. لنجرب هذا الخيار.
private void transferMoney(int fromAccountId, int toAccountId) { try (Transaction tx = ignite.transactions().txStart(OPTIMISTIC, SERIALIZABLE)) { Account fromAccount = cache.get(fromAccountId); Account toAccount = cache.get(toAccountId); int amount = getRandomAmount(fromAccount.balance); if (amount < 1) { return; } int fromAccountBalanceBeforeTransfer = fromAccount.balance; int toAccountBalanceBeforeTransfer = toAccount.balance; fromAccount.withdraw(amount); toAccount.deposit(amount); cache.put(fromAccountId, fromAccount); cache.put(toAccountId, toAccount); tx.commit(); } catch (Exception e){ e.printStackTrace(); } }
هوراي ، حصلت على 1000 دولار ، كما هو متوقع. في المحاولة الثالثة.
اختبار تحت الحمل
الآن سنجعل اختبارنا أكثر واقعية - سنختبر تحت الحمل. وإضافة عقدة خادم إضافية. هناك العديد من الأدوات لإجراء اختبار الإجهاد ، في Sberbank نستخدم HP Performance Center. هذه أداة قوية إلى حد ما ، وتدعم أكثر من 50 بروتوكولًا ، وهي مصممة للفرق الكبيرة وتكلف الكثير من المال. كتبت مثالي على JMeter - إنه مجاني ويحل مشكلتنا بنسبة 100٪. لا أرغب في إعادة كتابة الكود في جافا ، لذلك سأستخدم أداة أخذ العينات JSR223.
سنقوم بإنشاء أرشيف JAR من فئات تطبيقنا وتحميله في خطة الاختبار. لإنشاء ذاكرة التخزين المؤقت ونشرها ، قم بتشغيل فئة CreateCache. بعد تهيئة ذاكرة التخزين المؤقت ، يمكنك تشغيل البرنامج النصي JMeter.
كل شيء رائع ، وحصلت على 1000 دولار.
عقدة العنقودية اغلاق الطوارئ
سنكون الآن أكثر تدميراً: أثناء تشغيل نظام المجموعة ، سوف نتعطل أحد عقدتي الخادم. من خلال الأداة المساعدة Visor ، المضمنة في حزمة Gridgain ، يمكننا مراقبة نظام Apache Ignite وعمل عينات بيانات مختلفة. في علامة تبويب SQL Viewer ، قم بتنفيذ استعلام SQL للحصول على الرصيد الكلي لجميع الحسابات.
ما هذا؟ 553 دولار. العملاء خائفون ، البنك يعاني من خسائر السمعة. ماذا فعلنا خطأ هذه المرة؟
تبين أن هناك أنواع ذاكرة التخزين المؤقت في Apache Ignite:
- مقسمة - يتم تخزين نسخة احتياطية واحدة أو عدة نسخ داخل المجموعة
- ذاكرات تخزين متماثلة - يتم تخزين كل الأقسام (جميع أجزاء ذاكرة التخزين المؤقت) داخل خادم واحد. هذه المخازن مناسبة بالدرجة الأولى للكتب المرجعية - وهو أمر نادرًا ما يتغير وغالبًا ما يتم قراءته.
- محلي - كل ذلك على عقدة واحدة

سنقوم غالبًا بتغيير بياناتنا ، لذلك سنختار ذاكرة التخزين المؤقت المقسمة ونضيف نسخة احتياطية إضافية إليها. وهذا هو ، سيكون لدينا نسختين من البيانات - الأساسي والنسخ الاحتياطي.
CacheConfiguration<Integer, Account> cfg = new CacheConfiguration<>(CACHE_NAME); cfg.setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL); cfg.setCacheMode(CacheMode.PARTITIONED); cfg.setBackups(1);
نطلق التطبيق. أذكرك أنه قبل التحويلات لدينا 1000 دولار. نبدأ وأثناء العملية "نطفئ" إحدى العقد
في الأداة المساعدة Visor ، نقوم بعمل استعلام SQL للحصول على رصيد إجمالي قدره 1000 دولار. عملت كل شيء عظيم!
حالات الموثوقية
قبل عامين ، بدأنا للتو في اختبار نظام Sberbank الجديد لتكنولوجيا المعلومات. بطريقة ما ذهبنا إلى المهندسين المرافقين لنا وسألنا: ما الذي يمكن أن ينكسر على الإطلاق؟ أجابونا: كل شيء يمكن أن يكسر ، يختبر كل شيء! بالطبع ، هذا الجواب لم يناسبنا. جلسنا معًا ، وقمنا بتحليل إحصائيات الفشل وأدركنا أن الحالة الأكثر احتمالًا التي قد نواجهها هي فشل العقدة.
علاوة على ذلك ، يمكن أن يحدث هذا لأسباب مختلفة تماما. على سبيل المثال ، قد يتعطل تطبيق ما أو تعطل JVM أو تعطل نظام التشغيل أو تعطل الأجهزة.

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

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

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

يحدث أن يختفي الاتصال بين مراكز البيانات. على سبيل المثال ، في العام الماضي ، بسبب تلف كابل الألياف الضوئية من قبل حفارة ، لم يجرِ عملاء مصارف Tochka و Otkrytie و Rocketbank المعاملات عبر الإنترنت لعدة ساعات ، ولم تقبل المحطات الطرفية البطاقات ولم تعمل أجهزة الصراف الآلي. لقد كتب الكثير عن هذا الحادث على تويتر.
في حالتنا ، يجب التعامل مع الوضع المنفصل عن المخ بشكل صحيح. تحدد الشبكة انقسام الدماغ - تقسيم الكتلة إلى جزأين. نصف يذهب إلى وضع القراءة. هذا هو النصف حيث يوجد المزيد من العقد الحية أو يوجد المنسق (أقدم عقدة في الكتلة).
حالات الموثوقية: البرمجيات
هذه هي الحالات المتعلقة فشل النظم الفرعية المختلفة:
- DPL ORM - وحدة الوصول إلى البيانات ، مثل السبات ORM
- النقل المتعدد الوسائط - المراسلة بين الوحدات (الخدمات الصغيرة)
- نظام التسجيل
- نظام الوصول
- اباتشي اشعل الكتلة
- ...
نظرًا لأن معظم البرامج مكتوبة بلغة Java ، فنحن عرضة لجميع المشكلات الكامنة في تطبيقات Java. اختبارات مختلف إعدادات جامع القمامة. تشغيل الاختبارات مع تعطل الجهاز الظاهري جافا.
بالنسبة إلى نظام Apache Ignite ، هناك حالات خاصة لإيقاف التشغيل - هذه هي مساحة الذاكرة التي يتحكم فيها Apache Ignite. أكبر بكثير من java heap وهو مصمم لتخزين البيانات والفهارس. هنا يمكنك ، على سبيل المثال ، اختبار الفائض. نحن نفيض خارج الكومة ونرى كيف تعمل الكتلة عندما لا تتلاءم بعض البيانات مع ذاكرة الوصول العشوائي ، أي قراءة من القرص.

حالات أخرى
هذه هي الحالات التي لم يتم تضمينها في المجموعات الثلاث الأولى. تتضمن هذه الأدوات المساعدة التي تسمح باستعادة البيانات في حالة وقوع حادث كبير أو عندما يتم ترحيل البيانات إلى كتلة أخرى.
- الأداة المساعدة لإنشاء لقطات (احتياطية) للبيانات - اختبار لقطات كاملة وتدريجية.
- الاسترداد إلى نقطة زمنية محددة - آلية PITR (الاسترداد في الوقت المحدد).
المرافق للحقن خطأ
أذكر
رابط الأمثلة من تقريري. يمكنك تنزيل توزيع Apache Ignite من الموقع الرسمي -
Apache Ignite Downloads . والآن سأشارك الأدوات المساعدة التي نستخدمها في سبيربنك ، إذا أصبحت مهتمًا بالموضوع فجأة.
الأطر
إدارة التكوين:
أدوات لينكس:
أدوات اختبار الحمل:
في كل من العالم الحديث وفي سبيربنك ، كل التغييرات ديناميكية ومن الصعب التنبؤ بالتكنولوجيات التي سيتم استخدامها في العامين المقبلين. لكنني أعلم بالتأكيد أننا سوف نستخدم طريقة Fault Injection. هذه الطريقة عالمية - إنها مناسبة لاختبار أي تقنية ، إنها تعمل حقًا ، فهي تساعد على التقاط الكثير من الأخطاء وجعل المنتجات التي نطورها أفضل.