Fanout Exchange
copy ให้ทุกคน
หัวข้อที่มีชื่อว่า “copy ให้ทุกคน”fanout exchange เป็น router ที่ง่ายที่สุด: ไม่สนใจ routing key เลย และ copy message ทุกตัวไปยัง ทุก queue ที่ bind ไว้ publish ครั้งเดียว ได้ N queue เป็น copy อิสระ N ชุด
นี่คือวิธีทำ broadcast — หรือถ้ามองเป็น pattern ก็คือ publish/subscribe producer ประกาศ event หนึ่งครั้ง แล้ว subscriber ที่สนใจแต่ละตัวมี queue ของตัวเอง bind กับ exchange และรับ copy ของตัวเองไปประมวลผลอิสระ
ตัวอย่าง: event เดียว สาม reaction
หัวข้อที่มีชื่อว่า “ตัวอย่าง: event เดียว สาม reaction”เมื่อมีคนสั่ง order สามอย่างที่ไม่เกี่ยวกันต้องเกิดขึ้น: ส่ง email ยืนยัน, เขียน audit record และ invalidate cache ไม่มีอันไหนควร block อันอื่น และการเพิ่ม reaction ที่สี่ทีหลังไม่ควรต้องแตะ producer เลย fanout exchange เหมาะเป๊ะ
flowchart LR p["publish 'order.placed'"] --> x["fanout exchange 'orders'"] x --> qe["queue: email"] x --> qa["queue: audit"] x --> qc["queue: cache-invalidation"]
consumer แต่ละตัวประกาศ queue ของตัวเอง แล้ว bind เข้ากับ fanout exchange เพราะแต่ละตัวมี queue แยกกัน subscriber ที่ช้าตัวหนึ่งจึงไม่หน่วงตัวอื่น และแต่ละตัวประมวลผลตาม pace ของตัวเอง
const ex = 'orders';await channel.assertExchange(ex, 'fanout', { durable: true });
// each subscriber: its own queue, bound with NO routing keyconst q = await channel.assertQueue('email', { durable: true });await channel.bindQueue(q.queue, ex, ''); // key ignored for fanout
// producer: publish once; routing key is irrelevantchannel.publish(ex, '', Buffer.from(JSON.stringify({ orderId: 123 })));ex = "orders"channel.exchange_declare(exchange=ex, exchange_type="fanout", durable=True)
# each subscriber: its own queue, bound with NO routing keychannel.queue_declare(queue="email", durable=True)channel.queue_bind(queue="email", exchange=ex) # key ignored for fanout
# producer: publish once; routing key is irrelevantchannel.basic_publish(exchange=ex, routing_key="", body='{"orderId": 123}')ex := "orders"ch.ExchangeDeclare(ex, "fanout", true, false, false, false, nil)
// each subscriber: its own queue, bound with NO routing keych.QueueDeclare("email", true, false, false, false, nil)ch.QueueBind("email", "", ex, false, nil) // key ignored for fanout
// producer: publish once; routing key is irrelevantch.PublishWithContext(ctx, ex, "", false, false, amqp.Publishing{Body: []byte(`{"orderId":123}`)})fanout เทียบกับ work queue
หัวข้อที่มีชื่อว่า “fanout เทียบกับ work queue”ระวังอย่าสับสนกับ work queue (โมดูลถัดไป) ความต่างอยู่ที่ว่า queue อยู่ตรงไหน:
- fanout / pub-sub: subscriber แต่ละตัวมี queue ของตัวเอง ทุกคนได้ ทุก message ใช้ broadcast event ไปยัง handler อิสระหลายตัว
- work queue: worker หลายตัวใช้ queue เดียวร่วมกัน message แต่ละตัวไปหา worker แค่ตัวเดียว ใช้กระจาย task
broker เดียวกัน แต่เจตนาตรงข้าม และตัวชี้ขาดคือแค่ “ใช้ queue เดียวร่วมกัน หรือ queue ต่อ consumer หนึ่งตัว?”