Очереди задач и балансировка
1) Зачем очереди задач
Очередь задач (job queue/work queue) разъединяет производителей и исполнителей по времени и скорости:- Сглаживает пики: буфер между фронтом и тяжелыми подсистемами.
- Стабилизирует SLA: приоритеты и изоляция классов нагрузки.
- Упрощает отказоустойчивость: ретраи, DLQ, повторная постановка.
- Масштабируется горизонтально: добавляйте воркеры без смены API.
Типовые домены: обработка платежей, нотификации, генерация отчетов/медиа, ETL/ML-постпроцессинг, интеграции с внешними API.
2) Модель и основные понятия
Продюсер: публикует задачу (payload + метаданные: idempotency key, приоритет, дедлайн).
Очередь/топик: буфер/лог задач.
Воркер: берет задачу, обрабатывает, подтверждает (ack) или возвращает с ошибкой.
Visibility Timeout/Lease: «аренда» задачи на время обработки, после — авто-редоставка.
DLQ (Dead Letter Queue): «захоронение» задач после лимита попыток/фатальных ошибок.
Rate Limit/Concurrency: ограничения на потребление per-воркер/пер-очередь/пер-тенант.
- Pull: воркер сам запрашивает задачу (дозирует нагрузку).
- Push: брокер пушит; нужна защита от «заливки» слабых воркеров.
3) Семантики доставки и подтверждений
At-most-once: без ретраев; быстрее, но возможна потеря.
At-least-once (дефолт для большинства очередей): возможны дубликаты → требуется идемпотентность обработчика.
Effectively exactly-once: достигается на уровне приложения (идемпотентность, дедуп-стор, транзакции/аутбокс). Брокер может помочь, но не «волшебная пуля».
- Ack/Nack: явный результат.
- Requeue/Retry: с backoff + jitter.
- Poison message: отправка в DLQ.
4) Балансировка и планирование
4.1 Очередность и алгоритмы
FIFO: просто и прогнозируемо.
Priority Queue: приоритетные классы (P0…P3).
WRR/WSR (Weighted Round-Robin/Random): доли CPU/черезput между классами.
WFQ/DRR (аналог «справедливых» очередей в сетях): доли per-тенант/клиент.
Deadline/EDF: для задач с дедлайнами.
Fair Share: ограничение «шумных соседей» (per-tenant quotas).
4.2 Потоки обработки
Single-flight/Coalescing: объединяйте дубликаты задачи по ключу.
Concurrency caps: жесткие лимиты на параллелизм по типам задач/интеграциям (внешние API).
4.3 Гео и шардирование
Шарды по ключу (tenant/id) → локальность данных, стабильный порядок в пределах шардов.
Sticky кэшам/ресурсам: хэш-роутинг на воркеры с «прикрепленным» состоянием.
5) Ретраи, backoff и DLQ
Экспоненциальный backoff + jitter: `base 2^attempt ± random`.
Максимум попыток и общий дедлайн (time-to-die) на задачу.
Классификация ошибок: `retryable` (сеть/лимит), `non-retryable` (валидация/бизнес-запрет).
Parking/Delay Queue: отложенные задачи (например, повторить через 15 мин).
DLQ-политика: обязательно укажите, куда и на каких условиях попадает «ядовитое» сообщение; предусмотрите reprocessor.
6) Идемпотентность и дедупликация
Idempotency-Key в задаче; стор (Redis/DB) с TTL для последних N ключей:- seen → skip/merge/result-cache.
- Natural keys: используйте `order_id / payment_id` вместо случайных UUID.
- Outbox: запись факта задачи и ее статуса в одной БД-транзакции с бизнес-операцией.
- Exactly-once в синке: `UPSERT` по ключу, проверка версий, «at-least-once» в очереди + идемпотентность в БД.
7) Мульти-тенантность и классы SLA
Разделяйте очереди/стримы по классам: `critical`, `standard`, `bulk`.
Квоты и приоритеты per-тенант (Gold/Silver/Bronze).
Изоляция: dedicate-пулы воркеров под P0; фоновые — в отдельном кластере/ноды.
Admission control: не принимать больше, чем можете обработать в дедлайны.
8) Автоскейлинг воркеров
Метрики для скейлинга: queue depth, arrival rate, processing time, SLA-дедлайны.
KEDA/Horizontal Pod Autoscaler: триггеры по глубине SQS/Rabbit/Kafka lag.
Сдерживающие факторы: внешние API rate limits, база данных (не разрушить бэкэнд).
9) Технологические варианты и паттерны
9.1 RabbitMQ/AMQP
Exchanges: direct/topic/fanout; Queues с ack/ttl/DLQ (dead-letter exchange).
Prefetch (QoS) регулирует «сколько задач на воркере».
ini x-dead-letter-exchange=dlx x-dead-letter-routing-key=jobs.failed x-message-ttl=60000
9.2 SQS (и аналоги)
Visibility Timeout, DelaySeconds, RedrivePolicy (DLQ).
Идемпотентность — на приложении (дедуп-таблица).
Лимиты: батчи 1–10 сообщений; ориентируйтесь на идемпотентные синки.
9.3 Kafka/NATS JetStream
Для масштабных пайплайнов: высокая пропускная, ретеншн/реплей.
Task-очередь поверх логов: одна задача = одно сообщение; контроль «один воркер на ключ» через партиционирование/subject.
Ретраи: отдельные топики/subject-суффиксы с backoff.
9.4 Redis-очереди (Sidekiq/Resque/Bull/Celery-Redis)
Очень низкая латентность; следите за устойчивостью (RDB/AOF), ключами retry и lock-keys для single-flight.
Подходит для «легких» задач, не для долговременного ретеншна.
9.5 Фреймворки
Celery (Python), Sidekiq (Ruby), RQ/BullMQ (Node), Huey/Resque — готовые ретраи, расписания, middleware, метрики.
10) Схемы маршрутизации и балансировки
Round-Robin: равномерно, но не учитывает «тяжесть» задач.
Weighted RR: распределение по мощностям воркеров/пулу.
Fair/Backpressure-aware: воркер забирает новую задачу только при готовности.
Priority lanes: отдельные очереди на класс; воркеры читают в порядке [P0→…→Pn] при наличии.
Hash-routing: `hash(key) % shards` — для stateful/кэшируемой обработки.
11) Таймауты, дедлайны и SLA
Per-task timeout: внутренняя «аренда» работы (в коде воркера) ≤ Visibility Timeout брокера.
Global deadline: задача не имеет смысла после T времени — NACK→DLQ.
Budget-aware: сокращайте работу (brownout) при приближении дедлайна (частичные результаты).
12) Наблюдаемость и управление
12.1 Метрики
`queue_depth`, `arrival_rate`, `service_rate`, `lag` (Kafka), `invisible_messages` (SQS).
`success/failed/retired_total`, `retry_attempts`, `dlq_in_total`, `processing_time_ms{p50,p95,p99}`.
`idempotency_hit_rate`, `dedup_drops_total`, `poison_total`.
12.2 Логи/трейсинг
Корреляция: `job_id`, `correlation_id`, ключ дедупликации.
Отмечайте `retry/backoff/dlq` как события; линковка со span исходного запроса.
12.3 Дашборды/алерты
Триггеры: глубина > X, p99 > SLO, рост DLQ, «залипшие» задачи (visibility истек > N), «горячие» ключи.
13) Безопасность и соответствие
Изоляция арендаторов: отдельные очереди/ключ-пространства, ACL, квоты.
Шифрование на транспорте и/или «в покое».
PII-минимизация в payload; хэш/ID вместо сырой PII.
Секреты: не класть токены в тело задач, использовать vault/refs.
14) Анти-паттерны
Ретраи без идемпотентности → дубли операций/деньги «дважды».
Одна гигантская очередь «на все» → нет изоляции, непредсказуемые задержки.
Бесконечные ретраи без DLQ → вечные «ядовитые» задачи.
Visibility Timeout < время обработки → каскадные дубликаты.
Большие payload в очереди → давит сеть/память; лучше хранить в объектном сторе и передавать ссылку.
Пуш-модель без backpressure → воркеры захлебываются.
Смешивание критичных и bulk-задач в одном пуле воркеров.
15) Чек-лист внедрения
- Классифицируйте задачи по SLA (P0/P1/P2) и объему.
- Выберите брокер/фреймворк с нужной семантикой и ретеншном.
- Спроектируйте ключи, приоритеты и маршрутизацию (hash/shards/priority lanes).
- Включите ретраи с backoff + jitter и DLQ-политику.
- Реализуйте идемпотентность (ключи, upsert, дедуп-стор с TTL).
- Настройте таймауты: per-task, visibility, общий дедлайн.
- Ограничьте concurrency и rate по интеграциям/тенантам.
- Автоскейлинг по глубине/лагу с предохранителями.
- Метрики/трейсинг/алерты; runbooks на «шторм» и переполнение DLQ.
- Тесты на фейлы: падение воркера, «ядовитое» сообщение, перегрузка, долгие задачи.
16) Примеры конфигураций и кода
16.1 Celery (Redis/Rabbit) — базовый флоу
python app = Celery("jobs", broker="amqp://...", backend="redis://...")
app.conf.task_acks_late = True # ack после выполнения app.conf.broker_transport_options = {"visibility_timeout": 3600}
app.conf.task_default_retry_delay = 5 app.conf.task_time_limit = 300 # hard timeout
@app.task(bind=True, autoretry_for=(Exception,), retry_backoff=True, retry_jitter=True, max_retries=6)
def process_order(self, order_id):
if seen(order_id): return "ok" # идемпотентность do_work(order_id)
mark_seen(order_id)
return "ok"
16.2 RabbitMQ — DLQ/TTL
ini x-dead-letter-exchange=dlx x-dead-letter-routing-key=jobs.dlq x-message-ttl=600000 # 10 минут x-max-priority=10
16.3 Kafka — ретраи по уровням
orders -> orders.retry.5s -> orders.retry.1m -> orders.dlq
(Перекладывайте с отложенной доставкой через scheduler/cron-consumer.)
16.4 NATS JetStream — consumer с backoff
bash nats consumer add JOBS WORKERS --filter "jobs.email" \
--deliver pull --ack explicit --max-deliver 6 \
--backoff "1s,5s,30s,2m,5m"
17) FAQ
Q: Когда выбирать push против pull?
A: Pull дает естественный backpressure и «честную» балансировку; push проще на низких скоростях и когда нужен минимальный TTFB, но требует ограничителей.
Q: Как избежать «горячего» ключа?
A: Шардируйте по составному ключу (`order_id%N`), буферизуйте и делайте batch-обработку, вводите лимиты per-ключ.
Q: Можно ли «exactly-once»?
A: Практически — через идемпотентность и транзакционный аутбокс. Полностью «математическое» exactly-once на всем пути редко достижимо и дорого.
Q: Где хранить большие вложения задачи?
A: В объектном хранилище (S3/GCS), а в задаче — ссылку/ID; снижает давление на брокер и сеть.
Q: Как выбирать TTL/visibility?
A: Visibility ≥ p99 времени обработки × запас 2–3×. TTL задачи — меньше бизнес-дедлайна.
18) Итоги
Сильная система очередей — это баланс между семантикой доставки, приоритетами и ограничителями. Проектируйте ключи и маршрутизацию, обеспечьте идемпотентность, ретраи с backoff и DLQ, распределяйте ресурсы по классам SLA и следите за метриками. Тогда ваши фоновые процессы будут предсказуемыми, устойчивыми и масштабируемыми — без сюрпризов под пиками.