Khi nào bạn thực sự cần đến Data Streaming?
Thời mới vào nghề, mình thường có thói quen đẩy mọi thứ trực tiếp vào database qua REST API. Mọi chuyện vẫn ổn cho đến khi dự án mình tham gia đạt mốc 50.000 người dùng hoạt động cùng lúc. Lúc này, hàng triệu log, tracking event và thông báo đổ về mỗi giây khiến server bắt đầu “hụt hơi”. Latency tăng từ 200ms lên tận 10s. Database liên tục báo lỗi ‘Too many connections’ và hệ thống sập hoàn toàn chỉ vì lượng người nhấn Like tăng đột biến.
Vấn đề nằm ở chỗ: Database truyền thống không được thiết kế để ghi dữ liệu với cường độ cao và liên tục như vậy. Chúng ta cần một “hố đen” có khả năng nuốt gọn hàng triệu message mỗi giây, lưu trữ chúng an toàn rồi mới thong thả phân phối cho các dịch vụ xử lý sau. Đó chính là lúc Apache Kafka tỏa sáng.
Chọn vũ khí: Kafka, RabbitMQ hay Redis Pub/Sub?
Trước khi gõ dòng code đầu tiên, hãy cùng nhìn lại bảng cân đối kế toán cho các giải pháp Message Broker phổ biến hiện nay.
1. Redis Pub/Sub
Điểm mạnh: Tốc độ cực nhanh vì chạy trên RAM, độ trễ gần như bằng 0.
Điểm yếu: Hoạt động theo cơ chế “gửi và quên”. Nếu Consumer (phía nhận) mất kết nối đúng lúc message gửi đến, dữ liệu đó sẽ bốc hơi vĩnh viễn. Redis không phù hợp nếu bạn cần sự đảm bảo về dữ liệu.
2. RabbitMQ (Message Queue)
Điểm mạnh: Quản lý routing phức tạp rất tốt, đảm bảo message đến đúng đích.
Điểm yếu: Khi hàng đợi tích tụ khoảng vài triệu message chưa xử lý, hiệu năng của RabbitMQ bắt đầu giảm rõ rệt. Nó ưu tiên việc phân phối tin nhắn hơn là xử lý luồng dữ liệu khổng lồ (throughput).
3. Apache Kafka (Event Streaming)
Điểm mạnh: Kafka ghi dữ liệu vào đĩa cứng theo dạng append-only log, giúp nó cực kỳ bền vững. Bạn có thể thoải mái “replay” lại dữ liệu từ 3 ngày trước để debug. Về khả năng mở rộng, các ông lớn như Netflix hay Uber đang dùng Kafka để xử lý hàng nghìn tỷ event mỗi ngày.
Điểm yếu: Đường cong học tập hơi dốc và tốn tài nguyên vận hành hơn Redis.
Lời khuyên: Nếu hệ thống của bạn cần độ tin cậy tuyệt đối, khả năng truy hồi dữ liệu cũ và scale không giới hạn, hãy chọn Kafka.
Triển khai nhanh với Docker
Cài đặt Kafka thủ công rất dễ gây nản lòng vì các phụ thuộc vào Java và Zookeeper. Cách nhanh nhất để bắt đầu là dùng Docker. Bạn chỉ mất chưa đầy 30 giây để có một môi trường chuẩn chỉnh.
Hãy tạo file docker-compose.yml:
version: '3'
services:
zookeeper:
image: confluentinc/cp-zookeeper:latest
environment:
ZOOKEEPER_CLIENT_PORT: 2181
kafka:
image: confluentinc/cp-kafka:latest
depends_on:
- zookeeper
ports:
- "9092:9092"
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
Gõ lệnh docker-compose up -d. Bây giờ, máy tính của bạn đã trở thành một Kafka Broker thực thụ.
Code Producer và Consumer với Node.js
Trong thế giới Node.js, thư viện kafkajs là ứng cử viên sáng giá nhất. Nó nhẹ, viết hoàn toàn bằng JavaScript và có hiệu năng rất ấn tượng.
Bước 1: Setup dự án
mkdir kafka-demo && cd kafka-demo
npm init -y
npm install kafkajs
Bước 2: Viết Producer (Người gửi)
Producer giống như một phóng viên gửi tin bài về tòa soạn. Ở đây, mình giả lập việc gửi một sự kiện đặt hàng thành công.
const { Kafka } = require('kafkajs');
const kafka = new Kafka({ clientId: 'order-service', brokers: ['localhost:9092'] });
const producer = kafka.producer();
(async () => {
await producer.connect();
console.log("✅ Producer đã sẵn sàng");
await producer.send({
topic: 'orders',
messages: [
{ value: JSON.stringify({ id: 1, item: 'MacBook M3', price: 2500 }) },
],
});
await producer.disconnect();
})();
Bước 3: Viết Consumer (Người nhận)
Consumer sẽ túc trực để xử lý tin nhắn ngay khi nó xuất hiện trong Topic.
const { Kafka } = require('kafkajs');
const kafka = new Kafka({ clientId: 'inventory-service', brokers: ['localhost:9092'] });
const consumer = kafka.consumer({ groupId: 'inventory-group' });
(async () => {
await consumer.connect();
await consumer.subscribe({ topic: 'orders', fromBeginning: true });
await consumer.run({
eachMessage: async ({ message }) => {
const order = JSON.parse(message.value.toString());
console.log(`📦 Đang xử lý kho cho đơn hàng: ${order.id}`);
},
});
})();
3 bài học xương máu khi triển khai thực tế
Sau khi quản lý hệ thống xử lý hơn 500GB dữ liệu mỗi ngày, mình nhận ra code chạy được chỉ là 30% chặng đường. 70% còn lại nằm ở việc tối ưu vận hành.
1. Đừng bao giờ dùng 1 Partition cho môi trường Production
Kafka song song hóa việc xử lý thông qua Partition. Nếu Topic chỉ có 1 partition, bạn chỉ có thể dùng duy nhất 1 Consumer để đọc dữ liệu. Dù bạn có nâng cấp lên 10 server thì 9 cái vẫn sẽ ngồi chơi. Hãy bắt đầu với ít nhất 3 hoặc 6 partitions để dễ dàng scale-out sau này.
2. Xử lý Idempotency (Tính giao hoán)
Trong hệ thống phân tán, việc Consumer nhận trùng một message là điều chắc chắn sẽ xảy ra (do network timeout hoặc rebalancing).
Giải pháp: Luôn kiểm tra trạng thái trong database trước khi xử lý. Ví dụ: Nếu order_id này đã được trừ kho rồi thì bỏ qua, không trừ tiếp lần nữa.
3. Luôn theo dõi Consumer Lag
Consumer Lag là con số cho biết bạn đang chậm hơn so với dữ liệu thực tế bao nhiêu message. Nếu con số này lên tới hàng trăm nghìn, nghĩa là code xử lý của bạn quá chậm hoặc lượng dữ liệu đổ về quá lớn. Đừng để đến khi ổ cứng đầy (Retention Policy) mới phát hiện ra Consumer đã ngừng chạy từ 2 ngày trước.
Tổng kết
Kafka không chỉ là một công nghệ, nó thay đổi cách chúng ta tư duy về phần mềm: từ Request-Response sang Event-Driven. Node.js với cơ chế non-blocking cực kỳ phù hợp để làm Consumer cho Kafka nhờ khả năng xử lý I/O mượt mà.
Nếu bạn đang xây ứng dụng nhỏ, đừng làm phức tạp hóa vấn đề. Nhưng nếu mục tiêu là phục vụ hàng triệu người dùng với độ trễ thấp, Kafka chính là chiếc chìa khóa mà bạn cần nắm vững.

