تخطَّ إلى المحتوى

عمّال طوابيرك يشغّلون شيفرة الأسبوع الماضي

العامل عملية PHP طويلة العمر حمّلت تطبيقك مرة ولم تنظر مجددًا. النشر لا يصل إليها، والأعطال الناتجة مصلَحة في المستودع وما زالت في الإنتاج.

قراءة 3 دقيقة

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

لماذا لا يصل النشر إلى عامل

دورة حياة الطلب في PHP هي ما يجعل هذا مفاجئًا. كل طلب HTTP يُقلع الإطار ويؤدي عمله ويخرج، فتُلتقط الشيفرة الجديدة بحكم التعريف - الطلب التالي يحمّل الملفات الجديدة لأنه لم تكن هناك عملية قديمة تحتفظ بالقديمة.

أما عامل الطابور فعكس ذلك. الأمر php artisan queue:work يُقلع الإطار مرة ثم يدور، يسحب المهام من الطابور ما دام حيًّا. والأصناف التي حمّلها عند الإقلاع تبقى محمّلة. استبدل كل ملف تحته والعملية الجارية لا تعلم ولا تعبأ: هي تحتفظ بنسختها المجمَّعة من تطبيقك، من وقت بدئها أيًّا كان.

فبعد النشر يكون لديك طبقة ويب تشغّل الإصدار الجديد وطبقة طوابير تشغّل ما كان حاليًا حين بدأت تلك العمليات آخر مرة - وقد يكون الإصدار السابق، أو إصدارًا من ثلاثة أسابيع.

كيف يبدو الأمر حين يسوء

أنماط الفشل يمكن التعرف عليها متى عرفت أن تبحث عنها.

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

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

الإصلاح سطر واحد في سكربت النشر

php artisan queue:restart

لا يقتل شيئًا. يكتب طابعًا زمنيًا في التخزين المؤقت، ويفحص كل عامل ذلك الطابع بين المهام؛ فالعامل الذي أقلع قبله ينهي مهمته الحالية ويخرج. ويرى مشرف العمليات لديك - systemd أو Supervisor أو ما توفره المنصة - الخروج فيبدأ عاملًا جديدًا يُقلع الشيفرة الجديدة.

ويجب أن يتحقق شرطان ليعمل أصلًا، وكلاهما يُغفَل كثيرًا بما يستحق الذكر.

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

يجب أن يعيد شيء تشغيل العامل الخارج. الأمر queue:restart يوقف العمّال. ولا يبدؤهم. وبلا مشرف يراقب، يتركك النشر بصمت بلا أي عمّال، وتتكدس المهام حتى يلاحظ أحد عمق الطابور.

ومع Horizon يكون الاستدعاء php artisan horizon:terminate، ويجلب Horizon إشرافه الخاص - لكن يجب إخباره أيضًا، لأنه لا يستطيع رؤية نشرك.

الترتيب أهم من الأمر

موضع إعادة التشغيل في السكربت يقرر هل النافذة بين الشيفرة القديمة والجديدة خطرة:

# الشيفرة الجديدة في مكانها أولًا
php artisan migrate --force
php artisan config:cache
php artisan queue:restart

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

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

تأكّد أنه نجح

لا تثق بالسكربت. اسأل العمّال:

ps -eo lstart,cmd | grep "[q]ueue:work"

أوقات بدء أقدم من النشر تعني أن إعادة التشغيل لم تصلهم، وذلك يستحق أن يُعرف الآن لا أثناء الحادث التالي. وفي Horizon الجواب نفسه في اللوحة، تحت قائمة عمليات المشرف.

سطر واحد في سكربت نشر، وتكفّ فئة كاملة من الأعطال تلتهم أمسيات بأكملها عن الوجود.

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

أسئلة ذات صلة

هل يقتل queue:restart المهام الجارية؟
لا، وتلك هي فكرته. يكتب علامة بطابع زمني؛ ويفحص كل عامل تلك العلامة بين المهام ويخرج بنظافة إن كان قد بدأ قبل الطابع. فينتهي العمل الجاري أولًا. أما قتل العملية فهو ما يفقد مهمة نصف منجزة.
نشغّل Horizon. هل الأمر محلول لنا؟
يشرف Horizon على العمّال ويعيد تشغيلهم عند الإنهاء، وهو يعالج الآلية جيدًا. لكنه لا يعلم أن نشرًا حدث ما لم يخبره النشر، فيبقى استدعاء artisan جزءًا من السكربت - والفارق أي أمر تستدعي.
لماذا فشلت بعض المهام دون غيرها؟
لأن بعض العمّال فقط استُبدلوا. الطابور الذي تخدمه ست عمليات تُعاد تشغيلها كلٌّ حين تنتهي صدفةً يقضي نافذة يشغّل فيها نسختين من تطبيقك في آن، وأي نسخة تصيب مهمةً قرعة. ولهذا أيضًا تبدو الإخفاقات متقطعة وغير قابلة لإعادة الإنتاج.
هل لهذا تقول المهمة إن عمودًا غير موجود؟
مرجّح جدًا. الهجرة التي تضيف عمودًا تعمل على قاعدة البيانات فورًا؛ والعامل الممسك بنموذج الإصدار السابق لا يعلم عنه شيئًا. والعكس أسوأ: هجرة تحذف عمودًا بينما العمّال القدامى ما زالوا يكتبون فيه.

← العودة إلى كل المقالات

اتصل بنا+1 848 272 7583واتساب+90 850 308 5436البريدinfo@codefacture.comصفحة التواصل