Cài đặt Apache Kafka trên CentOS Stream 9: Triển khai KRaft mode không cần ZooKeeper

1. Vấn đề thực tế: Khi hạ tầng sụp đổ vì nghẽn giao dịch

Giả sử bạn đang vận hành một website bán vé ca nhạc trực tuyến. Ngày thường, hệ thống chỉ ghi nhận vài chục giao dịch rải rác. Mọi thứ vận hành rất êm.

Bi kịch ập đến lúc 10h sáng ngày mở bán đợt vé VIP độc quyền. Khoảng 20.000 người cùng bấm ‘Thanh toán’ trong đúng một giây.

Hậu quả xuất hiện ngay lập tức. Máy chủ thanh toán quá tải, CPU nhảy vọt lên 100%. Dịch vụ kho không cập nhật kịp dẫn đến tình trạng bán trùng vé cho khách. Nghiêm trọng hơn, các microservice đang gọi nhau theo dạng đồng bộ HTTP (A gọi B, B đợi C). Chỉ cần một service phản hồi trễ 3 giây, toàn bộ chuỗi xử lý lập tức tắc nghẽn.

Người dùng thấy màn hình quay tròn liền liên tục bấm F5. Lượng request ảo tăng vọt gấp 5 lần. Database chính cạn sạch connection pool rồi rơi vào trạng thái tê liệt hoàn toàn.

2. Phân tích nguyên nhân: Bẫy khớp nối chặt và gánh nặng ZooKeeper

Sự cố trên xuất phát từ hai điểm yếu cốt lõi trong kiến trúc:

  • Khớp nối quá chặt (Tight Coupling): Website muốn xác nhận đơn bắt buộc service kho và email phải online. Nếu service email bị nghẽn, giao dịch mua vé bị treo theo. Kiến trúc này khiến hệ thống mất hẳn khả năng chịu lỗi cục bộ.
  • Dùng sai công cụ lưu trữ: Database quan hệ (RDBMS) sinh ra để lưu trạng thái cuối. Nó không thể gánh 20.000 đến 30.000 sự kiện (event) đổ về cùng một giây.

Trước đây, giải pháp tiêu chuẩn là triển khai Apache Kafka để hấp thụ dòng dữ liệu này. Nhưng giải pháp cũ lại đi kèm một gánh nặng khác: Apache ZooKeeper.

Ai từng trực ca đêm đều hiểu nỗi khổ vận hành cụm ZooKeeper song song với Kafka. ZooKeeper chạy JVM riêng, tốn thêm ít nhất 2 đến 4 GB RAM mỗi node và cần cơ chế backup dữ liệu riêng. Khi xảy ra phân vùng mạng (network partition), việc đồng bộ metadata giữa ZooKeeper và Kafka broker thường bị lệch, khiến việc khôi phục cụm mất rất nhiều thời gian.

3. So sánh các hướng giải quyết phổ biến

Để giải bài toán nghẽn dòng dữ liệu, các kỹ sư thường cân nhắc ba hướng:

  • Nâng cấp phần cứng database (Vertical Scaling): Nâng RAM từ 32GB lên 128GB, dùng ổ cứng NVMe Enterprise. Cách này giải quyết được triệu chứng trước mắt nhưng chi phí hạ tầng tăng gấp 3 lần. Điểm nghẽn chỉ tạm dời sang kỳ bán vé tiếp theo.
  • Dùng Message Queue truyền thống (RabbitMQ, Redis Pub/Sub): Tách rời được các dịch vụ rất tốt. Tuy nhiên, khi cần lưu lại log sự kiện trong 30 ngày để xử lý lại (event replay) hoặc cần throughput vượt ngưỡng 100.000 messages/giây, RabbitMQ bắt đầu đuối sức về bộ nhớ.
  • Kafka chạy KRaft mode: Giữ nguyên sức mạnh xử lý hàng triệu message/giây của Kafka, nhưng loại bỏ hoàn toàn tầng ZooKeeper trung gian.

4. Giải pháp: Triển khai Apache Kafka với KRaft trên CentOS Stream 9

Từ phiên bản 3.3, KRaft (Kafka Raft Metadata Mode) đã chính thức sẵn sàng cho môi trường production. Kafka tự bầu chọn controller và quản lý metadata nội bộ thông qua thuật toán đồng thuận Raft. Cụm khởi động chỉ mất vài giây thay vì vài phút, đồng thời hỗ trợ quản lý hàng triệu partition mượt mà.

Bước 1: Chuẩn bị môi trường và cài đặt Java 17

Kafka chạy trên nền JVM. CentOS Stream 9 hỗ trợ OpenJDK 17 rất ổn định theo vòng đời dài hạn (LTS):

sudo dnf update -y
sudo dnf install -y java-17-openjdk-headless wget tar
java -version

Tạo riêng một user hệ thống không có quyền đăng nhập shell tương tác để tăng tính bảo mật:

sudo useradd -r -d /opt/kafka -s /sbin/nologin kafka

Bước 2: Tải và giải nén Apache Kafka

Tải bản phân phối Kafka nhị phân (khuyên dùng bản 3.7.0 tích hợp Scala 2.13):

cd /tmp
wget https://archive.apache.org/dist/kafka/3.7.0/kafka_2.13-3.7.0.tgz
sudo tar -xzf kafka_2.13-3.7.0.tgz -C /opt
sudo mv /opt/kafka_2.13-3.7.0 /opt/kafka
sudo chown -R kafka:kafka /opt/kafka

Bước 3: Cấu hình Kafka KRaft (Single-node)

Với chế độ KRaft, file cấu hình chính nằm tại /opt/kafka/config/kraft/server.properties. Mở file để chỉnh sửa:

sudo vi /opt/kafka/config/kraft/server.properties

Thiết lập các thông số cơ bản như sau (thay 192.168.1.50 bằng IP LAN/Public máy chủ của bạn):

# Khai báo node vừa làm broker vừa làm controller metadata
process.roles=broker,controller
node.id=1
controller.quorum.voters=1@localhost:9093

# Thiết lập port lắng nghe kết nối
listeners=PLAINTEXT://0.0.0.0:9092,CONTROLLER://localhost:9093
advertised.listeners=PLAINTEXT://192.168.1.50:9092
controller.listener.names=CONTROLLER

# Thư mục chứa message log và thời gian lưu trữ (7 ngày)
log.dirs=/var/lib/kafka/data
num.partitions=3
log.retention.hours=168

Tạo thư mục lưu log dữ liệu và cấp quyền cho user kafka:

sudo mkdir -p /var/lib/kafka/data
sudo chown -R kafka:kafka /var/lib/kafka

Bước 4: Khởi tạo Storage Cluster ID

Trước khi chạy lần đầu, KRaft bắt buộc phải format thư mục lưu trữ bằng một Cluster ID duy nhất:

# Tạo mã UUID định danh cho cụm
KAFKA_CLUSTER_ID=$(sudo -u kafka /opt/kafka/bin/kafka-storage.sh random-cluster-id)
echo "Cluster ID của bạn: $KAFKA_CLUSTER_ID"

# Format thư mục log bằng Cluster ID
sudo -u kafka /opt/kafka/bin/kafka-storage.sh format -t $KAFKA_CLUSTER_ID -c /opt/kafka/config/kraft/server.properties

Bước 5: Tạo Systemd Service quản trị tiến trình

Tạo service unit để Kafka tự khởi động cùng CentOS Stream 9 khi reboot máy chủ:

sudo tee /etc/systemd/system/kafka.service << 'EOF'
[Unit]
Description=Apache Kafka Server (KRaft mode)
Documentation=https://kafka.apache.org/documentation/
After=network.target

[Service]
Type=simple
User=kafka
Group=kafka
Environment="JAVA_HOME=/usr/lib/jvm/jre-17-openjdk"
Environment="KAFKA_LOG_DIR=/opt/kafka/logs"
Environment="KAFKA_HEAP_OPTS=-Xmx1G -Xms1G"
ExecStart=/opt/kafka/bin/kafka-server-start.sh /opt/kafka/config/kraft/server.properties
ExecStop=/opt/kafka/bin/kafka-server-stop.sh
Restart=on-failure
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
EOF

Nạp lại daemon và khởi chạy dịch vụ:

sudo systemctl daemon-reload
sudo systemctl enable --now kafka
sudo systemctl status kafka

Bước 6: Mở port trên Firewalld

Nếu server đang bật tường lửa, hãy mở cổng 9092 cho client kết nối:

sudo firewall-cmd --permanent --add-port=9092/tcp
sudo firewall-cmd --reload

Bước 7: Kiểm thử luồng gửi và nhận dữ liệu

Hãy kiểm tra xem đường ống truyền tin có thông suốt không bằng CLI có sẵn trong bộ cài.

Tạo một topic test tên là orders-stream với 3 partition:

/opt/kafka/bin/kafka-topics.sh --create --topic orders-stream --partitions 3 --replication-factor 1 --bootstrap-server localhost:9092

Khởi động console producer và đẩy thử 2 bản ghi giao dịch JSON:

/opt/kafka/bin/kafka-console-producer.sh --topic orders-stream --bootstrap-server localhost:9092
>{"order_id": 1001, "amount": 250000, "status": "PENDING"}
>{"order_id": 1002, "amount": 500000, "status": "PAID"}

Mở thêm một tab terminal khác và chạy console consumer để đọc dữ liệu từ đầu topic:

/opt/kafka/bin/kafka-console-consumer.sh --topic orders-stream --from-beginning --bootstrap-server localhost:9092

Cửa sổ consumer ngay lập tức in ra hai bản tin JSON. Dữ liệu đã được ghi bền bỉ xuống đĩa. Kể cả khi consumer gặp sự cố và tắt trong vài giờ, toàn bộ message vẫn nằm an toàn trên Kafka broker sẵn sàng cho việc xử lý lại bất cứ lúc nào.

Share: