Обычная consumer group делит партиции между своими участниками. Несколько независимых групп читают один топик каждая со своей позицией. Выбор group.id определяет, будут процессы разделять работу или независимо получать всю доступную историю.
Группа является одним логическим подписчиком
Consumer group объединяет экземпляры одного логического читателя. Участники с одинаковым group.id согласованно распределяют партиции подписанных топиков. Для обычной группы одна партиция в установившемся назначении принадлежит одному участнику этой группы. Один участник может получить несколько партиций.
Пусть два процесса с group.id=group-a выполняют одну обработку, а процесс с group.id=group-b — другую. Обе группы читают один топик, но у каждой свои commits. Повторное чтение того же факта другой группой является независимой подпиской, а не лишней попыткой первой обработки.
| Группа | Участник | Партиции в учебном назначении |
|---|---|---|
| group-a | a1 | 0, 1 |
| group-a | a2 | 2, 3 |
| group-b | n1 | 0, 1, 2, 3 |
Если добавить n1 в group-a, группа распределит журналы между тремя участниками. Тогда три участника будут разделять один поток: каждому достанется только часть партиций. Для независимого чтения всех партиций нужна отдельная группа. Поэтому группа соответствует одному назначению обработки, а имя экземпляра различает процессы внутри неё.
Партиции ограничивают параллельное чтение
У одного топика четыре партиции, все участники подписаны на него. Два участника могут получить по две. При шести участниках максимум четыре получат партиции, остальные будут без назначения для этого топика. При десяти партициях и трёх участниках некоторым достанется несколько журналов. Конкретное распределение выбирает assignor с учётом подписок и протокола.
Это предел участников с назначением, а не обещание одинаковой нагрузки. Партиции могут различаться объёмом и стоимостью событий. Если одна содержит 90% работы, равное число партиций на процесс не выровняет время обработки. Нужны измерения по партициям, а иногда изменение маршрутизации ключей.
У группы, читающей несколько топиков, считать нужно весь набор доступных партиций и подписок участников. Формула «лишние consumer всегда idle при числе больше партиций одного топика» относится к простому случаю одинаковой подписки на этот топик. Внутри приложения обработку можно распараллелить дополнительно, но порядок эффектов и безопасное продвижение commits тогда контролирует приложение.
Rebalance передаёт владение журналами
Rebalance перераспределяет партиции, когда участник добавился, потерял связь, завершился или изменился набор подписанных партиций. Координатор группы и клиенты согласуют новое назначение. Это позволяет заменить упавший процесс без ручного выбора каждой партиции.
На схеме показаны два установившихся назначения: сначала два участника, затем три. Партиция 3 переходит от a2 к a3. Другая группа group-b из предыдущего примера продолжает читать независимо; её назначение от этого не меняется.
Представь, что a2 получил записи 20…24 партиции 2, обработал до 22, а сохранил следующую позицию 21. После его отказа партиция перейдёт к a1, который восстановится с 21. Два уже применённых факта 21 и 22 могут повториться. Правило «одна партиция у одного участника» регулирует назначение чтения, а не число попыток применения события.
При передаче владения нужно завершить или отменить работу по отзываемым партициям и сохранить только действительно обработанные позиции, если протокол ещё позволяет commit. Запоздалый результат старого исполнителя может столкнуться с работой нового. Внешний приёмник должен устойчиво обрабатывать повтор и, при необходимости, отвергать устаревшего владельца.
Rebalance имеет цену: изменённые партиции временно ждут назначения, приложению может понадобиться восстановить локальное состояние. Современные протоколы и кооперативное назначение уменьшают число перемещений и остановок. Поэтому неверно считать, что любое изменение обязательно полностью останавливает всю группу на одно и то же время.
Живой процесс должен продолжать чтение
Heartbeats позволяют обнаружить потерю участника. Кроме связи, важна способность приложения продолжать цикл получения. Например, обработка пачки заняла десять минут, а max.poll.interval.ms в Java-клиенте допускает пять. Клиент может утратить назначение, хотя сам процесс продолжает работать.
max.poll.records уменьшает число записей, выдаваемых одним poll, и помогает уложить обработку пачки в допустимый интервал. Увеличение интервала может помочь медленной работе, но также продлевает обнаружение зависшего обработчика. Сначала найди задержку и измерь длительность пачек, затем меняй настройки.
Частые рестарты, долгие операции и разрывы связи создают rebalance storm, цепочку перераспределений. Lag растёт, работа повторяется, часть времени уходит на восстановление. Лечить это увеличением числа процессов вслепую опасно: новые участники сами запускают перераспределение. Полезнее проверить причины рестартов, время обработки и стабильность подключения.
Есть и чтение с явным assign, без автоматического распределения партиций группой. Тогда приложение само выбирает журналы и организует замену исполнителя. Утверждение «два consumer без группы обязательно читают всё» неверно: всё зависит от их явных назначений и начальных позиций.
Самопроверка
Сначала раздели два решения: сколько экземпляров требуется одной обработке и сколько независимых подписчиков нужно потоку. Затем проследи пример отказа a2: какой commit увидит a1, какие факты повторятся и какая часть системы защитит результат.