Как реализовать разделение задач между воркерами с учетом падения воркера и ребалансировки задач?
Что проверяют
Понимаете ли вы гарантии доставки и то, что «упавший» воркер в распределённой системе неотличим от медленного. Следом обычно спрашивают, как избежать двойной обработки одной задачи.
Как отвечать
Основа любого решения: задача считается выполненной после подтверждения, а не в момент выдачи. Отсюда семантика «как минимум один раз» и требование идемпотентности обработчика.
Варианты по инструментам:
- Celery:
acks_late=True, чтобы подтверждение шло после выполнения,task_reject_on_worker_lost=True, чтобы задача вернулась в очередь при гибели процесса, иworker_prefetch_multiplier=1, чтобы воркер не набирал задач впрок. С брокером Redis неподтверждённая задача вернётся послеvisibility_timeout(по умолчанию час), с RabbitMQ после разрыва соединения; - своя очередь в PostgreSQL: у задачи есть
lease_until. Воркер забирает пачку черезFOR UPDATE SKIP LOCKEDи продлевает аренду heartbeat-запросом, задачи с истёкшей арендой снова доступны другим; - Redis Streams: группы потребителей,
XPENDINGпоказывает зависшие сообщения,XAUTOCLAIMпередаёт их живому потребителю; - Kafka: единица распределения это партиция. При гибели потребителя группа перераспределяет партиции, а смещение коммитится только после обработки.
Ребалансировка здесь получается сама: задачи лежат в общей очереди, их берут оставшиеся воркеры. Если задачи привязаны к воркеру по ключу, например ради локального кеша, берут консистентное хеширование: при уходе узла переезжает только его доля ключей.
Где спотыкаются
Путают повтор при исключении с восстановлением после падения. self.retry() в Celery срабатывает, когда код бросил ошибку, а при убитом процессе задачу спасает только позднее подтверждение.
Не делают обработку идемпотентной. Воркер мог выполнить задачу и умереть до подтверждения, и повтор спишет деньги второй раз. Нужен ключ идемпотентности или уникальное ограничение в базе.
Объявляют воркер мёртвым по одному пропущенному heartbeat. После долгой паузы процесса такой «зомби» дописывает результат, когда задачу уже взял другой. Спасают токен ограждения (fencing token) и проверка аренды при записи результата.