Kafka: nhật ký phân tán, không phải hàng đợi thông thường

Xem thử

Ứng dụng bán hàng của GDev Mart bắt đầu gửi mỗi sự kiện đơn hàng trực tiếp tới ba hệ thống: kho dữ liệu, dịch vụ gửi email xác nhận và dịch vụ phát hiện gian lận. Khi dịch vụ gian lận chậm vài giây, ứng dụng bán hàng bị chậm theo, và khi dịch vụ email chết, đơn hàng đó mất sự kiện email. Mỗi lần thêm một hệ thống mới (ví dụ dịch vụ khuyến mãi) lại phải sửa mã của ứng dụng bán hàng.

Hãy dự đoán: nếu giữa ứng dụng và các hệ thống tiêu thụ đặt một "bộ đệm trung gian" nơi ứng dụng chỉ việc ghi sự kiện một lần, thì bộ đệm đó cần có những tính chất nào để thêm hệ thống mới không cần sửa ứng dụng, để hệ thống chậm không kéo ứng dụng chậm, và để hệ thống chết xong sống lại đọc tiếp được từ chỗ dừng? Viết ra ít nhất ba tính chất.

Sau bài này, bạn có thể

  • Giải thích vì sao Kafka là nhật ký chỉ ghi thêm chứ không phải hàng đợi thông thường.
  • Mô tả vai trò của topic, partition, offset và broker.
  • Giải thích cách nhân bản (leader, follower, ISR) cùng acks=all và min.insync.replicas bảo vệ dữ liệu.
  • Quyết định khi nào dùng Kafka và khi nào không.

Cần biết trước: bài Idempotent và backfill: chạy lại mà không hỏng dữ liệu ở chương Thiết kế pipeline ETL/ELT; nếu muốn chạy thử cần Docker (xem bài Docker và Docker Compose: môi trường lab tái lập được).

Mô hình tư duy 1: Kafka là một cuốn nhật ký chỉ được ghi thêm

Apache Kafka là nền tảng truyền sự kiện. Điểm cốt lõi, khác với hàng đợi thông thường: Kafka lưu sự kiện trong một nhật ký (log) chỉ được ghi thêm vào cuối (append-only) và giữ lại theo thời gian cấu hình (mặc định 7 ngày), không xóa khi có người đọc. Mỗi người tiêu thụ tự nhớ vị trí (offset) mình đã đọc tới.

Bốn khái niệm nền:

Khái niệmÝ nghĩa
TopicMột "kênh" sự kiện cùng loại, ví dụ order_events
PartitionMỗi topic chia thành nhiều nhật ký con; mỗi partition là một dãy có thứ tự
OffsetSố thứ tự của một bản ghi trong một partition (0, 1, 2, ...)
BrokerMột máy chủ Kafka; cụm gồm nhiều broker

Đang tải sơ đồ…

Cách đọc hình: producer chỉ ghi vào cuối một trong các partition. Mỗi partition là một dãy bản ghi đánh số liên tiếp (offset) và không bao giờ bị sửa. Offset chỉ có nghĩa trong một partition; "offset 3" ở partition 0 và ở partition 1 là hai bản ghi khác nhau.

Giải đáp: ba tính chất của bộ đệm trung gian

Chính các tính chất của nhật ký trả lời câu hỏi đầu bài:

  1. Thêm hệ thống mới không sửa ứng dụng: mỗi hệ thống tiêu thụ tự đọc nhật ký theo offset riêng, nên dịch vụ khuyến mãi mới chỉ cần đăng ký đọc topic mà không ai khác thay đổi.
  2. Hệ thống chậm không kéo ứng dụng chậm: producer chỉ ghi vào Kafka (rất nhanh), việc đọc chậm hay nhanh là chuyện riêng của từng consumer (tách rời hai phía về thời gian).
  3. Chết rồi sống lại đọc tiếp: vì dữ liệu được giữ lại và consumer nhớ offset, nó khởi động lại và đọc từ đúng chỗ dừng; nó còn có thể đọc lại quá khứ (replay) bằng cách đặt lại offset, ví dụ sau khi sửa một lỗi trong logic xử lý.

Đang tải sơ đồ…

Cách đọc hình: cùng một nhật ký, ba người đọc ở ba vị trí khác nhau, không ảnh hưởng nhau. Đó là khác biệt lớn so với hàng đợi truyền thống (mỗi tin được giao rồi xóa cho một người nhận): ở Kafka dữ liệu ở lại và nhiều nhóm người đọc cùng độc lập.

Đang tải Animation…

Đang tải Animation…

Mô hình tư duy 2: nhân bản để chịu lỗi

Một broker có thể chết. Kafka giữ nhiều bản sao (replica) của mỗi partition trên các broker khác nhau, theo replication factor (thường 3). Một bản là leader (nhận ghi và đọc), các bản còn lại là follower sao chép theo. Tập follower đã theo kịp gọi là ISR (in-sync replicas). Khi leader chết, một follower trong ISR được bầu làm leader mới.

Hai cấu hình quyết định độ bền của dữ liệu khi ghi:

  • acks=all: producer chỉ coi là thành công khi mọi bản trong ISR đã ghi.
  • min.insync.replicas=2: yêu cầu tối thiểu 2 bản đồng bộ mới cho phép ghi (kết hợp acks=all), để một broker chết không làm mất bản ghi đã báo thành công.

Với replication factor 3 và min.insync.replicas=2, cụm chịu được một broker hỏng mà không mất dữ liệu và vẫn nhận ghi. Từ phiên bản 4.0, Kafka chạy ở chế độ KRaft không còn phụ thuộc ZooKeeper để quản lý siêu dữ liệu cụm.

Khi nào dùng Kafka và khi nào không

Tình huốngGợi ý
Nhiều hệ thống cùng cần một luồng sự kiện, cần phát lạiKafka phù hợp
Khối lượng sự kiện lớn, thứ tự theo khóa quan trọngKafka phù hợp
Một producer và một consumer, vài tin mỗi phútMột hàng đợi đơn giản hoặc chính cơ sở dữ liệu là đủ
Cần truy vấn dữ liệu tùy ý theo điều kiệnKafka không phải cơ sở dữ liệu truy vấn; ghi sự kiện sang kho rồi truy vấn

Cập nhật Kafka 4.x: Kafka 4.0 đã bỏ hẳn ZooKeeper, KRaft là chế độ duy nhất. Kafka cũng đang bổ sung ngữ nghĩa hàng đợi bằng share group (KIP-932): nhiều consumer cùng đọc các partition, xác nhận từng bản ghi riêng và không bị giới hạn bởi số partition; tính năng này ở dạng early access từ 4.0 và được công bố GA ở Kafka 4.2. Dù vậy bản ghi vẫn nằm trong nhật ký theo retention chứ không bị xóa khi một consumer xác nhận, nên ý chính của bài vẫn đúng: Kafka là nhật ký có thể phát lại, hàng đợi chỉ là một cách đọc nó.

Những hiểu lầm thường gặp

  • "Kafka là hàng đợi tin nhắn." Kafka là nhật ký lưu trữ sự kiện: tin không bị xóa khi đọc và nhiều nhóm đọc độc lập; nó có thể đóng vai hàng đợi nhưng mạnh hơn.
  • "Offset là số thứ tự toàn topic." Offset chỉ có nghĩa trong một partition.
  • "Dữ liệu trong Kafka là vĩnh viễn." Mặc định giữ theo thời gian hoặc dung lượng (ví dụ 7 ngày); sau đó bị xóa trừ khi cấu hình khác (như log compaction).
  • "Nhiều partition hơn luôn tốt hơn." Partition tăng mức song song nhưng cũng tăng chi phí quản lý; chọn theo số consumer và thông lượng thực.

Tự kiểm tra (có đáp án)

Câu 1. Vì sao Kafka cho phép đọc lại sự kiện cũ còn hàng đợi truyền thống thì không? Đáp án: vì Kafka giữ bản ghi theo thời gian cấu hình và consumer chỉ nhớ offset; đặt lại offset là đọc lại, còn hàng đợi truyền thống xóa tin sau khi giao.

Câu 2. Replication factor 3, min.insync.replicas=2, acks=all. Hai broker cùng hỏng thì điều gì xảy ra với việc ghi? Đáp án: chỉ còn 1 bản đồng bộ, ít hơn mức tối thiểu 2 nên producer bị từ chối ghi (chọn tính toàn vẹn hơn tính sẵn sàng), nhưng dữ liệu đã ghi vẫn an toàn.

Câu 3. Offset 5 của partition 0 và offset 5 của partition 1 có phải cùng một bản ghi không? Đáp án: không, offset chỉ có nghĩa trong từng partition; hai partition là hai nhật ký độc lập.

Nếu chỉ nhớ 3 điều

  1. Kafka là nhật ký chỉ ghi thêm, chia thành partition có thứ tự; dữ liệu được giữ lại và mỗi consumer tự nhớ offset.
  2. Tính chất này tách producer khỏi consumer, cho phép thêm hệ thống mới, chịu hệ thống chậm, và phát lại dữ liệu.
  3. Độ bền đến từ nhân bản (leader, follower, ISR) cùng acks=all và min.insync.replicas.

Nguồn tham khảo