Bài 1

Khoá nào rơi vào partition nào

Kafka chỉ bảo đảm thứ tự bên trong một partition. Vì vậy câu hỏi “khoá của tôi rơi vào partition nào” không phải chi tiết cài đặt: nó quyết định hai bản ghi của cùng một khách hàng có được đọc theo đúng thứ tự ghi hay không. Bài này tính ra câu trả lời bằng đúng công thức producer dùng, rồi đối chiếu với 256 cặp khoá–partition đo trên Kafka 4.3.1.

Vì sao câu hỏi này quan trọng

Một topic có P partition là P hàng đợi độc lập. Consumer đọc song song từ nhiều partition, và không có gì đồng bộ giữa chúng: bản ghi ở partition 3 có thể được xử lý trước bản ghi ghi sớm hơn ở partition 0. Điều duy nhất Kafka hứa là trong một partition, thứ tự đọc bằng thứ tự ghi.

Nên nếu hai bản ghi cần giữ thứ tự với nhau — “đơn KH07 tạo”, rồi “đơn KH07 huỷ” — chúng phải nằm chung một partition. Cách duy nhất để bảo đảm điều đó là cho chúng cùng một khoá. Phần còn lại của bài là: cùng khoá thì thật sự rơi vào đâu, và khi nào lời hứa đó gãy.

Công thức thật

Khi bản ghi có khoá, producer Java tính partition bằng đúng một dòng (BuiltInPartitioner.partitionForKey):

partition = (murmur2(khoá dạng byte) & 0x7fffffff) % số_partition

Ba chi tiết đáng nhớ, vì cả ba đều là chỗ hay hiểu sai:

Ba khoá đầu của bộ đo, tính từng bước:

khoámurmur2sau khi tắt bit dấu% 3% 8
KH01134 561 646134 561 64606
KH07−1 895 882 538251 601 11026
KH321 810 218 0491 810 218 04911

Cách đo: ghi 32 khoá KH01…KH32 vào tám topic có 1, 2, 3, 4, 5, 6, 8 và 12 partition, rồi đọc lại và in kèm số hiệu partition.

kafka-console-producer.sh --topic t8 \
  --property parse.key=true --property key.separator=: < keys.txt

kafka-console-consumer.sh --topic t8 --from-beginning \
  --property print.key=true --property print.partition=true
# Partition:6	KH01
# Partition:1	KH02
Engine của lab khớp 256/256 Tám topic × 32 khoá cho 256 cặp khoá–partition. Bản viết lại bằng JavaScript trong js/kafka-partition.js cho đúng cả 256. Con số đó là lý do phòng thí nghiệm bên dưới đáng tin: nó không minh hoạ, nó tính đúng thứ Kafka tính.

Đổi số partition: bao nhiêu khoá phải sang chỗ khác

Vì số partition nằm trong phép chia lấy dư, thêm partition sẽ đẩy một phần khoá sang partition khác. Bản ghi đã ghi thì nằm nguyên chỗ cũ — Kafka không chuyển dữ liệu khi bạn --alter --partitions. Hệ quả: với những khoá đổi chỗ, bản ghi cũ ở một partition và bản ghi mới ở partition khác, và thứ tự giữa chúng không còn được bảo đảm nữa.

Đo trực tiếp: topic 3 partition, ghi 32 khoá, nâng lên 6 partition, ghi lại đúng 32 khoá đó.

kafka-topics.sh --alter --topic grow --partitions 6
# WARNING: If partitions are increased for a topic that has a key,
# the partition logic or ordering of the messages will be affected

Kết quả: 16 trên 32 khoá đổi partition — đúng một nửa, như phép chia lấy dư dự đoán khi nhân đôi số partition. Ví dụ KH03 đi từ partition 0 sang 3, KH05 từ 1 sang 4, còn KH01 và KH07 ở nguyên.

Toàn bộ ma trận, đếm trên cùng 32 khoá đó:

từ → tới1234681216
1023232730293132
2230201720242429
3232002316282331
4271723018151827
6302016180241630
8292428152402219
12312423181622027

Nhân đôi là trường hợp nhẹ nhất: 3→6 đổi 16 khoá, 4→8 đổi 15, 6→12 đổi 16. Những bước nhảy khác đắt hơn: 3→8 đổi 28 trên 32, và 1→2 đổi 23 vì khi có một partition thì mọi khoá đều ở partition 0, nên chỉ những khoá tình cờ được chia về 0 mới đứng yên.

Không có cách thêm partition mà không đổi ánh xạ Đây là hệ quả của phép chia lấy dư, không phải thiếu sót của Kafka. Nếu thứ tự theo khoá là bắt buộc, hoặc chọn số partition đủ lớn ngay từ đầu, hoặc dừng producer, để consumer đọc hết rồi mới nâng — bản ghi cũ và bản ghi mới của một khoá không được chồng lấn về thời gian.

Bản ghi không có khoá không đi vòng tròn

Niềm tin phổ biến là khoá null thì producer gửi lần lượt từng partition. Đo lại thì không phải: gửi 30 bản ghi không khoá vào một topic 6 partition, trong một phiên producer, thì cả 30 rơi vào đúng một partition.

kafka-console-producer.sh --topic nokey < 30-dong.txt

kafka-console-consumer.sh --topic nokey --from-beginning \
  --property print.partition=true | sort | uniq -c
#   30 Partition:5

Chạy lần thứ hai bằng một phiên producer khác, 30 bản ghi tiếp theo cùng rơi vào một partition — nhưng là partition 3. Đây là bộ phân vùng “dính” (sticky) mà producer dùng từ Kafka 2.4 và đưa vào làm mặc định từ 3.3: nó chọn một partition rồi gửi vào đó cho tới khi lô đầy hoặc hết linger.ms, vì gom thành lô lớn rẻ hơn rải đều.

Hệ quả cho việc đo thử Một bài kiểm tra “ghi vài chục bản ghi không khoá rồi xem chúng trải đều chưa” sẽ luôn kết luận sai. Muốn thấy rải đều thì phải ghi đủ nhiều để lô đầy nhiều lần, hoặc đặt khoá.

Nhiều partition hơn không có nghĩa là đều hơn

Hàm băm chia đều về mặt kỳ vọng, không phải về mặt thực tế trên một tập khoá cụ thể. Với đúng 32 khoá ở trên:

số partitionlý tưởng mỗi partitionthậtnhiều nhấtpartition rỗng
216,009, 23230
48,005, 10, 4, 13130
84,003, 5, 1, 8, 2, 5, 3, 580
122,671, 7, 1, 5, 1, 1, 1, 3, 3, 2, 2, 570
162,000, 3, 0, 2, 0, 4, 1, 3, 3, 2, 1, 6, 2, 1, 2, 263
241,331, 3, 0, 4, 0, 0, 1, 1, 1, 1, 1, 2, 0, 4, 1, 1, 1, 1, 0, 2, 2, 1, 1, 345

Ở 8 partition, partition đông nhất giữ 8 khoá trong khi lý tưởng là 4 — gấp đôi — và partition vắng nhất giữ 1. Ở 16 partition đã có 3 partition không nhận khoá nào, ở 24 thì 5. Thêm partition làm tăng khả năng song song tối đa, nhưng với một tập khoá hữu hạn nó cũng làm tải lệch hơn, vì mỗi partition nhận ít khoá hơn nên một khoá lẻ nặng hơn về tỉ trọng.

Đây mới chỉ là lệch theo số khoá. Trong hệ thật, số bản ghi của mỗi khoá còn khác nhau, nên một khoá nóng (một khách hàng lớn, một thiết bị gửi liên tục) sẽ dồn hết vào một partition và không có cách nào chia nhỏ nó mà vẫn giữ thứ tự theo khoá.

Phòng thí nghiệm

Gõ khoá của chính bạn, chọn số partition, rồi xem chúng rơi vào đâu. Ô thứ hai là số partition sau khi nâng: lab sẽ đánh dấu những khoá phải đổi chỗ. Thuật toán là bản viết lại của BuiltInPartitioner.partitionForKey và đã khớp 256/256 với Kafka 4.3.1.

Khoá

Các partition

Số partition

Lệch tải

    Nhớ gì

    Bài tiếp theo trả lời nửa còn lại của câu chuyện: ai đọc partition nào, và vì sao có lúc 9 partition mà chỉ 3 trong 10 consumer làm việc.