0Pricing
PHP Academy · บทเรียน

เหตุผลที่ควรใช้การส่งข้อความแบบอะซิงโครนัส

ดูว่าคิวช่วยแยกผู้ผลิตออกจากผู้บริโภคได้อย่างไร

เหตุผลที่ควรใช้การส่งข้อความแบบอะซิงโครนัส เป็นบทเรียน PHP Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 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 รายการ และเปิดให้กลุ่มเวิร์กเกอร์ระบายข้อความออกในอัตราที่ยั่งยืน หากไม่มีคิว ปริมาณที่พุ่งขึ้นจะทำให้ฐานข้อมูลหรือส่วนติดต่อโปรแกรมประยุกต์ปลายทางรับภาระโดยตรงจนเกินกำลัง

นี่คือ การปรับระดับภาระงาน — แลกเวลาแฝง (ข้อความอาจรออยู่ชั่วครู่) กับเสถียรภาพ (ไม่มีส่วนใดล่มลง)

การรับประกันการส่งมอบ

ระบบส่งข้อความทุกระบบจะให้คำมั่นเกี่ยวกับการส่งมอบ โปรดทราบว่าคุณกำลังใช้รูปแบบใด:

  • อย่างมากหนึ่งครั้ง — ส่งแล้วลืมได้ ข้อความอาจสูญหาย แต่จะไม่ซ้ำ
  • อย่างน้อยหนึ่งครั้ง — ส่งซ้ำจนกว่าจะได้รับการยืนยันรับ ข้อความซ้ำอาจเกิดขึ้นได้ นี่คือค่าเริ่มต้นที่พบได้ทั่วไป
  • หนึ่งครั้งพอดี — ไม่สูญหายและไม่ซ้ำ มีค่าใช้จ่ายสูง และมักเป็นเพียงภาพลวงตาบางส่วนในระดับแอปพลิเคชัน

เนื่องจากระบบจริงส่วนใหญ่ให้การรับประกันแบบ อย่างน้อยหนึ่งครั้ง ผู้บริโภคของคุณจึงต้องรองรับการเห็นข้อความเดิมสองครั้ง

ผู้บริโภคที่ให้ผลลัพธ์เดิมเมื่อทำซ้ำ

วิธีแก้ปัญหาข้อความซ้ำจากการส่งอย่างน้อยหนึ่งครั้งคือ การให้ผลลัพธ์เดิมเมื่อทำซ้ำ: การประมวลผลข้อความสองครั้งต้องให้ผลลัพธ์เดียวกับการประมวลผลครั้งเดียว เทคนิคทั่วไปคือใช้คีย์สำหรับตรวจจับข้อความซ้ำ (รหัสข้อความ) ซึ่งจัดเก็บไว้ในดัชนีที่ไม่อนุญาตให้ซ้ำ

<?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 หลังจากผลข้างเคียงถูกยืนยันแล้วเท่านั้น ไม่ใช่ก่อนหน้านั้น

ยืนยันรับเร็วเกินไปแล้วเกิดการหยุดทำงาน ข้อความจะสูญหาย ยืนยันรับช้าเกินไปแล้วเกิดการหยุดทำงาน คุณจะได้ข้อความซ้ำ — ซึ่งชั้นจัดการการให้ผลลัพธ์เดิมเมื่อทำซ้ำของคุณจะรองรับได้

<?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) จะลบข้อความเมื่อได้รับการยืนยันรับแล้ว เหมาะอย่างยิ่งสำหรับการกระจายงานไปยังกลุ่มผู้บริโภคที่แข่งขันกัน โดยมีการกำหนดเส้นทางและอายุข้อความแยกตามข้อความ
  • บันทึกการยืนยัน (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";

ตรวจสอบอย่างรวดเร็ว

การใช้เหตุผลเกี่ยวกับการรับประกันการส่งมอบ

ทบทวน

ขณะนี้คุณมีแบบจำลองทางความคิดเบื้องหลังการส่งข้อความแบบอะซิงโครนัสแล้ว:

  • คิวช่วยให้เกิด การแยกการพึ่งพิงเชิงพื้นที่และเชิงเวลา และทำหน้าที่เป็น บัฟเฟอร์ สำหรับการปรับระดับภาระงาน
  • ระบบส่วนใหญ่ใช้รูปแบบ อย่างน้อยหนึ่งครั้ง ดังนั้นผู้บริโภคต้อง ให้ผลลัพธ์เดิมเมื่อทำซ้ำ
  • ยืนยันรับหลังจากผลข้างเคียงถูกยืนยันแล้ว ปล่อยให้ความล้มเหลวทำให้เกิดการส่งซ้ำ
  • การจัดลำดับต้องใช้ การแบ่งพาร์ทิชันตามคีย์ ส่วนข้อความเสียต้องใช้ DLQ
  • อย่าใส่ลงคิวงานที่ผู้เรียกต้องการคำตอบแบบซิงโครนัส

ถัดไป คุณจะนำแนวคิดนี้ไปใช้จริงกับ RabbitMQ ใน PHP

คำถามที่พบบ่อย

บทเรียน “เหตุผลที่ควรใช้การส่งข้อความแบบอะซิงโครนัส” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “เหตุผลที่ควรใช้การส่งข้อความแบบอะซิงโครนัส” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส PHP Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส PHP Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “เหตุผลที่ควรใช้การส่งข้อความแบบอะซิงโครนัส”

ดูว่าคิวช่วยแยกผู้ผลิตออกจากผู้บริโภคได้อย่างไร คุณปฏิบัติ PHP Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน PHP Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน PHP Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน

บทเรียน “เหตุผลที่ควรใช้การส่งข้อความแบบอะซิงโครนัส” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน PHP Academy นี้ได้ไหม

ได้ บทเรียน PHP Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. เหตุผลที่ควรใช้การส่งข้อความแบบอะซิงโครนัส
  2. การทำงานกับ RabbitMQ ใน PHP
  3. Apache Kafka กับ PHP
  4. การสร้างเวิร์กโฟลว์ที่ขับเคลื่อนด้วยเหตุการณ์
← กลับไปที่ PHP Academy