python3/celery入門

一、Celery 簡介

Celery是一個專注於實時處理和任務調度的分佈式任務隊列。所謂任務就是消息,消息中的有效載荷中包含要執行任務需要的全部數據。

使用Celery的常見場景如下:

  1. Web應用。當用戶觸發的一個操作需要較長時間才能執行完成時,可以把它作爲任務交給Celery去異步執行,執行完再返回給用戶。這段時間用戶不需要等待,提高了網站的整體吞吐量和響應時間。
  2. 定時任務。生產環境經常會跑一些定時任務。假如你有上千臺的服務器、上千種任務,定時任務的管理很困難,Celery可以幫助我們快速在不同的機器設定不同種任務。
  3. 同步完成的附加工作都可以異步完成。比如發送短信/郵件、推送消息、清理/設置緩存等。

Celery還提供瞭如下的特性:

  1. 方便地查看定時任務的執行情況,比如執行是否成功、當前狀態、執行任務花費的時間等。
  2. 可以使用功能齊備的管理後臺或者命令行添加、更新、刪除任務。
  3. 方便把任務和配置管理相關聯。
  4. 可選多進程、Eventlet和Gevent三種模式併發執行。
  5. 提供錯誤處理機制。
  • 提供多種任務原語,方便實現任務分組、拆分和調用鏈。
  • 支持多種消息代理和存儲後端。

二、Celery的架構

Celery 扮演生產者和消費者的角色

Celery包含如下組件:

  1. Celery Beat:任務調度器,Beat進程會讀取配置文件的內容,週期性地將配置中到期需要執行的任務發送給任務隊列。
  2. Celery Worker:執行任務的消費者,通常會在多臺服務器運行多個消費者來提高執行效率。
  3. Broker:消息代理,或者叫作消息中間件,接受任務生產者發送過來的任務消息,存進隊列再按序分發給任務消費方(通常是消息隊列或者數據庫)。
  4. Producer:調用了Celery提供的API、函數或者裝飾器而產生任務並交給任務隊列處理的都是任務生產者。
  5. Result Backend:任務處理完後保存狀態信息和結果,以供查詢。Celery默認已支持Redis、RabbitMQ、MongoDB、Django ORM、SQLAlchemy等方式。

Celery的架構圖如圖所示:

在這裏插入圖片描述

任務發佈者 就是單一的執行任務,trigger
定時任務調用 定時執行任務
worker 就是任務,後臺啓動的進程,shell方式調用

產生任務的方式:
  • 發佈者發佈任務(WEB 應用)
  • 任務調度按期發佈任務(定時任務)
celery 依賴三個庫: 這三個庫, 都由 Celery 的開發者開發和維護
  • billiard : 基於 Python2.7 的 multisuprocessing 而改進的庫, 主要用來提高性能和穩定性.
  • librabbitmp : C 語言實現的 Python 客戶端,
  • kombu : Celery 自帶的用來收發消息的庫, 提供了符合 Python 語言習慣的, 使用 AMQP 協議的高級藉口

三、消息代理

Celery目前支持RabbitMQ、Redis、MongoDB、Beanstalk、SQLAlchemy、Zookeeper等作爲消息代理,但適用於生產環境的只有RabbitMQ和Redis,至於其他的方式,一是支持有限,二是可能得不到更好的技術支持。

Celery官方推薦的是RabbitMQ,Celery的作者Ask Solem Hoel最初在VMware就是爲RabbitMQ工作的,Celery最初的設計就是基於RabbitMQ,所以使用RabbitMQ會非常穩定,成功案例很多。如果使用Redis,則需要能接受發生突然斷電之類的問題造成Redis突然終止後的數據丟失等後果。

四、Celery序列化

在客戶端和消費者之間傳輸數據需要 序列化和反序列化. Celery 支出的序列化方案如下所示:

方案 說明
pickle pickle 是Python 標準庫中的一個模塊, 支持 Pyuthon 內置的數據結構, 但他是 Python 的專有協議. Celery 官方不推薦.
json json 支持多種語言, 可用於跨語言方案.
yaml yaml 表達能力更強, 支持的數據類型較 json 多, 但是
python 客戶端的性能不如 json
msgpack 二進制的類 json 序列化方案, 但比 json 的數據結構更小, 更快.

五、安裝,配置與簡單示例

Celery 配置參數彙總

配置項 說明
CELERY_DEFAULT_QUEUE 默認隊列
BROKER_URL 代理人即rabbitmq的網址
CELERY_BROKER_URL Broker 地址
CELERY_RESULT_BACKEND 結果存儲地址
CELERY_TASK_SERIALIZER 任務序列化方式
CELERY_RESULT_SERIALIZER 任務執行結果序列化方式
CELERY_TASK_RESULT_EXPIRES 任務過期時間
CELERY_ACCEPT_CONTENT 指定任務接受的內容類型(序列化)

代碼示例 :

普通腳本方式

app.py文件

from celery import Celery

app = Celery('hello', broker='redis://localhost:6379/')


@app.task
def hello():
    return 'hello world'

命令行啓動

$ celery -A hello.app worker --loglevel=info

進入同級目錄

(env_local) MacBook:hello clin$ python3
Python 3.6.6 (v3.6.6:4cf1f54eb7, Jun 26 2018, 19:50:54) 
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from app import hello
>>> hello.delay()
<AsyncResult: 3c33b8d5-31c1-49b2-9a6b-0b1f2c0c1d04>
>>> 

同樣支持文件格式

項目方式

文件目錄結構

├── celery_project
│   ├── app.py
│   ├── celeryconfig.py
│   └── task.py
└── celery_project-test.py

安裝$ pip install celery, redis, 沒有RabbitMQ ,僅使用redis,不安裝 msgpack

Celery 也定義了一組用於安裝 Celery 和給定特性依賴的捆綁。

你可以在 requirements.txt 中指定或在 pip 命令中使用方括號。多個捆綁用逗號分隔。

$ pip install celery[librabbitmq]
或
$ pip install celery[librabbitmq,redis,auth,msgpack]

配置文件 celeryconfig.py

BROKER_URL = 'redis://localhost:6379/1'     # 配置消息隊列,默認使用 RabbitMQ
# BROKER_URL = 'amqp://dongwm:123456@localhost:5672/web_develop' # 使用RabbitMQ作爲消息代理,默認地址 'amqp://guest:**@127.0.0.1:5672/'
CELERY_BROKER_URL = 'redis://localhost:6379/2'  # 把任務結果存在了Redis 區分生成的key,使用不同的庫,使用 keys * 查看 # 把任務結果存在了Redis
CELERY_RESULT_BACKEND = 'redis://localhost:6379/3'
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'           # 讀取任務結果一般性能要求不高,所以使用了可讀性更好的JSON
CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24   # 任務過期時間,不建議直接寫86400,應該讓這樣的magic數字表述更明顯
CELERY_ACCEPT_CONTENT = ["json"]            # 指定任務接受的內容類型

要驗證你的配置文件可以正確工作,且不包含語法錯誤,你可以嘗試導入它:

$ python -m celeryconfig

初始化文件 app.py ,celery 默認會到目錄下找celery這個文件,儘管會引起文件名衝突,所以改名叫app

# from __future__ import absolute_import
from celery import Celery

app = Celery('celery_project', include=["celery_project.task"])
app.config_from_object("celery_project.celeryconfig")

if __name__ == "__main__":
    app.start()
    
    # When this module is executed the tasks will be named starting with “__main__”,
    # but when the module is imported by another process,
    # say to call a task, the tasks will be named starting with “tasks” (the real name of the module):
    # 當使用 __main__ 的方式導入模塊,需要使用 app.worker_main() 或者 app.start() 的方式,等於導入真實的 task 下裝飾的函數 add

1."from future import absolute_import"是拒絕隱式引入,因爲celery.py的名字和celery的包名衝突,需要使用這條語句讓程序正確地運行。因爲沒有使用 celery.py 命名,所以註釋了
2.app是Celery類的實例,創建的時候添加了 celery_project.tasks這個模塊,也就是包含了 celery_project/task.py這個文件。
3.把Celery配置存放進 celery_project/celeryconfig.py文件,使用app.config_from_object加載配置。

任務文件 task.py

# from __future__ import absolute_import
from celery_project.app import app


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

task.py只有一個任務函數add,讓它生效的最直接的方法就是添加app.task這個裝飾器。

啓動消費者,這個例子中沒有任務調度相關的內容, 所以只需要啓動消費者:

$ celery -A celery_project worker -l info # 指定項目下存在 celery.py 文件,默認啓動文件
或者
$ celery -A celery_project.app worker -l info
-A參數默認會尋找proj.celery這個模塊,其實使用celery作爲模塊文件名字不怎麼合理。可以使用其他名字。舉個例子,假如是proj/app.py,可以使用如下命令啓動:

命令行啓動輸出

 -------------- celery@MacBook v4.1.0 (latentcall)
---- **** ----- 
--- * ***  * -- Darwin-19.0.0-x86_64-i386-64bit 2019-10-22 19:35:32
-- * - **** --- 
- ** ---------- [config]
- ** ---------- .> app:         celery_project:0x10505db38
- ** ---------- .> transport:   redis://localhost:6379/1
- ** ---------- .> results:     redis://localhost:6379/3
- *** --- * --- .> concurrency: 12 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** ----- 
 -------------- [queues]
                .> celery           exchange=celery(direct) key=celery
                

[tasks]
  . celery_project.task.add

[2019-10-22 19:35:32,418: INFO/MainProcess] Connected to redis://localhost:6379/1
[2019-10-22 19:35:32,428: INFO/MainProcess] mingle: searching for neighbors
[2019-10-22 19:35:33,448: INFO/MainProcess] mingle: all alone
[2019-10-22 19:35:33,460: INFO/MainProcess] celery@MacBook ready.

手動觸發任務,也可以在命令行執行
celery_project-test.py

from celery_project.task import add
r = add.delay(1, 3)
print(r)
print(r.result)
print(r.status)
print(r.successful())
print(r.backend)

輸出結果

abb2242a-1590-4240-ab6e-40a1e834bc42
None
PENDING
False
<celery.backends.redis.RedisBackend object at 0x1112d11d0>

work進程輸出日誌

[2019-10-22 19:51:38,945: INFO/MainProcess] Received task: celery_project.task.add[abb2242a-1590-4240-ab6e-40a1e834bc42]  
[2019-10-22 19:51:38,948: WARNING/ForkPoolWorker-8] ==========
[2019-10-22 19:51:38,956: INFO/ForkPoolWorker-8] Task celery_project.task.add[abb2242a-1590-4240-ab6e-40a1e834bc42] succeeded in 0.008541842995327897s: 4

六、使用任務調度

調用定時任務

from datetime import timedelta
CELERYBEAT_SCHEDULE = {
    'add': {
        'task': 'celery_project.task.add',
        'schedule': timedelta(seconds=3),
        'args': (16, 16)
    }
}

CELERYBEAT_SCHEDULE中指定了task.add這個任務每3秒跑一次,執行的時候的參數是16和16。

單獨啓動celery

$ celery beat -A celery_project.app

celery beat輸出結果

celery beat v4.1.0 (latentcall) is starting.
__    -    ... __   -        _
LocalTime -> 2019-10-22 20:36:13
Configuration ->
    . broker -> redis://localhost:6379/1
    . loader -> celery.loaders.app.AppLoader
    . scheduler -> celery.beat.PersistentScheduler
    . db -> celerybeat-schedule
    . logfile -> [stderr]@%WARNING
    . maxinterval -> 5.00 minutes (300s)

然後再啓動worker,輸出結果,因爲有堆積,所以一下有3個消費掉了

$ celery -A celery_project.app worker -l info
 
 -------------- celery@MacBook v4.1.0 (latentcall)
---- **** ----- 
--- * ***  * -- Darwin-19.0.0-x86_64-i386-64bit 2019-10-22 20:36:18
-- * - **** --- 
- ** ---------- [config]
- ** ---------- .> app:         celery_project:0x105b37b00
- ** ---------- .> transport:   redis://localhost:6379/1
- ** ---------- .> results:     redis://localhost:6379/3
- *** --- * --- .> concurrency: 12 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** ----- 
 -------------- [queues]
                .> celery           exchange=celery(direct) key=celery
                

[tasks]
  . celery_project.task.add

[2019-10-22 20:36:18,558: INFO/MainProcess] Connected to redis://localhost:6379/1
[2019-10-22 20:36:18,567: INFO/MainProcess] mingle: searching for neighbors
[2019-10-22 20:36:19,591: INFO/MainProcess] mingle: all alone
[2019-10-22 20:36:19,602: INFO/MainProcess] celery@MacBook ready.
[2019-10-22 20:36:19,833: INFO/MainProcess] Received task: celery_project.task.add[fb82b3e6-ef18-494b-be0e-8105e9f04cd4]  
[2019-10-22 20:36:19,835: INFO/MainProcess] Received task: celery_project.task.add[ba54b389-273a-4c6b-b8dd-6c3a50522992]  
[2019-10-22 20:36:19,836: INFO/MainProcess] Received task: celery_project.task.add[c55f7140-4477-4f15-945e-2728dcd039ac]  
[2019-10-22 20:36:19,836: WARNING/ForkPoolWorker-1] ==========
[2019-10-22 20:36:19,837: WARNING/ForkPoolWorker-8] ==========
[2019-10-22 20:36:19,839: WARNING/ForkPoolWorker-2] ==========
[2019-10-22 20:36:19,846: INFO/ForkPoolWorker-1] Task celery_project.task.add[ba54b389-273a-4c6b-b8dd-6c3a50522992] succeeded in 0.00965010700019775s: 32
[2019-10-22 20:36:19,846: INFO/ForkPoolWorker-8] Task celery_project.task.add[fb82b3e6-ef18-494b-be0e-8105e9f04cd4] succeeded in 0.009201998997014016s: 32
[2019-10-22 20:36:19,847: INFO/ForkPoolWorker-2] Task celery_project.task.add[c55f7140-4477-4f15-945e-2728dcd039ac] succeeded in 0.008784970996202901s: 32
[2019-10-22 20:36:22,554: INFO/MainProcess] Received task: celery_project.task.add[7281dd7c-cf58-411e-bd8c-ac0bc31168a8]  
[2019-10-22 20:36:22,556: WARNING/ForkPoolWorker-3] ==========
[2019-10-22 20:36:22,563: INFO/ForkPoolWorker-3] Task celery_project.task.add[7281dd7c-cf58-411e-bd8c-ac0bc31168a8] succeeded in 0.008048085001064464s: 32
[2019-10-22 20:36:25,553: INFO/MainProcess] Received task: celery_project.task.add[6894cdf8-f04d-4e7d-a895-0a3181b32b37]  
[2019-10-22 20:36:25,556: WARNING/ForkPoolWorker-4] ==========
[2019-10-22 20:36:25,563: INFO/ForkPoolWorker-4] Task celery_project.task.add[6894cdf8-f04d-4e7d-a895-0a3181b32b37] succeeded in 0.008065512003668118s: 32
^C
worker: Hitting Ctrl+C again will terminate all running tasks!

worker: Warm shutdown (MainProcess)

一起啓動

$ celery -B -elery_project.app worker -l info

簡書參考

知乎參考

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