تحسين أداء معدل النقل لتطبيقات Python في وظائف Azure

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

تحجيم أفقي

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

تحسين أداء معدل النقل

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

نوع حِمل العمل سمات تطبيق الوظيفة الأمثلة
حدود الإدخال/إخراج • تحتاج التطبيقات إلى معالجة الكثير من الاستدعاءات المتزامنة.
• يعالج التطبيق عددًا كبيرًا من أحداث الإدخال/الإخراج، مثل عمليات استدعاء الشبكة وقراءة/كتابة القرص.
• ويب APIs
معالج-ارتباط • يقوم التطبيق بحسابات التشغيل الطويل، مثل تغيير حجم الصورة.
• يقوم التطبيق بتحويل البيانات.
• معالجة البيانات
• استنتاج التعلم الآلي

يجب تأدية التطبيق وفقًا لتحميلات إنتاج واقعية، إذ عادة ما تكون وظيفة أحمال العمل عبارة عن مزيج من الإدخال/الإخراج ومعالج ارتباط.

التكوينات الخاصة بالأداء

بعد فهم ملف تعريف حمل العمل لتطبيق الوظائف، فيما يلي التكوينات التي يمكنك استخدامها لتحسين أداء معدل النقل لوظائفك.

غير متزامنة

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

لتشغيل وظيفة بشكل غير متزامن، استخدمasync defالعبارة التي تقوم بتشغيل الوظيفة مع asyncioمباشرة:

async def main():
    await some_nonblocking_socket_io_op()

فيما يلي مثال على دالة مع مشغل HTTP يستخدم عميل aiohttp http:

import aiohttp

import azure.functions as func

async def main(req: func.HttpRequest) -> func.HttpResponse:
    async with aiohttp.ClientSession() as client:
        async with client.get("PUT_YOUR_URL_HERE") as response:
            return func.HttpResponse(await response.text())

    return func.HttpResponse(body='NotFound', status_code=404)

يتم تشغيل دالة async بدون الكلمة الأساسية تلقائيا في تجمع مؤشر ترابط ThreadPoolExecutor:

# Runs in a ThreadPoolExecutor threadpool. Number of threads is defined by PYTHON_THREADPOOL_THREAD_COUNT. 
# The example is intended to show how default synchronous functions are handled.

def main():
    some_blocking_socket_io()

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

فيما يلي بعض الأمثلة على مكتبات العميل التي نفذت أنماطا غير متزامنة:

  • aiohttp- Http العميل/الخادم لـ asyncio
  • Microsoft Stream API- إعدادات أولية ذات مستوى عال من عدم التزمن/جاهزة-مترقبة للعمل مع اتصال الشبكة
  • Janus Queue- مؤشر ترابط asyncio الآمن على علم بقائمة انتظار Python
  • pyzmq - ربط بيانات Python لـ ZeroMQ
فهم عدم التزامن في عامل Python

عند تعريف async أمام توقيع دالة، يضع Python علامة على الدالة ك coroutine. عند استدعاء coroutine، يمكن جدولته كمهمة في حلقة حدث. عند استدعاء await دالة غير متزامنة، فإنه يسجل متابعة في حلقة الحدث، مما يسمح لحلقة الحدث بمعالجة المهمة التالية أثناء وقت الانتظار.

في عامل Python الخاص بنا، يشارك العامل حلقة الحدث مع وظيفة العميل async وهو قادر على التعامل مع طلبات متعددة بشكل متزامن. نحن نشجع عملائنا بشدة على الاستفادة من المكتبات المتوافقة مع asyncio، مثل aiohttp وpyzmq. يؤدي اتباع هذه التوصيات إلى زيادة معدل نقل الدالة مقارنة بتلك المكتبات عند تنفيذها بشكل متزامن.

إشعار

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

استخدام عمليات عامل لغة كمبيوتر متعدد

