Log-based broker — Kafka, offset, và log chỉ append
Kafka lưu mỗi sự kiện vào log chỉ-append có offset cố định, không xoá sau khi đọc — nhiều consumer đọc độc lập ở tốc độ riêng. Khác broker truyền thống.
TL;DR: Message broker truyền thống (RabbitMQ/ActiveMQ) xoá message sau khi consumer đọc — một message chỉ đến tay đúng một nhóm. Kafka thì ngược: mỗi message được ghi vào log chỉ-append có địa chỉ cố định gọi là offset và giữ lại trong retention window (mặc định 168 giờ). Nhờ đó nhiều nhóm consumer hoàn toàn độc lập có thể đọc cùng topic ở tốc độ riêng. Điểm then chốt: offset là con trỏ của consumer group chứ không phải trạng thái broker — consumer tự giữ offset của mình, có thể tua lại (replay) bất cứ lúc nào trong window. Đây là lý do Kafka xử lý được 7 nghìn tỷ message mỗi ngày tại LinkedIn năm 2019.
Bạn đang xây hệ thống e-commerce và cần ba việc xảy ra mỗi khi có đơn hàng mới: (1) team analytics tổng hợp doanh thu theo giờ, (2) team billing ghi hoá đơn, (3) team audit giữ nhật ký bất biến cho bộ phận pháp lý. Cả ba cần đọc cùng một luồng sự kiện nhưng ở tốc độ và thời điểm khác nhau — audit đôi khi cần replay lại tuần trước để kiểm tra.
Với broker truyền thống, bạn phải dựng ba queue riêng và fan-out message vào cả ba — mỗi message bị sao chép ba lần, và khi audit cần replay tuần trước thì không thể: message đã bị xoá. Kafka giải quyết cả hai vấn đề bằng một thay đổi mang tính kiến trúc: thay queue (xoá sau đọc) bằng log (giữ lại).
Bài này giải thích cách log-based broker hoạt động, vì sao nó khác broker truyền thống khi nhiều team cần đọc cùng luồng sự kiện, và pitfall phổ biến khi mới dùng Kafka.
Bạn đã thấy cơ chế offset và append-only log của Kafka ở track Thuật toán ứng dụng. Bài này không dạy lại cơ chế đó — mà đặt nó trong ngữ cảnh so sánh với broker truyền thống, giải thích vì sao thiết kế "giữ log" thắng "xoá sau đọc" khi hệ thống có nhiều consumer group.
1. Analogy — thùng thư cá nhân vs bảng thông báo chung
Hình dung khu chung cư có hai cách thông báo cho cư dân.
Cách một — thùng thư cá nhân (broker truyền thống): ban quản lý in thông báo, bỏ vào từng thùng thư. Anh A lấy thư ra thì thùng trống — chị B không đọc được thư đó. Muốn cả hai nhận, phải in hai bản. Một khi đã lấy thư, bạn không thể lấy lại "bản cũ từ tuần trước".
Cách hai — bảng thông báo chung (log-based broker): ban quản lý ghim thông báo theo thứ tự lên bảng ở hành lang. Anh A đọc đến tờ số 5, chị B đọc đến số 3. Bảng không gỡ thông báo khi ai đó đọc — chúng ở đó theo thứ tự trong vòng 7 ngày. Mỗi người tự nhớ mình đọc đến đâu. Muốn đọc lại tờ số 2 từ hôm qua, chỉ cần quay lại số 2 trên bảng.
| Thùng thư cá nhân | Bảng thông báo chung | Khái niệm kỹ thuật |
|---|---|---|
| Mỗi hộ có thùng riêng | Một bảng, mọi người đọc chung | Queue riêng vs Shared log |
| Lấy thư rồi — hộ khác không đọc được | Tờ vẫn đó sau khi ai đó đọc | Xoá sau đọc vs Giữ lại |
| Muốn hai hộ nhận → in hai bản | Hai người đọc cùng một tờ | Fan-out copy vs Read-only offset |
| Không lấy lại thư cũ | Quay lại đọc từ tờ cũ được | Không replay vs Replay trong window |
| Thùng hết thư = rỗng | Bảng gỡ tờ cũ sau 7 ngày | Dequeue vs Retention window |
Queue = thùng thư (xoá khi lấy, mỗi người cần bản riêng). Log = bảng thông báo (giữ lại, mọi người đọc cùng bản, tự nhớ vị trí đọc).
2. Broker truyền thống — queue, AMQP, và xoá sau đọc
Message broker truyền thống (RabbitMQ, ActiveMQ, IBM MQ) được xây trên mô hình queue: producer gửi message vào queue, broker giao cho consumer, và xoá message ngay khi consumer ACK (xác nhận đã nhận).
# Broker truyen thong: queue model
Producer --> [Queue: orders] --> Consumer Group A
(ACK) --> broker xoa message khoi queue
# Neu Consumer Group B cung can doc cung event:
Producer --> [Queue: orders-for-analytics] --> Consumer Group A (analytics)
Producer --> [Queue: orders-for-billing] --> Consumer Group B (billing)
# Can fan-out: message sao chep N lan cho N consumer group
Giao thức AMQP (Advanced Message Queuing Protocol) là nền của mô hình này: producer publish tới exchange, exchange route sang một hoặc nhiều queue theo routing key, consumer subscribe vào queue. Toàn bộ protocol được thiết kế cho point-to-point hoặc pub/sub với fan-out tường minh.
Điểm mạnh: routing linh hoạt (direct, topic, fanout, headers exchange), message priority, dead-letter queue, độ trễ p99 thấp ở tải nhẹ.
Điểm yếu căn bản khi cần nhiều consumer group đọc cùng luồng sự kiện:
- Phải fan-out tường minh: 3 team đọc cùng event → 3 queue riêng, message sao chép 3 lần, producer (hoặc fan-out middleware) phải biết về tất cả downstream.
- Không replay: message đã ACK thì biến mất — không tua lại được để kiểm tra lịch sử.
- Không audit: queue có thể drain hết và không có cách dựng lại trạng thái đã qua.
3. Vì sao nhiều team cần đọc cùng một luồng sự kiện?
Trong hệ thống hiện đại, một sự kiện nghiệp vụ thường cần được xử lý bởi nhiều hệ thống độc lập ở tốc độ hoàn toàn khác nhau:
| Team / hệ thống | Event quan tâm | Làm gì với event | Tốc độ chấp nhận được |
|---|---|---|---|
| Analytics | order.placed | Tổng hợp doanh thu theo giờ | Vài giây lag OK |
| Billing | order.placed | Tạo hoá đơn, ghi sổ | Gần real-time |
| Audit/Legal | order.placed | Lưu nhật ký bất biến 7 năm | Batch, cần replay |
| Recommendation | order.placed | Cập nhật model gợi ý | Async, vài phút OK |
| Notification | order.placed | Gửi email xác nhận | Gần real-time |
Với queue truyền thống, bạn phải dựng 5 queue và fan-out — khi thêm hệ thống thứ 6, bạn phải sửa cấu hình fan-out. Audit team đặt thêm yêu cầu: cần replay lại event từ tuần trước để xác minh báo cáo thuế — điều không thể với queue truyền thống vì message đã xoá.
Với log-based broker: mỗi team tự tạo consumer group của mình và đọc cùng topic ở offset riêng. Producer không cần biết downstream. Hệ thống thứ 6 tự tạo consumer group mới, replay từ đầu nếu muốn, hoàn toàn không ảnh hưởng các group khác.
4. Log-based broker — Kafka và cấu trúc append-only log
Kafka lưu mỗi message vào một log phân vùng (partitioned log). Ba khái niệm cốt lõi:
Topic là kênh sự kiện có tên (ví dụ orders, payments). Producer ghi vào topic; consumer đọc từ topic.
Partition là một phần của topic — một file log tuần tự trên đĩa. Kafka chia topic thành nhiều partition để song song hoá: nhiều producer ghi đồng thời, nhiều consumer trong cùng consumer group đọc song song (mỗi consumer giữ một tập partition không trùng).
Offset là số thứ tự của mỗi message trong một partition — tăng đơn điệu và không bao giờ thay đổi sau khi ghi. Đây là địa chỉ cố định của mỗi sự kiện trong log.
# Topic "orders", Partition 0 — append-only
offset: [ 0 ] [ 1 ] [ 2 ] [ 3 ] [ 4 ] -->
event: [ord A] [ord B] [ord C] [ord D] [ord E] --> chi them vao cuoi
# Kafka KHONG xoa sau khi consumer doc
# Offset 0 van o do du Consumer Group analytics da doc no 3 lan
# Xoa chi xay ra khi het retention window (mac dinh 168 gio)
Kafka không push message đến consumer — consumer tự poll (kéo) theo offset hiện tại. Khi poll xong batch, consumer tự commit offset mới lên Kafka. Broker không cần theo dõi ai đã đọc gì — nó chỉ lưu log và phục vụ poll.
5. Offset — con trỏ của consumer group, không phải trạng thái broker
Đây là điểm khác biệt kiến trúc cốt lõi giữa Kafka và broker truyền thống.
Ở broker truyền thống, broker theo dõi trạng thái từng message (đã giao, đang chờ ACK, đã xoá). Trạng thái "đã đọc đến đâu" nằm ở phía broker.
Ở Kafka, consumer group tự giữ offset của mình — lưu trong một topic nội bộ gọi là __consumer_offsets. Broker chỉ là nơi lưu log và phục vụ poll; nó không biết consumer nào đang ở offset nào ngoài những gì consumer tự báo về.

