Рубрики
Без рубрики

Как построить прогресс-бар для Интернета с Джанго и сельдереем

Автор оригинала: Cory Zue.

Удивительная сложность сделать что-то, что на его поверхности, смешно просто

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

И все же – сделать хороший прогресс бар – удивительно сложная задача!

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

Этот пост описывает все, что мне нужно было учиться (и некоторые вещи, которые я не сделал!), Чтобы сделать сельдерей – прогресс , библиотека, которая, надеюсь, позволяет легко упасть в запрещенные прогрессные стержни для ваших приложений Django/Crinery.

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

Почему прогресс бары?

Это может быть очевидно, но просто чтобы получить его с пути – почему мы используем прогрессные бары?

Основная причина заключается в том, чтобы предоставить пользователям отзывы пользователя для чего-то, что занимает дольше, чем они привыкли ждать. По словам Kissmetrics 40% людей отказываются от сайта, который занимает более 3 секунд, чтобы загрузить! И хотя вы можете использовать что-то вроде спина, чтобы помочь смягчить это ждать, пробовал и истинный способ общаться с вашими пользователями, пока они ждут что-то, что произойдет, – это использовать батончик выполнения.

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

Некоторые примеры включают в себя:

  • Когда ваша заявка на первые нагрузки (если требуется много времени для загрузки)
  • При обработке больших импорт данных
  • При подготовке файла для скачивания
  • Когда пользователь находится в очереди, ожидая их запроса, чтобы обработать

Компоненты прогресса

Хорошо, с этим из того, как давайте попадаем в то, как на самом деле построить эти вещи!

Это просто небольшой бар, заполняя через экран. Насколько сложно это было?

На самом деле, совсем!

Следующие компоненты, как правило, являются частью любой реализации любого прогресса:

  1. А Фронт-конец , который обычно включает визуальное представление прогресса и (необязательно) текстового состояния.
  2. А Backend Это на самом деле сделает работу, которую вы хотите отслеживать.
  3. Один или несколько каналов связи для переднего конца для передачи работы на бэкэнда.
  4. Один или несколько каналов связи для бэкэнда для передачи прогресса до интерфейса.

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

Эти каналы связи – это большая часть сложности. В относительно стандартном проекте Django Бесплодный браузер может подать HTTP-запрос AJAX (JavaScript) на Backend Web App (Джанго). Это, в свою очередь, может пройти этот запрос на Очередь задачи (Сельдерей) через Сообщение Брокер (Rabritmq/redis). Тогда все это должно произойти в обратном направлении, чтобы получить информацию обратно на передний конец!

Весь процесс может выглядеть что-то подобное:

Давайте погрузимся во все эти компоненты и посмотрим, как они работают в практическом примере.

Передний конец

Передний конец определенно самая легкая часть прогресса. Благодаря нескольким небольшим линиям HTML/CSS вы можете быстро сделать достойный вид горизонтальной панели, используя цвет фона и атрибуты ширины. Всплеск в небольшом JavaScript, чтобы обновить его, и вы добруетесь!

function updateProgress(progressBarElement, progressBarMessageElement, progress) {
  progressBarElement.style.backgroundColor = '#68a9ef';
  progressBarElement.style.width = progress.percent + "%";
  progressBarMessageElement.innerHTML = progress.current + ' of ' + progress.total + ' processed.';
}

var trigger = document.getElementById('progress-bar-trigger');
trigger.addEventListener('click', function(e) {
  var barWrapper = document.getElementById('progress-wrapper');
  barWrapper.style.display = 'inherit'; // show bar
  var bar = document.getElementById("progress-bar");
  var barMessage = document.getElementById("progress-bar-message");
  for (var i = 0; i < 11; i++) {
    setTimeout(updateProgress, 500 * i, bar, barMessage, {
      percent: 10 * i,
      current: 10 * i,
      total: 100
    })
  }
})

Бэкэнда

Орден одинаково прост. Это, по сути, просто какой-то код, который будет выполнен на вашем сервере, чтобы выполнить работу, которую вы хотите отслеживать. Это, как правило, было бы написано на любом использовании приложений, который вы используете (в этом случае Python и Django). Вот чрезмерно упрощенная версия того, что может выглядеть бэкэнда:

def do_work(self, list_of_work): 
    for work_item in list_of_work: 
        do_work_item(work_item) 
    return 'work is complete'

Делать работу

Итак, у нас есть наш интерфейсный прогресс бар, и у нас есть наш рабочий участник. Что дальше?

Ну, мы на самом деле ничего не сказали о том, как эта работа будет выгнана. Так что давайте начнем там.

Неправильный путь: делать это в веб-приложении

В типичном рабочем процессе Ajax это будет работать следующим образом:

  1. Фронт-конец инициирует запрос в веб-приложение
  2. Веб-приложение работает в запросе
  3. Веб-приложение возвращает ответ при выполнении

В виде Джанго, это будет выглядеть что-то подобное:

def my_view(request): 
    do_work() 
    return HttpResponse('work done!')

Неправильный путь: вызывая функцию с вида

Проблема вот что do_work Функция может сделать много работы, которая занимает много времени (если она не сделала, это не имеет смысла добавить для нее прогрессную панель).

Делать много работы по мнению, обычно считается плохой практикой по нескольким причинам, в том числе:

  • Вы создаете плохой пользовательский опыт, поскольку люди должны ждать длительных запросов, чтобы закончить
  • Вы открываете свой сайт до потенциальных проблем стабильности с большим количеством долгосрочных запросов на работу (которые могут быть вызваны злонамеренным или случайно)

По этим причинам и другим нам нужен лучший подход для этого.

Лучший способ: асинхронные очереди задач (ака сельдерей)

Большинство современных веб-каркасов создали Асинхронные очереди задач иметь дело с этой проблемой. В Python, наиболее распространенным является Сельдерей Отказ В рельсах есть Sidekiq ( среди прочего ).

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

Эта асинхронная архитектура имеет несколько преимуществ, в том числе:

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

Механика асинхронных задач

Основная механика асинхронной архитектуры относительно проста и включает в себя три основных компонента: Клиент (ы) , Работник и Сообщение Брокер Отказ

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

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

Клиент и очередь задач разговаривают друг с другом через Сообщение Брокер , который несет ответственность за принятие задач от клиента (ы) и доставляя их на работника (ов). Наиболее распространенным брокером сообщений для сельдерей является RABLIPMQ, хотя Redis также является широко используемым и функционирует полный брокер сообщений.

При создании стандартного приложения сельдерея вы обычно вы будете заниматься разработкой клиента и работника работника, но сообщение Broker будет частью инфраструктуры, которую вам просто нужно встать (и помимо того, что может [в основном] игнорировать).

Пример

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

Чтобы преобразовать нашу работу по работе на что-то, что можно выполнить асинхронно, все, что нам нужно сделать, это добавить специальный декоратор:

from celery import task 
# this decorator is all that's needed to tell celery this is a
# worker task
@task 
def do_work(self, list_of_work): 
    for work_item in list_of_work: 
        do_work_item(work_item) 
    return 'work is complete'

Аннотация функции работы, которая должна называться сельдерея

Аналогично, вызывая функцию асинхронно из клиента Django, аналогично простым:

def my_view(request): 
    # the .delay() call here is all that's needed
    # to convert the function to be called asynchronously     
    do_work.delay() 
    # we can't say 'work done' here anymore 
    # because all we did was kick it off 
    return HttpResponse('work kicked off!')

Вызывая функцию работы асинхронно

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

Отслеживание прогресса

Хорошо, поэтому мы наконец получили нашу задачу на заднем плане. Но теперь мы хотим отслеживать прогресс на этом. Так как это работает, точно?

Мы снова должны сделать несколько вещей. Сначала нам понадобится способ отслеживания прогресса в рабочей работе. Затем нам нужно будет сообщить, что прогресс дозадается до нашего интерфейса, поэтому мы можем обновить панель прогресса на странице. Еще раз, это заканчивается довольно сложнее, чем вы думаете!

Используя объект наблюдателя для отслеживания прогресса на работнике

Читатели семена Банда четырех узоров дизайна может быть знаком с Узор наблюдателя Отказ Типичный шаблон наблюдателя включает в себя Тема какие отслеживает состояние, а также один или несколько Наблюдатели которые делают что-то в ответ на состояние. В нашем сценарии прогресса субъект является рабочим процессом/функцией, которая делает работу, и наблюдатель – это то, что собирается отслеживать прогресс.

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

Это выглядит что-то подобное:

@task 
def do_work(self, list_of_work, progress_observer):     
    total_work_to_do = len(list_of_work)     
    for i, work_item in enumerate(list_of_work):             
        do_work_item(work_item)         
        # tell the progress observer how many out of the total items 
        # we have processed
        progress_observer.set_progress(i, total_work_to_do)        
    return 'work is complete'

Использование наблюдателя для мониторинга прогресса работы

Теперь все, что нам нужно сделать, это пройти в действительности Progress_observer И Voilà, наш прогресс будет отслежен!

Получение прогресса обратно к клиенту

Вы можете думать «Подожди минутку … ты только что назвал функцию под названием Set_Progress, вы на самом деле ничего не делали!»

Правда! Так как это делает на самом деле Работа?

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

К счастью, сельдерей также обеспечивает механизм для передачи сообщений назад клиенту. Это сделано через механизм под названием Результат Backends и, как брокеры У вас есть возможность нескольких разных брюк. Оба RABLIPMQ и REDIS могут быть использованы в качестве брокеров и резервуаров результатов и являются разумными вариантами, хотя между брокером и резульностью нет муфты.

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

В сельдерее это абстрагируется довольно хорошо через Государство связано с задачей. Государство Позволяет нам установить общий статус, а также прикреплять произвольные метаданные к задаче. Это идеальное место для хранения нашего текущего и полного прогресса.

Установка состояния

task.update_state( 
    state=PROGRESS_STATE, 
    meta={'current': current, 'total': total} 
)

Чтение состояния

from celery.result import AsyncResult 
result = AsyncResult(task_id) 
print(result.state) # will be set to PROGRESS_STATE print(result.info) # metadata will be here

Получение обновлений прогресса на передний конец

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

Если вы хотите вообразить, вы можете использовать что-то вроде WebSockets, чтобы сделать это в режиме реального времени. Но простейшая версия – просто опросить URL каждый так часто, чтобы проверить прогресс. Мы можем просто обслуживать информацию о прогрессе, как JSON через вид и процесс и обработку Django и оказать его на стороне клиента.

Джанго Вид:

def get_progress(request, task_id): 
    result = AsyncResult(task_id) 
    response_data = { 
        'state': result.state, 
        'details': self.result.info,
    } 
    return HttpResponse(
        json.dumps(response_data), 
        content_type='application/json'
    )

Вид Джанго, чтобы вернуть прогресс как JSON.

Код JavaScript:

function updateProgress (progressUrl) {
    fetch(progressUrl).then(function(response) { 
        response.json().then(function(data) { 
            // update the appropriate UI components 
            setProgress(data.state, data.details); 
            // and do it again every half second
            setTimeout(updateProgress, 500, progressUrl); 
        }); 
    }); 
}

Код JavaScript для опроса для прогресса и обновления пользовательского интерфейса.

Положить все это вместе

Это было довольно много деталей по тому, что на его лице – очень простая и повседневная часть нашей жизни с компьютерами! Я надеюсь, вы что-то узнали.

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

Спасибо за прочтение! Если вы хотите получить уведомление всякий раз, когда я публикую контент, как это на создании вещей с Python и Django, пожалуйста, зарегистрируйтесь, чтобы получать обновления ниже!

Первоначально опубликовано buildwithdjango.com .