بشكل افتراضي، كل مثيل مضيف وظائف لديه عملية عاملة بلغة واحدة. يمكنك زيادة عدد عمليات العامل لكل مضيف (حتى 10) باستخدام FUNCTIONS_WORKER_PROCESS_COUNT إعداد التطبيق. يحاول Azure Functions بعد ذلك توزيع استدعاءات الوظيفة المتزامنة عبر هذه العوامل بالتساوي.

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

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

FUNCTIONS_WORKER_PROCESS_COUNT ينطبق على كل مضيف تقوم Azure Functions بإنشائه عند توسيع نطاق التطبيق الخاص بك لتلبية الطلب.

إعداد الحد الأقصى للعمال ضمن عملية عامل لغة الكمبيوتر

كما هو مذكور في مقطععدم التزامن، يعامل عامل لغة Pythonالوظائف وcoroutines بشكل مختلف. يتم تشغيل coroutine في نفس حلقة تكرار الحدث الذي يتم تشغيل عامل لغة الكمبيوتر فيها. من ناحية أخرى، يتم تشغيل استدعاء دالة داخل ThreadPoolExecutor، والذي يحتفظ به عامل اللغة كمترابط.

يمكنك تعيين قيمة الحد الأقصى المسموح به للعمال لتشغيل وظائف المزامنة باستخدام إعداد تطبيقمؤشر ترابط حساب PYTHON_THREADPOOL_THREAD_COUNT. تعين هذه القيمةmax_workerوسيطة العنصر ThreadPoolExecutor الذي يتيح لـ Python استخدام مجموعة تحتوي في الغالب علىmax_worker مؤشرات الترابط لتنفيذ الاستدعاءات بشكل غير متزامن. PYTHON_THREADPOOL_THREAD_COUNTينطبق على كل عامل يقوم مضيف وظائف بإنشائه، مع تقرير Paython لوقت إنشاء مؤشر ترابط جديد أو إعادة استخدام مؤشر الترابط الخمول الحالي. بالنسبة إلى الإصدارات القديمة من Python (أي3.8و3.7و3.6)، يتمmax_worker تعيين القيمة إلى 1. في إصدار Python 3.9، تم تعيين max_worker على None.

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

بالنسبة إلى التطبيقات المرتبطة بالإدخال/الإخراج، يجب أن ترى مكاسب كبيرة من خلال زيادة عدد مؤشرات الترابط التي تعمل على كل استدعاء. التوصية هي البدء ب Python الافتراضي (عدد الذاكرات الأساسية) + 4 ثم تعديل استنادا إلى قيم الإنتاجية التي تراها.

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

إشعار

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

إدارة حلقة تكرار الحدث

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

هناك العديد من مستندات Python الرسمية المفيدة التي تناقش Coroutines والمهام وحلقة الأحداث باستخدام مكتبة asyncio المضمنة.

خذمكتبة الطلباتالتالية كمثال، تستخدم هذه القصاصة البرمجيةمكتبة asyncioلتضمينrequests.get()الأسلوب في coroutine، مشغلاً طلبات ويب متعددة لاختبار عنوان URL في وقت واحد.

import asyncio
import json
import logging

import azure.functions as func
from time import time
from requests import get, Response


async def invoke_get_request(eventloop: asyncio.AbstractEventLoop) -> Response:
    # Wrap requests.get function into a coroutine
    single_result = await eventloop.run_in_executor(
        None,  # using the default executor
        get,  # each task call invoke_get_request
        'SAMPLE_URL'  # the url to be passed into the requests.get function
    )
    return single_result

async def main(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Python HTTP trigger function processed a request.')

    eventloop = asyncio.get_event_loop()

    # Create 10 tasks for requests.get synchronous call
    tasks = [
        asyncio.create_task(
            invoke_get_request(eventloop)
        ) for _ in range(10)
    ]

    done_tasks, _ = await asyncio.wait(tasks)
    status_codes = [d.result().status_code for d in done_tasks]

    return func.HttpResponse(body=json.dumps(status_codes),
                             mimetype='application/json')

تحجيم عمودي

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

الخطوات التالية

للحصول على مزيدٍ من المعلومات حول تطوير Python في وظائف Azure، راجع الموارد التالية: