Kafka hay bị gọi là "hàng đợi tin nhắn", và cái tên đó dẫn tới phần lớn hiểu nhầm về nó. Phần này đo đặc tính thật sự định nghĩa Kafka, và hai con số mà mọi bài giới thiệu đều nhắc nhưng ít ai đặt cạnh nhau.
Đọc không xoá tin
Đây là khác biệt gốc, và nó đo được trong ba mươi giây.
Ghi ba tin vào một topic, rồi cho ba nhóm tiêu thụ khác nhau cùng đọc:
nhóm-thanh-toán đọc được 3 tin
nhóm-kho đọc được 3 tin
nhóm-báo-cáo đọc được 3 tin
Ba nhóm, mỗi nhóm nhận đủ ba tin. Không ai làm mất của ai.
Với một hàng đợi truyền thống, tin bị xoá khi có người nhận và xác nhận. Ba người tiêu thụ chia nhau ba tin, mỗi người được một.
Đọc tiếp bằng chính nhóm-thanh-toán:
từ offset đã chốt: 0 tin
một nhóm hoàn toàn mới, từ đầu: 3 tin
Nhóm cũ không đọc lại vì nó đã chốt vị trí. Nhưng dữ liệu vẫn nằm trên đĩa — một nhóm mới đọc lại được đủ.
Nghĩa là: "đã đọc tới đâu" là trạng thái của từng nhóm, không phải của tin. Kafka không nhớ ai đã nhận cái gì; nó chỉ giữ một nhật ký và mỗi nhóm tự nhớ mình đang ở đâu trong nhật ký đó.
Đó là lý do đúng để gọi Kafka là nhật ký được chia sẻ chứ không phải hàng đợi.
Cái này đổi cách thiết kế hệ thống
Ba hệ quả trực tiếp:
Thêm một hệ thống tiêu thụ mới không ảnh hưởng hệ thống cũ. Đội phân tích muốn đọc luồng đơn hàng? Tạo một nhóm mới. Đội thanh toán không biết và không quan tâm.
Xử lý lại được. Mã có bug và đã xử lý sai 100.000 đơn? Đưa offset về, chạy lại. Với hàng đợi thì dữ liệu đã biến mất.
Người tiêu thụ chậm không làm nghẽn người khác. Mỗi nhóm có offset riêng; nhóm chậm chỉ tụt lại phía sau chứ không giữ tin lại.
Đổi lại, Kafka không làm được vài thứ mà hàng đợi làm tốt: định tuyến phức tạp theo nội dung tin, ưu tiên tin, và xoá một tin cụ thể. Nếu bài toán của bạn là "gửi việc này cho đúng một worker rảnh nhất", hàng đợi truyền thống hợp hơn.
Thông lượng: đo ba lần vì lần đầu luôn sai
300.000 tin, mỗi tin 200 byte, trên một broker duy nhất:
| Lần | Thông lượng | Băng thông | Độ trễ TB |
|---|---|---|---|
| 1 | 378.788 tin/giây | 72,25 MB/giây | 144,95 ms |
| 2 | 463.679 tin/giây | 88,44 MB/giây | 63,32 ms |
| 3 | 493.421 tin/giây | 94,11 MB/giây | 42,61 ms |
Ba lần tăng dần, và độ trễ giảm hơn ba lần. Đó là JVM đang khởi động dần — biên dịch JIT, làm nóng bộ nhớ đệm, cấp phát vùng nhớ.
Lần đo đầu tiên trên Kafka luôn thấp hơn thực tế. Nếu bạn thấy một bài viết công bố đúng một con số thông lượng mà không nói đã chạy bao nhiêu lần, con số đó đáng ngờ.
Nhưng con số đó đến từ gộp lô
Đây là chỗ hai con số cần đặt cạnh nhau.
| Cấu hình | Thông lượng | Độ trễ TB |
|---|---|---|
| Gộp lô mặc định | 493.421 tin/giây | 42,61 ms |
batch.size=1, linger.ms=0 |
— | 1,25 ms (p50 1 ms, p99 6 ms) |
Cùng một broker, cùng một mạng. Bỏ gộp lô thì mỗi tin nhanh hơn 34 lần, nhưng thông lượng sụp đổ.
Kafka nhanh không phải vì mỗi tin đi nhanh. Nó nhanh vì nó gom nhiều tin lại rồi ghi một lần, và ghi tuần tự vào cuối tệp. Mỗi tin riêng lẻ phải đợi lô của nó đầy hoặc hết linger.ms.
Điều này định ra một câu hỏi bạn phải trả lời trước khi chọn Kafka: bạn cần thông lượng hay cần độ trễ thấp? Kafka mặc định chọn thông lượng. Nó chỉnh về phía độ trễ được, nhưng khi đó nó mất phần lớn lợi thế của mình.
Phần 5 sẽ đo batch.size và linger.ms chi tiết.
Một topic trống chiếm 20 MB
Sau khi ghi đúng ba tin:
00000000000000000000.log 97 byte
00000000000000000000.index 10.485.760 byte
00000000000000000000.timeindex 10.485.756 byte
97 byte dữ liệu thật, và 20 MB tệp chỉ mục.
Kafka cấp phát trước hai tệp chỉ mục theo log.index.size.max.bytes (mặc định 10 MB) để không phải mở rộng tệp trong lúc đang ghi. Chúng thưa và sẽ được cắt lại khi segment đóng.
Đừng hoảng khi thấy một topic vừa tạo đã chiếm 20 MB mỗi partition. Nhưng cũng đừng quên nhân với số partition: 100 topic × 10 partition là 20 GB trước khi có tin nào. Phần 16 sẽ đo cơ chế này.
Một phép đo tôi không dựng được
Đề bài của phần này là so với hàng đợi truyền thống, và tôi định chạy RabbitMQ cạnh Kafka trên cùng máy.
RabbitMQ không khởi động được trong môi trường Docker của tôi. Nó chết ngay lúc khởi động với:
Error when reading /var/lib/rabbitmq/.erlang.cookie: eacces
Tôi thử bốn cách: ảnh alpine, ảnh debian, gắn tmpfs, và volume có tên, kèm khai RABBITMQ_ERLANG_COOKIE tường minh. Cả bốn đều cùng một lỗi. Đây là vấn đề quyền tệp của môi trường chứ không phải của RabbitMQ.
Nên phép so trực tiếp phải đợi tới phần 39, khi tôi dựng được cả hai trên cùng một nền. Tôi ghi lại chỗ này thay vì mượn số liệu của người khác — con số đo trên máy khác, cấu hình khác thì không so được với con số ở trên.
Khi nào chọn Kafka
| Bài toán | Kafka | Hàng đợi |
|---|---|---|
| Nhiều hệ thống cùng đọc một luồng | có | không |
| Cần xử lý lại từ quá khứ | có | không |
| Thông lượng rất cao | có | vừa |
| Độ trễ dưới mili giây cho từng tin | không | có |
| Định tuyến theo nội dung tin | không | có |
| Ưu tiên tin | không | có |
| Xoá một tin cụ thể | không | có |
| Giữ lịch sử để phát lại | có | không |
Bốn dòng đầu là lý do người ta chọn Kafka. Bốn dòng giữa là lý do người ta vẫn giữ RabbitMQ bên cạnh.
Rất nhiều hệ thống chạy cả hai, và đó không phải thừa: chúng giải hai bài toán khác nhau.
Thử ba mươi giây
Dựng một broker và tự kiểm đặc tính ở đầu bài:
docker run -d --name kf -e KAFKA_NODE_ID=1 \
-e KAFKA_PROCESS_ROLES=broker,controller \
-e KAFKA_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093 \
-e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kf:9092 \
-e KAFKA_CONTROLLER_QUORUM_VOTERS=1@kf:9093 \
-e KAFKA_CONTROLLER_LISTENER_NAMES=CONTROLLER \
-e KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT \
-e KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 \
apache/kafka:3.9.0
Rồi ghi vài tin và đọc bằng hai nhóm khác nhau:
docker exec kf sh -c "printf 'a\nb\nc\n' | \
/opt/kafka/bin/kafka-console-producer.sh --bootstrap-server kf:9092 --topic thu"
docker exec kf /opt/kafka/bin/kafka-console-consumer.sh --bootstrap-server kf:9092 \
--topic thu --from-beginning --timeout-ms 3000 --group nhom-a
docker exec kf /opt/kafka/bin/kafka-console-consumer.sh --bootstrap-server kf:9092 \
--topic thu --from-beginning --timeout-ms 3000 --group nhom-b
Cả hai nhóm phải đọc được đủ ba tin. Nếu bạn đến từ thế giới hàng đợi, đó là điều đầu tiên cần chỉnh lại trong đầu.
Phần sau đo việc dựng broker kỹ hơn: thời gian khởi động, bộ nhớ chiếm, và độ trễ của tin đầu tiên.