Một vụ kẹt xe đo bằng "số xe xếp hàng" gần như vô dụng với người lái. Hai nghìn xe trên một cao tốc thoáng là chuyện vài phút; hai nghìn xe trên một con phố tắc là chuyện nửa ngày. Thứ tài xế thật sự cần biết là tôi sẽ trễ bao lâu — và con số đó chỉ hiện ra khi bạn lấy số xe chia cho tốc độ dòng chảy. Consumer lag của Kafka đúng là vụ kẹt xe đó, và cả bài này xoay quanh việc chọn đúng đơn vị để đo nó.
Lag là chỉ số quan trọng nhất của một hệ thống Kafka, và cũng là chỉ số bị đặt cảnh báo sai nhiều nhất. Bài này đo cùng một trạng thái theo hai cách và cho thấy vì sao cách phổ biến gần như vô dụng.
Cùng một trạng thái, hai con số
Một nhóm consumer đã ngừng đọc, đo được:
| Partition | Lag theo tin | Lag theo thời gian |
|---|---|---|
| p0 | 511.277 | 102,6 giây |
| p1 | 511.386 | 102,6 giây |
| p2 | 511.319 | 102,6 giây |
| p3 | 518.058 | 102,6 giây |
Tổng lag theo tin là 2.052.040. Nghe rất tệ, nhưng không hành động được: hai triệu tin là nhiều hay ít? Bao lâu nữa thì đuổi kịp? Dữ liệu hạ nguồn đang cũ cỡ nào?
102,6 giây trả lời tất cả những câu đó ngay lập tức: dữ liệu hạ nguồn đang cũ hơn hai phút.
Sự khác biệt trở nên rõ hơn khi so hai topic:
100.000 tin lag ở tốc độ 20.000 tin/giây = 5 giây
100.000 tin lag ở tốc độ 10 tin/giây = 2,8 GIỜ
Cùng một con số, chênh nhau hai nghìn lần về ý nghĩa. Đây là lý do một ngưỡng cố định theo số tin không dùng chung được cho hai topic khác nhau — và trong một cụm thật thì có hàng chục topic với tốc độ khác nhau hàng nghìn lần.
Cách tính lag thời gian
Ba bước, và không cần công cụ đặc biệt nào:
- Lấy
CURRENT-OFFSETcủa partition từkafka-consumer-groups --describe. - Đọc đúng bản ghi tại offset đó và lấy dấu thời gian của nó.
lag thời gian = bây giờ − dấu thời gian đó.
kafka-consumer-groups.sh --bootstrap-server kf:9092 --describe --group nhom \
| awk 'NR>1 && $6 ~ /^[0-9]+$/ {print $3, $4}' \
| while read P CUR; do
T=$(kafka-console-consumer.sh --bootstrap-server kf:9092 --topic ten-topic \
--partition $P --offset $CUR --max-messages 1 \
--property print.timestamp=true --timeout-ms 6000 2>/dev/null \
| grep -oE "CreateTime:[0-9]+" | cut -d: -f2)
echo "p$P tre $(( ($(date +%s%3N) - T) / 1000 )) giay"
done
Cách này đúng cả khi lưu lượng thay đổi, và — quan trọng hơn — đúng cả trên topic có giao dịch, nơi hiệu số offset không bao giờ về 0.
Nếu bạn dùng Burrow hoặc Kafka Lag Exporter, chúng đã tính sẵn con số này. Nếu bạn tự dựng bảng theo dõi, đây là thứ đáng dựng.
Bốn cách tính lag sai
1. Ngưỡng cố định theo số tin.
Cùng một ngưỡng lag > 100.000 áp cho topic 20.000 tin/giây và topic 10 tin/giây sẽ hoặc kêu suốt ngày ở cái đầu, hoặc không bao giờ kêu ở cái sau. Không có con số nào đúng cho cả hai.
2. Cảnh báo ở lag > 0 trên topic có giao dịch.
Phần 26 đã đo: giao dịch cỡ 100 để lại 1.000 bản ghi điều khiển chiếm offset mà không consumer nào đọc được. Consumer đã đọc hết mọi thứ vẫn hiện lag = 1.000, mãi mãi. Cảnh báo ở > 0 sẽ kêu liên tục cho tới khi có người tắt nó đi — và khi có sự cố thật thì không còn ai nhìn nữa.
3. Cộng lag của mọi partition rồi cảnh báo trên tổng.
Một partition kẹt hoàn toàn bị chín partition khoẻ pha loãng. Với topic 10 partition, một partition không có consumer nào đọc chỉ đẩy tổng lag lên 10% — dưới mọi ngưỡng hợp lý — trong khi một phần mười dữ liệu của bạn đang dừng.
Cảnh báo phải chạy trên lag lớn nhất của một partition, không trên tổng.
4. Đếm lag qua một danh sách hiển thị bị giới hạn.
Công cụ và giao diện thường cắt ở 50 hay 100 dòng. Đếm qua danh sách đó thì con số trần ở 50 — càng kẹt nhiều càng sai, đúng vào lúc cần con số đúng nhất. Luôn tính bằng truy vấn, không tính qua thứ đã hiển thị.
Cảnh báo đúng: theo xu hướng
Ngưỡng theo mức đòi bạn biết trước "bao nhiêu là nhiều", và với hàng chục topic khác nhau thì không ai biết.
Xu hướng thì không cần biết:
lag tăng liên tục trong 10 phút -> cảnh báo
Nó bắt được mọi tốc độ. Consumer khoẻ có lag dao động quanh một mức; consumer không theo kịp có lag tăng đơn điệu. Đó là dấu hiệu rõ ràng và không phụ thuộc quy mô.
Kết hợp với một ngưỡng thời gian cứng cho những đường ống có yêu cầu rõ ràng:
lag thời gian > 5 phút trên topic don-hang -> cảnh báo
Con số 5 phút này đến từ nghiệp vụ, không từ kỹ thuật — và đó là điều khiến nó đúng.
Lag âm, và lag của nhóm không có ai
Hai tình huống hay gây bối rối trong --describe:
CONSUMER-ID là - nghĩa là partition đó không có consumer nào đang giữ. Lag vẫn được tính (từ offset đã chốt lần cuối tới cuối log), nhưng không ai đang xử lý. Đây là tình huống nghiêm trọng hơn lag cao, và nó không hiện ra trong bất kỳ con số lag nào.
Đếm nó riêng:
kafka-consumer-groups.sh --bootstrap-server kf:9092 --describe --group nhom \
| awk 'NR>1 && $7=="-" {n++} END {print n+0, "partition khong co consumer"}'
Lag hiện rỗng hoặc âm xảy ra khi nhóm chưa bao giờ chốt offset cho partition đó. Không phải lỗi — chỉ là chưa có gì để so.
Lag không phải lúc nào cũng là lỗi của consumer
Ba nguyên nhân, và chỉ nguyên nhân đầu tiên là "consumer chậm":
Consumer xử lý chậm. Chữa bằng cách tăng số consumer — nhưng chỉ tới trần là số partition (phần 11). Hết partition thì phải tối ưu chính việc xử lý.
Producer tăng đột ngột. Lag tăng trong khi consumer vẫn chạy đúng tốc độ cũ. Đồ thị MessagesInPerSec của broker phân biệt được hai trường hợp này ngay.
Nhóm đang cân bằng lại. Phần 9 đo được 3 giây cho kiểu hợp tác, và phần 27 đo được 44 giây cho một ứng dụng Streams khởi động lại nhanh. Trong khoảng đó không ai đọc gì và lag tăng — rồi tự hết. Cảnh báo có độ trễ 5–10 phút sẽ không kêu vì chuyện này.
Nếu bảng theo dõi của bạn chỉ có lag mà không có tốc độ ghi, bạn không phân biệt được ba trường hợp trên, và mọi sự cố lag đều bắt đầu bằng việc đoán.
Cùng một định luật "tồn đọng chỉ có nghĩa khi chia cho tốc độ", đổi ngăn xếp
Chuyện "số lượng tồn đọng là vô nghĩa nếu không biết tốc độ rút" không phải đặc sản Kafka. Nó là một định luật — đúng nghĩa đen, định luật Little: thời gian một phần tử nằm trong hàng bằng độ dài hàng chia cho thông lượng. Độ dài hàng một mình không bao giờ là một khoảng thời gian.
Trong thế giới cơ sở dữ liệu, đây đúng là cách đo replication lag cho đàng hoàng: khoảng cách LSN tính bằng byte gần như vô dụng (replica tụt "200 MB" là bao lâu?), còn now() - pg_last_xact_replay_timestamp() cho ngay số giây tụt lại — con số hành động được, y như 102,6 giây ở trên. Cùng một trạng thái, một đơn vị nói được, một đơn vị thì không.
Trong thế giới Java, độ sâu của một BlockingQueue hay hàng đợi thread pool cũng vậy: "10.000 tác vụ đang chờ" chẳng nói gì cho tới khi bạn chia cho tốc độ xử lý để ra thời gian chờ. Và khi đường cơ sở mỗi hàng một khác, cảnh báo đúng là theo đạo hàm — hàng có đang dài ra không — chứ không theo một ngưỡng tuyệt đối mà không giá trị nào đúng cho mọi hàng.
Mẫu số chung: một tồn đọng đếm bằng số lượng là vô nghĩa nếu thiếu tốc độ rút; đơn vị hành động được luôn là thời gian bị tụt lại — đúng bằng số lượng chia cho thông lượng theo định luật Little — chứ không phải số phần tử; và khi mỗi luồng có đường cơ sở chênh nhau hàng nghìn lần, bạn phải cảnh báo theo xu hướng tăng chứ không theo một ngưỡng tuyệt đối mà không con số nào đúng cho tất cả. Nên câu hỏi đúng khi nhìn bất kỳ hàng đợi hay độ trễ sao chép nào không phải "tồn bao nhiêu phần tử" mà là "quy ra bao nhiêu thời gian, và nó đang dài ra hay co lại" — đó là hai con số duy nhất hành động được.
Muốn tìm partition tệ nhất của bạn, không phải tổng, chạy thử:
kafka-consumer-groups.sh --bootstrap-server kf:9092 --all-groups --describe 2>/dev/null \
| awk 'NR>1 && $6 ~ /^[0-9]+$/ {print $6, $1, $2, $3, $7}' \
| sort -rn | head -10
Mười dòng đầu là mười chỗ đau nhất trong cụm. Nếu cột cuối là -, partition đó không có ai đọc — và đó là dòng đáng xem trước mọi dòng khác.
Phần sau đo bảo mật: TLS, SASL và cái giá thật của việc bật chúng.