[英]Using Celery as a control channel for Twisted applications
我正在嘗試使用Celery作為Twisted應用程序的控制通道。 My Twisted應用程序是一個抽象層,為各種本地運行的進程提供標准接口(通過ProcessProtocol)。 我想使用Celery來遠程控制這個--AMQP似乎是從中央位置控制許多Twisted應用程序的理想方法,我想利用Celery的基於任務的功能,例如任務重試,子任務等。
這不符合我的計划,我希望有人可以幫我指出正確的方向來實現這個目標。
我在運行腳本時嘗試實現的行為是:
'略微修改的芹菜'是芹菜的一個小修改,允許任務通過self.app.twisted訪問Twisted反應器,並通過self.app.process生成進程。 為了簡單起見,我正在使用Celery的“獨立”流程池實現,它不會為任務工作者分配新流程。
當我嘗試使用Celery任務初始化ProcessProtocol(即啟動外部進程)時,會出現問題。 該過程正確啟動,但ProcessProtocol的childDataReceived永遠不會被調用。 我認為這與文件描述符沒有被正確繼承/設置有關。
下面是一些示例代碼,基於ProcessProtocol文檔中的“wc”示例。 它包括兩個Celery任務 - 一個用於啟動wc進程,另一個用於計算某些文本中的單詞(使用之前啟動的wc進程)。
這個例子是相當有意思的,但是如果我可以使它工作,它將作為實現我的ProcessProtocols的一個良好的起點,這是長時間運行的進程,它將響應寫入stdin的命令。
我通過首先運行Celery守護進程來測試它:
python2.6 mycelery.py -l info -P solo
然后,在另一個窗口中,運行一個發送兩個任務的腳本:
python2.6 command_test.py
command_test.py的預期行為是執行兩個命令 - 一個啟動wc進程,另一個發送一些文本到CountWordsTask。 實際發生的是:
任何人都可以對此有所了解,或就如何最好地使用Celery作為Twisted ProcessProtocols的控制通道提供一些建議?
為Celery編寫一個Twisted支持的ProcessPool實現會更好嗎? 我通過reactor.callLater調用WorkerCommand.execute_from_commandline的方法是否正確,以確保在Twisted線程內發生的一切?
我已經讀過AMPoule,我認為它可以提供一些這樣的功能,但是如果可能的話我想堅持使用Celery,因為我在我的應用程序的其他部分使用它。
任何幫助或幫助將不勝感激!
from functools import partial
from celery.app import App
from celery.bin.celeryd import WorkerCommand
from twisted.internet import reactor
class MyCeleryApp(App):
def __init__(self, twisted, *args, **kwargs):
self.twisted = twisted
super(MyCeleryApp, self).__init__(*args, **kwargs)
def main():
get_my_app = partial(MyCeleryApp, reactor)
worker = WorkerCommand(get_app=get_my_app)
reactor.callLater(1, worker.execute_from_commandline)
reactor.run()
if __name__ == '__main__':
main()
from twisted.internet import protocol
from twisted.internet.defer import Deferred
class WCProcessProtocol(protocol.ProcessProtocol):
def __init__(self, text):
self.text = text
self._waiting = {} # Dict to contain deferreds, keyed by command name
def connectionMade(self):
if 'startup' in self._waiting:
self._waiting['startup'].callback('process started')
def outReceived(self, data):
fieldLength = len(data) / 3
lines = int(data[:fieldLength])
words = int(data[fieldLength:fieldLength*2])
chars = int(data[fieldLength*2:])
self.transport.loseConnection()
self.receiveCounts(lines, words, chars)
if 'countWords' in self._waiting:
self._waiting['countWords'].callback(words)
def processExited(self, status):
print 'exiting'
def receiveCounts(self, lines, words, chars):
print >> sys.stderr, 'Received counts from wc.'
print >> sys.stderr, 'Lines:', lines
print >> sys.stderr, 'Words:', words
print >> sys.stderr, 'Characters:', chars
def countWords(self, text):
self._waiting['countWords'] = Deferred()
self.transport.write(text)
return self._waiting['countWords']
from celery.task import Task
from protocol import WCProcessProtocol
from twisted.internet.defer import Deferred
from twisted.internet import reactor
class StartProcTask(Task):
def run(self):
self.app.proc = WCProcessProtocol('testing')
self.app.proc._waiting['startup'] = Deferred()
self.app.twisted.spawnProcess(self.app.proc,
'wc',
['wc'],
usePTY=True)
return self.app.proc._waiting['startup']
class CountWordsTask(Task):
def run(self):
return self.app.proc.countWords('test test')
Celery可能在等待來自網絡的新消息時阻塞。 由於您在一個單螺紋過程中與Twisted反應器一起運行它,它會阻止反應堆運行。 這將禁用大部分Twisted,這需要反應器實際運行(你調用reactor.run
,但Celery阻塞它,它實際上沒有運行)。
reactor.callLater
只會延遲Celery的啟動。 一旦芹菜開始,它仍然阻塞反應堆。
您需要避免的問題是阻塞反應堆。
一種解決方案是在一個線程中運行Celery,在另一個線程中運行反應器。 使用reactor.callFromThread
從Celery線程向Twisted(“反應器線程中的調用函數”)發送消息。 如果需要從Twisted線程將消息發送回Celery,請使用Celery等效項。
另一個解決方案是將Celery協議(AMQP? - 請參閱txAMQP )實現為本機Twisted庫,並使用它來處理Celery消息而不會阻塞。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.