Очереди в программировании. Просто о сложном

Очереди в программировании. Просто о сложном

Распространенная ошибка начинающих разработчиков - это избыточная функциональность, выполняющаяся за один запрос. Бывает, что за единичный запрос разработчик пытается выполнить: создание записи в бд, загрузку видео, создание превью, и отправку уведомления по почте. Звучит страшно, но на практике бывает часто. Потому, сегодня, моей целью будет открыть для вас ещё одну продвинутую технологии, именуемой "очередью".

Представьте, что вы владелец сайта фотогалереи. И каждый пользователь может загрузить несколько картинок за один раз. Задача скрипта состоит в том, чтобы обработать все картинки, загруженные пользователем, и создать превью маленького размера для каждой из них.

Если, при загрузке, пользователь выберет одно, или два изображения, то он не будет ждать слишком долгого окончания загрузки и ответ сервера будет достаточно комфортным. Но, представьте, что случится, если пользователь попытается загрузить целую папку из 10, а то и 30 картинок. То мало того, что он будет ждать, пока все превьюхи сгенерируются, так ещё, и при единственной ошибке, загружать всё придётся по-новому.

Браузер безпроблемно поддерживает многофайловую загрузку. Это удобно, и существенно повышает юзабилити на сайте. Так, вместо многоразового скроллинга, и выбора файла, можно указать за один раз все необходимые. Это отличная функция на клиенте, однако, стоит научиться работать с многофайловой загрузкой на самом сервере.

Синхронная загрузка в этом случае - это не лучшее решение. Потому как, из него вытекают последствия в виде долгой загрузки страницы, и прерывание обработки при закрытии страницы. Вместо этого, пользователю можно позволить загрузить множество картинок, а создание их превьюшек делать уже отдельно, в фоне. Пример повседневного применения очереди:
youtube - при загрузке личного видео, оно становится доступным только через какое-то время, т.е. когда обработается в фоне;
vk - при загрузке песни, или видео, аналогично, становится доступным не сразу после загрузки, а только после обработки.

Жизненный цикл загрузки файлов без очереди

Обычный процесс загрузки выглядит так:
lifecicle_default
Исходя из этой картинки, можно понять, что при таком подходе, может возникнуть множество ситуаций, при которых скрипт отработает неправильно.
Основные проблемы, которые могут возникнуть, когда:

  • Пользователь отменит загрузку, закрыв вкладку
  • Пользователю надоест ожидать долгую работу скрипта, и он отменит загрузку
  • Соединение будет разорвано браузером по таймауту

И при любой из этих ошибок, пользователь будет необходимо повторить загрузку заново. А, поверьте мне, не каждый пользователь вытерпит загрузку изображений в виде лотереи, и уйдёт с вашего сайта после первой-второй такой ошибки.
Не исключая и благоприятный исход, пользователь будет продолжать пользоваться вашим сервисом, так что это тоже имеет право на жизнь.

PHP + очередь

Что же, а как тогда будет выглядеть схема работы PHP с очередью? Просто:
queues
Из картинки можно увидеть, что там изображено 3 очереди, каждая из которых работает отдельно над своей задачей: обработка изображений, видео, и отправка email писем.

Как только пользователь завершает загрузку изображений, в очередь добавляется новая задача, и выполняется. Воркер очереди картинок видит новую задачу, и начинает генерировать их превью.

Вы же, на стороне клиента можете показать сообщение "мы обрабатываем ваши изображения, ожидайте", а в то время, пока они будут обрабатываться пользователь безпрепяственно может просматривать любые страницы вашего приложения. Теперь вы даете возможность пользователю провести больше времени с пользой на вашем сайте, пока процессы работают в фоне. Пользователь может делать что угодно, даже добавлять новые изображения :).

Воркер обрабатывает задачи настолько быстро, насколько это возможно. Если будет загружено 5 картинок, то задача будет выполнена быстро, если 1000, то это займёт какое-то время. Но, зато, теперь пользователь это время обработки может потратить более полезно, чем при стандартном решении.

В результате, по завершению работы, воркер может сразу же оповестить пользователей. Каким способом отправлять уведомления - это всё на ваш выбор. Можете оповещать по вебсокетам, по email, или обычные оповещения после перезагрузки страницы.

После выполнения всех заданий из очереди, воркер опять начинает ожидать новые задачи.
Остальные 2 воркера, которые были показаны выше, в это время, не делают никакой работы, пока в их очереди нет задач. Но, как только они получают в пул новые, то сразу же обрабатывают их, в зависимости от вашей программной реализации.

Больше очередей

Более того, вы можете создавать целую цепочку очередей. К примеру, изнутри самой очереди обработки картинок, вы можете добавить новую задачу в очередь на отправку email письма.

Это вы можете увидеть на примере предыдущей картинки, немного видоизменённой:
couble-queue
Здесь показано, что, как только воркер завершит свою работу, будет создана новая задача по отправке письма, в другой очереди. В свою очередь, email-воркер уже отрабатывает, и оповещает пользователя о завершении работы над изображениями.

До сих пор непонятно?

Если вы всё ещё до конца не понимаете, что такое очередь, и как они работают, то, этот раздел для вас. Уйдём от примеров из программирования, и рассмотрим бытовой пример. Представьте, что вы позвонили в кафе, чтобы заказать пиццу. От готовой пиццы и у вас на столе, вас отделяет несколько шагов:

  1. Кто-то принимает ваш заказ, и начинает готовить пиццу (добавляя нужные ингредиенты, соусы, и т.д.)
  2. Запаковывает в коробку, и едет по адресной доставке.
  3. На месте, вы уже расплачиваетесь с курьером, и получаете свою пиццу.

Представьте, насколько долгим будет этот процесс, если только один работник будет выполнять все задачи. Конечно, если у него только один заказ в день, то звучит не так плохо. Но что случится, если он получит 5 заказов течении одной минуты? Очевидно, что сил одного тут будет слишком мало, и этот работник будет перегружен.

В результате, некоторые из заказов могут потеряться, в некоторых могут несоответствовать ингредиенты тем, что заказывали. Или, когда доставка производится в 5 разных мест, они могут находиться слишком далеко друг от друга, что повлечёт за собой опоздание курьера, и еда клиентов станет холодной.

Но, с точки зрения очереди, эту ситуацию можно пересмотреть. Давайте представим, что теперь работник не один, а несколько, и у каждого есть своя задача.

  1. Один человек ждёт входящие звонки, и принимает заказы
  2. Второй готовит пиццу
  3. И третий осуществляет доставку пиццы по адресу

И в итоге, когда каждый имеет чёткие обязанности, и занят только одним делом, то меньше вероятность ошибки, и процесс создания продукта является более оптимизированным.

На этом простом примере, можно вывести понятие очереди.
Очередь - это последовательное выполнение списка задач в фоне. При том, что воркеров (выполняющих задачи из очереди) может быть много, каждый отвечающий за свою типовую задачу. На примере пиццерии - каждый работник имеет свои обязанности, которые и выполняет (это и есть воркеры).

Резюме

В этой статье я описал принцип работы очереди, что такое php очередь, и что из себя представляет php сервер очередей. Здесь была разобрана только теория, без привязки к конкретному языку программирования. Для полного понимания, подготовлена практическая статья с реализацией php скрипта работы с очередью.