Gunicorn: يفشل POST عند العودة> 13k استجابة على Heroku

تم إنشاؤها على ٤ أغسطس ٢٠١٤  ·  34تعليقات  ·  مصدر: benoitc/gunicorn

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

بالنسبة إلى طلب POST معين مع معلمات POST ، قد يفشل الطلب مع وجود خطأ H18 (sock = backend) في جهاز توجيه Heroku يشير إلى أن الخادم أغلق المقبس عندما لا يكون كذلك.

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

يتوفر رمز إعادة إنتاج هذا على https://github.com/erjiang/gunicorn-issue - ما عليك سوى نشر الريبو إلى Heroku كما هو واتباع التعليمات الواردة في README.

( Feedback Requested unconfirmed help wanted - Bugs -

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

تمكنت من إعادة الإنتاج باستخدام حقيبة الاختبار على https://github.com/erjiang/gunicorn-issue (والتي تستخدم gunicorn 19.9.0 ، Python 2.7.14 ، عامل المزامنة ، --workers 4 ). وتجدر الإشارة إلى أن تقارير مخرجات سجل الوصول الخاصة بـ Gunicorn تعتقد أنها أعادت HTTP 200.

لم يكن للتحديث إلى Python 3.7.3 + gunicorn master ، والتقليل إلى --workers 1 أي تأثير على قابلية التكرار ، إلا أن التبديل من عامل المزامنة إلى gevent جعل الخطأ يحدث بشكل أقل تكرارًا (على الرغم من أنه لا يزال يحدث). استخدام --log-level debug لم يكشف عن أي شيء مهم (الناتج الإضافي الوحيد أثناء الطلب كان السطر [DEBUG] POST /test1 ).

بعد ذلك جربت --spew ، ومع ذلك لم تعد المشكلة تتكرر. قادني هذا لمحاولة إضافة time.sleep(1) قبل resp.close() هنا مما حال بالمثل دون حدوث المشكلة.

على هذا النحو ، يبدو أن السبب هو أن المخزن المؤقت لإرسال المقبس قد لا يكون فارغًا في وقت close() ، مما قد يؤدي إلى فقد الاستجابة:

ملاحظة: يقوم close() بإصدار المورد المرتبط بالاتصال ولكن لا يقوم بالضرورة بإغلاق الاتصال على الفور. إذا كنت تريد إغلاق الاتصال في الوقت المناسب ، فاتصل بـ shutdown() قبل close() .

(انظر https://docs.python.org/3/library/socket.html#socket.socket.close)

أدت إضافة sock.shutdown(socket.SHUT_RDWR) ( مستندات ) قبل sock.close() هنا إلى حل المشكلة بالنسبة لي. قد يكون الحل البديل هو استخدام SO_LINGER ، على الرغم من أن ما قرأته يحتوي على مقايضات.

يصعب الحصول على مستندات حول هذا الموضوع ، لكني وجدت:
https://stackoverflow.com/questions/8874021/close-socket-directly-after-send-unsafe
https://blog.netherlabs.nl/articles/2009/01/18/the-ultimate-so_linger-page-or-why-is-my-tcp-not-reliable

امل ان يساعد :-)

ال 34 كومينتر

تقرير مفيد حقًا ، شكرًاerjiang.

ليس لدي حساب heroku للاختبار منه. يمكن لأي شخص لديه مثل هذا الحساب اختباره؟ تضمين التغريدة

يسعدني ذلك ولكن ربما لن أحصل عليه قريبًا.

كتحقق سريع للعقل ، قمت بتشغيله محليًا وفحصت بعض الأشياء باستخدام curl لمقارنة النادلة و gunicorn:

  • [x] طول المحتوى هو نفسه
  • [x] نفس محتوى الجسم
  • [x] تشفير النقل نفسه (لا أحد يحدد مقطعًا ، وكلاهما يستخدم طول المحتوى)

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

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

tilgovi أعتقد أن السلوك الذي تراه مع النادلة يمكن إعادة إنتاجه مع العامل الملولب. على أي حال بفضل الاهتمام بهذا :)

تحية للجميع،
أواجه نفس المشكلة. هل حصل أي منكم على فرصة لفحص هذه المسألة بدقة أكبر؟
tilgovierjiangbenoitc

هتافات
حكمة - قول مأثور

maximkgn هل تستخدم القارورة أيضًا؟ هل من مزيد من التفاصيل؟

أنا أستخدم django 1.7.
كان لدينا رد منشور معين كان دائمًا أطول من 13 ألفًا ، ومع وجود احتمال معين ~ 0.5 ، سيتم اقتطاع استجابة العميل إلى ما يزيد قليلاً عن 13 ألف. في سجلات heroku ، رأينا نفس خطأ h18 ، وبعد أن تأكدنا من عدم حدوث أي خطأ في كود الثعبان ، كان علينا أن نستنتج أنه يحدث في طبقة gunicorn بين heroku والثعبان.
عندما تحولنا إلى نادلة / uwsgi توقف الخطأ عن الحدوث ..

