Sao chép Kafka giữa hai vùng là nền của mọi kế hoạch phục hồi thảm hoạ. Bài này dựng hai cụm thật, chạy MirrorMaker 2, và tìm ra rằng phần dễ chạy hoàn hảo còn phần khó thì im lặng không chạy.

Tốc độ sao chép dữ liệu, và offset không sang được

Dựng

Hai cụm độc lập AB, sao chép topic don từ A sang B:

clusters = A, B
A.bootstrap.servers = kA:9092
B.bootstrap.servers = kB:9092
A->B.enabled = true
A->B.topics = don
sync.group.offsets.enabled = true
connect-mirror-maker.sh mm2.props

Dữ liệu sang rất nhanh

chép hết 200.000 tin đã có 8,7 giây
độ trễ khi đang chạy 1.467 / 498 / 1.457 / 1.436 ms

Khoảng một giây rưỡi cho một tin mới (đã bao gồm khoảng 200–300 ms chạy công cụ). Trên mạng cục bộ thì đây gần như là mức sàn; giữa hai vùng thật, cộng thêm thời gian đi về của mạng.

Tên topic ở cụm đích mang tiền tố cụm nguồn:

cụm A:  don      ->     cụm B:  A.don

Tiền tố là cố ý và quan trọng: nó ngăn vòng lặp vô tận khi bạn sao chép hai chiều. Không có nó, don ở B sẽ được chép ngược về A thành don, rồi lại sang B, mãi mãi.

Đổi lại, mọi consumer ở cụm B phải biết tên có tiền tố. Đây là chỗ kế hoạch chuyển vùng hay bị hụt: ứng dụng đọc don ở A, và khi chuyển sang B nó phải đọc A.don — một thay đổi cấu hình mà ai đó phải nhớ, đúng vào lúc đang có sự cố.

Offset thì không sang

Đây là phần quan trọng nhất, và nó không hoạt động.

Nhóm gfail ở cụm A đã đọc tới:

p0 offset 16.200    p1 offset 16.900    p2 offset 16.900

Ở cụm B, sau khi bật sync.group.offsets.enabled=true và chờ:

topic A.checkpoints.internal:0:0        không có bản ghi nào
nhóm gfail:                             không tồn tại

Tôi thử thêm A->B.groups=.*, emit.checkpoints.enabled=true, sync.group.offsets.interval.seconds=5, khởi động lại MirrorMaker, và thử cả với một nhóm đang hoạt động thay vì nhóm đã dừng. Vẫn 0 bản ghi.

Trong thời gian tôi dành cho nó, tôi không tìm ra nguyên nhân, và ghi lại đúng như vậy thay vì đoán.

Có một dòng lỗi trong log nhưng nó thuộc chiều ngược lại (B->A, chiều tôi không bật):

ERROR [Worker clientId=B->A] Failed to reconfigure connector's tasks
      (MirrorCheckpointConnector), retrying after backoff.
RetriableException: Timeout while loading consumer groups.

Điều đáng nói nhất không phải là nó hỏng, mà là cách nó hỏng: topic checkpoint được tạo ra đàng hoàng, rỗng, và im lặng. Không có gì trên bảng theo dõi cho biết việc dịch offset đang không chạy.

Bài học vận hành

Kế hoạch chuyển vùng dựa vào offset đã dịch chỉ đúng nếu offset thật sự được dịch. Hãy kiểm chứ đừng cấu hình rồi tin:

kafka-get-offsets.sh --bootstrap-server kB:9092 --topic A.checkpoints.internal

Ra 0 nghĩa là khi cụm A chết, mọi consumer khởi động ở cụm B sẽ không tìm thấy offset nào và rơi về auto.offset.reset:

  • earliestxử lý lại toàn bộ lịch sử còn giữ trên topic
  • latestbỏ qua mọi thứ chưa xử lý

Cả hai đều là hỏng, và cả hai đều xảy ra trong im lặng đúng vào lúc tệ nhất.

Câu tổng kết: sao chép dữ liệu là phần dễ. Chuyển vùng có trạng thái mới là phần khó.

Bảy topic nội bộ

MM2 tạo ra khá nhiều thứ ở cả hai cụm:

cụm B:  A.don                      bản sao dữ liệu
        A.checkpoints.internal     offset đã dịch
        heartbeats                 kiểm tra đường sống
        mm2-configs.A.internal     cấu hình Connect
        mm2-offsets.A.internal     tiến độ của chính MM2
        mm2-status.A.internal      trạng thái connector

cụm A:  mm2-offset-syncs.B.internal  ánh xạ offset A <-> B

Đáng biết vì hai lý do: chúng chiếm chỗ và cần chính sách lưu trữ, và khi dọn dẹp một thiết lập MM2, xoá connector không xoá chúng.

Ba kiến trúc, chọn cái nào

Chủ động – bị động. Ghi vào A, sao chép sang B, B chỉ dùng khi A chết. Đơn giản nhất và là lựa chọn đúng cho phần lớn trường hợp. Điểm yếu là chính bài toán offset ở trên.

Chủ động – chủ động. Ghi vào cả hai, sao chép hai chiều. Tiền tố topic ngăn vòng lặp, nhưng ứng dụng phải đọc cả don lẫn B.don để thấy đủ dữ liệu. Và không có thứ tự toàn cục giữa hai cụm — hai sự kiện cùng khoá ghi ở hai nơi sẽ không có quan hệ trước-sau nào.

Cụm trải vùng. Một cụm Kafka duy nhất với broker ở nhiều vùng, dùng rack awareness để rải bản sao. Không cần MM2, không có tiền tố, offset đúng tự nhiên. Đổi lại acks=all phải chờ qua liên vùng — phần 4 đo được p50 434 ms trên mạng cục bộ, và liên vùng thì cộng thêm 50–150 ms mỗi chiều.

Kiến trúc thứ ba là kiến trúc duy nhất giải quyết được bài toán offset một cách sạch sẽ, và nó chỉ khả thi khi độ trễ giữa các vùng đủ thấp.

Thử ba mươi giây

Nếu bạn đang chạy MM2, kiểm ba con số:

# dữ liệu có sang không
kafka-get-offsets.sh --bootstrap-server kB:9092 --topic A.ten-topic

# offset có được dịch không
kafka-get-offsets.sh --bootstrap-server kB:9092 --topic A.checkpoints.internal

# nhóm consumer có xuất hiện ở cụm đích không
kafka-consumer-groups.sh --bootstrap-server kB:9092 --list | grep ten-nhom

Con số đầu tiên tăng còn hai con số sau không đổi nghĩa là bạn có bản sao dữ liệu nhưng không có kế hoạch chuyển vùng — và khác biệt đó chỉ lộ ra vào ngày cụm chính chết.

Phần sau đo quy mô cụm: bao nhiêu broker là đủ, và dấu hiệu nào nói đã đến lúc thêm.