"Kafka có độ trễ cao" là câu hay được nói. Bài này đo thật, tách ra từng chặng, và tìm ra thứ gây độ trễ — nó không phải Kafka.
Cách đo
Gửi từng tin một, mỗi tin mang dấu thời gian nano giây trong thân, rồi đo hai mốc:
- ack — từ lúc
send()đến lúc callback của producer chạy. Chặng producer → broker. - e2e — từ lúc
send()đến lúc consumer nhận được. Cả đường.
Hiệu của hai số là chặng broker → consumer.
Gửi từng tin một là cố ý: nó là kịch bản tệ nhất cho gộp lô, và cũng là kịch bản của phần lớn ứng dụng phản hồi sự kiện thật.
Nền
Một broker, tin 200 byte, 2.000 lần:
| Trung bình | p50 | p99 | |
|---|---|---|---|
| producer → broker | 0,21 ms | 0,17 | 0,53 |
| producer → consumer | 0,35 ms | 0,29 | 1,08 |
| chặng consumer | 0,14 ms |
Dưới một phần ba mili giây, không chỉnh gì cả.
Kafka mặc định không chậm. Nếu hệ thống của bạn có độ trễ Kafka tính bằng trăm mili giây, gần như chắc chắn đó là cấu hình chứ không phải Kafka.
Hai bộ hẹn giờ
Hai tham số được khuyên vặn nhiều nhất trong các bài "tối ưu Kafka" đều là bộ hẹn giờ chờ dữ liệu:
| ack | e2e | So với nền | |
|---|---|---|---|
| mặc định | 0,21 ms | 0,35 ms | 1× |
linger.ms=50 |
54,10 ms | 55,53 ms | 159× |
fetch.min.bytes=65536 |
1,47 ms | 503,68 ms | 1.439× |
fetch.min.bytes=65536 + fetch.max.wait.ms=100 |
1,00 ms | 102,00 ms | 291× |
Hai con số nằm ở hai chặng khác nhau, và việc tách chặng ra làm rõ ngay chuyện gì đang xảy ra.
linger.ms=50 nằm hết ở chặng producer. ack đã là 54,10 ms — producer ngồi đợi 50 ms xem có tin nào nữa để gộp không. Không có, nên nó đợi đủ rồi mới gửi.
fetch.min.bytes=65536 nằm hết ở chặng consumer. ack vẫn 1,47 ms — tin đã nằm trên broker gần như ngay lập tức. Nhưng broker giữ lại phản hồi cho consumer tới khi gom đủ 64 KB, hoặc tới khi hết fetch.max.wait.ms. Mặc định của nó là 500 ms, và 503,68 ms đo được chính là con số đó.
Hạ fetch.max.wait.ms xuống 100 thì e2e xuống 102 ms — khớp chính xác. Không có gì bí ẩn, chỉ là một bộ hẹn giờ.
Điểm chung của cả hai: chúng chờ dữ liệu không bao giờ đến, vì tôi đang gửi từng tin một. Cả hai đều là tham số dành cho tải cao, và ở tải cao chúng gần như miễn phí — phần 5 đã đo linger.ms ở tải cao và chênh lệch chỉ 4%.
Nguy hiểm nằm ở chỗ chúng được đặt lúc tải cao rồi ở lại đó khi tải xuống thấp. Đêm khuya, ít giao dịch, và độ trễ tăng gấp nghìn lần — đúng lúc không ai nhìn bảng số.
Hai thứ hay bị đổ lỗi thì gần như không đáng kể
| ack | e2e | ||
|---|---|---|---|
| mặc định | 0,21 ms | 0,35 ms | |
acks=all |
0,22 ms | 0,36 ms | +3% |
compression.type=zstd |
0,24 ms | 0,39 ms | +11% |
acks=all không tốn gì ở đây — nhưng đó là vì replication-factor=1, không có bản sao nào để chờ. Phần 4 đã đo trên cụm ba broker và ở đó thông lượng còn một nửa. Con số +3% này chỉ đúng cho cụm một node, và tôi để nó ở đây để nhắc lại chính cái bẫy đó.
compression.type=zstd thêm 0,04 ms. Với tin 200 byte thì nén gần như không có gì để làm.
Ghi chú về phép đo: công cụ có sẵn không dùng được
Tôi bắt đầu bằng kafka-e2e-latency.sh — công cụ chính thức, nhận một tệp cấu hình. Chạy sáu cấu hình khác nhau, kể cả fetch.min.bytes=65536, và cả sáu đều ra 0,29 ms.
Sáu kết quả giống hệt nhau là dấu hiệu tệp cấu hình không có tác dụng. Công cụ này ghi đè fetch.min.bytes thành 1 sau khi nạp tệp — nó cố ý làm vậy để đo đúng độ trễ tối thiểu, và điều đó khiến nó không dùng được cho phép đo tôi cần.
Chỉ khi tự viết bộ đo, con số 503 ms mới hiện ra.
Bài học chung: kết quả giống nhau qua nhiều cấu hình khác nhau không phải phát hiện, nó là dấu hiệu phép đo hỏng. Trước khi kết luận "tham số này không ảnh hưởng gì", hãy kiểm rằng nó thật sự được áp dụng — đặt một giá trị vô lý và xem có gì thay đổi không.
Đặt thế nào
Cho hệ thống cần độ trễ thấp:
# producer
linger.ms=0
batch.size=16384 # vẫn giữ — nó không phải bộ hẹn giờ
# consumer
fetch.min.bytes=1 # mặc định
fetch.max.wait.ms=500 # không quan trọng khi fetch.min.bytes=1
max.poll.records=100
Điểm dễ nhầm: batch.size không phải bộ hẹn giờ. Với linger.ms=0, producer gửi ngay khi có thể; batch.size chỉ là trần của lô. Đặt nó lớn không làm tăng độ trễ — phần 5 đã đo, batch.size=262144 cho p50 là 1 ms.
Cho hệ thống cần thông lượng:
linger.ms=5..20
batch.size=65536
fetch.min.bytes=16384
fetch.max.wait.ms=100 # trần độ trễ khi tải xuống thấp
fetch.max.wait.ms ở đây là lưới an toàn: nó đặt trần cho độ trễ khi lưu lượng giảm. Để mặc định 500 ms là chấp nhận nửa giây độ trễ vào giờ thấp điểm.
Cái gì thật sự gây độ trễ trong hệ thống thật
Bốn con số trên đo Kafka trong một mạng cục bộ. Trong hệ thống thật, phần lớn độ trễ đầu-cuối không nằm ở Kafka:
- Consumer xử lý chậm và lag dồn lại. Đây là nguyên nhân số một, và nó không hiện ra trong bất kỳ phép đo độ trễ nào của Kafka.
- Cân bằng lại nhóm — phần 9 đo được 3 giây cho kiểu hợp tác.
- Mạng giữa các vùng, thường 50–150 ms mỗi chiều.
acks=alltrên cụm nhiều broker, phần 4 đo p50 434 ms ở tải cao.
Trước khi vặn linger.ms, hãy nhìn lag của consumer. Nếu lag đang tăng thì độ trễ của bạn là do xử lý không kịp, và không tham số producer nào chữa được.
Thử ba mươi giây
kafka-e2e-latency.sh kf:9092 thu 10000 1 200
Nếu con số ra dưới 1 ms — như 0,31 ms tôi đo được — thì Kafka không phải nguồn độ trễ của bạn. Bước tiếp theo là so nó với độ trễ đầu-cuối mà ứng dụng thật sự thấy; phần chênh là chỗ đáng đi tìm.
Phần sau chuyển sang lưu trữ: dữ liệu ở lại bao lâu, và điều gì xảy ra khi hết hạn.