ข้ามไปยังเนื้อหา

Publish/Subscribe

บางครั้ง event เดียวควร trigger หลายอย่างที่ ไม่เกี่ยวกัน order ถูกสร้าง แล้วต่างคนต่างทำ: email service ส่ง confirmation, analytics service บันทึกไว้, cache service invalidate หน้า แต่ละตัวไม่รู้จักตัวอื่น และแต่ละตัวต้องได้ copy ของ event ของตัวเอง

นั่นคือ publish/subscribe และกลไกคือ fanout exchange (หรือ topic exchange เมื่ออยากได้ filter ด้วย) กับ queue หนึ่งตัวต่อ subscriber

flowchart LR
  p["Producer
(order placed)"] --> x["fanout exchange"]
  x --> qe["queue: email"] --> se["email service"]
  x --> qa["queue: analytics"] --> sa["analytics service"]
  x --> qc["queue: cache"] --> sc["cache service"]
fanout exchange copy แต่ละ event ไปยัง queue ของทุก subscriber

นี่คือจุดที่ทั้งโมดูลนี้ยึดไว้ ทำให้เป็นรูปธรรมกันเลย:

Work queuePublish/subscribe
Queuequeue เดียวที่แชร์กันqueue หนึ่งตัวต่อ subscriber
แต่ละ message ไปหาworker ตัวเดียวทุก subscriber (ตัวละ copy)
เพิ่ม consumer เพื่อ…เพิ่ม throughputเพิ่ม reaction ที่เป็นอิสระ
Exchangeมักเป็น default/directfanout (หรือ topic)

ถ้า “subscriber” สามตัว bind queue เดียวกัน คุณจะได้ work queue โดยไม่ตั้งใจ — message จะไปหาแค่ตัวเดียว pub/sub ต้องให้ แต่ละ subscriber declare และ bind queue ของตัวเอง

setup ที่สะอาดและพบบ่อย: แต่ละ subscriber declare queue แบบ exclusive, auto-delete (unique ต่อ consumer instance นั้น) แล้ว bind เข้ากับ fanout exchange ที่แชร์กัน exchange จะ copy ทุก message เข้าไปในทุก queue ที่ bind ไว้

const channel = await conn.createChannel();
await channel.assertExchange('orders', 'fanout', { durable: true });
// Each subscriber gets its own queue.
const { queue } = await channel.assertQueue('', { exclusive: true });
await channel.bindQueue(queue, 'orders', '');
channel.consume(queue, (msg) => {
if (!msg) return;
handleOrderEvent(msg.content.toString());
channel.ack(msg);
});

producer ไม่เปลี่ยนเลยเวลาคุณเพิ่ม subscriber — ยังคง publish ไปที่ exchange orders อยากได้ reaction ใหม่สำหรับ fraud detection? เปิด service ที่ bind queue ของตัวเอง นี่คือผลตอบแทนด้าน decoupling จากโมดูล foundations ในรูปธรรม

queue แบบ exclusive auto-delete จะหายไปเมื่อ subscriber disconnect — เหมาะกับ dashboard สด ๆ ที่สนใจ event เฉพาะตอนกำลังดูอยู่ ส่วน subscriber ที่ต้อง ไม่พลาด event ตอนที่ตัวเองล่มชั่วครู่ (เช่น email service) ให้ใช้ queue แบบ named, durable แทน เพื่อให้ message สะสมรอไว้จนกว่าจะ reconnect

ใน publish/subscribe subscriber สามตัวได้ copy ของ event กี่ชุด?
ความผิดพลาดใดที่เปลี่ยน pub/sub ให้กลายเป็น work queue โดยไม่ตั้งใจ?
exchange type ใดที่เหมาะกับ broadcast ล้วน ๆ ที่สุด?
subscriber ต้องไม่พลาด event ตอนที่ตัวเอง restart สั้น ๆ ควรใช้อะไร?