Celery入門

Celery是一個簡單可靠的分佈式任務隊列,它主要關注於實時任務處理,同時也能夠支持定時任務。這個項目目前在github上有14.8k顆星,是一個比較熱門的項目。Celery是採用Python語言編寫的,但其他語言也可以實現它的協議,或者通過webhook與之交互。Celery本身也比較精巧,據官方文檔介紹,Celery的核心總共只有7000多行代碼,但有14000多行測試代碼,所以質量應該也還是不錯的。那今天我們來一起看一下Celery的基本概念和一個小例子。

 

Celery簡化的架構示意圖如下

 

 

從這個圖中我們可以看出,基本上Celery的架構就是一個典型的生產者-消費者模式。Celery中有一些基本的概念與術語,下面我們分別來了解一下

 

基本概念

任務(Task)

任務是被Celery的Worker執行的基本單位,一個任務一般是Python中的一個函數

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

生產者(Producer)

生產者,也就是請求任務執行的客戶端。他通過發送一個消息給broker,來要求某個任務的執行。採用delay方法能發出一個這樣的message,delay()方法實際上會把工作交給apply_async()

add.delay(2, 2)
# 等效於
add.apply_async((2, 2))

消費者(Consumer)

在Celery中消費者就是是Worker,Worker本身並不執行任務。當Worker從broker接收到消息之後,會讓Worker的子進程(execution pool中)執行Task。Worker可以啓動多個,也可以分佈在不同的機器上,所以能很好的支持水平擴展。

 

創建Worker的命令爲

 celery -A celery_demo worker -l info -Q demo --concurrency=3 -n worker1@%h --autoscale=3,1

消息(Message)

任務消息是生產者發給broker的要求Worker執行任務的請求。通常消息由消息頭和消息體組成,消息頭描述了消息的序列化類型,消息體包含任務的名字、ID和參數,一個Json格式的消息體是長這樣的

{ 'task': 'myapp.tasks.add', 'id': '54086c5e-6193-4575-8308-dbab76798756', 'args': [4, 4], 'kwargs': {}}

Broker

Broker是把消息從生產者傳遞到消費者的消息中間件,在Celery中,一般可以使用RabbitMQ或者Redis作爲broker。也有一些其他的試驗性質的broker,具體支持的列表可以參見官網文檔

 

Queue

在Celery中我們可以定義多個Queue,任務可以路由到指定的Queue中,不同的Worker可以分別處理不同的Queue中的消息

 

Result Backend

Result Backend是用來存放Task執行的狀態和結果的。在Celery中可以使用數據庫、Redis/RabbitMQ等等作爲Result Backend

 

一個例子

瞭解了這些概念之後,我們來看一個簡單的例子。在這個例子中,我們選用redis作爲我們的消息隊列。你可以選擇你喜歡的方式安裝reids,或者也可以使用docker

docker run -d -p 6379:6379 redis

然後我們需要安裝Python的Celery和redis依賴

pip install celerypip install redis

接下來我們來編寫兩個簡單的task,分別求兩個數的和與差,內容放在celery_test.py中。

from celery import Celeryapp = Celery('celery_test', broker='redis://localhost')app.conf.task_routes = {'celery_test.sub': {'queue': 'qsub'}, 'celery_test.add': {'queue': 'qadd'} } @app.taskdef add(x, y) return x+ y @app.taskdef sub(x, y) return x -y

在代碼中,我們初始化了一個Celery的對象。還定義了task的路由規則,指定sub任務路由到qsub隊列中, add任務路由到qadd隊列中。

 

然後我們就可以啓動Celery Worker了,這裏我們使用命令行啓動兩個Worker,分別關注不同的queue

celery -A celery_test worker --loglevel=info -Q qsubcelery -A celery_test worker --loglevel=info -Q qadd

執行命令後如果一切正常的話,會出現以下日誌

-------------- celery@host_name v4.4.2 (cliffs) --- ***** -----  -- ******* ---- Darwin-19.3.0-x86_64-i386-64bit 2020-04-12 > 09:55:21 - *** --- * ---  - ** ---------- [config] - ** ---------- .> app: celery_test:0x1049aeed0 - ** ---------- .> transport: redis://localhost:6379//- ** ---------- .> results: disabled:// - *** --- * --- .> concurrency: 8 (prefork) -- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker) --- ***** -----  -------------- [queues] .> qsub exchange=qsub(direct) key=qsub

 

在日誌中顯示了我們的app的名字celery_test,使用的redis連接信息,並沒有啓用Result Backend,處理任務採用prefork的方式,最大允許8個併發,並且關注一個叫qsub的隊列。

 

啓動Worker之後,應用就可以向broker發請求消息了,我們一般通過delay()方法來發送,如果需要更多的選項,如發送到指定的queue,設定延時執行等,可以使用apply_async()方法

add.delay(2, 2)sub.delay(4, 2)

 

用Python執行以上兩句之後,在兩個Worker的console中分別可以看到收到對應task並執行的日誌。

[2020-04-12 10:41:20,410: INFO/MainProcess] Received task: celery_test.sub[50747bc7-9fa4-45f5-a5be-ad08b93dea09] [2020-04-12 10:41:20,413: INFO/ForkPoolWorker-6] Task celery_test.sub[50747bc7-9fa4-45f5-a5be-ad08b93dea09] succeeded in 0.0005345229999997869s: 3

 

如果生產者發送請求消息的時刻,對應的Worker還沒有啓動,那消息將會被保存在broker中,等Worker上線之後,任務還是可以被正常執行。

 

對於不需要返回結果的任務,那到這裏差不多就夠了。如果像我們用的add/sub這樣需要返回結果的任務,我們在定義celery app的時候,還要指定一個Result Backend。Celery支持多種Result Backend,如SQLAlchemy/Django ORM, Memcached, Redis, RPC (RabbitMQ/AMQP),我們甚至也可以根據celery的協議定義我們自己的backend。

 

這裏我們還是使用redis作爲我們的Result Backend,我們將上面那個創建Celery對象的語句改爲

app = Celery('tasks', backend='redis://localhost', broker='redis://localhost')

然後重啓Worker,可以看到之前打印的啓動日誌中的 results: disabled:// 變成了 results:  redis://localhost/

 

然後我們就可以在生產者這邊獲得任務執行的結果了

result = add.delay(2, 2)result.ready() # 判斷任務是否完成result.get(timeout=1) # 獲得結果

如果任務執行拋出了異常,那調用result.get()方法也會拋出這個異常。如果我們不想要這個異常,可以設置propagate參數

result.get(propagate=False)
result.traceback # 獲得異常的stacktrace

特別要注意的是,任務執行結果的保存和傳輸都是要消耗Result Backend的資源的,根據使用Result Backend的不同,會有不一樣的資源佔用情況,比方說在redis的話,結果會作爲redis的鍵值對保存,默認超時時間爲一天。爲了確保資源能夠正常釋放,在開啓Backend的情況下,建議總是去調用一下result.get或者result.forget方法

 

關於Celery今天就介紹到這裏,Celery還有很多靈活的配置項和一些高級的話題,如通過Clery Beat支持定時的任務、消息的限流、安全認證、Django框架的集成等等。對這些內容感興趣的同學們可以查看官方文檔,或者期待一下也許還會有的下文

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