Xây dựng pipeline dữ liệu thời gian thực với Apache Kafka
Cách thiết kế pipeline Kafka dễ quan sát, replay được, và khi nào một job chạy theo lịch tốt hơn broker: partition, delivery semantics, replay và sink.

Một pipeline Kafka chỉ hữu ích trên production khi bốn thứ được thiết kế trước khi consumer đầu tiên chạy: contract của sự kiện, kế hoạch partition, delivery semantics và đường replay. Trước tất cả những điều đó, hãy kiểm tra xem bạn có thật sự cần Kafka. Đưa message vào Kafka là phần dễ; phần khó là một hệ thống vẫn dễ quan sát, replay được và hợp lý về chi phí khi lưu lượng tăng.
Bắt đầu từ quyết định ở downstream
Trước khi tạo topic hoặc viết consumer, hãy xác định quyết định nào cần dữ liệu mới.
Ví dụ:
- chấm điểm rủi ro cần tín hiệu gian lận theo từng phút
- giám sát tồn kho cần cảnh báo bất thường nhanh
- quy trình hỗ trợ khách hàng cần context cho agent từ các hệ thống đang chạy
Nếu không có quyết định nào tốt hơn đáng kể nhờ dữ liệu nhanh hơn, pipeline batch thường là câu trả lời tốt hơn. Đây là câu hỏi quyết định phần còn lại của bài viết có áp dụng cho bạn hay không, và rất đáng để trả lời thành thật. Một dashboard mà con người chỉ xem hai lần mỗi ngày không trở nên hữu ích hơn vì các con số phía sau mới bốn giây thay vì bốn giờ.
Kiểm tra xem bạn có thật sự cần Kafka
Thời gian thực là một dải, và Kafka nằm ở một đầu của dải đó. Một pipeline của chính chúng tôi cho thấy ranh giới thường nằm ở đâu.
Một đội phân tích thị trường năng lượng cần mọi báo cáo điều độ của AEMO, đơn vị vận hành thị trường điện Úc, vốn phát hành một tệp CSV nén mới mỗi năm phút cho năm vùng của Thị trường Điện Quốc gia. Dữ liệu đến dưới dạng một tệp mỗi năm phút thì không cần message broker. Pipeline chúng tôi xây là một hàm AWS Lambda chạy theo lịch, được CloudWatch kích hoạt mỗi năm phút: lấy báo cáo mới nhất, lưu vào S3 (bỏ qua nếu đã có) và ghi 19 chỉ số điều độ cho mỗi vùng vào DynamoDB, với khóa ghép từ thời điểm thanh toán và mã vùng. Một ứng dụng Plotly Dash đọc các bản ghi đó cho các nhà phân tích. Không có cluster nào phải vận hành, và một lần chạy lỗi có thể chạy lại an toàn vì cả hai thao tác ghi đều idempotent.
Kafka chỉ thật sự xứng đáng khi bài toán đổi hình dạng: nhiều producer phát sự kiện liên tục, nhiều đội độc lập cùng đọc một luồng, consumer cần replay lịch sử theo nhịp riêng, hoặc cần bảo đảm thứ tự nghiêm ngặt theo từng khóa giữa các dịch vụ.
| Lựa chọn | Phù hợp khi | Cái giá phải trả | | --- | --- | --- | | Job chạy theo lịch (cron, Lambda, bộ lập lịch của cloud) | Nguồn phát hành theo chu kỳ, một hai consumer đọc, chậm vài phút vẫn ổn | Gần như không tốn công vận hành; độ trễ bị giới hạn bởi lịch chạy | | Hàng đợi được quản lý (SQS, Pub/Sub) | Sự kiện đến liên tục, vài consumer, ít khi cần replay lịch sử cũ | Dễ vận hành; replay và thứ tự hạn chế hơn so với offset của Kafka | | Kafka (tự vận hành hoặc dịch vụ được quản lý) | Lưu lượng sự kiện lớn, nhiều consumer độc lập, replay theo offset, thứ tự theo khóa | Partition, schema, retention và consumer lag phải vận hành mỗi ngày |
Nếu bạn ở dòng thứ nhất hoặc thứ hai, phần lớn bài viết vẫn áp dụng được, vì contract, sink idempotent và replay quan trọng với mọi pipeline, nhưng bạn không cần vận hành một broker.
Thiết kế contract trước khi viết code
Một trong những sai lầm lớn nhất với streaming là cứ phát sự kiện ra trước rồi mới tính chuyện quản trị. Trên thực tế, event contract cần được thiết kế trước khi hệ thống mở rộng quy mô.
Điều đó có nghĩa là thống nhất về:
- cách đặt tên sự kiện
- quyền sở hữu payload
- quy tắc versioning
- cách xử lý dead-letter
- kỳ vọng về replay
Mục tiêu không phải là quan liêu. Mục tiêu là bảo đảm các đội downstream có thể tin vào ý nghĩa của luồng dữ liệu.
Kiểm soát schema ngay từ lúc build
Schema registry biến câu "chúng ta đã thống nhất payload" thành thứ mà build có thể kiểm soát. Phần đáng hiểu là chế độ tương thích, vì nó quyết định bên nào phải triển khai trước:
- BACKWARD (mặc định phổ biến): schema mới đọc được dữ liệu ghi bằng schema cũ. Consumer nâng cấp trước. An toàn khi thêm trường tùy chọn hoặc bỏ trường.
- FORWARD: schema cũ đọc được dữ liệu ghi bằng schema mới. Producer nâng cấp trước.
- FULL: đúng cả hai chiều. Chặt nhất, và là lựa chọn phù hợp cho topic có nhiều consumer độc lập mà bạn không kiểm soát.
Chọn chế độ không phải thủ tục hình thức. Đó là khác biệt giữa một lần đổi tên trường diễn ra êm đẹp và một lần làm sập consumer lúc 2 giờ sáng vì không ai biết bên nào phải triển khai trước.
Thiết kế partition là quyết định khó sửa nhất
Phần lớn những điều khiến người dùng Kafka phải hối tiếc đều xoay quanh partition, vì hai đặc tính mà nó kiểm soát lại kéo về hai hướng ngược nhau.
Số partition đặt trần cho khả năng xử lý song song. Trong một consumer group, mỗi partition chỉ được đúng một consumer đọc. Mười partition nghĩa là nhiều nhất mười consumer hữu ích; consumer thứ mười một sẽ ngồi không. Hãy tính từ throughput đo được thay vì chọn một con số tròn: lấy số sự kiện mỗi giây ở đỉnh mà bạn cần hấp thụ, chia cho mức một consumer xử lý được với workload thật, rồi cộng dự phòng cho tăng trưởng và cho giai đoạn phục hồi sau sự cố, khi bạn cần đuổi kịp nhanh hơn thời gian thực.
Chia quá nhiều partition cũng không miễn phí. Nhiều partition hơn đồng nghĩa nhiều file handle hơn, nhiều metadata hơn và rebalance lâu hơn, đúng thứ bạn không muốn gặp giữa lúc sự cố.
Partition key quyết định bảo đảm về thứ tự. Kafka bảo đảm thứ tự trong một partition, không phải trên toàn topic. Dùng account_id làm key nghĩa là mọi sự kiện của một tài khoản đến đúng thứ tự, và đó thường mới là bảo đảm thực sự quan trọng. Dùng key có cardinality cao nhưng vô nghĩa, hoặc để key null cho round-robin, sẽ cho bạn throughput nhưng mất thứ tự.
Hãy cảnh giác với hot key. Nếu một tenant tạo ra ba mươi phần trăm lưu lượng và bạn dùng tenant làm key, một partition sẽ gánh ba mươi phần trăm tải và một consumer trở thành nút thắt. Key ghép như tenant_id:entity_id giúp phân tán tải mà vẫn giữ được thứ tự cần thiết, miễn là thứ tự theo từng thực thể thực sự là đủ.
Chủ động lựa chọn delivery semantics
"Exactly-once" là cụm từ bị hiểu sai nhiều nhất trong streaming. Nên tách nó thành hai câu hỏi riêng.
Bên trong Kafka, exactly-once là có thật. Producer idempotent khử trùng lặp khi retry, và transaction làm cho chu trình đọc, xử lý, ghi trở nên nguyên tử:
# producer
acks=all
enable.idempotence=true
max.in.flight.requests.per.connection=5
retries=2147483647
delivery.timeout.ms=120000
compression.type=zstd
linger.ms=20
batch.size=131072
# consumer
enable.auto.commit=false
isolation.level=read_committed
auto.offset.reset=earliest
partition.assignment.strategy=org.apache.kafka.clients.consumer.CooperativeStickyAssignor
Có hai dòng trong đó quan trọng hơn vẻ ngoài của chúng. enable.auto.commit=false nghĩa là bạn commit offset sau khi công việc thành công, không phải theo bộ đếm thời gian có thể xác nhận những message bạn chưa từng xử lý. CooperativeStickyAssignor khiến rebalance diễn ra theo kiểu incremental thay vì stop-the-world, nên việc thêm một consumer không còn làm mọi consumer trong group phải tạm dừng.
Bên ngoài Kafka, ngay khi dữ liệu đi vào data warehouse, search index hay một lời gọi API, transaction không còn bảo vệ bạn nữa. Pattern khả thi là delivery at-least-once vào một sink idempotent: mang theo event_id ổn định và ghi bằng merge thay vì insert. Khi đó replay tạo ra đúng các dòng cũ thay vì nhân bản, và bạn không bao giờ phải suy luận xem một lô đã được áp dụng một hay hai lần.
Lập kế hoạch replay từ ngày đầu
Backfill và khởi động lại consumer không phải trường hợp biên hiếm gặp. Chúng là sự kiện vận hành bình thường, và thời điểm để thiết kế cho chúng là trước lần đầu tiên.
Retention là đòn bẩy thứ nhất. Một topic mà bạn có thể cần replay lại thì không thể chỉ giữ dữ liệu bảy ngày nếu cửa sổ phục hồi trong trường hợp xấu nhất là hai tuần:
cleanup.policy=delete
retention.ms=1209600000 # 14 days
Với topic biểu diễn trạng thái hiện tại thay vì lịch sử sự kiện, compaction thường phù hợp hơn. Nó giữ vô thời hạn giá trị mới nhất của từng key và loại bỏ các giá trị đã bị thay thế, nên một consumer mới có thể dựng lại trạng thái từ topic mà bạn không phải lưu mọi thay đổi trung gian:
cleanup.policy=compact
min.compaction.lag.ms=3600000
Đòn bẩy thứ hai là sink. Replay chỉ an toàn khi xử lý lại cùng các sự kiện hội tụ về cùng kết quả, và điều đó quay lại chuyện ghi idempotent. Một câu lệnh merge dựa trên định danh sự kiện khiến replay trở nên nhàm chán, theo nghĩa tốt:
MERGE INTO analytics.orders AS target
USING staging.order_events AS source
ON target.event_id = source.event_id
WHEN NOT MATCHED THEN INSERT *
WHEN MATCHED AND source.ingested_at > target.ingested_at THEN UPDATE SET *;
Xem observability là một tính năng của sản phẩm
Nếu consumer chậm lại hoặc âm thầm bỏ các sự kiện sai định dạng, tác động kinh doanh có thể lớn hơn cả một lần ngừng hoạt động hoàn toàn, vì sẽ không có cảnh báo nào đánh thức ai dậy khi dữ liệu âm thầm sai lệch.
Hãy đo tối thiểu những chỉ số sau:
- Consumer lag, khoảng cách giữa log end offset và offset đã commit, theo từng partition thay vì lấy trung bình. Giá trị trung bình sẽ che mất đúng partition đang bị kẹt, vốn mới là vấn đề thật.
- Tuổi của message, chênh lệch giữa thời điểm sự kiện xảy ra và thời điểm xử lý. Đây mới là chỉ số nên đặt cảnh báo, vì nó là con số mà người phụ trách nghiệp vụ hiểu được. "Tín hiệu gian lận đang trễ chín phút" có ý nghĩa; "lag là 40.000" thì không.
- Tỷ lệ dead-letter, tính theo tỷ lệ trên throughput thay vì số tuyệt đối.
- Tần suất rebalance. Rebalance liên tục thường nghĩa là
max.poll.interval.msquá chặt so với thời gian xử lý thực tế.
Cho poison message một lối thoát
Chỉ một message không parse được thì không nên làm kẹt cả partition. Hãy chuyển các message lỗi sang một topic dead-letter kèm đủ ngữ cảnh để chẩn đoán về sau, và giữ offset gốc trong header để bạn tìm lại được sự kiện nguồn:
orders.v1 -> main topic
orders.v1.dlq -> failures, with headers:
x-error-class, x-error-message,
x-source-partition, x-source-offset, x-failed-at
DLQ chỉ hữu ích nếu có người nhìn vào nó. Đặt cảnh báo theo tỷ lệ message rơi vào DLQ là cách biến nó từ một nghĩa địa thành một vòng phản hồi.
Giữ enrichment gần với giá trị kinh doanh
Không phải phép biến đổi nào cũng nên làm trong Kafka. Hãy đặt các bước chuẩn hóa sự kiện nhẹ ngay cạnh luồng dữ liệu, nơi chúng giữ cho contract sạch sẽ: đổi tên trường, ép kiểu, loại bỏ các bản ghi sai định dạng rõ ràng. Chuyển phần mô hình hóa nặng sang những hệ thống dễ kiểm thử, quản lý version và backfill hơn, thường là data warehouse.
Phép thử thực tế là logic đó có cần lịch sử hay không. Bất cứ thứ gì phải join với dữ liệu nhiều tháng, hoặc mà analyst sẽ muốn sửa lại sau này, đều là công việc mô hình hóa và nên đặt ở nơi mà việc dựng lại có chi phí thấp.
Kiểm soát chi phí data warehouse
Streaming vào data warehouse sẽ đắt lên rất nhanh nếu mỗi message kích hoạt một lần ghi. Ba mô hình sau mang lại phần lớn khoản tiết kiệm:
- Micro-batch thay vì ghi theo từng message. Gom đệm theo ngưỡng kích thước hoặc thời gian, cái nào đến trước, rồi ghi một lần. Đây thường là khác biệt giữa hóa đơn data warehouse tăng theo số sự kiện và hóa đơn tăng theo khối lượng dữ liệu.
- Tôn trọng bố cục bảng. Hãy partition và cluster bảng sink theo đúng các cột mà truy vấn thực sự lọc, thường là ngày sự kiện cộng với key của tenant hoặc thực thể.
- Gộp file nhỏ. Ghi streaming tần suất cao tạo ra rất nhiều file nhỏ, và các engine truy vấn sẽ trả giá cho điều đó ở mọi lần quét về sau.
Thiết kế đường xử lý lỗi mà con người dễ hiểu
Khi có sự cố, người vận hành cần câu trả lời nhanh cho hai câu hỏi:
- điều gì đã lỗi
- hành động nào khôi phục hệ thống an toàn
Câu trả lời này phải có trước sự cố, không phải sau đó. Trên thực tế, điều đó nghĩa là mỗi topic cần một runbook ngắn nêu rõ DLQ nằm ở đâu, quy trình replay an toàn ra sao, và những bảng downstream nào cần dựng lại nếu bạn thực sự replay. Đội viết sẵn tài liệu này trước sự cố đầu tiên sẽ phục hồi trong vài phút thay vì vài giờ.
AI agent phù hợp ở đâu
Pipeline Kafka có giá trị hơn khi kết hợp với tóm tắt có hỗ trợ AI hoặc quy trình phát hiện bất thường. Một pattern hữu ích là:
- stream sự kiện vào kho lưu trữ vận hành
- tổng hợp thành bảng giám sát
- chỉ kích hoạt agent hoặc trợ lý khi ngưỡng thực sự đáng chú ý
Cách này giữ hoạt động của agent tập trung vào sự kiện tín hiệu cao thay vì lưu lượng thô đầy nhiễu, và giữ chi phí ở mức dự đoán được. Chĩa một mô hình vào luồng thô vừa tốn kém vừa cho kết quả tệ hơn: tín hiệu mà người duyệt cần gần như luôn nằm ở mức tổng hợp, không phải ở từng sự kiện riêng lẻ. Nguyên tắc này cũng định hình cách chúng tôi làm agentic BI: agent trả lời câu hỏi của ban lãnh đạo từ dữ liệu dashboard đã được mô hình hóa, không phải từ sự kiện thô.
Danh sách kiểm tra trước khi chạy thật
Trước khi một pipeline streaming gánh bất cứ thứ gì mà doanh nghiệp phụ thuộc vào:
- quyết định ở downstream đã được ghi lại rõ ràng, và nó tốt lên nhờ dữ liệu mới hơn
- schema đã đăng ký với chế độ tương thích mà cả đội đã thống nhất
- partition key và số partition được xác định từ throughput đo được và một bảo đảm thứ tự đã nêu rõ
- sink là idempotent, để replay hội tụ thay vì nhân bản
- thời gian lưu trữ phủ được cửa sổ phục hồi xấu nhất, không phải trường hợp thông thường
- cảnh báo lag đặt theo tuổi message, theo từng partition
- đã có topic dead-letter và có người chịu trách nhiệm về tỷ lệ của nó
- runbook replay đã tồn tại và đã được thử một lần, có chủ đích, trước khi cần đến
Đọc thêm: Chọn xử lý batch hay streaming cho pipeline dữ liệu hiện đại và cách chúng tôi xây dựng pipeline dữ liệu.
Kết luận
Kafka là nền tảng tốt cho hệ thống thời gian thực, nhưng chỉ khi pipeline được thiết kế xoay quanh niềm tin, khả năng khôi phục và các quyết định kinh doanh ở downstream.
Một luồng dữ liệu "chạy được" là chưa đủ. Nền tảng streaming hữu ích là nền tảng mà người vận hành có thể hiểu và xử lý khi chịu áp lực, nơi replay là quy trình thường ngày thay vì một hành động liều lĩnh, và nơi chi phí của độ tươi dữ liệu là thứ cả đội đã chủ động chọn.

Nhà sáng lập Van Data Team, trực tiếp xây dựng Vanaxity và điều hành kỹ thuật dữ liệu, AI Agent cho các đội ngũ doanh nghiệp.
Về tác giảCâu hỏi thường gặp
Những câu hỏi người đọc thường đặt ra tiếp theo.
Các câu trả lời ngắn này làm rõ những câu hỏi thực tế thường xuất hiện sau khi đọc bài viết.
Bạn cần một hệ thống tương tự?
Nếu bài viết này phản ánh một quy trình đội ngũ bạn đang vận hành, bước tiếp theo thường là rà soát có phạm vi về hệ thống, ràng buộc và lộ trình triển khai.
Tư vấn
Xác định phạm vi cho quy trình AI tiếp theo.
Chia sẻ hệ thống và điểm nghẽn. Chúng tôi sẽ cùng làm rõ mức độ phù hợp, khoảng trống và bước tiếp theo.