Ba consumer group đọc cùng một partition ở ba offset khác nhau — hoàn toàn độc lập. Audit đang replay từ offset 0 (đầu log), analytics ở offset 3, billing đã đọc đến offset 4.
Hệ quả trực tiếp của "offset thuộc consumer group":
- Consumer group mới có thể bắt đầu từ
auto.offset.reset=earliest(replay toàn bộ log trong window) hoặclatest(chỉ đọc event mới từ lúc này). - Consumer gặp sự cố restart từ offset đã commit — không mất event (nếu commit đúng cách).
- Tua lại (rewind): audit team muốn kiểm tra lại dữ liệu tuần trước, chỉ cần reset offset về timestamp tương ứng — miễn còn trong retention window 168 giờ.
- Không block lẫn nhau: analytics bị chậm không làm billing chậm theo, vì mỗi group giữ offset riêng.
6. So sánh: broker truyền thống vs log-based — khi nào dùng gì?
Đây là trọng tâm của bài. Hai mô hình không thay thế nhau hoàn toàn — mỗi cái có điểm mạnh rõ ràng.
| Tiêu chí | Broker truyền thống (RabbitMQ/ActiveMQ) | Log-based broker (Kafka) |
|---|---|---|
| Xoá message sau đọc | Có — sau khi consumer ACK | Không — giữ trong retention window |
| Nhiều consumer group | Fan-out tường minh (nhiều queue riêng) | Mọi group đọc cùng log độc lập |
| Replay lịch sử | Không thể | Được — trong retention window (168h mặc định) |
| Trạng thái offset | Broker quản lý | Consumer group tự giữ |
| Đảm bảo thứ tự | Per-queue (toàn cục nếu 1 consumer) | Per-partition (đảm bảo trong partition) |
| Throughput (3 broker, 1KB, repl-3) | RabbitMQ classic ~38.000 msg/s (thấp hơn ~16×) | Kafka ~605.000 msg/s |
| Độ trễ p99 tải nhẹ | Tốt hơn Kafka | Cao hơn khi tải nhẹ |
| Routing linh hoạt | Có — direct/topic/fanout/headers exchange | Không có built-in routing |
| Phù hợp nhất | Task queue, RPC, routing phức tạp, message priority | Nhiều consumer group, replay, event sourcing, audit log |
Số liệu benchmark Confluent 2020 (3 broker, message 1 KB, replication factor 3): Kafka đạt 605.000 msg/s so với RabbitMQ classic 38.000 msg/s — Kafka cao hơn 16 lần về throughput. Lý do: Kafka không xoá sau đọc → không overhead bookkeeping per-message; sequential append-only I/O tối ưu hơn random access của queue; zero-copy (sendfile syscall) khi consumer poll.
Chọn RabbitMQ khi: (1) cần routing linh hoạt (message priority, dead-letter queue, delay exchange), (2) chỉ một consumer group quan tâm mỗi event, (3) độ trễ p99 thấp ở tải nhỏ quan trọng hơn throughput. Chọn Kafka khi: nhiều team đọc cùng luồng sự kiện, cần replay/audit, hoặc throughput trên 100.000 msg/s.
7. Pitfall tổng hợp
❌ Nhầm 1: "Đọc xong là mất" — chạy consumer mới với auto.offset.reset=latest
Người mới dùng Kafka hay tưởng Kafka xoá message như RabbitMQ, nên không để ý config reset offset khi deploy consumer group mới.
# SAI: start consumer group moi khong set auto.offset.reset
auto.offset.reset=latest # gia tri mac dinh khi group chua co offset
# Ket qua: group bat dau tu message MOI NHAT luc deploy
# Toan bo du lieu cu trong window retention bi bo qua
# Giong nhu "xoa" du lieu theo kieu broker truyen thong
# DUNG: replay tu dau trong retention window
auto.offset.reset=earliest
# Hoac: reset offset ve mot timestamp cu cu the
# kafka-consumer-groups.sh --reset-offsets --to-datetime 2026-07-01T00:00:00.000
✅ Kafka giữ dữ liệu trong window retention (168 giờ mặc định). Dữ liệu không mất khi consumer đọc — chỉ mất khi quá retention hoặc bạn tường minh reset offset về latest.
❌ Nhầm 2: Partition key sai làm mất thứ tự trong entity
Kafka chỉ đảm bảo thứ tự trong một partition. Nếu không set partition key, Kafka phân phối message round-robin — event của cùng một entity (ví dụ cùng orderId) có thể nằm ở partition khác nhau.
# SAI: khong set partition key
Producer.send(topic="orders", key=null, value=orderEvent)
# orderId=42 co the o partition 0, 1, hoac 2 -> thu tu khong bao dam
# DUNG: dung entity ID lam partition key
Producer.send(topic="orders", key=orderId.toString(), value=orderEvent)
# orderId=42 luon vao cung 1 partition (Kafka hash key -> partition)
# Thu tu event trong entity do duoc bao dam
✅ Luôn dùng entity ID (orderId, userId) làm partition key khi thứ tự trong entity đó quan trọng.
❌ Nhầm 3: Bật enable.idempotence=true tưởng đã exactly-once end-to-end
enable.idempotence=true (mặc định từ Kafka 3.0) chỉ đảm bảo idempotent producer trong một session — producer không ghi trùng khi retry trong cùng kết nối. Nếu consumer reprocess và ghi vào DB downstream, vẫn có thể double-count.
Exactly-once end-to-end cần thêm Kafka transaction (từ Kafka 0.11.0): atomic write nhiều partition cộng với commit offset trong cùng một transaction. Đây là chủ đề của bài 04.
8. 📚 Deep Dive
Spec / reference chính thức:
- Apache Kafka Design (v4.3) — trang thiết kế chính thức: log-as-storage, partition, offset, pull-based consumer, consumer group, delivery semantics. Đọc mục "Log Compaction" để hiểu tại sao append-only log không phình mãi mà vẫn giữ snapshot trạng thái mới nhất mỗi key.
- Kafka: a Distributed Messaging System for Log Processing — Kreps, Narkhede, Rao (NetDB 2011) — paper gốc từ LinkedIn. Xem mục "Consumer": thiết kế consumer-driven offset (consumer tự giữ offset) được giải thích ở đây, kèm benchmark so với ActiveMQ về throughput (producer ~50 MB/s trên 1 broker, 6 đĩa RAID 10, năm 2011).
- The Log — Jay Kreps (LinkedIn Engineering, 2013) — bài viết nền quan trọng nhất. Kreps lập luận "log" là abstraction thống nhất cho DB replication, stream processing, và distributed coordination. Năm 2013 LinkedIn dùng Kafka xử lý 60 tỷ message/ngày, tiền thân của 7 nghìn tỷ năm 2019.
Ghi chú: đọc theo thứ tự — paper 2011 cho cơ chế, The Log 2013 cho triết lý thiết kế, Kafka Design (living doc) cho implementation hiện đại. Nếu chỉ đọc một: The Log là lựa chọn tốt nhất để nắm "vì sao log thắng queue".
9. Liên hệ các bài khác
- Bài 01 — Tư duy stream: đặt nền mô hình event/producer/consumer và input unbounded — log-based broker là hạ tầng vận chuyển dòng sự kiện vô tận đó.
- Bài 03 — Windowing: windowing cần event-time chính xác. Kafka log giữ timestamp của producer (event-time), không phải lúc consumer đọc (processing-time) — phân biệt này trở nên quan trọng khi reprocess lịch sử.
- Bài 04 — Stream join và exactly-once: exactly-once end-to-end cần Kafka transaction — ghi kết quả cộng với commit offset trong cùng một atomic transaction (Kafka 0.11.0 trở đi).
- Module 1 — Batch (bài 01 — Tư duy batch): kỷ luật "input bất biến, output phái sinh" của batch tương đương với "log chỉ-append, consumer đọc offset riêng" của Kafka — hai biểu hiện của cùng triết lý bất biến.
10. Tóm tắt
- Offset là con trỏ của consumer group (không phải broker), giữ trong retention window (168 giờ mặc định) — replay bất cứ lúc nào mà không ảnh hưởng consumer khác.
- Throughput: Kafka 605.000 msg/s vs RabbitMQ classic 38.000 msg/s (Confluent 2020, cùng điều kiện) — 16× nhờ sequential append-only I/O.
- Pitfall: (1)
auto.offset.reset=latestbỏ sót dữ liệu cũ khi deploy consumer group mới; (2) partition key null mất thứ tự trong entity; (3) idempotent producer ≠ exactly-once end-to-end. - Khi nào dùng Kafka: nhiều team đọc cùng luồng, cần replay/audit, throughput cao. RabbitMQ: routing phức tạp, message priority, một consumer group.
11. Tự kiểm tra
- Q1Vì sao broker truyền thống (queue) cần fan-out sang nhiều queue khi có nhiều consumer group, trong khi Kafka không cần?
- Q2Offset là trạng thái của ai — broker hay consumer group? Nêu hai hệ quả cụ thể của câu trả lời đó.
- Q3Benchmark cho thấy Kafka đạt 605.000 msg/s còn RabbitMQ classic đạt 38.000 msg/s cùng điều kiện — kém 16 lần. Giải thích vì sao log-based broker có throughput cao hơn về mặt cơ chế I/O.
- Q4Một team deploy consumer group mới cho topic `orders` và chạy với cấu hình mặc định `auto.offset.reset=latest`. Họ phát hiện consumer không nhận được event nào từ tuần trước. Giải thích nguyên nhân và cách sửa.
- Q5Retention window của Kafka mặc định là 168 giờ (7 ngày). Sau khi hết retention, message bị xoá. Điều này có mâu thuẫn với tính 'không xoá sau khi đọc' của Kafka không? Giải thích sự khác biệt.
- Q6Bạn cần thiết kế hệ thống nhận event `order.placed` và fan-out cho 5 team downstream (analytics, billing, audit, recommendation, notification). Khi nào nên chọn Kafka thay RabbitMQ? Nêu một lý do cụ thể từ yêu cầu ở trên.
Bài tiếp theo: Windowing — tumbling, sliding, session và event-time
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên