توضح هذه المقالة كيف استخدم فريق التطوير القياسات للعثور على الازدحام وتحسين أداء النظام الموزَّع. تستند المقالة إلى اختبار التحميل الفعلي الذي أجريناه على تطبيق نموذجي.
هذا المقال جزء من سلسلة مقالات. اقرأ الجزء الأول هنا.
السيناريو: معالجة سلسلة من الأحداث باستخدام Azure Functions.
في هذا السيناريو، يقوم أسطول من الطائرات بدون طيار بإرسال بيانات الموقع في الوقت الحقيقي إلى Azure IoT Hub. يتلقى تطبيق Functions الأحداث، ويحول البيانات إلى تنسيق GeoJSON ، ويكتب البيانات المحولة إلى Azure Cosmos DB. يحتوي Azure Cosmos DB على دعم أصلي للبيانات الجغرافية المكانية، ويمكن فهرسة مجموعات Azure Cosmos DB للاستعلامات المكانية الفعالة. على سبيل المثال، يمكن لتطبيق العميل الاستعلام عن جميع الطائرات بدون طيار في نطاق كيلومتر واحد من موقع معين، أو العثور على جميع الطائرات بدون طيار داخل منطقة معينة.
تعد متطلبات المعالجة هذه بسيطة بما يكفي بحيث لا تتطلب محرك معالجة تدفق كامل. لا تنضم المعالجة إلى التدفقات أو البيانات المجمعة أو المعالجة عبر النوافذ الزمنية على وجه الخصوص. استناداً إلى هذه المتطلبات، تعد Azure Functions مناسبة تماماً لمعالجة الرسائل. يمكن ل Azure Cosmos DB أيضا التوسع لدعم معدل نقل الكتابة العالي جدا.
مراقبة معدل النقل
يقدم هذا السيناريو تحدياً مثيراً للاهتمام في الأداء. معدل البيانات لكل جهاز معروف، ولكن قد يتقلب عدد الأجهزة. بالنسبة لسيناريو الأعمال هذا، فإن متطلبات زمن الانتقال ليست صارمة بشكل خاص. يجب أن يكون الموقع المبلغ عنه للطائرة بدون طيار دقيقاً في غضون دقيقة واحدة فقط. ومع ذلك، بمرور الوقت، يجب أن يواكب التطبيق الوظيفي متوسط معدل الامتصاص.
يقوم IoT Hub بتخزين الرسائل في تدفق السجل. يتم إلحاق الرسائل الواردة بذيل التدفق. يتحكم قارئ الدفق - في هذه الحالة، التطبيق الوظيفي - في معدله الخاص لاجتياز التدفق. هذا الفصل بين مسارات القراءة والكتابة يجعل IoT Hub فعالاً للغاية، ولكنه يعني أيضاً أن القارئ البطيء يمكن أن يتخلف عن الركب. لاكتشاف هذه الحالة، أضاف فريق التطوير مقياساً مخصصاً لقياس تأخر الرسائل. يسجل هذا المقياس الدلتا بين وقت وصول الرسالة إلى IoT Hub، والوقت الذي تتلقى فيه الوظيفة الرسالة للمعالجة.
var ticksUTCNow = DateTimeOffset.UtcNow;
// Track whether messages are arriving at the function late.
DateTime? firstMsgEnqueuedTicksUtc = messages[0]?.EnqueuedTimeUtc;
if (firstMsgEnqueuedTicksUtc.HasValue)
{
CustomTelemetry.TrackMetric(
context,
"IoTHubMessagesReceivedFreshnessMsec",
(ticksUTCNow - firstMsgEnqueuedTicksUtc.Value).TotalMilliseconds);
}
تقوم الطريقة TrackMetric
بكتابة مقياس مخصص لـ Application Insights. للحصول على معلومات حول استخدام TrackMetric
داخل Azure Function، راجع القياس عن بعد المخصص في دالة C#.
إذا كانت الوظيفة تواكب حجم الرسائل، فيجب أن يظل هذا المقياس في حالة ثبات منخفضة. لا مفر من بعض التأخر في الاستجابة، لذلك لن تكون القيمة صفراً أبداً. ولكن إذا تأخرت الدالة، فستبدأ الدلتا بين الوقت المدرج في قائمة الانتظار ووقت المعالجة في الارتفاع.
اختبار 1: خط الأساس
أظهر اختبار التحميل الأول مشكلة فورية: تلقى تطبيق Function باستمرار أخطاء HTTP 429 من Azure Cosmos DB، مما يشير إلى أن Azure Cosmos DB كان يخنق طلبات الكتابة.
استجابة لذلك، قام الفريق بتوسيع نطاق Azure Cosmos DB عن طريق زيادة عدد وحدات الطلب المخصصة للمجموعة، ولكن استمرت الأخطاء. بدا هذا غريبا، لأن حساب خلف المغلف أظهر أن Azure Cosmos DB يجب ألا يكون لديه مشكلة في مواكبة حجم طلبات الكتابة.
لاحقاً في ذلك اليوم، أرسل أحد المطورين البريد الإلكتروني التالي إلى الفريق:
نظرت إلى Azure Cosmos DB للمسار الدافئ. هناك شيء واحد لا أفهمه. مفتاح القسم هو deliveryId، ولكننا لا نرسل deliveryId إلى Azure Cosmos DB. هل فاتني شيء؟
كان هذا هو المفتاح. بالنظر إلى خريطة الحرارة للقسم، اتضح أن جميع المستندات كانت تهبط على نفس القسم.
ما تريد رؤيته في خريطة الحرارة هو توزيع متساوٍ عبر جميع الأقسام. في هذه الحالة، نظراً لأنه تمت كتابة كل مستند على نفس القسم، فإن إضافة وحدات RU لم تحل المشكلة. تحولت المشكلة إلى خطأ في التعليمة البرمجية. على الرغم من أن مجموعة Azure Cosmos DB تحتوي على مفتاح قسم، إلا أن Azure Function لم تقم بالفعل بتضمين مفتاح القسم في المستند. لمزيد من المعلومات حول خريطة التمثيل اللوني للقسم، راجع تحديد توزيع معدل النقل عبر الأقسام.
اختبار 2: إصلاح مشكلة التقسيم
عندما نشر الفريق إصلاح التعليمات البرمجية وأعد تشغيل الاختبار، توقف Azure Cosmos DB عن التقييد. لفترة من الوقت، بدا كل شيء على ما يرام. ولكن عند وجود حمل معين، أظهر القياس عن بُعد أن الدالة كانت ترصد عدداً أقل من المستندات المطلوبة. يعرض الرسم البياني التالي الرسائل التي تتلقاها من IoT Hub مقابل المستندات المكتوبة إلى Azure Cosmos DB. الخط الأصفر هو عدد الرسائل المستلمة لكل دفعة، والأخضر هو عدد المستندات المكتوبة لكل دفعة. يجب أن تكون هذه متناسبة. بدلاً من ذلك، ينخفض عدد عمليات كتابة قاعدة البيانات لكل دفعة بشكل ملحوظ عند حوالي الساعة 07:30.
يوضح الرسم البياني التالي زمن الاستجابة بين وقت وصول رسالة إلى IoT Hub من أحد الأجهزة، ووقت معالجة تطبيق الدالة لتلك الرسالة. يمكنك أن ترى أنه في نفس الوقت، فإن التأخير يرتفع بشكل كبير، ويتوقف عند مستويات معينة، وينخفض.
السبب في ارتفاع القيمة إلى الذروة عند 5 دقائق ثم انخفاضها إلى الصفر هو أن تطبيق الدالة يتجاهل الرسائل التي تأخرت أكثر من 5 دقائق:
foreach (var message in messages)
{
// Drop stale messages,
if (message.EnqueuedTimeUtc < cutoffTime)
{
log.Info($"Dropping late message batch. Enqueued time = {message.EnqueuedTimeUtc}, Cutoff = {cutoffTime}");
droppedMessages++;
continue;
}
}
يمكنك رؤية هذا في الرسم البياني عندما يتراجع مقياس التأخير إلى الصفر. في غضون ذلك، تم فقد البيانات، لأن الدالة كانت ترمي الرسائل بعيداً.
ماذا كان يحدث؟ بالنسبة لاختبار التحميل المحدد هذا، كان لدى مجموعة Azure Cosmos DB وحدات طلب لتوفيرها، لذلك لم يكن الازدحام في قاعدة البيانات. بدلاً من ذلك، كانت المشكلة في حلقة معالجة الرسائل. ببساطة، لم تكن الدالة تكتب المستندات بسرعة كافية لمواكبة الحجم الوارد للرسائل. بمرور الوقت، تراجعت أكثر فأكثر.
اختبار 3: الكتابة المتوازية
إذا كان وقت معالجة الرسالة هو العقبة، فإن أحد الحلول هو معالجة المزيد من الرسائل بشكل متوازٍ. في هذا السيناريو:
- قم بزيادة عدد أقسام IoT Hub. يتم تعيين مثيل وظيفة واحد لكل قسم IoT Hub في كل مرة، لذلك نتوقع زيادة معدل النقل خطياً مع عدد الأقسام.
- قم بموازاة كتابة الوثيقة داخل الدالة.
لاستكشاف الخيار الثاني، قام الفريق بتعديل الدالة لدعم الكتابة المتوازية. استخدم الإصدار الأصلي من الدالة ربط إخراج Azure Cosmos DB. يستدعي الإصدار المحسن عميل Azure Cosmos DB مباشرة وينفذ عمليات الكتابة بالتوازي باستخدام Task.WhenAll:
private async Task<(long documentsUpserted,
long droppedMessages,
long cosmosDbTotalMilliseconds)>
ProcessMessagesFromEventHub(
int taskCount,
int numberOfDocumentsToUpsertPerTask,
EventData[] messages,
TraceWriter log)
{
DateTimeOffset cutoffTime = DateTimeOffset.UtcNow.AddMinutes(-5);
var tasks = new List<Task>();
for (var i = 0; i < taskCount; i++)
{
var docsToUpsert = messages
.Skip(i * numberOfDocumentsToUpsertPerTask)
.Take(numberOfDocumentsToUpsertPerTask);
// client will attempt to create connections to the data
// nodes on Azure Cosmos DB clusters on a range of port numbers
tasks.Add(UpsertDocuments(i, docsToUpsert, cutoffTime, log));
}
await Task.WhenAll(tasks);
return (this.UpsertedDocuments,
this.DroppedMessages,
this.CosmosDbTotalMilliseconds);
}
لاحظ أن ظروف السباق ممكنة مع الاقتراب. افترض أن رسالتين من نفس الطائرة بدون طيار تصادف وصولهما في نفس مجموعة الرسائل. من خلال كتابتها بالتوازي، يمكن للرسالة السابقة أن تحل محل الرسالة اللاحقة. بالنسبة لهذا السيناريو المحدد، يمكن للتطبيق أن يتسامح مع فقدان رسالة عرضية. ترسل الطائرات بدون طيار بيانات موضع جديدة كل 5 ثوان، لذلك يتم تحديث البيانات في Azure Cosmos DB باستمرار. ومع ذلك، في سيناريوهات أخرى، قد يكون من المهم معالجة الرسائل بالترتيب بدقة.
بعد توزيع هذا التغيير في التعليمة البرمجية، كان التطبيق قادراً على استيعاب أكثر من 2500 طلب / ثانية، باستخدام IoT Hub مع 32 قسماً.
الاعتبارات من جانب العميل
قد تتضاءل تجربة العميل الإجمالية من خلال التوازي العدواني على جانب الخادم. ضع في اعتبارك استخدام مكتبة المنفذ المجمع ل Azure Cosmos DB (غير الموضحة في هذا التنفيذ) التي تقلل بشكل كبير من موارد الحوسبة من جانب العميل اللازمة لتشبع معدل النقل المخصص لحاوية Azure Cosmos DB. يحقق التطبيق المفرد المترابط الذي يكتب البيانات باستخدام واجهة برمجة تطبيقات الاستيراد المجمّع ما يقرب من عشرة أضعاف معدل النقل في الكتابة عند مقارنته بالتطبيق متعدد الخيوط الذي يكتب البيانات بشكل متوازٍ أثناء تشبع وحدة المعالجة المركزية لجهاز العميل.
الملخص
بالنسبة لهذا السيناريو، تم تحديد الازدحام التالية:
- قسم الكتابة الساخنة، بسبب فقدان قيمة مفتاح القسم في المستندات التي تتم كتابتها.
- كتابة المستندات بالتسلسل لكل قسم IoT Hub.
لتشخيص هذه المشكلات، اعتمد فريق التطوير على القياسات التالية:
- الطلبات المقيدة في Azure Cosmos DB.
- خريطة حرارة التقسيم - الحد الأقصى من وحدات RU المستهلكة لكل قسم.
- تم استلام الرسائل مقابل إنشاء المستندات.
- تأخر الرسائل.