Phần 1 của loạt bài này hứa sẽ so Kafka với RabbitMQ và không dựng nổi RabbitMQ trong môi trường đo. Lần này dựng được, và bài đo cho ra hai kết quả — một về tốc độ, một quan trọng hơn nhiều.

Thông lượng hai chiều, ngữ nghĩa xoá tin, và ba tính năng chỉ RabbitMQ có

Điều kiện đo

200.000 tin × 300 byte, một hàng đợi RabbitMQ so với một partition Kafka — để phép so không bị lệch vì Kafka chia song song.

RabbitMQ đo bốn cấu hình: hàng đợi cổ điển và hàng đợi quorum, mỗi loại có và không có tin bền cộng xác nhận.

Thông lượng

Ghi Đọc
Kafka acks=1 207.900 740.741
Kafka acks=all 261.438 740.741
RabbitMQ classic, không bền 145.901 103.825
RabbitMQ classic, bền + confirm 91.059 114.749
RabbitMQ quorum, không bền 96.452 145.716
RabbitMQ quorum, bền + confirm 88.692 154.758

So công bằng — cả hai đều bền, đều xác nhận:

ghi:   261.438  so với   88.692   ->  Kafka nhanh 2,9 lần
đọc:   740.741  so với  154.758   ->  Kafka nhanh 4,8 lần

Một chi tiết đáng chú ý trong bảng: Kafka acks=all nhanh hơn acks=1. Với replication-factor=1 thì hai chế độ này giống hệt nhau và chênh lệch là nhiễu — phần 4 đã đo và giải thích chính cái bẫy này.

Và ở phía RabbitMQ, hàng đợi quorum đọc nhanh hơn classic (154.758 so với 114.749) dù ghi chậm hơn. Đó là kết quả tôi không chờ đợi, và nó nhắc rằng "quorum thì chậm hơn" là một khái quát không đúng ở mọi chiều.

Nhưng thông lượng không phải điểm khác nhau quan trọng nhất

RabbitMQ: đọc xong là xoá.

ghi 1000 tin  ->  hàng đợi có 1000
đọc hết       ->  hàng đợi có 0
đọc lại?      ->  KHÔNG. Chúng không còn tồn tại.

Kafka: đọc xong vẫn còn.

nhóm c1 đọc hết 400.000 tin, offset đầu vẫn 0
nhóm c2 dựng mới đọc lại toàn bộ ở 1.219.512 tin/giây

Đây là khác biệt kiến trúc, không phải khác biệt hiệu năng, và nó quyết định gần hết mọi lựa chọn khác.

Với RabbitMQ, mỗi tin có đúng một chủ. Xử lý xong thì hết chuyện. Muốn hai hệ thống cùng nhận thì phải cấu hình exchange rẽ nhánh trước khi tin được gửi.

Với Kafka, tin nằm đó cho tới khi hết hạn lưu trữ. Một hệ thống mới muốn đọc dữ liệu của sáu tháng trước chỉ cần một group.id mới — không cần ai đổi gì ở phía ghi. Phần 25 đo được việc đó chạy ở hơn một triệu tin mỗi giây.

Ba thứ RabbitMQ làm được mà Kafka không

Định tuyến theo mẫu khoá. Gửi bốn tin với khoá don.vn.hcm, don.vn.hn, don.jp.tokyo, don.us.ny:

hàng đợi mẫu don.vn.*  nhận  2 tin
hàng đợi mẫu don.#     nhận  4 tin

Broker quyết định tin nào đi đâu. Kafka không có khái niệm này — mọi tin vào topic đều tới mọi consumer của topic đó, và việc lọc là chuyện của consumer.

Hạn dùng theo hàng đợi. 100 tin với x-message-ttl=2000, sau 3,5 giây còn 0. Kafka có retention.ms nhưng nó áp cho cả topic và xoá theo segment (phần 17) — không có cách nào đặt hạn cho từng tin.

Ưu tiên tin, hàng đợi trì hoãn, xác nhận từng tin một. Kafka không có cái nào. basicNack cho phép trả lại đúng một tin trong khi những tin khác vẫn chạy; Kafka chỉ có offset tuyến tính, nên một tin hỏng chặn cả partition (phần 24).

Bảng chọn

Chọn Kafka khi:

  • Nhiều hệ thống cần cùng một dòng dữ liệu, và danh sách đó sẽ còn thay đổi.
  • Cần đọc lại lịch sử — dựng lại chỉ mục, tính lại báo cáo, sửa lỗi rồi xử lý lại.
  • Lưu lượng rất cao và ổn định.
  • Cần thứ tự theo khoá, và chấp nhận nó chỉ đúng trong phạm vi partition (phần 12).

Chọn RabbitMQ khi:

  • Mỗi tin có đúng một người xử lý, và xong là hết.
  • Cần định tuyến phức tạp ở phía broker.
  • Cần trả lại từng tin, ưu tiên, hoặc trì hoãn.
  • Đội nhỏ, tải vừa, và bạn muốn thứ đơn giản hơn — RabbitMQ dựng xong trong 3,8 giây với một lệnh.

Bộ nhớ khi rảnh: Kafka 541,7 MB, RabbitMQ 597,5 MB. Tương đương. Cả hai đều không nặng. Chọn theo bài toán, đừng chọn theo tài nguyên — và đặc biệt đừng chọn theo bảng thông lượng ở đầu bài, vì 88.692 tin/giây đã vượt xa nhu cầu của phần lớn hệ thống.

Hai câu hỏi thay cho một bảng so sánh

Nếu phải rút gọn cả bài xuống hai câu hỏi:

"Tin này có ai khác cần đọc không, kể cả sau này?" Có → Kafka.

"Tôi có cần xử lý từng tin một, với khả năng trả lại và ưu tiên không?" Có → RabbitMQ.

Hai câu trả lời "có" cùng lúc là chuyện thường, và khi ấy dùng cả hai không phải thất bại về kiến trúc: Kafka làm xương sống sự kiện, RabbitMQ làm hàng đợi công việc phía sau. Chúng giải hai bài toán khác nhau.

Ghi chú về phép đo trước đó

Ở phần 1, tôi không dựng nổi RabbitMQ trong môi trường này — mọi lần thử đều chết với Error when reading /var/lib/rabbitmq/.erlang.cookie: eacces, và tôi đã hoãn phép so sánh sang bài này thay vì mượn số của người khác.

Nguyên nhân hoá ra là ở chỗ gắn thư mục và tên máy chủ. Chạy rabbitmq:3.13-management-alpine với --hostname rmqRABBITMQ_NODENAME=rabbit@rmq, không gắn volume nào, thì nó lên trong 3.831 ms theo chính log của nó.

Bài học lặp lại từ nhiều phần trước: khi một thứ không chạy, nguyên nhân thường ở môi trường chứ không ở công cụ — và hoãn một phép đo lại tốt hơn là đoán kết quả của nó.

Thử ba mươi giây

Nếu bạn đang dùng RabbitMQ và tự hỏi có nên chuyển sang Kafka:

# hàng đợi nào đang tồn đọng
docker exec rmq rabbitmqctl list_queues name messages consumers

# có hàng đợi nào bạn ước có thể đọc lại không?

Nếu câu trả lời cho câu hỏi thứ hai là "không", RabbitMQ đang làm đúng việc của nó và 88.692 tin/giây nhiều khả năng là thừa đủ. Nếu là "có, nhiều lần rồi", đó là dấu hiệu rõ nhất cho thấy bài toán của bạn là bài toán của Kafka.

Phần cuối gom lại toàn bộ bốn mươi phép đo của loạt bài thành một danh sách kiểm.