0Pricing
PHP Academy · درس

لماذا نستخدم المراسلة غير المتزامنة

اكتشف كيف تفصل قوائم الانتظار بين المنتجين والمستهلكين.

لماذا نستخدم المراسلة غير المتزامنة درس مجاني في PHP Academy على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في PHP Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة PHP Academy 4 دروس في المجموع.

لماذا المراسلة غير المتزامنة

في طلب متزامن، تتوقف عملية PHP أثناء تواصلها مع بوابات البريد الإلكتروني أو معالجات الدفع أو الخدمات التابعة. وتحت الحمل، يربط هذا زمن الاستجابة والتوافر بكل تبعية تستدعيها. تفك المراسلة غير المتزامنة هذه السلسلة: يضع المنتج رسالة في طابور ويعود فورًا؛ ثم يعالجها المستهلكون لاحقًا، كلٌّ بالسرعة المناسبة له.

يركّز هذا الدرس على السبب — فك الارتباط، والتخزين المؤقت، والمفاضلات المتعلقة بضمانات التسليم التي يجب فهمها قبل التعامل مع RabbitMQ أو Kafka.

الفصل المكاني والزماني

يفصل الطابور بين المنتجين والمستهلكين على محورين:

  • مكانيًا — لا يحتاج أي من الطرفين إلى عنوان الطرف الآخر؛ فهما يعرفان الوسيط فقط.
  • زمانيًا — قد يكون المستهلك متوقفًا عند نشر المنتج؛ وتنتظر الرسالة.

تربط النسخة المتزامنة أدناه طلب الويب بتوافر خادم البريد وسرعته.

<?php
// Synchronous: the HTTP request blocks on SMTP
function registerUser(string $email): void {
    saveUser($email);
    // If SMTP is slow or down, the user waits or the request fails
    sendWelcomeEmail($email); // 800ms blocking call
    echo "Registered\n";
}

function saveUser(string $e): void { /* ... */ }
function sendWelcomeEmail(string $e): void { usleep(1); }

registerUser('dev@example.com');

انشر وتابع

يحفظ الإصدار غير المتزامن المستخدم، وينشر رسالة UserRegistered، ثم يعيد الاستجابة. ويرسل عامل منفصل البريد الإلكتروني. يعتمد زمن استجابة HTTP الآن فقط على نشر سريع إلى وسيط محلي، لا على رحلة SMTP ذهابًا وإيابًا.

<?php
function registerUser(string $email): void {
    saveUser($email);
    // Publish a lightweight event; worker handles email later
    publish('user.registered', json_encode(['email' => $email]));
    echo "Registered (email queued)\n";
}

function saveUser(string $e): void { /* ... */ }
function publish(string $routingKey, string $payload): void {
    echo "-> queued $routingKey: $payload\n";
}

registerUser('dev@example.com');

موازنة الحمل (التخزين المؤقت)

تحدث طفرات حركة المرور بشكل غير منتظم، بينما تكون قدرة المعالجة ثابتة. يعمل الطابور كمخزن مؤقت: يمتص دفعة من 10,000 رسالة، ويتيح لمجموعة من العمال تصريفها بمعدل مستدام. ومن دون طابور، ستُغرق الطفرة قاعدة البيانات أو واجهة API التابعة مباشرة.

هذه هي موازنة الحمل — فأنت تستبدل زمن الاستجابة (قد تبقى الرسائل قليلًا) بالاستقرار (لا ينهار شيء).

ضمانات التسليم

يقدّم كل نظام مراسلة وعدًا بشأن التسليم. اعرف الوعد الذي يقدمه نظامك:

  • مرة واحدة على الأكثر — أرسل وانسَ؛ قد تُفقد الرسائل، لكنها لا تتكرر أبدًا.
  • مرة واحدة على الأقل — يُعاد تسليم الرسائل حتى تأكيدها؛ والتكرارات ممكنة. وهذا هو الافتراضي الشائع.
  • مرة واحدة بالضبط — لا فقد ولا تكرار؛ لكنها مكلفة، وغالبًا ما تكون وهمًا جزئيًا على مستوى التطبيق.

وبما أن معظم الأنظمة الفعلية تمنحك ضمان مرة واحدة على الأقل، يجب أن يتحمّل المستهلكون رؤية الرسالة نفسها مرتين.

المستهلكون غير المتأثرين بالتكرار

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

<?php
function handle(array $msg, PDO $pdo): void {
    $pdo->beginTransaction();
    try {
        // Unique constraint on message_id makes the insert the dedup gate
        $stmt = $pdo->prepare(
            'INSERT INTO processed_messages (id) VALUES (?)'
        );
        $stmt->execute([$msg['id']]);
    } catch (PDOException $e) {
        $pdo->rollBack();
        echo "Duplicate {$msg['id']} skipped\n";
        return; // already handled
    }
    chargeCustomer($msg['amount']);
    $pdo->commit();
}
function chargeCustomer(int $a): void {}

الإقرارات وإعادة التسليم

يشير المستهلك إلى النجاح بإرسال ack. وإذا انهار قبل إرسال ack، يعيد الوسيط تسليم الرسالة إلى مستهلك آخر. وهذا ما يجعل ضمان التسليم مرة واحدة على الأقل ممكنًا — لكنه يعني أن إرسال ack يجب أن يأتي بعد تثبيت الأثر الجانبي، وليس قبله أبدًا.

أرسل ack مبكرًا جدًا، فيؤدي الانهيار إلى فقدان الرسالة. وأرسله متأخرًا جدًا ثم يحدث انهيار، فتحصل على تكرار — وتمتصه طبقة قابلية التكرار دون تغيير النتيجة.

<?php
// Pseudocode of the consumer contract
function consumeLoop($channel): void {
    while ($msg = $channel->get()) {
        try {
            processSideEffect($msg);   // commit DB write first
            $channel->ack($msg);       // only then ack
        } catch (\Throwable $e) {
            $channel->nack($msg, requeue: true); // let it redeliver
        }
    }
}
function processSideEffect($m): void {}

الترتيب ليس مجانيًا

لا تضمن الطوابير ترتيبًا عامًا عند التوسع بإضافة المستهلكين. فعندما يسحب عاملان الرسائل من الطابور نفسه، يعالجانها بالتزامن، ولذلك قد تكتمل الرسالة B قبل الرسالة A.

إذا كان الترتيب مهمًا (مثل تغيّرات رصيد الحساب)، فيجب عليك استخدام التقسيم حسب المفتاح بحيث تنتقل جميع الرسائل المرتبطة إلى مستهلك واحد بالتسلسل. ينفّذ Kafka ذلك أصليًا باستخدام الأقسام؛ أما مع RabbitMQ فتوجّه الرسائل باستخدام تجزئة متسقة إلى طوابير مخصصة لكل مفتاح.

طوابير الرسائل الميتة

لا يمكن لبعض الرسائل أن تنجح أبدًا — مثل الحمولات غير الصحيحة أو المراجع إلى صفوف محذوفة. وتؤدي إعادة محاولة معالجتها إلى ما لا نهاية إلى تعطيل الطابور (إنها رسالة سامة). ويتمثل النمط المتّبع في طابور الرسائل الميتة (DLQ): بعد N محاولات فاشلة، وجّه الرسالة إلى جانب منفصل لفحصها بدلًا من إعادة تسليمها.

<?php
function consume(array $msg, $channel): void {
    $attempts = ($msg['headers']['x-attempt'] ?? 0) + 1;
    try {
        process($msg);
        $channel->ack($msg);
    } catch (\Throwable $e) {
        if ($attempts >= 5) {
            $channel->deadLetter($msg); // park in DLQ
        } else {
            $channel->republish($msg, ['x-attempt' => $attempts]);
        }
    }
}
function process(array $m): void {}

متى لا تستخدم طابورًا

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

استخدم طابورًا عندما يكون العمل بطيئًا أو متذبذبًا أو قابلًا لإعادة المحاولة أو من نوع أرسل وانسَ. واجعله متزامنًا عندما يحتاج المستدعي فعلًا إلى النتيجة الآن (مثل سعر معتمد يجب أن يراه المستخدم) — فتغليف استدعاء يحتاج إلى إجابة داخل طابور لا يضيف سوى زمن استجابة وتعقيد.

الطابور مقابل السجل

تدعم هذه الأنماط بنيتان عامتان للوسطاء، ويستخدم باقي هذا المقرر كليهما:

  • يحذف طابور المهام (RabbitMQ) الرسالة بمجرد تأكيدها بـ ack. وهو ممتاز لتوزيع العمل على مجموعة من المستهلكين المتنافسين، مع توجيه لكل رسالة ومدد TTL.
  • يحتفظ سجل الالتزام (Kafka) بالرسائل وفق سياسة الاحتفاظ؛ ويتتبع كل مستهلك إزاحته الخاصة ويمكنه إعادة تشغيل السجل، كما تقرأ مجموعات مستقلة عديدة التدفق نفسه.

اختر الطابور لتوزيع العمل، والسجل لتدفقات الأحداث ذات معدل النقل العالي وإعادة التشغيل.

<?php
$useCase = 'replay events for a new analytics service';
$broker = str_contains($useCase, 'replay') || str_contains($useCase, 'stream')
    ? 'Kafka (commit log)'
    : 'RabbitMQ (task queue)';
echo $broker . "\n";

اختبار سريع

التفكير في ضمانات التسليم.

خلاصة

لديك الآن النموذج الذهني وراء المراسلة غير المتزامنة:

  • توفر الطوابير فصلًا مكانيًا وزمانيًا، وتعمل كمخزن مؤقت لموازنة الحمل.
  • تعمل معظم الأنظمة بضمان مرة واحدة على الأقل، ولذلك يجب أن يكون المستهلكون غير متأثرين بالتكرار.
  • أرسل ack بعد تثبيت الأثر الجانبي؛ ودَع حالات الفشل تؤدي إلى إعادة التسليم.
  • يحتاج الترتيب إلى التقسيم حسب المفتاح؛ وتحتاج الرسائل السامة إلى DLQ.
  • لا تضع في طابور عملًا يحتاج المتصل إلى إجابته بشكل متزامن.

والآن ستطبّق ذلك عمليًا باستخدام RabbitMQ في PHP.

الأسئلة الشائعة

هل درس «لماذا نستخدم المراسلة غير المتزامنة» مجاني؟

نعم — نص درس «لماذا نستخدم المراسلة غير المتزامنة» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة PHP Academy، انتقل إلى CoddyKit PRO. تتضمن دورة PHP Academy 4 دروس في المجموع.

ماذا ستتعلم في «لماذا نستخدم المراسلة غير المتزامنة»؟

اكتشف كيف تفصل قوائم الانتظار بين المنتجين والمستهلكين. تتمرن على PHP Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ PHP Academy؟

لا تُشترط خبرة سابقة. PHP Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.

كم من الوقت يستغرق درس «لماذا نستخدم المراسلة غير المتزامنة»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس PHP Academy هذا؟

نعم. كل درس في PHP Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. لماذا نستخدم المراسلة غير المتزامنة
  2. العمل مع RabbitMQ في PHP
  3. Apache Kafka مع PHP
  4. بناء سير عمل موجّه بالأحداث
← العودة إلى PHP Academy