مليون طلب في الثانية مع بايثون

هل من الممكن أن تصل إلى مليون طلب في الثانية مع بايثون؟ ربما ليس حتى وقت قريب.

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

يقوم مجتمع Python بالكثير حول الأداء مؤخرًا. عزز CPython 3.6 الأداء العام للمترجم مع تطبيق القاموس الجديد. سيكون CPython 3.7 أسرع ، بفضل إدخال اصطلاحات المكالمات الأسرع وذاكرة التخزين المؤقت للبحث في القاموس.

بالنسبة لمهام تحليل الأرقام ، يمكنك استخدام PyPy مع تجميع التعليمات البرمجية في الوقت المناسب. يمكنك أيضًا تشغيل مجموعة اختبار NumPy ، والتي حسنت الآن التوافق العام مع امتدادات C. في وقت لاحق من هذا العام ، من المتوقع أن تصل PyPy إلى مطابقة Python 3.5.

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

أدخل Japronto!

Japronto هو إطار عمل صغير جديد تمامًا مصمم لاحتياجات الخدمات الدقيقة الخاصة بك. وتشمل أهدافها الرئيسية هي سريعة ، قابلة للتطوير، و خفيفة الوزن . فإنه يتيح لك القيام كلا متزامن و غير متزامن بفضل البرمجة ل asyncio . وهو سريع بلا خجل . حتى أسرع من NodeJS و Go.

خطأ: كما يشير userheppu ، يمكن أن يكون خادم HTTP stdlib الخاص بـ Go أسرع بنسبة 12٪ مما يظهره هذا الرسم البياني عند كتابته بعناية أكبر. هناك أيضًا خادم Fasthttp رائع لـ Go والذي يبدو أنه أبطأ بنسبة 18٪ فقط من Japronto في هذا المعيار المحدد. رائع! للحصول على التفاصيل ، راجع //github.com/squeaky-pl/japronto/pull/12 و //github.com/squeaky-pl/japronto/pull/14.

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

أجريت هذا الاختبار المصغر باستخدام "Hello world!" التطبيق ، لكنه يوضح بوضوح حمل إطار عمل الخادم لعدد من الحلول.

تم الحصول على هذه النتائج على مثيل AWS c4.2xlarge الذي يحتوي على 8 وحدات VCPU ، تم إطلاقها في منطقة ساو باولو مع الإيجار المشترك الافتراضي والمحاكاة الافتراضية HVM والتخزين المغناطيسي. كان الجهاز يعمل بنظام Ubuntu 16.04.1 LTS (Xenial Xerus) مع نواة Linux 4.4.0–53-generic x86_64. كان نظام التشغيل يبلغ عن وحدة المعالجة المركزية Xeon® CPU E5–2666 v3 @ 2.90 جيجا هرتز. لقد استخدمت Python 3.6 ، الذي جمعته حديثًا من كود المصدر الخاص به.

لكي نكون منصفين ، كان جميع المتسابقين (بما في ذلك Go) يديرون عملية فردية. تم اختبار الحمل على الخوادم باستخدام wrk مع مؤشر ترابط واحد ، و 100 توصيل ، و 24 طلبًا متزامنًا (عبر الأنابيب) لكل اتصال (التوازي التراكمي لـ 2400 طلب).

يعد تسلسل HTTP أمرًا بالغ الأهمية هنا لأنه أحد التحسينات التي يأخذها Japronto في الاعتبار عند تنفيذ الطلبات.

تنفذ معظم الخوادم الطلبات من عملاء الأنابيب بنفس الطريقة التي ينفذونها من العملاء غير المتصلين. لا يحاولون تحسينه. (في الواقع ، ستقوم Sanic و Meinhold أيضًا بإسقاط الطلبات بصمت من عملاء خطوط الأنابيب ، وهو ما يعد انتهاكًا لبروتوكول HTTP 1.1.)

بعبارات بسيطة ، فإن الأنابيب هي تقنية لا يحتاج فيها العميل إلى انتظار الاستجابة قبل إرسال الطلبات اللاحقة عبر نفس اتصال TCP. لضمان تكامل الاتصال ، يرسل الخادم عدة استجابات بنفس الترتيب الذي تم استلام الطلبات فيه.

التفاصيل الدموية للتحسينات

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

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

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

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

كن حذرًا عند ضبط الاستدلال ، وفكر في تكلفة مكالمات النظام والوقت المتوقع لإكمال الطلب.

إلى جانب تأخير عمليات الكتابة للعملاء عبر خطوط الأنابيب ، هناك العديد من التقنيات الأخرى التي يستخدمها الكود.

تمت كتابة Japronto بالكامل تقريبًا في C. تتم كتابة المحلل اللغوي والبروتوكول وحاصد الاتصال وجهاز التوجيه والطلب وكائنات الاستجابة كملحقات C.

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

يعتمد Japronto على مكتبة picohttpparser C الممتازة لتحليل سطر الحالة ، والعناوين ، ونص رسالة HTTP مقسم. يستخدم Picohttpparser تعليمات معالجة النصوص الموجودة في وحدات المعالجة المركزية الحديثة ذات الامتدادات SSE4.2 (تقريبًا أي وحدة معالجة مركزية x86_64 عمرها 10 سنوات) لمطابقة حدود رموز HTTP بسرعة. يتم التعامل مع الإدخال / الإخراج بواسطة uvloop الرائع للغاية ، والذي يعد بحد ذاته غلافًا حول libuv. في أدنى مستوى ، يعد هذا بمثابة جسر لاستدعاء نظام epoll الذي يوفر إعلامات غير متزامنة حول جاهزية القراءة والكتابة.

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

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

يحاول Japronto عدم النسخ بين المخازن المؤقتة دون داع ، ويقوم بالعديد من العمليات في نفس المكان. على سبيل المثال ، تقوم النسبة المئوية بفك تشفير المسار قبل المطابقة في عملية جهاز التوجيه.

المساهمون مفتوحون المصدر ، يمكنني استخدام مساعدتكم.

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

أعتقد أن الوقت قد حان لتقاسم ثمار عملي مع المجتمع.

تنفذ Japronto حاليًا مجموعة ميزات قوية جدًا:

  • تنفيذ HTTP 1.x مع دعم التحميلات المقسمة
  • دعم كامل لتسيير HTTP
  • اتصالات على قيد الحياة مع حصادة شكلي
  • دعم وجهات النظر المتزامنة وغير المتزامنة
  • نموذج متعدد العمال يعتمد على التفرع
  • دعم إعادة تحميل التعليمات البرمجية عند التغييرات
  • توجيه بسيط

أود أن أنظر إلى Websockets وتدفق استجابات HTTP بشكل غير متزامن بعد ذلك.

هناك الكثير من العمل الذي يتعين القيام به فيما يتعلق بالتوثيق والاختبار. إذا كنت مهتمًا بالمساعدة ، فيرجى الاتصال بي مباشرة على Twitter. هذا هو مستودع مشروع Japronto GitHub.

أيضًا ، إذا كانت شركتك تبحث عن مطور Python وهو مهووس بالأداء ويقوم أيضًا بتنفيذ DevOps ، فأنا منفتح على سماع ذلك. سأفكر في المواقف في جميع أنحاء العالم.

الكلمات الأخيرة

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

أود أن أشكر مجتمع Python على استثمارهم المستمر في هندسة الأداء. وبالتحديد فيكتور ستينر @ VictorStinner و INADA Naokimethane و Yury Selivanov @ 1st1 وفريق PyPy بأكمله.

من أجل حب بايثون.