المعالجة غير المتزامنة والمراسلة

مشروع: تصميم خط معالجة غير متزامن

18 دقيقة الدرس 10 من 10

مشروع: تصميم خط معالجة غير متزامن

كل ما تناولناه في هذا الفصل كان يبني نحو مهارة واحدة: الجلوس أمام لوح فارغ وتصميم خط معالجة غير متزامن على مستوى الإنتاج من الصفر. يأخذنا هذا الدرس عبر هذه العملية بالكامل باستخدام حمل عمل واقعي — منصة رفع وترميز الفيديو — مشابهة لما تشغّله YouTube وVimeo وMux على نطاق واسع. ستتخذ كل قرار معماري بوضوح، وستبرر المقايضات، وتنتج تصميماً قادراً على الدفاع عنه في أي مقابلة هندسية أو عرض أمام فريق تطوير.

حمل العمل النموذجي: رفع الفيديو والترميز

المتطلبات محددة عمداً:

  • يرفع المستخدمون ملفات فيديو خام بحجم يصل إلى 10 جيجابايت لكل ملف.
  • يجب ترميز كل ملف مرفوع إلى خمسة مستويات جودة: 2160p و1080p و720p و480p و360p.
  • يستغرق ترميز دقيقة واحدة من لقطات 4K نحو 3–8 دقائق من وقت المعالج لكل مستوى جودة.
  • فيديو خام مدته 30 دقيقة يُنتج نحو 2.5 ساعة من عمل المعالج الإجمالي.
  • ذروة التحميل: 500 عملية رفع متزامنة خلال فعالية مباشرة أو إطلاق منتج.
  • يجب أن يتلقى من يرفع الملف تأكيداً خلال ثانيتين؛ أما الترميز فقد يستغرق حتى 30 دقيقة.
  • يجب إشعار المستخدمين والأنظمة المتكاملة (CDN، مُفهرس البحث، التحليلات) عند اكتمال كل مستوى جودة.
  • يجب ألا تُفقد عملية ترميز فاشلة بصمت — يجب إعادة محاولتها وتوجيهها للمراجعة البشرية بعد استنفاد المحاولات.
الفكرة الجوهرية: حين ترى "تأكيد في ثانيتين، لكن العمل يستغرق 30 دقيقة"، تعلم أن النظام يجب أن يكون غير متزامن. المعالجة المتزامنة مستحيلة هيكلياً هنا. يجب على معالج الرفع قبول الملف وإشعار المستخدم وتفويض العمل فوراً.

الخطوة الأولى — تحديد المنتجين والمستهلكين

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

  • المنتجون: خدمة Upload API (حدث واحد لكل رفع مكتمل)، ثم عمال الترميز أنفسهم لاحقاً (حدث واحد لكل مستوى جودة مكتمل).
  • المستهلكون: عمال الترميز (المستهلك الثقيل الرئيسي)، خدمة الإشعارات، مُفهرس البحث، مُحلل التحليلات، خدمة CDN.

لاحظ أن عمال الترميز هم في آنٍ واحد مستهلكون (يستهلكون أحداث الرفع) ومنتجون (يُصدرون أحداث اكتمال الترميز). هذه الازدواجية إشارة قوية على أن منصة بث الأحداث مثل Kafka هي العمود الفقري الصحيح — لا قائمة انتظار بسيطة من نقطة إلى نقطة.

الخطوة الثانية — اختيار تقنية الرسائل

مع وجود خمسة مستهلكين لحدث اكتمال الترميز، فإن قائمة انتظار واحدة كـ RabbitMQ بنمط المستهلكين المتنافسين ستوصّل كل رسالة إلى مستهلك واحد فقط. هذا خطأ تماماً — كل مستهلك (الإشعارات، البحث، التحليلات، CDN، المراقبة) يحتاج نسخته الخاصة. الخيار يضيق إلى:

  • مواضيع Kafka مع مجموعات مستهلكين متعددة — كل مجموعة مستهلكين تحصل على كل رسالة باستقلالية، والرسائل محتجزة على القرص لساعات أو أيام، وإعادة التشغيل سهلة. هذا هو الخيار الصحيح.
  • SNS + SQS fan-out على AWS — موضوع SNS يوزّع على قوائم SQS متعددة، واحدة لكل نوع مستهلك. صالح إذا كنت تعمل بالكامل على AWS.