maximkgn ماذا يحدث إذا استخدمت الإعداد --threads ؟

أي شخص قادر على اختبار هذا؟

لدي نفس المشكلة مع flask و gunicorn (الإصدارات المختبرة 19.3 و 19.4.5). benoitc لقد جربت 1 و 2 و 4 خيوط (باستخدام الخيار --threads) ، ولا يحدث فرق.

اسمحوا لي أن أعرف إذا كان بإمكاني المساعدة في اختبار ذلك بأي شكل من الأشكال؟

cbaines كيف تبدو الطلبات؟

Friendpaste قادر على قبول أكثر من مليون مشاركة .... لذلك بالتأكيد ليس هناك حد داخل gunicorn.

لم يكن لديه إجابة. إغلاق المشكلة لأنها غير قابلة للتكرار. لا تتردد في إعادة فتح واحد إذا لزم الأمر.

لا يزال يتكاثر بعد تحديث التبعيات ليشمل Flask 1.0.2 و Gunicorn 19.9.0. قد يكون من الجيد لفت انتباه شخص ما في Heroku حول هذا على الرغم من - سمعت أن لديهم بعض الأشخاص المتفانين في Python.

شاهد أحدث التزام هنا: https://github.com/erjiang/gunicorn-issue/

أتلقى أيضًا خطأ H18 هذا على طلب GET كبير بانتظام.

التحول إلى النادلة حل المشكلة. لست متأكدًا من سبب إنتاجه لـ gunicorn ، ولكن يتم تنفيذ نفس الشفرة بالضبط.

جسم الاستجابة هو 21.54 كيلو بايت

لا يزال يتكاثر بعد تحديث التبعيات ليشمل Flask 1.0.2 و Gunicorn 19.9.0. قد يكون من الجيد لفت انتباه شخص ما في Heroku حول هذا على الرغم من - سمعت أن لديهم بعض الأشخاص المتفانين في Python.

شاهد أحدث التزام هنا: https://github.com/erjiang/gunicorn-issue/

لقد أنشأت بطاقة دعم على Heroku. سيتم التحديث هنا إذا كان أي شيء مفيد يأتي منه.

benoitc يبدو أن erjiang قدم

أعيد فتحه. سأقوم بتعيين ذاتي وإلقاء نظرة عندما أستطيع ذلك.

لا يزال يتكاثر بعد تحديث التبعيات ليشمل Flask 1.0.2 و Gunicorn 19.9.0. قد يكون من الجيد لفت انتباه شخص ما في Heroku حول هذا على الرغم من - سمعت أن لديهم بعض الأشخاص المتفانين في Python.
شاهد أحدث التزام هنا: https://github.com/erjiang/gunicorn-issue/

لقد أنشأت بطاقة دعم على Heroku. سيتم التحديث هنا إذا كان أي شيء مفيد يأتي منه.

هل حصلت على رد من heroku؟

تمكنت من إعادة الإنتاج باستخدام حقيبة الاختبار على https://github.com/erjiang/gunicorn-issue (والتي تستخدم gunicorn 19.9.0 ، Python 2.7.14 ، عامل المزامنة ، --workers 4 ). وتجدر الإشارة إلى أن تقارير مخرجات سجل الوصول الخاصة بـ Gunicorn تعتقد أنها أعادت HTTP 200.

لم يكن للتحديث إلى Python 3.7.3 + gunicorn master ، والتقليل إلى --workers 1 أي تأثير على قابلية التكرار ، إلا أن التبديل من عامل المزامنة إلى gevent جعل الخطأ يحدث بشكل أقل تكرارًا (على الرغم من أنه لا يزال يحدث). استخدام --log-level debug لم يكشف عن أي شيء مهم (الناتج الإضافي الوحيد أثناء الطلب كان السطر [DEBUG] POST /test1 ).

بعد ذلك جربت --spew ، ومع ذلك لم تعد المشكلة تتكرر. قادني هذا لمحاولة إضافة time.sleep(1) قبل resp.close() هنا مما حال بالمثل دون حدوث المشكلة.

على هذا النحو ، يبدو أن السبب هو أن المخزن المؤقت لإرسال المقبس قد لا يكون فارغًا في وقت close() ، مما قد يؤدي إلى فقد الاستجابة:

ملاحظة: يقوم close() بإصدار المورد المرتبط بالاتصال ولكن لا يقوم بالضرورة بإغلاق الاتصال على الفور. إذا كنت تريد إغلاق الاتصال في الوقت المناسب ، فاتصل بـ shutdown() قبل close() .

(انظر https://docs.python.org/3/library/socket.html#socket.socket.close)

أدت إضافة sock.shutdown(socket.SHUT_RDWR) ( مستندات ) قبل sock.close() هنا إلى حل المشكلة بالنسبة لي. قد يكون الحل البديل هو استخدام SO_LINGER ، على الرغم من أن ما قرأته يحتوي على مقايضات.

يصعب الحصول على مستندات حول هذا الموضوع ، لكني وجدت:
https://stackoverflow.com/questions/8874021/close-socket-directly-after-send-unsafe
https://blog.netherlabs.nl/articles/2009/01/18/the-ultimate-so_linger-page-or-why-is-my-tcp-not-reliable

امل ان يساعد :-)

كامل STR:

  1. قم بإنشاء حساب Heroku مجاني على https://signup.heroku.com
  2. قم بتثبيت Heroku CLI (راجع https://devcenter.heroku.com/articles/heroku-cli)
  3. قم بتسجيل الدخول إلى CLI باستخدام heroku login
  4. git clone https://github.com/erjiang/gunicorn-issue && cd gunicorn-issue
  5. heroku create (يؤدي هذا إلى إنشاء تطبيق Heroku مجاني باسم تم إنشاؤه عشوائيًا وتكوين جهاز تحكم عن بُعد git باسم heroku )
  6. git push heroku master
  7. curl --data "foo=bar" https://YOUR_GENERATED_APP_NAME.herokuapp.com/test1 (الإخفاقات> 75٪ من الوقت)
  8. عند الانتهاء ، قم بتشغيل heroku destroy لحذف التطبيق.

أنتجtilgovi يبدو مثل edmorley تفسيرًا معقولًا لما هو خطأ. هل أنت قادر على إلقاء نظرة ومعرفة ما هو الإصلاح الصحيح؟ يمكنني أيضًا إرسال PR لإضافة sock.shutdown() لكني لا أعرف ما يكفي لأقول ما إذا كان هذا هو الإصلاح الصحيح أو ما إذا كان سيؤثر سلبًا على المواقف الأخرى.

مرحبًا ، لقد واجهت نفس المشكلة بحجم استجابة 503 كيلوبايت. بيانات الاستجابة هي مصفوفة JSON.
السلوك المرصود هو:

  1. أرى نص استجابة مقطوعًا ولا يزال عميل http (Chrome ، curl) ينتظر الاستجابة.
  2. ~ 75٪ من الطلبات تواجه وقت استجابة يتراوح بين 120-130 ثانية. يتم حل الطلبات المتبقية تحت 400 مللي ثانية.
  3. الطلبات ذات حجم الاستجابة الصغير سريعة.

يمكن استنساخه على كلا:

  1. تثبيت Docker المحلي على نظام التشغيل Windows 10
  2. تشغيل حاوية عامل ميناء على AWS ECS

إعداد بيئة وقت التشغيل
صورة meinheld-gunicorn-docker تحمل علامة _python3.6_ مع Python 3.6.7 و Flask 1.0.2 و flask-restplus 0.12.1 و simpe Flask-caching

تكوين Docker : 3 وحدات معالجة مركزية ، ذاكرة وصول عشوائي (RAM) 1024 ميجابايت

تكوين Gunicorn :

  • العمال = 2 * وحدات المعالجة المركزية + 1 (موصى به من قبل المستند)
  • الخيوط = 1 (نفس السلوك مع 2 * خيوط CPU)
  • worker_class = " egg: meinheld # gunicorn_worker "

في https://github.com/benoitc/gunicorn/issues/2015 ، واجه شخص آخر مشاكل مع عامل معلق معلق ، واستخدام نوع عامل مختلف حل المشكلة. أتساءل عما إذا كانت هناك مشكلة عامة معها. @ stapetro هل أنت قادر على تجربة عامل مختلف؟

مرحبا jamadden ،
اقتراحك حل المشكلة. لا توجد مشكلة في فئتي العاملين _gevent_ و _gthread_. ابتعدت عني. شكرا لك على الرد السريع والمساعدة! :)

كامل STR:

  1. قم بإنشاء حساب Heroku مجاني على https://signup.heroku.com
  2. قم بتثبيت Heroku CLI (راجع https://devcenter.heroku.com/articles/heroku-cli)
  3. قم بتسجيل الدخول إلى CLI باستخدام heroku login
  4. git clone https://github.com/erjiang/gunicorn-issue && cd gunicorn-issue
  5. heroku create (يؤدي هذا إلى إنشاء تطبيق Heroku مجاني باسم تم إنشاؤه عشوائيًا وتكوين جهاز تحكم عن بُعد git باسم heroku )
  6. git push heroku master
  7. curl --data "foo=bar" https://YOUR_GENERATED_APP_NAME.herokuapp.com/test1 (الإخفاقات> 75٪ من الوقت)
  8. عند الانتهاء ، قم بتشغيل heroku destroy لحذف التطبيق.

كان لدي سلوك مشابه جدًا على تطبيقي ، ووجدت أنه عند استخدام curl -H بدلاً من curl --data (نظرًا لأنه طلب GET) ، فإنه يعمل مع تطبيقي (Django و Gunicorn و Heruko). لم أختبر تطبيق gunicorn-issue. يعتقد أن هذا قد يكون مفيدًا لشخص ما.

تضمين التغريدة يتصرف تطبيق يحتوي على Flask / Flask RestPlus و Gunicorn بهذه الطريقة: الرد على طلب POST يعطي خطأ 503 [إذا كانت الحمولة> 13 كيلو] ، بينما لا يحدث الخطأ إذا رد التطبيق على GET. بالضبط نفس الرمز!
هل يمكن لأي شخص أن يشرح هذا السلوك المزعج للغاية؟ هل التبديل إلى النادلة هو الحل الوحيد لإصلاح هذه المشكلة؟ أشعر أن تعديل Gunicorn "يدويًا" ليس حلاً قابلاً للتطبيق ...

تقدمت وفتحت العلاقات العامة للاتصال بالإغلاق () قبل الإغلاق (). بصراحة ، من الغريب أن يستمر Heroku في التوصية بـ Gunicorn عندما يتم كسره افتراضيًا على Heroku.

إذا ، كما يقولerijang بشكل صحيح ، إذا أوصى Heroku بـ Gunicorn عندما لا يكون Gunicorn هو الطريق الصحيح: ما هي البدائل البسيطة والقابلة للتطبيق لـ Gunicorn (وكيف يمكنك تكوينها بشكل أفضل على Heroku)؟
AFAIK ، يختار العديد من العملاء Heroku لمجرد أنه لا ينبغي أن يتطلب معرفة عميقة في هياكل الخادم وتفاصيل التكوين ...: |

RinaldoNani ماذا تقصد؟ أيضا أي عامل نتحدث عنه؟ .

benoitc تؤثر هذه المشكلة على أنواع متعددة من العاملين ، كما هو مذكور في:
https://github.com/benoitc/gunicorn/issues/840#issuecomment -482491267

مرحبًاbenoitc. وكما ذكرت في الوظيفة السابقة، قمنا بنشر جميلة بسيطة قارورة / FlaskRestPlus التطبيق على Heroku بعد مع المبادئ التوجيهية الرعاية Heroku لبيثون / قارورة نشر التطبيقات جانب الخادم (الذي، كما فهمت، تشير إلى استخدام Gunicorn متزامنة "الويب" عمال ).

يعكس سلوك تطبيقنا عنوان هذا الموضوع.

تم اختباره محليًا ، كل شيء يعمل بشكل جيد ، يقدم التطبيق 20k + JSON دون أي مشاكل ؛ ولكن عند نشر التطبيق على Heroku ، تصبح مشكلة الخطأ 503 منهجية: حتى مع عدم وجود حركة مرور حرفيًا ، لا يتم تسليم الإخراج.
كما أشار آخرون ، تُظهر السجلات أنه على مستوى HTTP يبدو كل شيء على ما يرام (يتم تسجيل 200 رمز استجابة).
إذا كانت الحمولة أقل من 13 كيلو ، فإن Heroku / Gunicorn تستجيب لـ POST كما هو متوقع.
اتبعنا فكرة

لسنا خبراء في Gunicorn ، وبصراحة توقعنا أن حالة الاستخدام البسيطة الخاصة بنا يمكن أن تعمل "خارج الصندوق".
إذا كان لديك أي اقتراحات لمساعدتنا ، فسنكون ممتنين إلى الأبد :)

RinaldoNani لقطة في الظلام ... في مكان ما في معالج الطلبات ، حاول قراءة كل request.data . فمثلا:

@route('/whatever', methods=['POST'])
def whatever_handler():
    str(request.data)
    return flask.jsonify(...)

هل هذا له أي تأثير على أخطائك؟

أكتب هذا في الساعة 1:00 صباحًا بعد صراع مع مشكلة H18 لأكثر من أسبوعين حتى الآن (لم أستطع الانتظار للمشاركة).

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

  1. تم تغيير طلب POST إلى GET.
  2. تحتوي بياناتي على قيم NaN / Null ، لذا قمت بتغيير النموذج الخاص بي وقدمت قيمة افتراضية. (أعتقد أن هذا حل المشكلة)
    بعد ذلك ، توقفت عن تلقي هذا الخطأ.
    أتمنى أن يساعد هذا شخص ما!
هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات