Celery常見問題

譯文出處
http://docs.celeryproject.org/en/latest/faq.html

This document describes the current stable version of Celery (4.1).

本文長期更新地址: Celery4.1常見問題

術語翻譯對照

英文 中文
celery celery
worker worker
queue 隊列
message 消息
task 任務
常規
應該用Celery來處理什麼樣的事情?
答案:Queue everything and delight everyone (我的譯文參見)解釋了爲什麼你會需要在web的上下文中使用隊列。

這裏是一些普遍的使用案例:

在後臺運行。例如For example, to finish the web request as soon as possible, then update the users page incrementally. This gives the user the impression of good performance and “snappiness”, even though the real work might actually take some time.
在web請求結束後運行
通過異步執行和重試,確保一些事情完成了
制定週期任務
以及一定程度上:

分佈式計算
並行執行
誤解
Celery真的有50000行代碼嗎?
答案:沒有。這個和類似的龐大數字在各種場合經常被報道。

核心:7141行代碼
測試:14209行
後端,貢獻,兼容性代碼:9032行
代碼行數不是有用的度量標準,因此,即便Celery真的有50K行代碼,你也不能從這個數字中得到任何結論。

Celery有很多依賴嗎?
一個普遍的批評是說Celery使用了太多的依賴。這種擔憂背後的原理很難想象,特別是考慮到代碼複用在現代軟件開發中已經作爲成熟的解決複雜性的方式,而且在使用諸如pip和PyPI包管理工具後引入依賴的開銷非常低——安裝和維護依賴的麻煩已經成爲過去式了。

一路上,Celery替換了一些依賴,現在的依賴列表如下:

celery
kombu
Kombu是Celery生態系統的一部分,是用來發送和接收messages的庫。也是使得Celery支持如此多不同的message brokers的庫。Kombu也被用在OpenStack項目中,和其他許多項目中,驗證了將其從Celery基礎代碼中分割出來的選擇是有效的。

billiard
billiard是Python多進程模塊的一個分叉fork,包含了許多性能和穩定性改善。有一天這些改善最終將會被合併到Python中。

billiard也被用來處理沒有多進程模塊的老版本python的兼容問題。

pytz
提供時區定義和相關工具。
kombu
Kombu依賴於下面的包:
amqp
純Python實現的amqp客戶端。AMQP作爲默認broker是很自然的依賴。

Note:
爲了解決流行的配置選擇的依賴,Celery定義了許多“bundle”包(捆綁安裝_)。詳見

Celery是heavy-weight,很重的嗎?
Celery在內存足跡(memory footprint)和性能上造成了非常輕微的開銷。但是請注意,默認配置並未在時間或空間上進行優化,優化。

Celery依賴於pickle(序列化庫)嗎?
答案:不,Celery可以支持任何序列化策略。
我們內建支持JSON、YAML、Pickle和msgpack。每個任務都和一種content type掛鉤,因此你甚至可以一個任務用pickle,另一個用JSON。默認的序列化支持是pickle,但是從4.0版本起,是JSON。如果你需要發送複雜的Python對象作爲任務參數,你可以使用pickle作爲序列化格式,但是需要注意Notes in Serializers。

如果你需要和其他語言通信,你應該使用合適於任務的序列化格式,通常意味着不能用pickle了。
你可以設置一個全局的默認序列化器serializer,默認的serializer用於特定的任務,或者發送單個任務 instance的時候決定用什麼serializer。

Celery只支持Django嗎?
答案:不。

我必須使用AMQP/RabbitMQ嗎?
答案:不,儘管使用RabbitMQ是推薦的,你也可以使用Redis,SQS,或者Qpid。更多參見broker

Redis作爲broker表現不如AMQP,但是RabbitMQ作爲broker,Redis作爲結果存儲的組合方式很常用。如果你有嚴格的可靠性要求,最好使用RabbitMQ,或者其他AMQP broker。一些transports也用輪詢(polling),因此他們可能會消耗更多的資源。但是,如果你因爲某些原因不能使用AMQP,可以放心使用這些替代品,在大部分場景下都能工作良好,而且以上不是爲Celery量身定製的。如果你之前使用Redis/database作爲隊列也工作得很好,那現在也能。你一直可以到需要的時候再升級。

Celery是多語言的嗎?
答案:是。
worker是用Python實現的。如果某門語言有AMQP客戶端,那用這門語言創建一個worker不需要做太多事情。一個Celery的worker只是一個連接broker來處理messages的程序。

而且,有另一種方式來做到語言獨立,就是用REST的任務,這樣你的任務就不是函數而是url了。有這個信息,你甚至可以創建一個簡單的web服務器開啓代碼預加載。簡單地暴露一個表現一個操作的端點endpoint,再創建一個任務,這個任務只是將一個HTTP請求表現給那個端點。

解決問題
MySQL拋出死鎖錯誤,怎麼辦?
答案:MySQL有默認的隔離級別設置爲REPEATABLE-READ(可重複讀),如果你並不真正需要它,可以設置爲READ-COMMITTED(讀提交)。你可以通過在my.cnf中加入:

[mysqld]
transaction-isolation = READ-COMMMITTED
更多有關InnoDB的事務模型,參見MySQL - The InnoDB Transaction Model and Locking .

worker什麼都不做,hanging掛起了
答案:參見MySQL死鎖,或者 Task.delay

任務結果返回不可靠
答案:如果你使用數據庫後端存儲結果,特別是MySQL,可能是死鎖。參見上上個問題。

爲什麼Task.delay/apply*這些調用之後worker只是掛起?
答案: 一些AMQP客戶端有一個bug,如果當前用戶無法認證、密碼不匹配或者用戶沒有訪問指定虛擬主機的權限,就會掛起。檢查broker的日誌(RabbitMQ的在/var/log/rabbitmq/rabbit.log),通常會有消息描述原因。

兼容FreeBSD系統嗎?
答案:看情況。
When using the RabbitMQ (AMQP) and Redis transports it should work out of the box.

For other transports the compatibility prefork pool is used and requires a working POSIX semaphore implementation, this is enabled in FreeBSD by default since FreeBSD 8.x. For older version of FreeBSD, you have to enable POSIX semaphores in the kernel and manually recompile billiard.

Luckily, Viktor Petersson has written a tutorial to get you started with Celery on FreeBSD here: http://www.playingwithwire.com/2009/10/how-to-get-celeryd-to-work-on-freebsd/

遇到了完整性錯誤(IntegrityError):Duplicate Key errors,什麼原因?
答案:MySQL死鎖。

我的任務爲什麼沒有被處理?
答案:用RabbitMQ的話,你可以通過運行如下命令看有多少個消費者當前在接收任務:

$ rabbitmqctl list_queues -p name messages consumers
Listing queues …
celery 2891 2
以上輸出表明任務-隊列裏有2891條messages在等待被處理,而且有兩個消費者正在處理他們。

隊列從未被清空的一個原因可能是你有一個過期的worker進程劫持了這些messages。如果這個worker沒有被正確地殺掉,就有可能發生這種情況。

當一個message被一個worker接收到了,這個worker在標記該message被處理前會等待被應答。這個worker不會重發message給另一個消費者,直到該消費者被正確地關閉。
如果你遇到這個問題,你必須手動殺掉所有的worker並重啓:

$ pkill ‘celery worker’

$ # - If you don’t have pkill use:
$ # ps auxww | grep ‘celery worker’ | awk ‘{print $2}’ | xargs kill
你可能必須等一會兒,知道所有的worker都結束了正在執行的任務。如果仍然長時間掛起,你可以強制殺掉:

$ pkill -9 ‘celery worker’

$ # - If you don’t have pkill use:
$ # ps auxww | grep ‘celery worker’ | awk ‘{print $2}’ | xargs kill -9
我的任務爲什麼不會運行?
答案:可能有語法錯誤導致任務模塊沒有被導入。
你可以看看通過手動執行任務,Celery能不能運行該任務:

from myapp.tasks import MyPeriodicTask
MyPeriodicTask.delay()
觀察worker的日誌文件,是否可以找到該任務,或者有沒有其他錯誤發生。

我的定時任務爲什麼不會運行?
答案:參見上一個問題。

我怎麼清理所有的等待中任務?
答案:你可以使用celery purge命令來清理所有的已配置的任務隊列。

$ celery -A proj purge
或者在代碼中:

from proj.celery import app
app.control.purge()
1753
如果你只是想清理特定隊列中的消息,你必須使用AMQP API,或者celery amqp的功能:

$ celery -A proj amqp queue.purge
1753是被清理的消息數。

你也可以開啓–purge選項去啓動worker,worker啓動的時候就會清理消息。

我清理了message,但是隊列隊列中仍然有消息殘留?
答案:只要任務真的被執行了,任務就被應答(從隊列中移除)了。在worker接收到一個任務之後,在真正被執行前需要一點時間,特別是如果有大量任務已經在等待執行。沒有被應答的消息,會被worker保持,直到消息關閉和broker(AMQP服務器)的連接。當連接關閉時(比如,因爲worker停止了),任務會被broker重發給下一個可用的worker(或者在worker重啓後又發給它),因此正確地清理等待任務的隊列需要停掉所有的worker,然後再用celery.control.purge清理任務。

結果
如果我有一個id指向一個任務,怎麼得到任務結果?
答案:用task.AsyncResult。

result = my_task.AsyncResult(task_id)
result.get()
這會用任務的當前結果後端(result backend)返回一個AsyncResult的實例。
如果你需要指定一個自定義的結果後端,或者你想使用當前應用的默認後端,你可以使用app.AsyncResult:

result = app.AsyncResult(task_id)
result.get()
安全
使用pickle不是有安全隱患嗎?
答案:事實上,自從Celery4.0起,默認的序列化器是現在的JSON,就確保了人們有意識地選擇序列化器並且意識到了這一擔憂。
防範未認證授權的worker、數據庫和其他傳輸pickled數據的服務接入是必要的。
注意到這不僅僅是你應該意識到有關Celery的問題,例如Django也使用pickle作爲其緩存客戶端。
對任務消息,你可以設置task_serializer爲json或者yaml,而不是pickle。
類似地可以設置result_serializer。

message是否可以加密?
答案:一些AMQPworker支持使用SSL(包括RabbitMQ),你可以通過broker_use_ssl開啓這一功能。
給消息加入額外的加密和安全性也是可能的,如果你有需求,應該聯繫郵件列表。

以root用戶運行worker是安全的嗎?
答案:不是!
我們現在還沒有發覺任何安全問題,但是認爲安全問題不存在就太天真了,因此推薦以非特權用戶運行Celery服務(celery woker, celery beat, celeryev等)。

Brokers
爲什麼RabbitMQ崩潰了?
答案:RabbitMQ如果用光內存就會崩潰。未來版本的RabbitMQ會修復這一個問題。https://www.rabbitmq.com/faq.html#node-runs-out-of-memory

注意:
這已經不再是問題,RabbitMQ2.0+包含了一個新的固件,對內存不足錯誤是容忍的。因此推薦RabbitMQ2.1+版本配合Celery使用。如果你還在使用老版本,而且還遇到崩潰問題,趕緊升級吧。

Celery的錯誤配置最終也會導致老版本RabbitMQ的崩潰。即便不崩潰,也會消耗大量的資源,因此意識到這一普遍陷阱很重要。

事件Events
加上-E選項運行worker將會在worker內部事件發生時發送消息。
事件應該只在你有一個活躍的監控器消費事件的時候才被開啓,否則你需要定期清理事件隊列。

AMQP 後端結果
在使用AMQP結果後端運行的時候,每個任務結果都會作爲消息發送。如果你不收集collect這些結果,他們會積累,RabbitMQ最終會耗盡內存。
結果後端現在被棄用了,所以你不應該再使用。如果你需要多個消費者訪問結果,可以用RPC後端來做rpc風格的調用,或者一個持久化的後端。
默認情況下結果在一天後失效。可以通過配置result_expires來降低這個有效期。

如果你不需要任務結果,確保你設置了ignore_result選項。

@app.task(ignore_result=True)
def mytask():
pass

class MyTask(Task):
ignore_result = True
Celery可以和ActiveMQ/STOMP一起使用嗎?
答案:不能。

不使用AMQP broker時,哪些特徵不支持了?
不完全列表:

遠程控制命令(僅由Redis支持)
事件監控在所有的虛擬傳輸中可能不會工作
header和fanout(扇出,Redis支持) exchange types
任務
調用tasks時如何複用連接?
答案:查看broker_pool_limit設置。v2.5+就默認開啓連接池了。

子進程中sudo反回了None
有一個sudo的配置選項來使得不經過tty運行sudo的處理是非法的。

Defaults requiretty
如果你在/etc/sudoers文件中有這項配置,那worker作爲守護進程跑的時候,任務將不能調用sudo。如果你想開啓,你需要移除上面那一行。http://timelordz.com/wiki/Apache_Sudo_Commands

爲什麼workers不能處理任務的時候還能將任務從隊列中刪除?
答案:worker拒絕未知任務、錯誤編碼的消息、不包含正確域field的消息(按照任務消息協議)。
如果不拒絕這些,會導致重複傳送,引發死循環。
最近版本的RabbitMQ有能力配置一個dead-letter隊列來交換,所以那些被拒的消息就被轉移到了那裏。

我可以通過任務名稱調用任務嗎?
答案:是的,用app.send_task。
你也可以在任何語言中使用AMQP客戶端通過名字調用一個任務:

app.send_task(‘tasks.add’, args=[2, 2], kwargs={})
<AsyncResult: 373550e8-b9a0-4666-bc61-ace01fa4f91d>
我能設置當前任務的id嗎?
答案:是的,當前id以及更多內容在任務請求裏都是可用的。

@app.task(bind=True)
def mytask(self):
cache.set(self.request.id, “Running”)
Task Request
如果你沒有任務實例的引用,你可以使用app.current_task:

app.current_task.request.id
但是需要注意,這可能是任何任務,一個被worker執行的任務,或者一個直接被任務調用的任務,或者一個急切eager調用的任務。(此處原文:But note that this will be any task, be it one executed by the worker, or a task called directly by that task, or a task called eagerly.)

可以用current_worker_task得到特定的當前被執行的任務:

app.current_worker_task.request.id
需要注意current_task, 和 current_worker_task 可能是None。

我如何指定一個自定義的task_id?
答案:能。

task.apply_async(args, kwargs, task_id=’…’)
任務上可以使用裝飾器嗎?
答案:能,但需要注意 Basics的側邊欄

我能使用自然數作爲task ids嗎?
答案:能。但是確保其是唯一的,因爲兩個相同id的任務的行爲是未定義的。

我能指定,一旦另一個task結束,馬上運行一個task嗎?
答案:能。你可以在一個任務裏面安全地啓動一個任務。一個常用的模式是給任務加上回調:

from celery.utils.log import get_task_logger

logger = get_task_logger(name)

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

@app.task(ignore_result=True)
def log_result(result):
logger.info(“log_result got: %r”, result)
調用:

(add.s(2, 2) | log_result.s()).delay()
獲取更多信息:Canvas: Designing Work-flows

我能取消任務的執行嗎?
答案:能。用result.revoke():

result = add.apply_async(args=[2, 2], countdown=120)
result.revoke()
或者,只有任務id時:

from proj.celery import app
app.control.revoke(task_id)
後者也支持傳入任務id列表作爲參數。

爲什麼我的遠程控制命令被所有的workers接收到了?
答案:爲了接收到廣播的遠程控制命令,每一個worker節點基於其節點名創建了一個唯一的隊列名。如果你有超過一個worker的主機名相同,控制命令將會在他們間循環接收。
爲解決這個問題,你可以用-n參數顯式地爲每個worker設置節點名:

$ celery -A proj worker -n worker1@%h
$ celery -A proj worker -n worker2@%h
這裏%h擴展成當前主機名。

我能發送一些任務到限定的一些服務器上嗎?
答案:是的。你可以使用不同的消息路由拓撲,將任務路由到一個或多個worker上,而且一個worker實例可以綁定到多個隊列。
Routing Tasks

我能禁掉任務的預取prefetching嗎?
答案:可能!AMQP的屬於prefetch令人疑惑,因爲它只被用來描述任務預取限制(task prefetching limit)。沒有涉及實際的預取。
禁掉預取限制是可能的,但是那意味着worker會消費儘可能快地消費儘可能多的任務。
一個有關預取限制的討論,和worker的配置設定:同一時間只預定一個任務

我可以在運行時改變週期任務的間隔時間嗎?
答案:可以。你可以使用Django的數據庫調度器,或者你可以創建一個新的調度子類,覆寫is_due():

from celery.schedules import schedule

class my_schedule(schedule):

def is_due(self, last_run_at):
    return run_now, next_time_to_check

Celery支持task優先級嗎?
答案:是的, RabbitMQv3.5.0+就支持優先級,Redis傳輸仿真實現了優先級支持。
你也可以通過將高優先級任務路由到不同的worker中,從而把工作優先級排好。在真實世界中,這通常比每一個消息的優先級更爲奏效。你可以使用速率限制(rate limiting)和單條消息優先級(per message priorities)的組合來實現響應式的系統。

我應該使用重試retry還是acks_late?
答案:看情況。用一個或者另一個都不是必要的,你可能想要使用兩個。
Task.retry用來重試任務,這是可以用try語句catch到可預知的錯誤(expected errors)的。AMQP事務不是用來處理這些錯誤的:如果任務引發了異常,仍然會被應答!

如果某些原因worker在執行中掛掉了,你需要任務再次執行時,可以使用acks_late設置。沒人知道worker掛掉了,這很重要,如果知道worker掛掉,通常有不可恢復的錯誤,需要人工介入(worker或者任務代碼的bug)。
理想情況下,你可以安全地重試任何失敗的任務,但是有少數情況例外,假設有如下任務:

def process_upload(filename, tmpfile):
# Increment a file count stored in a database
increment_file_counter()
add_file_metadata_to_db(filename, tmpfile)
copy_file_to_destination(filename, tmpfile)
如果它在拷貝文件時掛掉了,我們會知道這裏有個未完成狀態存在。這不是個嚴格的教學場景,但你大概可以想象一些更爲災難性的場景。目前爲止,編程較少的話則可靠性更弱。默認值是好的,需要它並且知道它們在幹什麼的用戶仍然能開啓acks_late(未來希望使用手動應答)。
此外,Task.retry在AMQP事務中有不可用的特性:在重試中延遲,最大重試次數等。
因此,可以對Python中的錯誤使用重試,如果你的任務是冪等的而且要求可靠性級別,結合acks_late一起使用。

我可以計劃讓tasks在特定的時間執行嗎?
答案:可以。使用Task.apply_async()的eta參數。週期任務

我可以安全地關閉worker嗎?
答案:是的,使用TERM信號。這會告訴worker去結束所有當前執行的作業,然後儘可能快地關閉。只要完全關閉,實驗性的傳輸中也不應該會有任務丟失。
你決不應該通過KILL信號(kill -9)來停止worker,除非你試過幾次TERM等了幾分鐘看有沒有關閉。

另外,確保你只是殺掉了worker的主進程,而不是它的任何子進程。如果你知道關閉worker所依賴的一個子進程正在執行一個任務,你可以給這個特定的子進程指定一個殺死信號(kill signal),這也意味着任務會被設定一個WorkerLostError狀態,因此這個任務不會再執行了。

如果你安裝了setproctitle模塊,指定進程類型很容易。

$ pip install setproctitle
安裝這個庫,你可以看到在ps命令的列表中看到進程類型,但是worker必須重啓才能生效。

停止worker

我可以在平臺的後端運行worker嗎?
Answer: Yes, please see [Daemonization[(http://docs.celeryproject.org/en/latest/userguide/daemonizing.html#daemonizing.

Django
django-celery-beat創建的數據庫表有什麼目的?
用到數據庫後端定時器(database-backend schedule)的時候,從PeriodicTask的數據模型中取出週期任務計劃有一些其他的輔助表 (IntervalSchedule, CrontabSchedule, PeriodicTasks)。

django-celery-results創建的數據庫表有什麼目的?
Django的數據庫結果後端擴展需要兩個額外的數據模型:TaskResult and GroupResult.

Windows
Celery支持Windows嗎?
答案:不。4.x版本以上就不支持Windows了。

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