في هذا التصميم نختار Kafka لأن ميزات الاحتجاز وإعادة التشغيل حيوية: إذا توقف مُفهرس البحث 20 دقيقة أثناء نشر جديد، يجب أن يتمكن من اللحاق من آخر offset مُرحَّل بدلاً من فقدان تلك الأحداث نهائياً.

الخطوة الثالثة — تحديد المواضيع واستراتيجية التقسيم

ثلاثة مواضيع Kafka تكفي لهذا الخط:

  • video.uploads.raw — رسالة واحدة لكل رفع مكتمل، مفتاحها video_id. عدد الأقسام: 50 (يدعم 500 رفع متزامن؛ كل قسم يعالجه عامل ترميز واحد).
  • video.transcodes.progress — رسالة واحدة لكل مستوى جودة مكتمل (أي 5 رسائل لكل فيديو). مفتاحها video_id لضمان أن جميع الأحداث الخمسة لنفس الفيديو تقع في نفس القسم (ترتيب مضمون لكل فيديو).
  • video.transcodes.dlq — موضوع الرسائل الميتة. وظائف الترميز الفاشلة تصل هنا بعد 3 محاولات إعادة للمراجعة اليدوية.
قسّم بحسب video_id لا user_id. التقسيم بـ user_id يركّز التحميل على مستخدمي القوة الذين يرفعون بكثرة، مما يخلق أقساماً ساخنة. أما video_id فهو UUID يتوزع بشكل متساوٍ.

الخطوة الرابعة — تصميم تدفق الخط الكامل

بعد تحديد التقنية والمواضيع، يصبح التدفق الكامل من البداية للنهاية واضحاً. المخطط أدناه يُظهر جميع المكونات ومسار البيانات من الرفع الخام حتى تسليم CDN.

Video Upload and Transcoding Async Pipeline Upload API POST /upload Object Storage S3 / GCS raw file Kafka Topic video.uploads.raw publish Transcode Worker Pool 50 consumers consume read raw Kafka Topic video.transcodes.progress publish DLQ (3 retries) on failure Notification Service Search Indexer Analytics Ingestor CDN Purge Service
خط المعالجة الكامل: Upload API تنشر إلى Kafka، وعمال الترميز يستهلكون ويُعيدون نشر أحداث التقدم، وخمس مجموعات مستهلكين مستقلة تتلقى كل حدث اكتمال ترميز.

الخطوة الخامسة — ضمانات التسليم والتعامل مع التكرار

يجب أن يستخدم عمال الترميز التسليم مرة على الأقل — لا يمكننا بأي حال فقدان وظيفة ترميز. هذا يعني أن العامل يُرحّل offset Kafka الخاص به فقط بعد رفع الملف المُرمَّز إلى التخزين الكائني ونشر حدث التقدم. إذا تعطّل العامل في منتصف العمل، سيُعيد Kafka تسليم الرسالة إلى عامل آخر. هذا آمن لأن الترميز متساوٍ: ترميز نفس الملف المصدر بنفس مستوى الجودة دائماً ينتج نفس المخرج. يجب على العامل التحقق مما إذا كان ملف المخرج موجوداً بالفعل في التخزين قبل بدء العمل لتجنب التكلفة الحسابية الزائدة.

لا تُرحّل الـ offsets بتسرع. إذا رحّل العامل offset Kafka فور استلام الرسالة — قبل إنهاء الترميز — فإن أي تعطّل سيُسقط ذلك الفيديو بصمت. رحّل دائماً بعد إنجاز العمل بشكل دائم.

الخطوة السادسة — الضغط الخلفي والتوسع

مع 50 قسماً في Kafka، يمكن للمجموعة العمالية التوسع أفقياً حتى 50 نموذجاً دون أي تغييرات في الكود — يعيد بروتوكول مجموعة مستهلكي Kafka توزيع الأقسام تلقائياً. خلال ارتفاع حاد بـ 500 رفع متزامن، تتراكم الرسائل في الموضوع (يكبر تأخر المستهلك في Kafka). هذا ضغط خلفي مقصود: تمتص القائمة حمل الارتفاع، ويُفرغها العمال بمعدل مستدام. اضبط تنبيهات تأخر المستهلك عند 200 رسالة لتشغيل التوسع التلقائي لمجموعة العمال.

