لوغاريتمات تحاكي الحقيقة

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

قضيت يومًا من العمل في Amazon fulfillment center منذ ما يزيد عن عقد. واسترشدت باللوغاريتم عند اختيار المنتجات من أماكنها، ونقل المنتجات من صندوق لآخر، ونقل الصناديق. وبالعمل بالتوازي مع العديد من الأشخاص الآخرين، وجدت أنه من الرائع أن أكون جزءًا من ما هو بالأساس دمج بدني منسق ببراعة.

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

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

الطبيعة الازدواجية التي تتسم بها قوائم الانتظار

تُعد قوائم الانتظار بمثابة أدوات فعالة تُستخدم لإنشاء أنظمة غير متزامنة موثوق بها. وتسمح قوائم الانتظار لنظام واحد بقبول رسالة من نظام آخر وتستمر الرسالة في الظهور إلى أن تتم معالجتها بالكامل حتى في حالة وجود حالات انقطاع تستمر لمدة طويلة أو حالات فشل في الخادم أو مشكلات في الأنظمة التابعة. وبدلاً من إفلات الرسائل عند حدوث حالة فشل، تعيد قائمة الانتظار تشغيل الرسائل إلى أن تتم معالجتها بنجاح. وفي النهاية، تعمل قائمة الانتظار على زيادة متانة النظام وإتاحته على حساب زمن الانتقال المتزايد أحيانًا بسبب عمليات إعادة المحاولة.
 
نصمم في شركة Amazon أنظمة غير متزامنة تحقق استفادة من قوائم الانتظار. وتعالج بعض هذه الأنظمة عمليات دفق العمل التي تستغرق وقتًا طويلاً وتستخدم أشياء مادية متحركة في جميع أنحاء العالم، مثل تعبئة طلبات الشراء في موقع amazon.com على الويب. بينما تنسق الأنظمة الأخرى الخطوات التي قد تستغرق فترة زمنية قصيرة. فعلى سبيل المثال، تطلب Amazon RDS مثيلات EC2 وتنتظر بدء تشغيلها، ثم تقوم بتكوين قواعد البيانات الخاصة بك بالنيابة عنك. وتحقق الأنظمة الأخرى استفادة من الإرسال في دُفعات. فعلى سبيل المثال، تتلقى الأنظمة التي استخدمت أدوات قياس CloudWatch التي يتم استيعابها والسجلات في مجموعة من البيانات، ثم تقوم بتجميعها و"تبسيطها" على أجزاء.
 
بينما يكون من السهل معرفة مزايا قائمة الانتظار المتعلقة بمعالجة الرسائل بشكل غير متزامن، تكون المخاطر الناجمة عن استخدام قائمة الانتظار أكثر دقة. واكتشفنا على مر السنوات أن الوضع في قائمة الانتظار الذي يعني تحسين الإتاحة يمكن أن يأتي بنتائج عكسية. وفي الحقيقة، يمكنه زيادة وقت الاستعادة بشكل كبير بعد حدوث حالة انقطاع.
 
وفي النظام المستند على قائمة انتظار وعند توقف المعالجة مع استمرار تلقي الرسائل، يمكن تجميع دين الرسائل في تراكم كبير مما يؤدي إلى زيادة وقت المعالجة. يمكن إكمال العمل في وقت متأخر جدًا بالنسبة للاستفادة من النتائج مما يؤدي بشكل أساسي إلى تحقيق معدل الإتاحة المطلوب من الوضع في قائمة الانتظار لتجنب حدوثه.
 
وبطريقة أخرى، يتمتع النظام المستند على قائمة انتظار بوضعي تشغيل أو سلوك ثنائيي الوضع. في حالة عدم وجود تراكم في قائمة الانتظار، ينخفض زمن الانتقال في النظام ويكون النظام في الوضع السريع. ولكن إذا حدث فشل أو نمط تحميل غير متوقع أدى إلى أن معدل الوصول يتجاوز معدل المعالجة، فيستم تحويله بسرعة إلى وضع تشغيل أكثر ضررًا. وفي هذا الوضع، يزيد زمن الانتقال الكامل أكثر وأكثر ويستغرق وقتًا كبيرًا للعمل عبر التراكم للعودة إلى الوضع السريع.

الأنظمة المستندة على قائمة انتظار

لتوضيح الأنظمة المستندة على قائمة انتظار خلال هذا المقال، أتناول الطريقة التي تعمل بها اثنتان من خدمات AWS ضمن الحيز وهما، AWS Lambda وهي خدمة تعمل على تنفيذ التعليمة البرمجية استجابة للأحداث دون أن تقلق بشأن البنية التحتية التي تعمل عليها، وAWS IoT Core وهي خدمة مُدارة تتيح للأجهزة المتصلة التفاعل مع تطبيقات السحابة والأجهزة الأخرى بسهولة وأمان.

يمكنك من خلال خدمة AWS Lambda تحميل رمز الدالة، ثم استدعاء الدوال الخاصة بك بإحدى الطريقتين أدناه:

• بطريقة متزامنة: عند إرجاع ناتج الدالة إليك في استجابة HTTP
• بطريقة غير متزامنة: عند إرجاع استجابة HTTP على الفور وتنفيذ الدالة وإعادة محاولتها في الخلفية

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

يمكن توصيل الأجهزة والتطبيقات من خلال خدمة AWS IoT Core، وكذلك الاشتراك في الموضوعات المتعلقة برسالة PubSub. عندما يقوم جهاز أو تطبيق بنشر رسالة، تتلقى التطبيقات التي تتضمن اشتراكات متطابقة نسختها الخاصة من الرسالة. وتحدث معظم مراسلات PubSub بشكل غير متزامن نظرًا لأن جهاز IoT المقيّد لا يريد استهلاك موارده المحدودة في انتظار التأكد من أن كل الأجهزة المشتركة وكذلك التطبيقات والأنظمة تتلقى نسخة. ويكون هذا الأمر مهمًا بشكل خاص نظرًا لأنه قد يكون الجهاز المشترك غير متصل عندما ينشر جهاز آخر رسالة مهمة بالنسبة له. عند إعادة اتصال الجهاز غير المتصل، من المتوقع أنه يستعيد السرعة أولاً وبعد ذلك تسليم الرسائل إليه لاحقًا (للحصول على معلومات حول إنشاء تعليمة برمجية لنظامك من أجل إدارة تسليم الرسالة بعد إعادة الاتصال، يمكنك الرجوع إلى الجلسات المستمرة من MQTT في دليل مطوّري AWS IoT). وتتوفر مجموعة متنوعة من الصفات التي تتسم بها الاستمرارية والمعالجة غير المتزامنة والتي تتم في الخلفية بغرض حدوث ذلك.

يتم غالبًا تنفيذ الأنظمة المستندة على قائمة انتظار مثل هذه الأنظمة من خلال قائمة انتظار متينة. توفر خدمة SQS دلالات تسليم الرسالة مرة واحدة على الأقل متينة وقابلة للتحجيم بحيث تستخدمها فِرق العمل في شركة Amazon بانتظام بما في ذلك Lambda وIoT عند تصميم الأنظمة غير المتزامنة القابلة للتحجيم. وفي الأنظمة المستندة على قائمة انتظار، يعمل مكون ما على إنتاج بيانات من خلال وضع الرسائل في قائمة الانتظار، بينما يستهلك مكون آخر هذه البيانات من خلال طلب الرسائل بشكل دوري ومعالجة الرسائل وأخيرًا حذفها بمجرد إكمالها.

حالات الفشل في الأنظمة غير المتزامنة

في خدمة AWS Lambda، إذا كان استدعاء الدالة أبطأ من المعتاد (فعلى سبيل المثال، بسبب وجود تبعية) أو إذا تعرّضت للفشل مؤقتًا، فلا يتم فقدان أي بيانات وستعيد خدمة Lambda محاولة تنفيذ الدالة. تضع خدمة Lambda طلبات الاستدعاء الخاصة بك في قائمة انتظار وعند إعادة تشغيل الدالة، تعمل خدمة Lambda من خلال تراكم الدالة. ولكن دعنا نضع في الاعتبار الوقت المُستغرق للعمل من خلال التراكم والرجوع إلى الوضع الطبيعي.

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

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

الطريقة التي نتبعها لقياس الإتاحة وزمن الانتقال

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

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

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

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

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

في خدمة AWS IoT، صممنا الخدمة لاستخدامها من خلال أداة قياس أخرى وهي: AgeOfFirstAttempt. علمًا بأن هذا المقياس يسجّل الآن الوقت مطروحًا من وقت إدراج الرسالة ويحدث ذلك فقط إذا كانت هذه هي المرة الأولى التي حاولت خلالها خدمة AWS IoT تسليم رسالة إلى جهاز. وعند إجراء النسخ الاحتياطي للأجهزة بهذه الطريقة، تتوفر لدينا أداة قياس جديدة غير مليئة بالأجهزة التي تعيد محاولة تسليم الرسائل أو إدراجها. من أجل أن تكون أداة القياس جديدة، حذفنا أداة القياس الثانية وهي – AgeOfFirstSubscriberFirstAttempt. في نظام PubSub مثل خدمة AWS IoT، لا يوجد تقييد عملي بعدد الأجهزة أو التطبيقات التي يمكن اشتراكها في موضوع معين لذا يكون زمن الانتقال أعلى عند إرسال الرسالة إلى ملايين الأجهزة مقارنة بإرسالها إلى جهاز واحد. لكي نمنح نفسنا أداة قياس ثابتة، فإننا نحذف أداة القياس بمؤقت خلال المحاولة الأولى لنشر رسالة إلى المشترك الأول في هذا الموضوع. ومن ثمّ، تتوفر لدينا أدوات قياس أخرى لقياس مدى تقدم النظام عند نشر الرسائل المتبقية.

