文章目錄
celery分佈式任務隊列
一. celery 簡介
Celery 是一個專注於實時處理和任務調度的分佈式任務隊列, 同時提供操作和維護分佈式系統所需的工具… 所謂任務就是消息, 消息中的有效載荷中包含要執行任務需要的全部數據.
Celery 是一個分佈式隊列的管理工具, 可以用 Celery 提供的接口快速實現並管理一個分佈式的任務隊列.
Celery 本身不是任務隊列, 是管理分佈式任務隊列的工具. 它封裝了操作常見任務隊列的各種操作, 我們使用它可以快速進行任務隊列的使用與管理.
Celery 特性 :
方便查看定時任務的執行情況, 如 是否成功, 當前狀態, 執行任務花費的時間等.
使用功能齊備的管理後臺或命令行添加,更新,刪除任務.
方便把任務和配置管理相關聯.
可選 多進程, Eventlet 和 Gevent 三種模型併發執行.
提供錯誤處理機制.
提供多種任務原語, 方便實現任務分組,拆分,和調用鏈.
支持多種消息代理和存儲後端.
Celery 是語言無關的.它提供了python 等常見語言的接口支持.
二. celery 組件
1. Celery 扮演生產者和消費者的角色,
Celery Beat : 任務調度器. Beat 進程會讀取配置文件的內容, 週期性的將配置中到期需要執行的任務發送給任務隊列.
Celery Worker : 執行任務的消費者, 通常會在多臺服務器運行多個消費者, 提高運行效率.
Broker : 消息代理, 隊列本身. 也稱爲消息中間件. 接受任務生產者發送過來的任務消息, 存進隊列再按序分發給任務消費方(通常是消息隊列或者數據庫).
Producer : 任務生產者. 調用 Celery API , 函數或者裝飾器, 而產生任務並交給任務隊列處理的都是任務生產者.
Result Backend : 任務處理完成之後保存狀態信息和結果, 以供查詢.
Celery架構圖
2. 產生任務的方式 :
1.發佈者發佈任務(WEB 應用)
2.任務調度按期發佈任務(定時任務)
3. celery 依賴三個庫: 這三個庫, 都由 Celery 的開發者開發和維護.
billiard : 基於 Python2.7 的 multisuprocessing 而改進的庫, 主要用來提高性能和穩定性.
librabbitmp : C 語言實現的 Python 客戶端,
kombu : Celery 自帶的用來收發消息的庫, 提供了符合 Python 語言習慣的, 使用 AMQP 協議的高級藉口.
三. 選擇消息代理
使用於生產環境的消息代理有 RabbitMQ 和 Redis, 官方推薦 RabbitMQ.
四. Celery 序列化
在客戶端和消費者之間傳輸數據需要 序列化和反序列化. Celery 支出的序列化方案如下所示:
五. 安裝,配置與簡單示例
Celery 配置參數彙總
代碼示例 :
# 安裝$ pip install celery, redis, msgpack
# 配置文件 celeryconfig.py
CELERY_BROKER_URL = ‘redis://localhost:6379/1’
CELERY_RESULT_BACKEND = ‘redis://localhost:6379/0’
CELERY_TASK_SERIALIZER = ‘json’
CELERY_RESULT_SERIALIZER = ‘json’
CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24
# 任務過期時間 CELERY_ACCEPT_CONTENT = [“json”]
# 指定任務接受的內容類型.
# 初始化文件 celery.py
from future import absolute_import
from celery import Celery
app = Celery(‘proj’, include=[“proj.tasks”])
app.config_from_object(“proj.celeryconfig”)
if name == “main”:
app.start()
# 任務文件 tasks.py
from future import absolute_import
from proj.celery import app
@app.task def add(x, y):
return x + y # 啓動消費者
$ celery -A proj worker -l info
# 在終端中測試 > from proj.tasks import add
> r = add.delay(2,4)
> r.result 6
> r.status u"SUCCESS"
> r.successful() True
> r.ready()
# 返回布爾值, 任務執行完成, 返回 True, 否則返回 False. > r.wait()
# 等待任務完成, 返回任務執行結果. > r.get()
# 獲取任務執行結果 > r.result
# 任務執行結果. > r.state
# PENDING, START, SUCCESS > r.status
# PENDING, START, SUCCESS
# 使用 AsyncResult 方式獲取執行結果.
# AsyncResult 主要用來存儲任務執行信息與執行結果(類似 js 中的 Promise 對象), > from celery.result import AsyncResult > AsyncResult(task_id).get() 4
說明:以上代碼爲原博客中內容,實測的話結合flask,redis 存在版本問題。後續博客處理
六. 調用任務的方法 :
1. delay
task.delay(args1, args2, kwargs=value_1, kwargs2=value_2)
2. apply_async
delay 實際上是 apply_async 的別名, 還可以使用如下方法調用, 但是 apply_async 支持更多的參數:
task.apply_async(args=[arg1, arg2], kwargs={key:value, key:value})
支持的參數 :
countdown : 等待一段時間再執行.
add.apply_async((2,3), countdown=5)
eta : 定義任務的開始時間.
add.apply_async((2,3), eta=now+tiedelta(second=10))
expires : 設置超時時間.
add.apply_async((2,3), expires=60)
retry : 定時如果任務失敗後, 是否重試.
add.apply_async((2,3), retry=False)
retry_policy : 重試策略.
max_retries : 最大重試次數, 默認爲 3 次.
interval_start : 重試等待的時間間隔秒數, 默認爲 0 , 表示直接重試不等待.
interval_step : 每次重試讓重試間隔增加的秒數, 可以是數字或浮點數, 默認爲 0.2
interval_max : 重試間隔最大的秒數, 即 通過 interval_step 增大到多少秒之後, 就不在增加了, 可以是數字或者浮點數, 默認爲 0.2 .
自定義發佈者,交換機,路由鍵, 隊列, 優先級,序列方案和壓縮方法:
task.apply_async((2,2), compression=‘zlib’, serialize=‘json’, queue=‘priority.high’, routing_key=‘web.add’, priority=0, exchange=‘web_exchange’)
七. 指定隊列 :
Celery 默認使用名爲 celery 的隊列 (可以通過 CELERY_DEFAULT_QUEUE 修改) 來存放任務. 我們可以使用 優先級不同的隊列 來確保高優先級的任務優先執行.
# 定義任務隊列.
Queue(‘default’, routing_key=“task.#”),
# 路由鍵 以 “task.” 開頭的消息都進入 default 隊列.
Queue(‘web_tasks’, routing_key=“web.#”)
# 路由鍵 以 “web.” 開頭的消息都進入 web_tasks 隊列.)
CELERY_DEFAULT_EXCHANGE = ‘tasks’
# 默認的交換機名字爲
tasksCELERY_DEFAULT_EXCHANGE_KEY = ‘topic’
# 默認的交換機類型爲
topicCELERY_DEFAULT_ROUTING_KEY = ‘task.default’
# 默認的路由鍵是 task.default , 這個路由鍵符合上面的 default 隊列.
CELERY_ROUTES = { ‘proj.tasks.add’: { ‘queue’: ‘web_tasks’, ‘routing_key’: ‘web.add’, }}
# 使用指定隊列的方式啓動消費者進程.$ celery -A proj worker -Q web_tasks -l info
# 該 worker 只會執行 web_tasks 中任務, 我們可以合理安排消費者數量, 讓 web_tasks 中任務的優先級更高.
這段沒試過
閱後即焚模式(transient):
from kombu import QueueQueue(‘transient’, routing_key=‘transient’, delivery_mode=1)
八. 使用任務調度
使用 Beat 進程自動生成任務.
# 修改配置文件,
# 下面的任務指定 tasks.add 任務 每 10s 跑一次, 任務參數爲 (16,16).
from datetime import timedelta
CELERYBEAT_SCHEDULE = { ‘add’: {
‘task’: ‘proj.tasks.add’,
‘schedule’: timedelta(seconds=10),
‘args’: (16, 16) }}
# crontab 風格
from celery.schedules import crontab
CELERYBEAT_SCHEDULE = { “add”: {
“task”: “tasks.add”,
“schedule”: crontab(hour="*/3", minute=12),
“args”: (16, 16), } }
# 啓動 Beat 程序$ celery beat -A proj
# 之後啓動 worker 進程.$ celery -A proj worker -l info 或者$ celery -B -A proj worker -l info
使用自定義調度類還可以實現動態添加任務. 使用 Django 可以通過 Django-celery 實現在管理後臺創建,刪除,更新任務, 是因爲他使用了自定義的 調度類 djcelery.schedulers.DatabaseScheduler .
九. 任務綁定, 記錄日誌, 重試
# 修改 tasks.py 文件.
from celery.utils.log import get_task_loggerlogger = get_task_logger(name)
@app.task(bind=True)def div(self, x, y):
logger.info(('Executing task id {0.id},
args: {0.args!r}’ ’
kwargs: {0.kwargs!r}’).format(self.request))
try:
result = x/y
except ZeroDivisionError as e:
raise self.retry(exc=e, countdown=5, max_retries=3)
# 發生 ZeroDivisionError 錯誤時, 每 5s 重試一次, 最多重試 3 次.
return result
當使用 bind=True 參數之後, 函數的參數發生變化, 多出了參數 self, 這這相當於把 div 編程了一個已綁定的方法, 通過 self 可以獲得任務的上下文.
日誌輸出目前未處理,實際問題需要後面處理,由於是與flask整合。所以需要看怎麼管理日誌
十. 信號系統 :
信號可以幫助我們瞭解任務執行情況, 分析任務運行的瓶頸. Celery 支持 7 種信號類型.
1.任務信號
before_task_publish : 任務發佈前
after_task_publish : 任務發佈後
task_prerun : 任務執行前
task_postrun : 任務執行後
task_retry : 任務重試時
task_success : 任務成功時
task_failure : 任務失敗時
task_revoked : 任務被撤銷或終止時
2.應用信號
3.Worker 信號
4.Beat 信號
5.Eventlet 信號
6.日誌信號
7.命令信號
代碼示例 :
# 在執行任務 add 之後, 打印一些信息.
@after_task_publish
def task_send_handler(sender=None, body=None, **kwargs):
print 'after_task_publish: task_id: {body[id]};
sender: {sender}’.format(body=body, sender=sender)
十一. 子任務與工作流:(這塊比較重要)
可以把任務 通過簽名的方法傳給其他任務, 成爲一個子任務.
from celery import signaturetask = signature(‘task.add’, args=(2,2), countdown=10) tasktask.add(2,2)
# 通過簽名生成任務task.apply_async()
還可以通過如下方式生成子任務 :
from proj.task import addtask = add.subtask((2,2), countdown=10)# 快捷方式 add.s((2,2), countdown-10) task.apply_async()
自任務實現片函數的方式非常有用, 這種方式可以讓任務在傳遞過程中財傳入參數.
partial = add.s(2)partial.apply_async((4,))
子任務支持如下 5 種原語,實現工作流. 原語表示由若干指令組成的, 用於完成一定功能的過程
1.chain : 調用連, 前面的執行結果, 作爲參數傳給後面的任務, 直到全部完成, 類似管道.
from celery import chainres = chain(add.s(2,2), add.s(4), add.s(8))()res.get() 管道式: (add.s(2,2) | add.s(4) | add.s(8))().get()
2.group : 一次創建多個(一組)任務.
from celery import group res = group(add.s(i,i)foriinrange(10))()res.get()
3.chord : 等待任務全部完成時添加一個回調任務.
res = chord((add.s(i,i)foriinrange(10)), add.s([‘a’]))()res.get()# 執行完前面的循環, 把結果拼成一個列表之後, 再對這個列表 添加 ‘a’.[0,2,4,6,8,10,12,14,16,18,u’a’]
4.map/starmap : 每個參數都作爲任務的參數執行一遍, map 的參數只有一個, starmap 支持多個參數.
add.starmap(zip(range(10), range(10))) 相當於: @app.taskdef temp():return[add(i,i)foriinrange(10)]
5.chunks : 將任務分塊.
res = add.chunks(zip(range(50), range(50)),10)()res.get()
在生成任務的時候, 應該充分利用 group/chain/chunks 這些原語.
十二. 其他
關閉不想要的功能 :
@app.task(ignore_result=True) # 關閉任務執行結果.def func(): pass CELERY_DISABLE_RATE_LIMITS=True # 關閉限速.
根據任務狀態執行不同操作 :
# tasks.py
class MyTask(Task):
def on_success(self, retval, task_id, args, kwargs):
print ‘task done: {0}’.format(retval)
return super(MyTask, self).on_success(retval, task_id, args, kwargs)
def on_failure(self, exc, task_id, args, kwargs, einfo):
print ‘task fail, reason: {0}’.format(exc)
return super(MyTask, self).on_failure(exc, task_id, args, kwargs, einfo)
# 正確函數, 執行
MyTask.on_success() :
@app.task(base=MyTask)
def add(x, y):
return x + y # 錯誤函數, 執行 MyTask.on_failure() :
@app.task #普通函數裝飾爲
celery taskdef add(x, y):
raise KeyError return x + y
十三. Celery 管理命令
任務狀態回調 :
普通啓動命令 :
$ celery -A proj worker -l info