Celery任務佇列

曾紀文發表於2019-02-16

文件

簡介

Celery 是一個“自帶電池”的的任務佇列。它易於使用,所以你可以無視其所解決問題的複雜程度而輕鬆入門。它遵照最佳實踐設計,所以你的產品可以擴充套件,或與其他語言整合,並且它自帶了在生產環境中執行這樣一個系統所需的工具和支援。

Celery 的最基礎部分。包括:

  • 選擇和安裝訊息傳輸方式(中間人)—-broker,如RabbitMQ,redis等。

    • RabbitMQ的安裝:sudo apt-get install rabbitmq-server
    • 本文使用redis
    • 官方推薦RabbitMQ
    • 當然部分nosql也可以
  • 安裝 Celery 並建立第一個任務
  • 執行職程並呼叫任務。
  • 追蹤任務在不同狀態間的遷移,並檢視返回值。

安裝

pip install celery

簡單使用

定義任務

tasks.py

from celery import Celery
#第一個引數是你的celery名稱
#backen 用於儲存結果
#broker 用於儲存訊息佇列
app = Celery(`tasks`,backend=`redis://:password@host:port/db`, broker=`redis://:password@host:port/db`)

@app.task
def add(x, y):
    return x + y

Celery 的第一個引數是當前模組的名稱,這個引數是必須的,這樣的話名稱可以自動生成。第二個引數是中間人關鍵字引數,指定你所使用的訊息中間人的 URL,此處使用了 RabbitMQ,也是預設的選項。更多可選的中間人見上面的 選擇中間人 一節。例如,對於 RabbitMQ 你可以寫 amqp://localhost ,而對於 Redis 你可以寫 redis://localhost .

你定義了一個單一任務,稱為 add ,返回兩個數字的和。

啟動celery服務

步驟:

  • 啟動任務工作者worker
  • 講任務放入celery佇列
  • worker讀取佇列,並執行任務

啟動一個工作者,建立一個任務佇列

// -A 指定celery名稱,loglevel制定log級別,只有大於或等於該級別才會輸出到日誌檔案
celery -A tasks worker --loglevel=info

如果你沒有安裝redis庫,請先pip install redis

使用celery

現在我們已經有一個celery佇列了,我門只需要將工作所需的引數放入佇列即可

from tasks import add
#呼叫任務會返回一個 AsyncResult 例項,可用於檢查任務的狀態,等待任務完成或獲取返回值(如果任務失敗,則為異常和回溯)。
#但這個功能預設是不開啟的,你需要設定一個 Celery 的結果後端(即backen,我們在tasks.py中已經設定了,backen就是用來儲存我們的計算結果)
result=add.delay(4, 4)
#如果任務已經完成
if(result.ready()):
  #獲取任務執行結果
  print(result.get(timeout=1))

常用介面

  • tasks.add(4,6) —> 本地執行
  • tasks.add.delay(3,4) –> worker執行
  • t=tasks.add.delay(3,4) –> t.get() 獲取結果,或卡住,阻塞
  • t.ready()—> False:未執行完,True:已執行完
  • t.get(propagate=False) 丟擲簡單異常,但程式不會停止
  • t.traceback 追蹤完整異常

使用配置

  • 使用配置來執行,對於正式專案來說可維護性更好。配置可以使用app.config.XXXXX_XXX=`XXX`的形式如app.conf.CELERY_TASK_SERIALIZER = `json`來進行配置
  • 配置資料

配置檔案

config.py

#broker
BROKER_URL = `redis://:password@host:port/db`
#backen
CELERY_RESULT_BACKEND = `redis://:password@host:port/db`
#匯入任務,如tasks.py
CELERY_IMPORTS = (`tasks`, )
#列化任務載荷的預設的序列化方式
CELERY_TASK_SERIALIZER = `json`
#結果序列化方式
CELERY_RESULT_SERIALIZER = `json`

CELERY_ACCEPT_CONTENT=[`json`]
#時間地區與形式
CELERY_TIMEZONE = `Europe/Oslo`
#時間是否使用utc形式
CELERY_ENABLE_UTC = True

#設定任務的優先順序或任務每分鐘最多執行次數
CELERY_ROUTES = {
    # 如果設定了低優先順序,則可能很久都沒結果
    #`tasks.add`: `low-priority`,
    #`tasks.add`: {`rate_limit`: `10/m`},
    #`tasks.add`: {`rate_limit`: `10/s`},
    #`*`: {`rate_limit`: `10/s`}
}
#borker池,預設是10
BROKER_POOL_LIMIT = 10
#任務過期時間,單位為s,預設為一天
CELERY_TASK_RESULT_EXPIRES = 3600
#backen快取結果的數目,預設5000
CELERY_MAX_CACHED_RESULTS = 10000

開啟服務

celery.py

from celery import Celery
#指定名稱
app = Celery(`mycelery`)
#載入配置模組
app.config_from_object(`config`)

if __name__==`__main__`:
      app.start()

任務定義

tasks.py

from .celery import app
@app.task
def add(a, b):
  return a + b

啟動

// -l 是 --loglevel的簡寫
celery -A mycelery worker -l info

執行/呼叫服務

from tasks import add
#呼叫任務會返回一個 AsyncResult 例項,可用於檢查任務的狀態,等待任務完成或獲取返回值(如果任務失敗,則為異常和回溯)。
#但這個功能預設是不開啟的,你需要設定一個 Celery 的結果後端(即backen,我們在tasks.py中已經設定了,backen就是用來儲存我們的計算結果)
result=add.delay(4, 4)
#如果任務已經完成
if(result.ready()):
  #獲取任務執行結果
  print(result.get(timeout = 1))

分散式

  • 啟動多個celery worker,這樣即使一個worker掛掉了其他worker也能繼續提供服務

    • 方法一
    // 啟動三個worker:w1,w2,w3
    celery multi start w1 -A project -l info
    celery multi start w2 -A project -l info
    celery multi start w3 -A project -l info
    // 立即停止w1,w2,即便現在有正在處理的任務
    celery multi stop w1 w2
    // 重啟w1
    celery multi restart w1 -A project -l info
    // celery multi stopwait w1 w2 w3    # 待任務執行完,停止
    • 方法二
    // 啟動多個worker,但是不指定worker名字
    // 你可以在同一臺機器上執行多個worker,但要為每個worker指定一個節點名字,使用--hostname或-n選項
    // concurrency指定處理程式數,預設與cpu數量相同,因此一般無需指定
    $ celery -A proj worker --loglevel=INFO --concurrency=10 -n worker1@%h
    $ celery -A proj worker --loglevel=INFO --concurrency=10 -n worker2@%h
    $ celery -A proj worker --loglevel=INFO --concurrency=10 -n worker3@%h

錯誤處理

celery可以指定在發生錯誤的情況下進行自定義的處理
config.py

def my_on_failure(self, exc, task_id, args, kwargs, einfo):
    print(`Oh no! Task failed: {0!r}`.format(exc))

// 對所有型別的任務,當發生執行失敗的時候所執行的操作
CELERY_ANNOTATIONS = {`*`: {`on_failure`: my_on_failure}}    

相關文章