Bỏ qua để đến nội dung
Knowledge Hub

Kafka Share Groups (Kafka Queues): nhiều worker cùng xử lý một partition Trung cấp

Thời gian đọc ước tính: 7 phút

Topic image-jobs có một partition. Bạn tăng consumer trong group từ 1 lên 8 nhưng throughput không đổi: ở consumer group thường, một partition chỉ giao cho một consumer, bảy consumer còn lại ngồi chơi. Tăng partition đổi phân phối key và thứ tự, và không thể giảm lại.

Share Groups (còn gọi Kafka Queues, KIP-932) cho nhiều consumer lấy các record khác nhau từ cùng một partition. Mỗi record được giữ bằng lock có thời hạn rồi được ack từng cái. Producer vẫn ghi vào topic Kafka bình thường.

Phiên bản Trạng thái Nguồn
4.0 Early access (chỉ cluster test, không nâng cấp lên 4.1 được) Release announcement 4.0
4.1 Preview, bật bằng kafka-features.sh (share.version=1) Upgrade notes
4.2 Production-ready Upgrade notes và release announcement

Lưu ý khi vận hành 4.2: cluster dưới 3 broker phải đặt share.coordinator.state.topic.replication.factor và share.coordinator.state.topic.min.isr về 1 trước khi dùng; docs ghi 4.2.1 sửa một deadlock nghiêm trọng trong đường share group, nên dùng bản vá mới nhất.

Repo này dùng broker cp-kafka:7.6.0 (Kafka 3.6, ZooKeeper) và KafkaJS 2.2 cho các demo cũ, nên không chạy được Share Groups; KafkaJS không có Share Consumer API. Ví dụ bên dưới là mô hình RAM.

Consumer group Share group RabbitMQ quorum queue
Đơn vị chia việc Partition Record Message
Số worker hữu ích Tối đa bằng số partition Không bị giới hạn bởi partition Nhiều consumer trên một queue
Thứ tự xử lý Theo offset trong partition Không bảo đảm Không bảo đảm khi có nhiều consumer
Ack Commit offset liên tục Từng record (ACCEPT/RELEASE/REJECT/RENEW) Từng message (ack/nack)
Giới hạn giao lại Không có group.share.delivery.count.limit, mặc định 5 delivery-limit, mặc định 20 từ 4.0
Lock hết hạn Không có share.record.lock.duration.ms, mặc định 30 giây Theo consumer ack timeout
Replay Seek offset Không phải mục tiêu Không (queue thường)
Nhiều group đọc cùng dữ liệu Có Có (retention của topic vẫn áp dụng) Cần fan-out qua exchange
Config Mặc định Ghi chú
group.share.record.lock.duration.ms (broker) / share.record.lock.duration.ms (group) 30000 Tối thiểu 1000 ở group config
group.share.delivery.count.limit (broker) 5 Quá số lần giao thì record không được giao nữa
group.share.partition.max.record.locks (broker) 2000 Trần số record đang bị lock mỗi partition
share.auto.offset.reset (group) latest Share partition mới bắt đầu từ cuối log, không phải từ đầu
share.isolation.level (group) read_uncommitted read_committed để bỏ qua transaction chưa commit
share.session.timeout.ms 45000 Phát hiện client chết
share.heartbeat.interval.ms 5000

Chú ý share.auto.offset.reset=latest: record produce trước khi share group đầu tiên subscribe sẽ không được giao; muốn xử lý backlog cũ phải đặt earliest.

Hai worker lấy record khác nhau từ cùng một partition
Hai worker lấy record khác nhau từ cùng một partitionBước 1/4: A acquire offset 7. Record này bị khóa tạm thời cho A, chưa có ack thành công.PartitionWorker AWorker Boffset 7
Partition → Worker Aoffset 7
Bước 1/4

A acquire offset 7. Record này bị khóa tạm thời cho A, chưa có ack thành công.

Kafka chọn record còn khả dụng và acquire cho một consumer bằng lock có thời hạn. Trong lúc lock còn hiệu lực, record không giao cho consumer khác; record kế tiếp của cùng partition có thể được worker khác lấy.

Với explicit acknowledgement (Java client):

Kết quả AcknowledgeType Ý nghĩa
Thành công ACCEPT Đánh dấu đã xử lý
Lỗi tạm RELEASE Thả để giao lại
Không thể xử lý REJECT Không giao lại trong group
Đang làm lâu RENEW Gia hạn lock

acknowledge() chỉ đánh dấu ý định ở client; ack được gửi qua lần poll tiếp theo hoặc commitSync()/commitAsync(). Chế độ implicit mặc định coi record của lần poll trước là thành công khi poll tiếp, tiện nhưng nguy hiểm nếu batch có record lỗi. Dùng explicit mode cho job cần phân loại lỗi.

  1. Hoàn tất đảo thứ tự. A lấy offset 7, B lấy offset 8 và xong trước. Nếu 8 phụ thuộc 7, kết quả sai. Share group không hứa thứ tự hoàn tất.
  2. Lock hết hạn khi worker còn chạy. A chậm quá 30 giây, record 7 được giao cho B, A vẫn gọi API ngoài: side effect trùng. Lock ở broker không thay idempotency; việc dài dùng RENEW.
  3. Lỗi vĩnh viễn. REJECT hoặc hết số lần giao chỉ dừng việc giao lại trong share group, không tự tạo dead-letter topic. Bạn phải tự thiết kế nơi lưu lỗi, alert và replay.
Tình huống Chọn
Đã có Kafka, job độc lập, cần nhiều worker hơn số partition, cùng dữ liệu còn được analytics đọc Share group (Kafka 4.2+)
Cần routing theo pattern, priority, TTL/DLX phong phú RabbitMQ (queue types)
Thứ tự theo key quan trọng Consumer group, không phải share group
Chưa vận hành Kafka 4.2 Giữ consumer group với đủ partition, hoặc RabbitMQ
  • Dùng: job độc lập (thumbnail, gửi email, gọi API), workload “queue trên Kafka”.
  • Không dùng: state machine theo entity, ledger cần thứ tự, bất kỳ nơi nào offset order là invariant.
  • Không dùng nếu bạn chưa nâng được cluster lên 4.2 và client lên Share Consumer API.

Mô hình RAM (không phải output từ Kafka thật): A acquire 7, B acquire 8 và accept, lock 7 hết hạn rồi B nhận lại 7 ở lần giao thứ hai; dòng cuối completion order: 8, 7. Lock trong demo là 30 ms cho chạy nhanh; mặc định thật là 30 giây.

Terminal window
npx --prefix examples ts-node examples/src/kafka-share-groups/share-group-model.ts

Để kiểm chứng thật: cluster Kafka 4.2+ và Java KafkaShareConsumer, topic một partition, hai consumer cùng group.id với share.acknowledgement.mode=explicit, dừng một worker sau acquire và trước ack để quan sát giao lại.

Khi share group hợp

  • Nhiều worker trên một partition, không bị giới hạn bởi partition count.
  • Ack và retry ở mức record, phân loại lỗi từng job.
  • Vẫn là topic Kafka: group khác đọc độc lập.

Đánh đổi

  • Mất bảo đảm thứ tự hoàn tất.
  • Lock hết hạn, ack chưa commit và retry vẫn gây xử lý trùng.
  • Cần broker và client 4.2+; thêm việc vận hành delivery attempts, lỗi, retention.
  1. Chỉ dùng cho job độc lập; xác nhận không có invariant về thứ tự.
  2. Mọi job có jobId ổn định và ghi kết quả idempotent.
  3. Dùng explicit acknowledgement; ACCEPT chỉ sau khi kết quả bền.
  4. Đặt lock duration lớn hơn thời gian xử lý p99, dùng RENEW cho việc dài.
  5. Tự thiết kế nơi lưu lỗi cho REJECT và job hết lượt giao (mặc định 5).
  6. Đặt share.auto.offset.reset có chủ đích (earliest nếu cần xử lý backlog).
  7. Theo dõi tuổi record so với retention của topic.

Tự kiểm tra: (1) Một partition và tám worker: consumer group dùng được mấy, share group thì sao? (2) Offset 8 có thể hoàn tất trước 7 không? (3) Vì sao worker A còn chạy sau khi lock hết hạn có thể gây trùng side effect?