وظائف الترميز كثيفة المعالج وطويلة (تصل إلى 30 دقيقة). اضبط max.poll.interval.ms في إعدادات مستهلك Kafka إلى 35 دقيقة على الأقل لمنع الوسيط من طرد عامل مشغول فعلاً بمعالجة ملف 4K كبير.

الخطوة السابعة — قائمة الرسائل الميتة والعمليات

بعد 3 محاولات إعادة (بتراجع أسي: دقيقة واحدة، 5 دقائق، 25 دقيقة)، تُنشر وظيفة الترميز الفاشلة إلى موضوع video.transcodes.dlq. لوحة عمليات تستهلك هذا الموضوع وتُنشئ تذكرة Jira/PagerDuty مع video_id ورسالة الخطأ وتتبع المكدس. يمكن للمشغّل فحص الملف المصدر في التخزين الكائني وإصلاح المشكلة وإعادة نشر الرسالة يدوياً إلى video.uploads.raw للمحاولة مجدداً.

تقدير السعة

تحقق سريع على الورق يُصحح التصميم:

  • 500 رفع في الذروة × 2.5 ساعة معالج لكل منها = 1,250 ساعة معالج من عمل الترميز لتفريغها.
  • عامل ترميز واحد يستخدم نحو 2 vCPU. نموذج واحد من c5.2xlarge (8 vCPUs) يشغّل 4 وظائف ترميز متوازية.
  • لتفريغ 1,250 ساعة معالج في 30 دقيقة تحتاج نحو 42 نموذجاً مماثلاً (1,250 / 0.5 ساعة / 4 وظائف × هامش أمان ≈ 40–50 عقدة). يتطابق هذا مع تصميم 50 قسماً بدقة.
  • بسعر 0.34 دولار/ساعة لكل c5.2xlarge، تكلف ذروة 30 دقيقة نحو 7.14 دولار — استخدم نماذج spot لتقليله بنسبة 70%.
افعل دائماً حسابات السعة قبل تحديد عدد الأقسام نهائياً. عدد الأقسام يُقيّد أقصى تزامن ممكن للمستهلكين. إذا كان حمل العمل يتطلب 200 عامل متزامن في الذروة، تحتاج 200 قسم على الأقل — ولا يمكنك تقليص الأقسام على موضوع Kafka مباشر دون إعادة إنشائه.

ملخص التصميم

يُطبق التصميم النهائي كل مفهوم من هذا الفصل:

  • الفصل غير المتزامن — تعود Upload API في أقل من ثانيتين بصرف النظر عن وقت الترميز.
  • مواضيع Kafka باستراتيجية تقسيم مدروسة لتوزيع الحمل بالتساوي.
  • توزيع Pub/Sub — خمس مجموعات مستهلكين تتلقى كل حدث تقدم باستقلالية.
  • التسليم مرة على الأقل مع عمال متساوي الأثر (التحقق من وجود المخرج قبل المعالجة).
  • الضغط الخلفي عبر تأخر المستهلك ومشغّلات التوسع التلقائي.
  • قائمة الرسائل الميتة مع سير عمل مراجعة بشرية وإعادة حقن يدوية.
  • الخدمات التالية المدفوعة بالأحداث — فهرسة البحث والتحليلات وإبطال CDN كلها تُشغَّل بالأحداث لا بمهام cron أو استطلاع.

هذا ما يبدو عليه خط المعالجة غير المتزامن في الإنتاج. ستختلف مكدس التقنية المحددة — RabbitMQ بدلاً من Kafka لاحتياجات توزيع أبسط، أو SQS+SNS على AWS، أو Pub/Sub على GCP — لكن القرارات الهيكلية تبقى كما هي: حدّد المنتجين والمستهلكين، اختر تقنية رسائل تتناسب مع متطلبات التوزيع والاحتجاز، قسّم للتوازي، رحّل الـ offsets فقط بعد الإنجاز الدائم، وابنِ دائماً مسار DLQ.

اكتمل الدرس!

تهانينا! لقد أكملت جميع الدروس في هذا البرنامج التعليمي.