Почему асинхронная задача Celery работает медленнее, чем синхронная задача? - PullRequest
2 голосов
/ 02 апреля 2019

Я работаю над приложением Django, которое использует Celery для асинхронного запуска некоторых задач. Я попытался выполнить нагрузочное тестирование и проверить время отклика с помощью Apache Bench. Из того, что я мог понять из результатов, является то, что время отклика быстрее без асинхронных задач сельдерея.

Я использую:

Джанго: 2.1.0 сельдерей: 4.2.1 Redis (Брокер): 2.10.5 Джанго-Редис: 4.9. 0

Конфигурация сельдерея в Django settings.py:

BROKER_URL = 'redis://127.0.0.1:6379/1'
CELERY_RESULT_BACKEND = 'django-db' # Using django_celery_results
CELERY_ACCEPT_CONTENT = ['application/json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'Asia/Kolkata'

Ниже приведен мой код (API предоставляется моей системой):

class CustomerSearch(APIView):

    def post(self, request):
        request_dict = {# Request parameters}
        # Async Block
        response = celery_search_customer_task.delay(request_dict)
        response = response.get()
        # Synchronous Block (uncomment following to make synchronous call)
        # api_obj = ApiCall(request=request_dict)
        # response = api_obj.search_customer() # this makes an API call to 
        return Response(response)

И задача сельдерея в tasks.py:

@app.task(bind=True)
def celery_search_customer_task(self, req_data={}):
    api_obj = ApiCall(request=req_data)
    response = api_obj.search_customer() # this makes an API call to another system
    return response

Команда Apache Bench:

ab -p req_data.data -T application/x-www-form-urlencoded -l -r -n 10 -c 10 -k -H "Authorization: Token <my_token>" http://<my_host_name>/<api_end_point>/

Ниже приведен результат ab:
Без сельдерея Async Task

Concurrency Level:      10
Time taken for tests:   1.264 seconds
Complete requests:      10
Failed requests:        0
Keep-Alive requests:    0
Total transferred:      3960 bytes
Total body sent:        3200
HTML transferred:       1760 bytes
Requests per second:    7.91 [#/sec] (mean)
Time per request:       1264.011 [ms] (mean)
Time per request:       126.401 [ms] (mean, across all concurrent requests)
Transfer rate:          3.06 [Kbytes/sec] received
                        2.47 kb/s sent
                        5.53 kb/s total

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:      259  270  10.7    266     298
Processing:   875  928  36.9    955     967
Waiting:      875  926  35.3    950     962
Total:       1141 1198  43.4   1224    1263

Percentage of the requests served within a certain time (ms)
  50%   1224
  66%   1225
  75%   1231
  80%   1233
  90%   1263
  95%   1263
  98%   1263
  99%   1263
 100%   1263 (longest request)

С заданием Async для сельдерея

Concurrency Level:      10
Time taken for tests:   10.776 seconds
Complete requests:      10
Failed requests:        0
Keep-Alive requests:    0
Total transferred:      3960 bytes
Total body sent:        3200
HTML transferred:       1760 bytes
Requests per second:    0.93 [#/sec] (mean)
Time per request:       10775.688 [ms] (mean)
Time per request:       1077.569 [ms] (mean, across all concurrent requests)
Transfer rate:          0.36 [Kbytes/sec] received
                        0.29 kb/s sent
                        0.65 kb/s total

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:      259  271   9.2    268     284
Processing:  1132 6128 4091.9   8976   10492
Waiting:     1132 6127 4091.3   8975   10491
Total:       1397 6399 4099.3   9244   10775

Percentage of the requests served within a certain time (ms)
  50%   9244
  66%   9252
  75%  10188
  80%  10196
  90%  10775
  95%  10775
  98%  10775
  99%  10775
 100%  10775 (longest request)

Разве асинхронная задача сельдерея не должна заставить задачи работать быстрее синхронных задач? Что мне здесь не хватает?

Любая помощь будет принята с благодарностью. Спасибо.

Ответы [ 2 ]

2 голосов
/ 02 апреля 2019

Синхронный запуск кода - это простой код блокировки в главном потоке, с другой стороны, сельдерей работает как механизм производитель-потребитель . Celery перенаправляет задачу в очередь сообщений брокера, например RabbitMQ или Redis , что добавляет дополнительное время обработки здесь. И в зависимости от того, где работает ваш сельдерей, вы можете добавить задержку в сети, если она не работает локально. Если вы звоните get или delay, то возвращает обещание, которое можно использовать для мониторинга состояния и получения результата, когда он будет готов. Таким образом, архитектура в основном становится

  • Веб

  • брокер

  • работник
  • бэкэнд-результат

Учитывая, что такая большая задача обработки сельдерея медленнее, чем работа в основном потоке

1 голос
/ 02 апреля 2019

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

Разве асинхронная задача сельдерея не должна заставить задачи работать быстрее, чем синхронные?

Как @Yugandhar указывает в своем ответе, используя что-то вроде Celery, вы добавляете дополнительные накладные расходы на обработку. Вместо того же процесса, выполняющего код, вы фактически делаете следующее:

  • Клиент отправляет сообщение брокеру.
  • Рабочий забирает сообщение и выполняет его.
  • Ответ работника о возврате брокеру.
  • Клиент получает ответ и обрабатывает его.

Как вы можете видеть, очевидно, что использование Celery связано с дополнительными издержками по сравнению с его синхронным выполнением. Из-за этого не обязательно говорить, что «асинхронная задача быстрее, чем синхронная».

Тогда возникает вопрос: зачем использовать асинхронные задачи? Если это добавляет дополнительные издержки и может замедлить выполнение, то в чем выгода? Преимущество в том, что вам не нужно ждать ответа!

Давайте возьмем ваш ApiCall() в качестве примера. Допустим, сам вызов занимает 10 секунд для выполнения. Выполнение этого синхронно означает, что вы блокируете все, что нужно сделать до завершения вызова. Если, например, у вас есть отправка формы, которая вызывает это, это означает, что пользователь должен ждать загрузки своего браузера в течение 10 секунд, прежде чем он получит свой ответ. Это довольно плохой пользовательский опыт.

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

Ожидание результатов по сравнению с обратными вызовами

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

Используя метод .get(), вы указываете объекту AsyncResult ожидать результатов. Это означает, что он будет блокировать (так же, как если бы вы выполняли это синхронно) что угодно, пока работник Celery не вернет ответ.

task.delay()        # Async, don't await any response.
task.delay().get()  # Blocks execution until response is returned.

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

...