简体   繁体   English

使用Celery作为Twisted应用程序的控制通道

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

I am trying to use Celery as the control channel for a Twisted application. 我正在尝试使用Celery作为Twisted应用程序的控制通道。 My Twisted application is an abstraction layer that provides a standard interface to various locally running processes (via ProcessProtocol). My Twisted应用程序是一个抽象层,为各种本地运行的进程提供标准接口(通过ProcessProtocol)。 I would like to use Celery to control this remotely - AMQP seems like the ideal method of controlling many Twisted apps from a central location, and I would like to take advantage of Celery's task-based features, eg task retries, subtasks etc. 我想使用Celery来远程控制这个--AMQP似乎是从中央位置控制许多Twisted应用程序的理想方法,我想利用Celery的基于任务的功能,例如任务重试,子任务等。

This is not working as I had planned, and I am hoping someone can help point me in the right direction to get this working. 这不符合我的计划,我希望有人可以帮我指出正确的方向来实现这个目标。

The behaviour I am trying to achieve when I run my script is: 我在运行脚本时尝试实现的行为是:

  • Start a slightly-modified celeryd (see below) 开始略微修改的芹菜(见下文)
  • Wait for Celery tasks 等待Celery任务
  • When the 'start process' task is received, spawn a ProcessProtocol 收到“启动进程”任务后,会生成ProcessProtocol
  • When other tasks are received, run a function on the Twisted protocol and return the result using Deferreds 收到其他任务后,在Twisted协议上运行一个函数,并使用Deferreds返回结果

The 'slightly-modified celeryd' is celeryd with a small modification that allows tasks to access the Twisted reactor via self.app.twisted, and the spawned process via self.app.process. '略微修改的芹菜'是芹菜的一个小修改,允许任务通过self.app.twisted访问Twisted反应器,并通过self.app.process生成进程。 To keep things simple I am using Celery's 'solo' process pool implentation, which does not fork a new process for task workers. 为了简单起见,我正在使用Celery的“独立”流程池实现,它不会为任务工作者分配新流程。

My problem occurs when I try and use a Celery task to initialise the ProcessProtocol (ie starting the external process). 当我尝试使用Celery任务初始化ProcessProtocol(即启动外部进程)时,会出现问题。 The process starts correctly, but the ProcessProtocol's childDataReceived never gets called. 该过程正确启动,但ProcessProtocol的childDataReceived永远不会被调用。 I think this is something to do with file descriptors not being inherited/set correctly. 我认为这与文件描述符没有被正确继承/设置有关。

Below is some sample code, based on the 'wc' example in the ProcessProtocol documentation. 下面是一些示例代码,基于ProcessProtocol文档中的“wc”示例。 It includes two Celery tasks - one to start the wc process, and another to count the words in some text (using the previously-started wc process). 它包括两个Celery任务 - 一个用于启动wc进程,另一个用于计算某些文本中的单词(使用之前启动的wc进程)。

This example is rather contrived but if I can get this working it will serve as a good starting point for implementing my ProcessProtocols, which are long-running processes which will respond to commands written to stdin. 这个例子是相当有意思的,但是如果我可以使它工作,它将作为实现我的ProcessProtocols的一个良好的起点,这是长时间运行的进程,它将响应写入stdin的命令。

I am testing this by running the Celery daemon first: 我通过首先运行Celery守护进程来测试它:

python2.6 mycelery.py -l info -P solo python2.6 mycelery.py -l info -P solo

Then, in another window, running a script which sends two tasks: 然后,在另一个窗口中,运行一个发送两个任务的脚本:

python2.6 command_test.py python2.6 command_test.py

The expected behaviour of command_test.py is for two commands to execute - one starts the wc process, and the other sends some text to CountWordsTask. command_test.py的预期行为是执行两个命令 - 一个启动wc进程,另一个发送一些文本到CountWordsTask。 What actually happens is: 实际发生的是:

  • The StartProcTask spawns the process, and receives 'process started' as a response via a Deffered StartProcTask生成进程,并通过Deffered接收'process started'作为响应
  • The CountWordsTask never receives a result, because childDataReceived is never called CountWordsTask永远不会收到结果,因为从不调用childDataReceived

Can anyone shed some light on this, or offer some advice on how best to use Celery as a control channel for Twisted ProcessProtocols? 任何人都可以对此有所了解,或就如何最好地使用Celery作为Twisted ProcessProtocols的控制通道提供一些建议?

Would it be better to write a Twisted-backed ProcessPool implementation for Celery? 为Celery编写一个Twisted支持的ProcessPool实现会更好吗? Is my method of calling WorkerCommand.execute_from_commandline via reactor.callLater the right approach to ensure everything happens within the Twisted thread? 我通过reactor.callLater调用WorkerCommand.execute_from_commandline的方法是否正确,以确保在Twisted线程内发生的一切?

I have read about AMPoule, which I think could provide some of this functionality, but would like to stick with Celery if possible as I use it in other parts of my application. 我已经读过AMPoule,我认为它可以提供一些这样的功能,但是如果可能的话我想坚持使用Celery,因为我在我的应用程序的其他部分使用它。

Any help or assistance will be gratefully appreciated! 任何帮助或帮助将不胜感激!

myceleryd.py 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 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 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 probably blocks while waiting for new messages from the network. Celery可能在等待来自网络的新消息时阻塞。 Since you're running it in one single-threaded process along with the Twisted reactor, it blocks the reactor from running. 由于您在一个单螺纹过程中与Twisted反应器一起运行它,它会阻止反应堆运行。 This will disable most of Twisted, which requires the reactor to actually run (you called reactor.run , but with Celery blocking it, it is effectively not running). 这将禁用大部分Twisted,这需要反应器实际运行(你调用reactor.run ,但Celery阻塞它,它实际上没有运行)。

reactor.callLater only delays the startup of Celery. reactor.callLater只会延迟Celery的启动。 Once Celery starts, it's still blocking the reactor. 一旦芹菜开始,它仍然阻塞反应堆。

The problem you need to avoid is blocking the reactor. 您需要避免的问题是阻塞反应堆。

One solution would be to run Celery in one thread and the reactor in another thread. 一种解决方案是在一个线程中运行Celery,在另一个线程中运行反应器。 Use reactor.callFromThread to send messages to Twisted ("call functions in the reactor thread") from the Celery thread. 使用reactor.callFromThread从Celery线程向Twisted(“反应器线程中的调用函数”)发送消息。 Use the Celery equivalent if you need to send messages back to Celery from the Twisted thread. 如果需要从Twisted线程将消息发送回Celery,请使用Celery等效项。

Another solution would be to implement the Celery protocol (AMQP? - see txAMQP ) as a native Twisted library and use that to process Celery messages without blocking. 另一个解决方案是将Celery协议(AMQP? - 请参阅txAMQP )实现为本机Twisted库,并使用它来处理Celery消息而不会阻塞。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM