Что такое backpressure и как его реализовать?
- Что делать, если производитель быстрее потребителя?
- Почему неограниченная очередь опасна?
- Как работает backpressure в Kafka, gRPC и реактивных потоках?
Backpressure — механизм, которым медленный потребитель заставляет источник замедлиться, вместо того чтобы копить работу без предела.
- Без него очереди растут: память до OOM, задержка каждого элемента — время в очереди
- Способы: ограниченный буфер с блокировкой, запрос по требованию (pull, request(n)), окно кредитов, явный отказ
- Встроено в TCP (окно получателя), gRPC/HTTP/2 (flow control), Reactive Streams, Kafka (consumer сам тянет)
А ещё Если источник не может замедлиться (пользователи) — остаётся отказ: rate limiting и load shedding
Подробно
Система из нескольких звеньев работает со скоростью самого медленного. Если быстрое звено продолжает толкать данные, излишек должен где-то лежать — в очереди. Неограниченная очередь:
- растёт в памяти, пока процесс не упадёт;
- увеличивает задержку: каждый новый элемент ждёт всех предыдущих, и к моменту обработки результат может быть уже не нужен (клиент ушёл по таймауту — работа впустую);
- прячет проблему до момента, когда она становится аварией.
Способы сказать «помедленнее»:
- ограниченный буфер с блокировкой —
putв полную очередь ждёт (BlockingQueue, буферизованный канал Go,CallerRunsPolicyв пуле потоков). Замедление распространяется вверх по цепочке; - pull вместо push — потребитель сам запрашивает следующую порцию: Kafka consumer делает
poll, Reactive Streams —request(n), итераторы и генераторы. Источник физически не может отдать больше, чем просили; - кредиты или окно — получатель выдаёт разрешение на N элементов (окно TCP, flow control HTTP/2 и gRPC, prefetch в RabbitMQ);
- явный отказ — 429/503,
nack, и источник повторяет позже с отсрочкой; - сброс лишнего — для данных, где потеря допустима: метрики, позиции на карте, кадры видео — оставить только последнее значение.
Где ломается. Backpressure работает, только если все звенья его поддерживают. Типичные разрывы:
- асинхронный HTTP-клиент без ограничения числа одновременных запросов;
LinkedBlockingQueueбез ёмкости в пуле потоков;Flux.createилиsubscribeс неограниченным буфером,onBackpressureBufferбез размера;- WebSocket-рассылка медленному клиенту — буфер на сервере растёт;
- Kafka как буфер: лаг потребителей растёт, это не авария, но нужен алерт и масштабирование потребителей.
Внешний мир (пользователи) замедлить нельзя — для него на входе ставят rate limiting и load shedding, а внутри системы — backpressure.
Пример
// Пул с ограниченной очередью: при заполнении задачу выполняет вызывающий поток,
// и источник естественно замедляется.
var pool = new ThreadPoolExecutor(8, 8, 0, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1000), new ThreadPoolExecutor.CallerRunsPolicy());
// Reactor: обрабатываем не больше 16 элементов одновременно, остальные запрашиваются по мере готовности
flux.flatMap(this::callService, 16)Что хотят услышать
- Определение: сигнал от потребителя к источнику замедлиться.
- Почему неограниченные очереди опасны.
- Конкретные механизмы: ограниченный буфер, pull, кредиты, отказ.
- Примеры в протоколах и библиотеках, разрывы цепочки.
Частые ошибки
- Добавлять «очередь побольше» как решение перегрузки.
- Считать брокер сообщений бесконечным буфером.
Проверьте себя
Один верный вариант. Подумайте, потом откройте ответ.
Показать варианты и ответ
- ✗Копить все запросы в неограниченной очереди, чтобы ничего не потерять
- ✓Медленный потребитель тормозит источник: ограниченный буфер или отказ
- ✗Повторять запросы при ошибке с паузой
- ✗TCP не имеет встроенного механизма backpressure
Что спросят дальше
Связанные вопросы
Разбор собеседования с sotto
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.