Статьи

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

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

Когда клиент запрашивает что-то в типичном веб-приложении, веб-сервер может обработать запрос за несколько секунд или меньше. Затем клиент отправляет ответ, чтобы проинформировать его о результате.

Это знакомый HTTP «цикл запроса / ответа», который обобщен на этой диаграмме:

Типичная архитектура веб-приложения

Хороший UX требует, чтобы веб-серверы реагировали быстро. По этой причине интенсивная задача не должна быть втиснута в цикл запроса / ответа.

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

Мы можем добавить очередь сообщений в архитектуру, чтобы добиться этого эффективно.

Архитектура веб-приложения с очередью сообщений

В этой статье мы рассмотрим этапы высокоуровневой реализации очереди сообщений в веб-приложении с использованием Vue и Laravel.

Выход из цикла запроса / ответа

Скажем, мы создаем приложение, которое обрабатывает CSV и записывает данные в базу данных. Особенно большой CSV может занять несколько минут для обработки.

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

клиент

Вот соответствующая часть клиентского приложения, куда мы загружаем CSV. Вы можете видеть, что у нас есть файл ввода и кнопка отправки с сообщением, которое мы можем отобразить.

CSVUpload.vue

<template>
  <div>
    <div v-if="message">{{ message }}</div>
    <form id="upload" enctype="multipart/form-data" @submit.prevent="submit">
      <p>Please select the file you'd like to upload.</p>
      <input type="file" name="csv" />
      <input type="submit" value="Upload" />
    </form>
  </div>
</template>

Мы будем использовать HTTP POST для отправки файла. Поскольку мы не собираемся обрабатывать CSV в цикле запрос / ответ, мы не ожидаем окончательного результата от ответа. Вместо этого мы просто хотим, чтобы сервер сообщал нам, что файл получен.

submit(event) {
  axios.post("/upload", new FormData(event.target))
    .then(res => {
      this.message = res.status;
    });
}

сервер

На сервере у нас будет контроллер, который обрабатывает этот запрос на загрузку файла. Мы уточним логику в следующем разделе, но важно отметить, что мы прикрепляем HTTP-код 202 (Accepted)к ответу. Это подходит, когда вы хотите сообщить клиенту, что запрос был получен, но еще не завершен.

App / HTTP / Контроллеры / CSVUploadController.php

public function store(Request $request) 
{
  if ($request->hasFile('csv')) {
    // TODO: logic for async processing
    return response("File received for processing.", 202);
  } else {
    return response("No file provided.", 400);
  }
}

Использование очереди сообщений

Как только файл получен веб-сервером, как мы обрабатываем его вне цикла запрос / ответ? Здесь мы хотим использовать очередь сообщений .

Очередь сообщений — это программное обеспечение, которое выполняется в отдельном процессе на вашем веб-сервере (или, возможно, даже на отдельной машине), и его задачей является управление асинхронными задачами. В типичном сценарии веб-сервер сообщит очереди сообщений, что у нас есть «задание», очередь сообщений выполнит задание (т.е. выполнит код), а затем сообщит о результатах, когда это будет сделано.

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

Примеры программного обеспечения очереди сообщений:

  • Beanstalkd
  • Amazon SQS (облачная очередь сообщений)
  • Redis (по сути не очередь сообщений, но отлично работает как единое целое)

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

Laravel Queues

Laravel Queues упрощает взаимодействие веб-приложения Laravel с очередью сообщений.

Вот краткий обзор того, как они работают — позже я приведу конкретный пример.

  1. Запустите очередь сообщений. Скажите Laravel, где находится и как получить к нему доступ через config/queues.phpфайл конфигурации.
  2. Запустите рабочий процесс очереди . Это посредник между веб-приложением и очередью сообщений, которая будет прослушивать новые задания и отправлять их в очередь. Поскольку нам нужно обрабатывать задачи очереди асинхронно, это будет запускаться как отдельный процесс для вашего веб-приложения.
  3. Отправьте «задание» и рабочий процесс очереди (т.е. некоторый код, который вы хотите выполнить — мы лучше определим задания ниже).
  4. Прослушайте событие, которое содержит результат работы (необязательно).

Например, мы можем использовать Redis в качестве очереди сообщений. Laravel включает в себя драйверы для этого из коробки, так что просто нужно запустить Redis на сервере и сообщить Laravel порт / пароль config/queues.php.

Laravel предоставляет рабочий процесс очереди из коробки через консоль Artisan. Откройте вкладку терминала и запустите:

$ php artisan queue:work redis

Далее мы увидим, как передавать задания в очередь сообщений, которые могут обрабатываться асинхронно.

работа

Теперь мы можем создать задание — код, который вы хотите запустить в очереди сообщений. Это обычно будет интенсивной или трудоемкой задачей, такой как обработка CSV.

Laravel предоставляет Jobкласс, в который вы помещаете свой код. Используйте Artisan для его создания:

$ php artisan make:job ProcessCSV

Этот handleметод вызывается при запуске этого задания, поэтому мы помещаем логику задачи.

App / Работа / ProcessCSV.php

public function handle()
{
  // Logic for processing CSV
}

Затем мы можем использовать статический dispatchметод этого класса заданий в нашем веб-приложении. Это скажет рабочему процессу очереди, что мы хотим, чтобы это обрабатывалось очередью сообщений:

App / HTTP / Контроллеры / CSVUploadController.php

public function store(Request $request) 
{
  if ($request->hasFile('csv')) {
    ProcessCSV::dispatch($request->file("csv"));
    return response("File received for processing!", 202);
  } else {
    return response("No file provided.", 400);
  }
}

Использование асинхронного протокола для информирования пользователя о результате

Наш начальник 202 Acceptedсказал клиенту, что мы работаем над задачей, но нам, вероятно, нужно сообщить им результат, когда задача будет выполнена.

Поскольку выполнение задачи может занять длительное время, было бы лучше, если бы UX использовал асинхронный протокол, такой как электронная почта или SMS, для информирования о результате, поэтому пользователь может продолжать использовать свой браузер для прокрутки Facebook или Reddit в течение нескольких минут и не нужно сидеть там в ожидании.

Вы также можете открыть соединение через веб-сокет между клиентом и сервером и отправить ответ таким образом. Я все еще думаю, что электронная почта или SMS лучше, так как от пользователя не требуется держать вкладку открытой и не забывать проверять.

клиент

Давайте изменим форму на клиенте, чтобы пользователь мог указать свой адрес электронной почты:

<form id="upload" enctype="multipart/form-data" @submit.prevent="submit">
  <p>Please select the file you'd like to upload. Provide an email address and we'll inform you of the result and spam you later.</p>
  <input type="file" name="csv" />
  <input type="email" name="email" />
  <input type="submit" value="Upload" />
</form>

сервер

Теперь, когда мы обрабатываем первоначальный запрос, мы можем передать адрес электронной почты работе:

public function store(Request $request) 
{
  if ($request->hasFile('csv')) {
    ProcessCSV::dispatch($request->file("csv"), $request->email);
    return response("File received for processing!", 202);
  } else {
    return response("No file provided.", 400);
  }
}

Рабочий процесс Laravel по очереди отправит событие, когда задание будет завершено, сообщив вам, что произошло, если оно не удалось, и т. Д.

Мы можем прослушать это событие и использовать его для отправки уведомления. И почему бы не создать другую работу для отправки электронной почты!

Приложение / Провайдеры / AppServiceProvider.php

Queue::after(function (JobProcessed $event) {
  $result = ... // get the job result from the DB
  SendEmail::dispatch($event->data["email"], $result);
});

Заворачивать

Если вашему веб-приложению нужно выполнить интенсивную или трудоемкую задачу для пользователя, не пытайтесь втиснуть его в цикл запроса / ответа. Отправка в очередь сообщений, чтобы вы могли не только быстро ответить пользователю, но и предотвратить перегрузку своего веб-сервера.

Laravel Queues — это фантастические возможности для передачи возможностей очередей сообщений в веб-приложение. Есть еще много функций, которые я здесь не описал, включая бесплатную панель управления Lizon от Horizon для управления вашей очередью.