【问题标题】:Why is Celery Async Task working slower than Synchronous task?为什么 Celery 异步任务的工作速度比同步任务慢?
【发布时间】:2019-08-23 06:58:42
【问题描述】:

我正在开发一个使用 Celery 异步运行一些任务的 Django 应用程序。我尝试使用 Apache Bench 执行负载测试并检查响应时间。从结果中我可以看出,如果没有 celery 异步任务,响应时间会更快。

我正在使用:

  • Django:2.1.0
  • 芹菜:4.2.1
  • Redis(代理):2.10.5
  • django-redis: 4.9.0
  • Django settings.py 中的 Celery 配置:

    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中的celery任务:

    @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 的结果:
    没有 celery 异步任务

    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)
    

    使用 celery 异步任务

    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)
    

    不是 celery 异步任务应该使任务比同步任务更快吗?我可能在这里错过了什么?

    任何帮助将不胜感激。谢谢。

    【问题讨论】:

    • 没有和有是什么意思?通过使用.get(),所有发送的任务都会被同步等待,同时阻塞其余的执行。您能否包含代码示例,说明您如何“使用异步”和“不使用异步”?
    • 我更新了我的问题,以演示我如何进行“同步”和“异步”API 调用(在 CustomerSearch 类的 post() 方法中)。在测试时,我只是注释或取消注释两个块以在异步和同步之间切换。

    标签: django redis django-celery


    【解决方案1】:

    同步运行代码是在主线程上直接阻塞代码,另一方面 celery 的工作方式类似于 生产者消费者 机制。 Celery 将任务转发到像 RabbitMQRedis 这样的代理消息队列,这在这里增加了额外的处理时间。并且根据您的 celery 运行的位置,如果不在本地运行,您可以考虑增加网络延迟。如果您正在调用getdelay,则返回一个可用于监视状态并在准备好时获取结果的承诺。 所以架构基本上变成了

    • 网络

    • 经纪人

    • 工人
    • 结果后端

    考虑到这么多处理 celery 任务比在主线程上运行要慢

    【讨论】:

      【解决方案2】:

      我认为您的问题中有多个误解需要回答。

      不是 celery 异步任务应该让任务比同步任务工作得更快吗?

      正如@Yugandhar 在他的回答中指出的那样,通过使用 Celery 之类的东西,您正在为您的处理增加额外的开销。您实际上是在执行以下操作,而不是执行代码的同一进程:

      • 客户端向代理发送消息。
      • Worker 拾取消息并执行它。
      • 工作人员向代理返回响应。
      • 客户端获取响应并进行处理。

      如您所见,与同步执行相比,使用 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 请求的执行,而是使用回调来处理您触发的任务的响应。

      【讨论】:

      • 感谢这么详细的解释!我现在明白了。但是,根据我系统的当前流程,System 1 正在调用我的 API,为此我正在调用 System 2 的 API。在收到来自 System 2 的结果并进行进一步处理后,我将结果发送回 System 1。因此,我有必要接收 celery 任务的结果。这种情况有什么解决方法吗?如果我理解正确的话,是不是意味着使用.get()总是让任务同步运行?
      • 是的 .get() 总是阻塞其余的执行,直到返回响应。这就是重点。关于处理结果,您可以查看链接任务或使用 linklink_error kwargs 传递回调。没有“正确”的方式,这取决于你想做什么。 docs.celeryproject.org/en/latest/userguide/…docs.celeryproject.org/en/latest/userguide/canvas.html#chains
      • 感谢您的参考。我会读那些。在进一步测试时,我发现当我增加用户和请求的数量(20 个用户 - 20 个请求,30 个用户 - 30 个请求,..)时,响应时间不断增加并且差异太大。我使用 Async Task 的另一个原因是确保用户数量的增加不会显着增加响应时间。这又是因为.get()吗?
      • 很难说出确切的原因,但理论上可能是您的 Django Web 服务器产生的执行代码的工作人员比 Celery 工作人员多。默认情况下,Celery 为每个 CPU 内核生成 1 个工作线程,如果您查看文档,可以进行配置。
      猜你喜欢
      • 2020-01-07
      • 2015-10-15
      • 1970-01-01
      • 1970-01-01
      • 2023-03-31
      • 1970-01-01
      相关资源
      最近更新 更多