Khối xây dựng Trung bình 4 phút đọc

Message queue cho .NET: RabbitMQ vs Azure Service Bus vs Kafka

Cách chọn message queue cho service .NET: RabbitMQ, Azure Service Bus, Kafka. Khi nào dùng queue vs topic, MassTransit vs client trần, semantic at-least-once.

Mục lục
  1. Khi nào message queue thực sự thay HTTP đồng bộ?
  2. Ngân sách số nào cho lựa chọn queue?
  3. Kiến trúc tối thiểu trông thế nào?
  4. Cấu hình .NET 10 với MassTransit?
  5. Queue tạo ra failure mode nào?
  6. Khi nào không nên dùng queue?
  7. Đi tiếp đâu từ đây?

Lần đầu một HTTP đồng bộ timeout và bạn nhận ra bên nhận chỉ chậm chứ không hỏng, bạn đã gặp ca yêu thích của queue. Chương này cho thấy khi nào queue thắng HTTP, queue nào chọn cho service .NET, và cách kết nối MassTransit khiến lựa chọn dễ tha thứ.

Khi nào message queue thực sự thay HTTP đồng bộ?

Ba tín hiệu cụ thể.

Việc dài vượt budget HTTP. Tạo PDF mất 5 giây; trình duyệt user không chờ. Nhận request, drop message vào queue, trả 202 Accepted với URL trạng thái.

Traffic bùng phát với downstream chậm. Flash sale nhảy checkout 100x. Cổng thanh toán hỗ trợ 200 req/s. Queue hấp thụ đỉnh; consumer rút theo tốc độ hỗ trợ.

Thông báo cross-service. Tạo đơn → giao kho → gửi email → update analytics. Mỗi bên nhận không nên làm bên ghi chậm; mỗi bên retry độc lập. Topic broadcast event; consumer subscribe theo nhịp riêng.

Nếu không, HTTP với Polly retry (chương 11) đơn giản và dễ debug hơn.

Ngân sách số nào cho lựa chọn queue?

Backend                  Throughput          Latency p99       Độ bền lưu
RabbitMQ (1 node)        ~30K msg/s          ~10 ms            disk tuỳ chọn
RabbitMQ (cluster)       ~100K msg/s         ~20 ms            disk + mirror
Azure Service Bus Std    ~2K msg/s           ~50 ms            sẵn có
Azure Service Bus Prem   ~10-50K msg/s       ~20 ms            sẵn có
Amazon SQS               ~3K msg/s/queue     ~50-200 ms        sẵn có
Kafka                    100K-1M msg/s       ~5 ms             disk, replicate

Cho phần lớn service .NET các con số throughput không liên quan - backend nào cũng xử lý nổi traffic của bạn. Chọn theo semantic (queue vs topic, ordered vs unordered, replay vs no-replay) và phù hợp vận hành (self-host vs managed, dung sai cloud lock-in).

Kiến trúc tối thiểu trông thế nào?

flowchart LR
    Producer[ASP.NET Core API] -->|publish| Q[(Queue RabbitMQ)]
    Q -->|consume| Worker[Background Service]
    Worker --> DB[(Postgres)]
    Worker -. khi vượt retry .-> DLQ[(Dead-letter queue)]

Producer nhận request user, trả 202, publish message. Consumer (host BackgroundService) rút queue, xử mỗi message, ack. Lỗi lặp lại chuyển sang dead-letter queue để xem xét. Đây là hình dạng của 90% việc nền .NET và scale bằng cách thêm replica consumer.

Cấu hình .NET 10 với MassTransit?

Phía producer, trong Program.cs:

builder.Services.AddMassTransit(x =>
{
    x.UsingRabbitMq((ctx, cfg) =>
    {
        cfg.Host(builder.Configuration.GetConnectionString("RabbitMq"));
        cfg.ConfigureEndpoints(ctx);
    });
});

// Publish từ controller:
public class OrderController(IPublishEndpoint bus) : Controller
{
    [HttpPost]
    public async Task<IActionResult> Create(OrderRequest req)
    {
        var orderId = Guid.NewGuid();
        await bus.Publish(new OrderCreated(orderId, req.UserId, req.Items));
        return Accepted(new { orderId });
    }
}

Phía consumer, trong project worker riêng:

public record OrderCreated(Guid OrderId, Guid UserId, IReadOnlyList<Item> Items);

public class OrderCreatedConsumer(IOrderProcessor processor)
    : IConsumer<OrderCreated>
{
    public async Task Consume(ConsumeContext<OrderCreated> ctx)
    {
        // Idempotent - gọi hai lần với cùng OrderId vẫn an toàn.
        await processor.ProcessAsync(ctx.Message.OrderId, ctx.CancellationToken);
    }
}

builder.Services.AddMassTransit(x =>
{
    x.AddConsumer<OrderCreatedConsumer>();
    x.UsingRabbitMq((ctx, cfg) =>
    {
        cfg.Host(builder.Configuration.GetConnectionString("RabbitMq"));
        cfg.ReceiveEndpoint("order-created", ep =>
        {
            ep.UseMessageRetry(r => r.Exponential(5,
                TimeSpan.FromSeconds(1), TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(5)));
            ep.ConfigureConsumer<OrderCreatedConsumer>(ctx);
        });
    });
});

Ba điểm cần nhớ. Cùng code chạy với Azure Service Bus chỉ bằng đổi một dòng (UsingAzureServiceBus). MassTransit xử lý retry với exponential backoff sẵn. Consumer phải idempotent - đọc lại hai lần phải ra cùng state.

Queue tạo ra failure mode nào?

Năm cái xuất hiện trước:

Chương 13 cho cách lộ queue_depth, consumer_lag_seconds, dlq_count_total qua OpenTelemetry.

Khi nào không nên dùng queue?

Ba mùi.

Một: việc đồng bộ user nhìn thấy. Nếu user đang nhìn spinner chờ kết quả, queue không giúp. Họ muốn câu trả lời trong 200 ms; queue ép họ chờ consumer lấy message. Dùng tầng cache hoặc query nhanh hơn.

Hai: throughput nhỏ. Queue ở 1 msg/phút là gánh vận hành không lợi. Một BackgroundService poll database đơn giản hơn.

Ba: workflow đòi nhất quán cross-service nghiêm ngặt. Queue là at-least-once và không có thứ tự. Nếu cần exactly-once đa service, saga pattern là hình dạng đúng - và ngay cả khi đó, queue chỉ là transport, không phải câu trả lời.

Đi tiếp đâu từ đây?

Chương kế tiếp: phong cách API cho .NET - REST, gRPC, GraphQL, và khi nào mỗi cái thắng. API đồng bộ và queue async lắp ghép trong mọi service .NET thật; chương kế tiếp hoàn thiện nửa đồng bộ.

Câu hỏi thường gặp

Khi nào queue thắng HTTP đồng bộ?
Ba trường hợp: (1) công việc lâu hơn timeout HTTP có thể chấp nhận (gửi email, tạo PDF, suy luận ML); (2) bên nhận có thể down tạm và bạn không được mất request (xử lý đơn lúc payment outage); (3) muốn hấp thụ đỉnh traffic mà không scale bên nhận. Nếu không, HTTP có retry đơn giản hơn.
RabbitMQ hay Kafka cho service .NET?
RabbitMQ cho task queue - một consumer lấy message, xử, xoá. Kafka cho event log - nhiều consumer đọc cùng stream theo nhịp riêng, replay lịch sử, producer không biết ai nghe. Tuyên bố throughput Kafka hiếm khi quan trọng cho app .NET dưới 10K msg/s; chọn theo semantic, không theo benchmark.
MassTransit hay client trần?
MassTransit, hầu như luôn vậy. Trừu tượng RabbitMQ, Azure Service Bus, Amazon SQS sau một API, cho saga (chương 12), retry với backoff, instrumentation. Client trần đúng khi cần tính năng MassTransit giấu (topology exchange RabbitMQ thấp, header Kafka) hoặc overhead trừu tượng đo được - hiếm dưới 10K msg/s.
At-least-once là gì và sao quan trọng?
Phần lớn queue không đảm bảo 'exactly once' - chỉ 'at least once'. Consumer có thể nhận cùng message hai lần nếu ack lạc. Handler phải idempotent: chạy hai lần ra cùng state với chạy một lần. Chương 10 dành riêng cho điều này.