Celery 異步任務隊列《快速入門二》

該篇着重介紹一下如何調用任務,隊列路由。詳見Celery中文手冊

一、在項目中使用Celery

  • 我的項目目錄:

    TestCelery/ 
    ├── proj
    │ ├── celeryconfig.py 
    │ ├── celery.py 
    │ ├── init.py 
    │ └── tasks.py 
    └── test.py
    
  • celery.py文件內容如下:

    from celery import Celery
    
    # 創建celery實例
    app = Celery('demo')
    app.config_from_object('proj.celeryconfig')
    
    # 自動搜索任務
    app.autodiscover_tasks(['proj'])
    
  • celeryconfig.p模塊內容如下:

    from kombu import Exchange, Queue
    BROKER_URL = 'redis://:[email protected]:6379/1'
    CELERY_RESULT_BACKEND = 'redis://:[email protected]:6379/2'
    
  • tasks.py模塊內容如下:

    from proj.celery import app as celery_app
    
    # 創建任務函數
    @celery_app.task
    def my_task1():
        print("任務函數(my_task1)正在執行....")
    
    @celery_app.task
    def my_task2():
        print("任務函數(my_task2)正在執行....")
    
    @celery_app.task
    def my_task3():
        print("任務函數(my_task3)正在執行....")
    
  • 啓動worker:如圖
    celery -A proj worker -l info
    在終端按 ctrl+c 即可關閉worker
    在這裏插入圖片描述

二、調用任務Calling Task

  • 調用任務,最簡單的方法就是使用delay()方法:

    my_task.delay(2, 2)
    
  • 也可以使用apply_async()方法,該方法可讓我們設置一些任務執行的參數,例如,任務多久之後才執行,任務被髮送到那個隊列中等等.

    my_task.apply_async((2, 2), queue='my_queue', countdown=10)
    

    任務my_task將會被髮送到my_queue隊列中,並且在發送10秒之後執行。

    • 如果我們直接執行任務函數,將會直接執行此函數在當前進程中,並不會向broker發送任何消息。

    • 無論是 delay()還是 apply_async() 方式都會返回AsyncResult對象,方便跟蹤任務執行狀態,但需要我們配置 result_backend.

    • 每一個被吊用的任務都會被分配一個ID,我們叫Task ID

三、Designing Work-flows

  • signature
    我們到目前爲止只是學習瞭如何使用delay()方法,當然這個方法也是非常常用的。但是有時我們並不想簡單的將任務發送到隊列中,我們想將一個任務函數(由參數和執行選項組成)作爲一個參數傳遞給另外一個函數中,爲了實現此目標,Celery使用一種叫做signatures的東西。

    一個signature包裝了一個參數和執行選項的單個任務調用。我們可將這個signature傳遞給函數。

    我們先看下tasks.py模塊中定義的任務函數:

    from proj.celery import app as celery_app
    
    # 創建任務函數
    @celery_app.task
    def my_task1():
        print("任務函數(my_task1)正在執行....")
    
    @celery_app.task
    def my_task2():
        print("任務函數(my_task2)正在執行....")
    
    @celery_app.task
    def my_task3():
        print("任務函數(my_task3)正在執行....")
    

    我們將my_task1()任務包裝稱一個signature:

    t1 = my_task1.signatures(countdown=10)
    t1.delay()
    
  • Primitives
    這些primitives本身就是signature對象,因此它們可以以多種方式組合成複雜的工作流程。primitives如下:

    • group: 一組任務並行執行,返回一組返回值,並可以按順序檢索返回值。
    • chain: 任務一個一個執行,一個執行完將執行return結果傳遞給下一個任務函數.

    tasks.py模塊如下:

    from proj.celery import app as celery_app
    
    # 創建任務函數
    @celery_app.task
    def my_task1(a, b):
        print("任務函數(my_task1)正在執行....")
        return a + b
    
    @celery_app.task
    def my_task2(a, b):
        print("任務函數(my_task2)正在執行....")
        return a + b
    
    @celery_app.task
    def my_task3(a, b):
        print("任務函數(my_task3)正在執行....")
        return a + b
    

    group案例如下(test.py模塊):

    from proj.tasks import my_task1
    from proj.tasks import my_task2
    from proj.tasks import my_task3
    from celery import group
    
    # 將多個signature放入同一組中
    my_group = group((my_task1.s(10, 10), my_task2.s(20, 20), my_task3.s(30, 30)))
    ret = my_group() # 執行組任務
    print(ret.get())  # 輸出每個任務結果
    

    在這裏插入圖片描述

    chain案例如下(test.py模塊):

    from proj.tasks import my_task1
    from proj.tasks import my_task2
    from proj.tasks import my_task3
    from celery import chain
    
    # 將多個signature組成一個任務鏈
    # my_task1的運行結果將會傳遞給my_task2
    # my_task2的運行結果會傳遞給my_task3
    my_chain = chain(my_task1.s(10, 10) | my_task2.s(20) | my_task3.s(30))
    ret = my_chain()  # 執行任務鏈
    print(ret.get())  # 輸出最終結果
    

    在這裏插入圖片描述

四、路由Routing

  • 問題:
    假如我們有兩個worker、一個worker專門用來處理郵件發送任務和圖像處理任務,一個worker專門用來處理文件上傳任務。

  • 解決方案:
    我們創建兩個隊列,一個專門用於存儲郵件任務隊列和圖像處理,一個用來存儲文件上傳任務隊列。
    Celery支持AMQP(Advanced Message Queue)所有的路由功能,我們也可以使用簡單的路由設置將指定的任務發送到指定的隊列中。

  • 我們需要配置在celeryconfig.py模塊中配置 CELERY_ROUTES 項;
    tasks.py模塊修改如下:

    from proj.celery import app as celery_app
    
    
    @celery_app.task
    def my_task1(a, b):
        print("my_task1任務正在執行....")
        return a + b
    
    
    @celery_app.task
    def my_task2(a, b):
        print("my_task2任務正在執行....")
        return a + b
    
    
    @celery_app.task
    def my_task3(a, b):
        print("my_task3任務正在執行....")
        return a + b
    
    
    @celery_app.task
    def my_task4(a, b):
        print("my_task3任務正在執行....")
        return a + b
    
    
    @celery_app.task
    def my_task5():
        print("my_task5任務正在執行....")
    
    
    @celery_app.task
    def my_task6():
        print("my_task6任務正在執行....")
    
    
    @celery_app.task
    def my_task7():
        print("my_task7任務正在執行....")
    
  • 我們通過配置,將send_email和upload_file任務發送到queue1隊列中,將image_process發送到queue2隊列中。
    我們修改celeryconfig.py:

    broker_url='redis://:@127.0.0.1:6379/1'
    result_backend='redis://:@127.0.0.1:6379/2'
    
    
    task_routes=({
        'proj.tasks.my_task5': {'queue': 'queue1'},
        'proj.tasks.my_task6': {'queue': 'queue1'},
        'proj.tasks.my_task7': {'queue': 'queue2'},
        },
    )
    

    修改test.py如下:

    from proj.tasks import *
    
    # 發送任務到路由指定的隊列中
    my_task5.delay()
    my_task6.delay()
    my_task7.delay()
    

    然後開啓兩個worker服務器,分別處理兩個隊列:

    celery -A proj worker --loglevel=info -Q queue1
    celery -A proj worker --loglevel=info -Q queue2
    
  • 補充:
    1、我們同樣也可以通過apply_aynsc()方法來設置任務發送到那個隊列中:

    my_task1.apply_async(queue='queue1')

    2、我們也可設置一個worker服務器處理兩個隊列中的任務:
    celery -A proj worker --loglevel=info -Q queue1,queue2

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章