تعمل أداة القياس AgeOfFirstAttempt بمثابة تحذير مبكر بالمشكلة على نطاق النظام ويرجع ذلك بشكل كبير إلى أنها تعمل على فلترة الرسائل غير المؤثرة من الأجهزة التي تختار قراءة رسائلها بصورة أبطأ. ومن الجدير بالذكر أن أنظمة مثل AWS IoT تخضع للاستخدام من قِبل العديد من أدوات القياس مقارنة بهذا النظام. ولكن عند استخدام كل أدوات القياس المتعلقة بزمن الانتقال، تُستخدم إستراتيجية تصنيف زمن الانتقال الخاص بالمحاولات الأولى المنفصلة عن زمن انتقال عمليات إعادة المحاولة بشكل شائع في Amazon.

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

التراكمات في الأنظمة غير المتزامنة في تعدد المؤسسات

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

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

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

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

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

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

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

إستراتيجيات Amazon الخاصة بإنشاء أنظمة غير متزامنة متعددة المؤسسات مرنة

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

فصل أحمال العمل إلى قوائم انتظار منفصلة

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

التقسيم العشوائي

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

إبعاد الحركة الزائدة إلى قائمة انتظار منفصلة

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

إبعاد الحركة القديمة إلى قائمة انتظار منفصلة

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

استبعاد الرسائل القديمة (مدة بقاء الرسالة)

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

تقييد العمليات الجزئية (والموارد الأخرى) لكل حمل عمل

كما هو الحال في خدماتنا المتزامنة، نصمم أنظمتنا غير المتزامنة لمنع حمل عمل واحد من استخدام أكثر من نصيبه العادل من العمليات الجزئية. إن أحد جوانب AWS IoT التي لم نتكلم عنها حتى الآن هو محرك القواعد. فيمكن للعملاء تهيئة AWS IoT لتوجيه الرسائل من أجهزتهم إلى نظام مجموعة Amazon Elasticsearch المملوكة للعميل، وKinesis Stream، وما إلى ذلك. إذا أصبح زمن الوصول إلى تلك الموارد المملوكة للعميل بطيئًا، ولكن يظل معدل الرسائل الواردة ثابتًا، يزداد مقدار التزامن في النظام. ونظرًا لأن كمية التزامن التي يمكن للنظام التعامل معها محدودة في أي وقت من الأوقات، فإن محرك القواعد يمنع أي حمل عمل واحد من استهلاك أكثر من نصيبه العادل من الموارد المرتبطة بالتزامن.

وتوصف قوة العمل بواسطة قانون ليتل، الذي ينص على أن التزامن في نظام ما يساوي معدل الوصول مضروبًا في متوسط زمن الوصول لكل طلب. على سبيل المثال، إذا كان الخادم يعالج 100 رسالة / ثانية بمتوسط 100 مللي ثانية، فسيستهلك 10 عمليات جزئية في المتوسط. وإذا ارتفع زمن الوصول بشكل مفاجئ إلى 10 ثوانٍ، فسيستخدم بشكل مفاجئ 1,000 عملية جزئية (في المتوسط، ولذا؛ قد يزداد ذلك عند التطبيق العملي)، مما قد يستهلك تجمع مؤشر الترابط.

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

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

إرسال المراحل التمهيدية للضغط الارتدادي

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

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

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

استخدام قوائم انتظار التأخير لتأجيل العمل حتى وقت لاحق

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

تجنب عدد كبير من الرسائل المستلمة غير المعالجة

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

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

استخدام قوائم انتظار الرسائل المهملة للرسائل التي لا يمكن معالجتها

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

التأكد من وجود مخزن مؤقت في تجميع مؤشرات الترابط لكل حمل عمل

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

كشف أخطاء اتصال الرسائل طويلة التشغيل

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

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

التخطيط لتصحيح الأخطاء عبر المضيف

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

الخاتمة

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

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

نصوص أخرى للقراءة

Queueing theory
Little's law
Amdahl's law
• Little A Proof for the Queuing Formula: L = λW, Case Western, 1961
• McKenney, Stochastic Fairness Queuing, IBM, 1990
• Nichols and Jacobson, Controlling Queue Delay, PARC, 2011

نبذة عن المؤلف

ديفيد ياناسيك هو كبير مهندسين يعمل في AWS Lambda. وعمل ديفيد مطورًا للبرمجيات في Amazon منذ عام 2006، وكان يعمل سابقًا في تطوير Amazon DynamoDB وAWS IoT، وكذلك أطر عمل خدمة الويب الداخلية وأنظمة التشغيل الآلي لعمليات الأسطول. ومن الأنشطة المفضلة لديفيد في العمل هي تحليل السجلات والتحري عن طريق المقاييس التشغيلية لإيجاد طرق لجعل الأنظمة تعمل بسلاسة أكثر بمرور الوقت.

اختيار القائد في الأنظمة الموزعة توجيه الأنظمة الموزعة للرؤية التشغيلية