簡體   English   中英

使用Celery作為Twisted應用程序的控制通道

[英]Using Celery as a control channel for Twisted applications

我正在嘗試使用Celery作為Twisted應用程序的控制通道。 My Twisted應用程序是一個抽象層,為各種本地運行的進程提供標准接口(通過ProcessProtocol)。 我想使用Celery來遠程控制這個--AMQP似乎是從中央位置控制許多Twisted應用程序的理想方法,我想利用Celery的基於任務的功能,例如任務重試,子任務等。

這不符合我的計划,我希望有人可以幫我指出正確的方向來實現這個目標。

我在運行腳本時嘗試實現的行為是:

  • 開始略微修改的芹菜(見下文)
  • 等待Celery任務
  • 收到“啟動進程”任務后,會生成ProcessProtocol
  • 收到其他任務后,在Twisted協議上運行一個函數,並使用Deferreds返回結果

'略微修改的芹菜'是芹菜的一個小修改,允許任務通過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。 實際發生的是:

  • StartProcTask生成進程,並通過Deffered接收'process started'作為響應
  • CountWordsTask永遠不會收到結果,因為從不調用childDataReceived

任何人都可以對此有所了解,或就如何最好地使用Celery作為Twisted ProcessProtocols的控制通道提供一些建議?

為Celery編寫一個Twisted支持的ProcessPool實現會更好嗎? 我通過reactor.callLater調用WorkerCommand.execute_from_commandline的方法是否正確,以確保在Twisted線程內發生的一切?

我已經讀過AMPoule,我認為它可以提供一些這樣的功能,但是如果可能的話我想堅持使用Celery,因為我在我的應用程序的其他部分使用它。

任何幫助或幫助將不勝感激!

myceleryd.py

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()

protocol.py

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']

tasks.py

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM