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