blog.dopana

Back

Message queue là xương sống của hệ thống phân tán. Nó cho phép các service giao tiếp bất đồng bộ, chịu lỗi và scale độc lập.

Khi Nào Cần Message Queue?#

  • Decoupling — Service A không cần biết Service B
  • Buffering — Chịu được traffic spike (queue làm đệm)
  • Retry — Nếu worker fail, message tự động retry
  • Fan-out — Một message đến nhiều consumer

RabbitMQ — Message Broker Cổ Điển#

Concepts#

Producer → Exchange → Queue → Consumer

        (routing key, binding)
txt
  • Exchange — nhận message từ producer, route đến queue
  • Queue — lưu message, consumer đọc từ queue
  • Binding — kết nối exchange → queue

Node.js với amqplib#

Exchange Types#

// Direct — route theo routing key chính xác
await channel.assertExchange('direct_logs', 'direct');
await channel.bindQueue('queue_errors', 'direct_logs', 'error');

// Topic — route theo pattern
await channel.assertExchange('topic_logs', 'topic');
await channel.bindQueue('queue_all', 'topic_logs', '*.critical');
await channel.bindQueue('queue_errors', 'topic_logs', 'error.#');

// Fanout — broadcast đến tất cả queue
await channel.assertExchange('fanout_logs', 'fanout');
// Tất cả queue bind đều nhận được message
typescript

Kafka — Event Streaming Platform#

Concepts#

Producer → Topic (Partition 0, 1, 2…) → Consumer Group

  • Topic — category của message
  • Partition — chia topic để song song
  • Consumer Group — nhiều consumer cùng group chia nhau partition
  • Offset — vị trí message trong partition

Node.js với kafkajs#

Kafka vs RabbitMQ#

RabbitMQKafka
ModelSmart broker, dumb consumerDumb broker, smart consumer
StorageXoá sau khi consumedGiữ message lâu (configurable)
Performance~10K msg/s~1M msg/s
Message orderingPer queuePer partition
Use caseTask distributionEvent streaming, log
Replay✅ (từ offset cũ)

NATS — Lightweight, Đơn Giản#

import { connect } from 'nats';

const nc = await connect({ servers: 'nats://localhost:4222' });

// Publish
nc.publish('order.created', JSON.stringify({ id: 123 }));

// Subscribe
const sub = nc.subscribe('order.created');
for await (const msg of sub) {
  console.log(msg.data.toString());
}

// Request-Reply
const response = await nc.request('user.get', JSON.stringify({ id: 1 }));
typescript

NATS cực kỳ nhẹ — latency micro giây.

Pattern Thực Tế#

1. Event Sourcing#

Mọi thay đổi state đều là event:

// Order service
await producer.send({
  topic: 'order-events',
  messages: [
    { value: JSON.stringify({ type: 'OrderCreated', data: order }) },
    { value: JSON.stringify({ type: 'OrderPaid', data: payment }) },
    { value: JSON.stringify({ type: 'OrderShipped', data: shipment }) },
  ],
});

// Notification service consume events
// → Gửi email khi OrderCreated
// → Gửi SMS khi OrderShipped
typescript

2. Competing Consumers#

Nhiều worker cùng xử lý queue:

Queue: [msg1, msg2, msg3, msg4, msg5]
         ↓      ↓      ↓      ↓      ↓
      Worker1 Worker2 Worker3 Worker1 Worker2
txt

Tự động scale — thêm worker = xử lý nhanh hơn.

3. Dead Letter Queue (DLQ)#

Message không xử lý được → chuyển sang DLQ:

channel.consume(queue, (msg) => {
  try {
    processMessage(msg);
    channel.ack(msg);
  } catch (err) {
    const retries = msg.properties.headers['x-retry'] || 0;
    if (retries < 3) {
      channel.nack(msg, false, true); // Requeue
    } else {
      channel.nack(msg, false, false); // → DLQ
    }
  }
});
typescript

Docker Compose#

Kết Luận#

  • RabbitMQ — task distribution, job queue, cần routing phức tạp
  • Kafka — event streaming, log, cần replay message
  • NATS — real-time, lightweight, micro giây latency

Chọn theo nhu cầu:

  • Đơn giản, cần retry → RabbitMQ
  • High throughput, event sourcing → Kafka
  • Siêu nhanh, real-time → NATS

Tài liệu tham khảo#