Gunicorn: وضح ماذا / كيف تعمل timeout و graceful_timeout

تم إنشاؤها على ٣ أبريل ٢٠١٧  ·  30تعليقات  ·  مصدر: benoitc/gunicorn

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

كما فهمت ، بشكل افتراضي:

  • بعد 30 ثانية (قابلة للتكوين باستخدام timeout ) من معالجة الطلب ، ترسل عملية Gunicorn الرئيسية SIGTERM إلى العملية المنفذة ، لبدء إعادة تشغيل رشيقة.
  • إذا لم يتم إيقاف العامل أثناء ثانية 30 ثانية (قابلة للتكوين باستخدام graceful_timeout ) ، ترسل العملية الرئيسية SIGKILL . يبدو أن هذه الإشارة يتم إرسالها أيضًا عندما _ يقوم العامل _ بإغلاق برشاقة أثناء فترة graceful_timeout (https://github.com/benoitc/gunicorn/commit/d1a09732256fa8db900a1fe75a71466cf2645ef9).

الأسئلة:

  • هل الإشارات صحيحة؟
  • ماذا يحدث في الواقع عندما يتلقى عامل gunicorn (sync) هذه الإشارات؟ كيف يخبر تطبيق WSGI أنه تم التقاط الإشارة ويجب أن يحدث شيء ما (حسنًا ، أفترض أنه "يمررها" فقط)؟
  • كيف ، على سبيل المثال ، يتعامل Flask مع إشارة SIGTERM - عمليًا ، ماذا يحدث أثناء معالجة الطلب؟ هل يقوم فقط بتعيين علامة لتطبيق WSGI (على مستوى werkzeug) بأنه يجب إيقاف تشغيله بعد اكتمال معالجة الطلب؟ أو هل يؤثر SIGTERM بطريقة ما بطريقة ما على معالجة الطلب الجارية - تقتل اتصالات IO أو شيء ما لتسريع معالجة الطلب ...؟

على SIGKILL ، أعتقد أنه تم إحباط معالجة الطلب بالقوة.

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

Discussion Documentation

التعليق الأكثر فائدة

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

ربما يكون تغيير الاسم فكرة جيدة إذا وجد الآخرون هذا الأمر محيرًا.

ال 30 كومينتر

حسنًا ، أعتقد أن https://github.com/benoitc/gunicorn/issues/1236#issuecomment -254059927 يؤكد افتراضاتي حول SIGTERM ببساطة تعيين العامل للإغلاق بعد اكتمال معالجة الطلب (وتعيين عامل عدم القبول أي اتصالات جديدة).

يبدو أن كيف فسّرت خطأ timeout و graceful_timeout . تشير كلتا الفترتين في الواقع إلى الوقت في بداية معالجة الطلب. لذلك ، بشكل افتراضي ، نظرًا لأن كلا الإعدادين مضبوطين على 30 ثانية ، لم يتم تمكين إعادة تشغيل رشيقة. إذا فعلت شيئًا مثل --graceful-timeout 15 --timeout 30 ، فهذا يعني أن إعادة التشغيل الرشيقة تبدأ في 15 ثانية ويتم إجبار العامل على الموت في 30 ثانية إذا لم يكتمل الطلب قبل ذلك.

ومع ذلك ، يبدو أنه إذا تم إرجاع الاستجابة بين graceful_timeout و timeout ، فلن يتم إعادة تشغيل العامل بعد كل شيء؟ أليس كذلك؟

لقد اختبرت بـ app.py :

import time
from flask import Flask

app = Flask(__name__)

@app.route('/foo')
def foo():
    time.sleep(3)
    return 'ok'

ثم:

12:51 $ gunicorn app:app --timeout 5 --graceful-timeout 1
[2017-04-03 12:51:37 +0300] [356] [INFO] Starting gunicorn 19.6.0
[2017-04-03 12:51:37 +0300] [356] [INFO] Listening at: http://127.0.0.1:8000 (356)
[2017-04-03 12:51:37 +0300] [356] [INFO] Using worker: sync
[2017-04-03 12:51:37 +0300] [359] [INFO] Booting worker with pid: 359

ثم أرسل curl localhost:8000/foo ، والذي يتم إرجاعه بعد 3 ثوانٍ. لكن لا شيء يحدث في gunicorn - لا أرى أي أثر لبدء أو حدوث إعادة تشغيل رشيقة؟

يبدو أنه في timeout ، تم طرح SystemExit(1,) ، مما أدى إلى إحباط معالجة الطلب الحالي في Flask. ما الرمز أو الإشارة التي تولده ، لا يمكنني القول.

يتم طرح هذا الاستثناء عبر مكدس Flask ، ويلتقطه أي معالجات teardown_request . هناك ما يكفي من الوقت لتسجيل شيء ما ، ولكن إذا قمت بعمل time.sleep(1) أو أي شيء آخر يستغرق وقتًا طويلاً في المعالج ، فسيتم قتله بصمت. يبدو الأمر كما لو كان هناك وقت يتراوح بين 100 و 200 مللي ثانية قبل إنهاء العملية بالقوة بالفعل وأنا أتساءل ما هو هذا التأخير. انها ليست مهلة رشيقة ، هذا الإعداد ليس له أي تأثير على التأخير. أتوقع أن يتم قتل العملية بالقوة في مكانها ، بدلاً من رؤية SystemExit يتم إلقاؤها من خلال المكدس ، ولكن بعد ذلك من المحتمل أن تقتل العملية في الهواء على أي حال.

في الواقع ، لا أرى أي شيء يفعله graceful_timeout - ربما لا يتم دعمه للعاملين في المزامنة ، أو ربما لا يعمل "بشكل مستقل" (أو مع timeout ) - فقط عندما ترسل يدويًا SIGTERM ؟

ما قد يكون غريبًا أيضًا هو أن https://github.com/benoitc/gunicorn/blob/master/gunicorn/arbiter.py#L392 لا يتحقق من graceful على الإطلاق. أعتقد أن https://github.com/benoitc/gunicorn/blob/master/gunicorn/arbiter.py#L390 يضمن أن self.WORKERS فارغ لذا لا يتم انتظار المهلة الرشيقة عند القيام بإيقاف غير رشيق.

benoitc tilgovi الرعاية لتقديم يد المساعدة هنا؟ آمل أن تكون كتاباتي أعلاه منطقية ...

@ tuco86 لا يتوفر graceful timeout إلا عند إنهاء الحكم أو ترقيته (USR2) أو إرسال إشارة HUP إلى الحكم أو إرسال إشارة QUIT إلى العامل. أي يتم استخدامه فقط عندما يكون الإجراء طبيعيًا

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

اه حسنا. هل يكون لـ timeout أي تأثير عندما:

قم بإنهاء الحكم ، أو قم بترقيته (USR2) ، أو أرسل إشارة HUP إلى الحكم أو أرسل إشارة استقالة إلى العامل

أعني ، ماذا لو لم يغلق العامل في graceful_timeout - سوف يبدأ timeout بعد ذلك وسيُقتل العمال بالقوة ، أو هل يُترك للمستخدم أن يطلب SIGQUIT في حال لم يموتوا برشاقة؟

استقال إشارة إلى العامل

أفترض أنك كنت تقصد TERM هنا (حيث أن QUIT موثق على أنه إغلاق سريع _ لكل من السيد والعاملين)؟

إذا لم يغلق العامل خلال الوقت الجميل فسوف يُقتل دون أي تأخير آخر.

بالطبع. شكرا لتوضيح الأمور!

benoitc السؤال في سياق هذه التذكرة القديمة - ماذا تعني الجملة الأخيرة في التوثيق timeout ؟

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

لست متحدثًا باللغة الإنجليزية ، أجد صعوبة في فهم هذا. هل هذا يعني أن timeout غير مدعوم للعمال غير المتزامنين (لأن هذا ما أشاهده على ما يبدو: أنا أستخدم عمال gthread ولا يتم تشغيل المهلة وتقتل الطلبات البطيئة جدًا )؟

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

ربما يكون تغيير الاسم فكرة جيدة إذا وجد الآخرون هذا الأمر محيرًا.

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

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

هل لديك مثال على وقت بدء تشغيل timeout مع عمال غير متزامنين؟ هل هو شيء لا يجب أن يحدث أبدًا ، حقًا - ربما فقط إذا كان هناك خطأ يتسبب في منع العامل / تجميده؟

هذا صحيح. قد يؤدي العامل غير المتزامن الذي يعتمد على نواة حلقة الحدث إجراءً مكثفًا لوحدة المعالجة المركزية لا ينتج عنه خلال المهلة.

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

يعد الوقوع في مهمة مكثفة لوحدة المعالجة المركزية مثالاً جيدًا ، شكرًا.

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

أيضا ، ما هو الفاصل الزمني لضربات القلب؟ ما هي القيمة المعقولة لاستخدامها في timeout مع العاملين غير المتزامنين؟

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

ربما لا يتعين عليك تغيير المهلة إلا إذا بدأت في رؤية مهلات العامل.

على ما يرام. شيء واحد فقط:

عامل gthread ليس غير متزامن

قد يكون محيرًا بعض الشيء أن العامل gthread ليس متزامنًا ولكنه مُدرج كعاملين "AsyncIO" على http://docs.gunicorn.org/en/stable/design.html#asyncio -workers. بخلاف ذلك ، فإن استخدام "الخيوط" لا يحتاج إلى التزامن ، وهذا يثير أيضًا أسئلة في القارئ. مجرد قول هذا من منظور مستخدم ساذج ، أنا متأكد من أنه قائم على أسس جيدة من الناحية الفنية.

باختصار ، تم تنفيذ العامل gthread باستخدام asyncio lib ولكنه يولد سلاسل رسائل للتعامل مع كود المزامنة. صححني إذا كنت مخطئا.

سعيد لأنك سألت!

العامل المترابط لا يستخدم غير متزامن ولا يرث من فئة العامل الأساسية غير المتزامنة.

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

سيكون من الرائع توضيح الوثائق وجعلها أكثر دقة لوصف جميع العمال.

نعم ، يجب ألا يُدرج عامل البيانات في عامل غير متزامن. ربما يكون وجود قسم يصف تصميم كل عامل أفضل؟

إعادة فتح هذا حتى نتمكن من تتبعه كعمل لتوضيح القسم الخاص بأنواع العمال والمهلة.

تضمين التغريدة

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

هل يتوفر خيار مهلة الطلب للعاملين غير المتزامنين؟ بمعنى آخر كيف تجعل الحكم يقتل عاملاً لم يقم بمعالجة الطلب خلال فترة زمنية محددة؟

aschatten ليس هناك ، للأسف. راجع أيضًا # 1658.

قتل عامل لم يقم بمعالجة الطلب خلال فترة زمنية محددة

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

أتذكر أن uWSGI كان يخطط لإدخال القتل المستند إلى الخيط في 2.1 أو نحو ذلك ، على الرغم من أنه ربما ينطبق هذا أيضًا على عمال المزامنة / الخيوط فقط (وتذكرتي حول هذا غامضة).

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

يمكن أن يكون الأسلوب هو نفسه بالنسبة لـ max_request ، حيث يوجد تنفيذ منفصل لكل نوع عامل.

نحن نعمل على إصدار هذا الأسبوع ، وعند هذه النقطة _ربما _ حان الوقت للتفرع لـ R20 ، حيث نخطط للتعامل مع بعض الأمور الرئيسية. قد يكون هذا هو الوقت المناسب لجعل المهلة الحالية في مهلة طلب مناسبة لكل نوع عامل.

التعليق هنا بدلاً من تقديم مشكلة منفصلة لأنني أحاول فهم كيفية عمل timeout ولست متأكدًا مما إذا كان هذا خطأ أم لا.

السلوك غير المتوقع IMO الذي أراه هو هذا:

تنتهي مهلة كل طلب max-request (الطلب الذي سيتم بعده إعادة تشغيل العامل) ، بينما يتم إكمال الطلبات الأخرى بنجاح. في المثال أدناه ، يتم تنفيذ 4 طلبات ، وتنجح الطلبات 1 و 2 و 4 ، بينما يفشل الطلب 3.

التكوين المناسب:

  • عامل gthread
  • يستغرق طلب الخدمة وقتًا أطول من انتهاء المهلة
  • الحد الأقصى للطلبات ليس صفريًا
import time

def app(environ, start_response):
    start_response('200 OK', [('Content-type', 'text/plain; charset=utf-8')])
    time.sleep(5)
    return [b"Hello World\n"]

غونيكورن:

gunicorn --log-level debug -k gthread -t 4 --max-requests 3 "app:app"
...
[2018-02-08 10:11:59 +0200] [28592] [INFO] Starting gunicorn 19.7.1
[2018-02-08 10:11:59 +0200] [28592] [DEBUG] Arbiter booted
[2018-02-08 10:11:59 +0200] [28592] [INFO] Listening at: http://127.0.0.1:8000 (28592)
[2018-02-08 10:11:59 +0200] [28592] [INFO] Using worker: gthread
[2018-02-08 10:11:59 +0200] [28595] [INFO] Booting worker with pid: 28595
[2018-02-08 10:11:59 +0200] [28592] [DEBUG] 1 workers
[2018-02-08 10:12:06 +0200] [28595] [DEBUG] GET /
[2018-02-08 10:12:11 +0200] [28595] [DEBUG] Closing connection.
[2018-02-08 10:12:15 +0200] [28595] [DEBUG] GET /
[2018-02-08 10:12:20 +0200] [28595] [DEBUG] Closing connection.
[2018-02-08 10:12:23 +0200] [28595] [DEBUG] GET /
[2018-02-08 10:12:23 +0200] [28595] [INFO] Autorestarting worker after current request.
[2018-02-08 10:12:27 +0200] [28592] [CRITICAL] WORKER TIMEOUT (pid:28595)
[2018-02-08 10:12:27 +0200] [28595] [INFO] Worker exiting (pid: 28595)
[2018-02-08 10:12:28 +0200] [28595] [DEBUG] Closing connection.
[2018-02-08 10:12:28 +0200] [28599] [INFO] Booting worker with pid: 28599
[2018-02-08 10:12:32 +0200] [28599] [DEBUG] GET /
[2018-02-08 10:12:37 +0200] [28599] [DEBUG] Closing connection.
^C[2018-02-08 10:12:39 +0200] [28592] [INFO] Handling signal: int

عميل:

[salonen<strong i="19">@mac</strong> ~]$ curl http://127.0.0.1:8000
Hello World
[salonen<strong i="20">@mac</strong> ~]$ curl http://127.0.0.1:8000
Hello World
[salonen<strong i="21">@mac</strong> ~]$ curl http://127.0.0.1:8000
curl: (52) Empty reply from server
[salonen<strong i="22">@mac</strong> ~]$ curl http://127.0.0.1:8000
Hello World

ماذا يجب ان تكون الخطة هناك؟ أفكر في ما يلي:

  • [] تحديث وصف العامل (إذا لزم الأمر)
  • [] توثيق بروتوكول الكشف عن العمال القتلى أو المحظورين

هل يجب أن يكون 20.0 أم يمكننا تأجيله؟

تأجيل.

مرحبًا ، إذن لن يكون هذا جزءًا من 20.0؟

قد يكون هذا هو الوقت المناسب لجعل المهلة الحالية في مهلة طلب مناسبة لكل نوع عامل.

أوضح. @ lucas03 ليس من الواضح ما هي مهلة الطلب هناك. الرجاء فتح تذكرة إذا كنت بحاجة إلى شيء محدد ؟.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات