Python 使用pika和Tornado连接进行多主题消费

Python 使用pika和Tornado连接进行多主题消费,python,rabbitmq,tornado,pika,Python,Rabbitmq,Tornado,Pika,我有一个pika消费者客户端的类,它基于pika的连接示例代码。我正在尝试从主题队列中使用。问题是,由于连接是以异步方式建立的,因此我无法知道何时建立通道或何时声明队列。我的班级: class PikaClient(object): """ Based on: http://pika.readthedocs.org/en/latest/examples/tornado_consumer.html https://reminiscential.wordpress.com/2

我有一个pika消费者客户端的类,它基于pika的连接示例代码。我正在尝试从主题队列中使用。问题是,由于连接是以异步方式建立的,因此我无法知道何时建立通道或何时声明队列。我的班级:

class PikaClient(object):
    """ Based on:
    http://pika.readthedocs.org/en/latest/examples/tornado_consumer.html
    https://reminiscential.wordpress.com/2012/04/07/realtime-notification-delivery-using-rabbitmq-tornado-and-websocket/
    """

    def __init__(self, exchange, exchange_type):
        self._connection = None
        self._channel = None
        self._closing = False
        self._consumer_tag = None

        self.exchange = exchange
        self.exchange_type = exchange_type
        self.queue = None

        self.event_listeners = set([])

    def connect(self):
        logger.info('Connecting to RabbitMQ')

        cred = pika.PlainCredentials('guest', 'guest')
        param = pika.ConnectionParameters(
            host='localhost',
            port=5672,
            virtual_host='/',
            credentials=cred,
        )

        return pika.adapters.TornadoConnection(param,
            on_open_callback=self.on_connection_open)

    def close_connection(self):
        logger.info('Closing connection')
        self._connection.close()

    def on_connection_closed(self, connection, reply_code, reply_text):
        self._channel = None
        if not self._closing:
            logger.warning('Connection closed, reopening in 5 seconds: (%s) %s',
                           reply_code, reply_text)
            self._connection.add_timeout(5, self.reconnect)

    def on_connection_open(self, connection):
        logger.info('Connected to RabbitMQ')
        self._connection.add_on_close_callback(self.on_connection_closed)
        self._connection.channel(self.on_channel_open)

    def reconnect(self):
        if not self._closing:
            # Create a new connection
            self._connection = self.connect()

    def on_channel_closed(self, channel, reply_code, reply_text):
        logger.warning('Channel %i was closed: (%s) %s',
                       channel, reply_code, reply_text)
        self._connection.close()

    def on_channel_open(self, channel):
        logger.info('Channel open, declaring exchange')
        self._channel = channel
        self._channel.add_on_close_callback(self.on_channel_closed)
        self._channel.exchange_declare(self.on_exchange_declareok,
                                       self.exchange,
                                       self.exchange_type,
                                       passive=True,
                                       )

    def on_exchange_declareok(self, unused_frame):
        logger.info('Exchange declared, declaring queue')
        self._channel.queue_declare(self.on_queue_declareok,
                                   exclusive=True,
                                   auto_delete=True,
                                   )

    def on_queue_declareok(self, method_frame):
        self.queue = method_frame.method.queue

    def bind_key(self, routing_key):
        logger.info('Binding %s to %s with %s',
                    self.exchange, self.queue, routing_key)
        self._channel.queue_bind(self.on_bindok, self.queue,
                                 self.exchange, routing_key)

    def add_on_cancel_callback(self):
        logger.info('Adding consumer cancellation callback')
        self._channel.add_on_cancel_callback(self.on_consumer_cancelled)

    def on_consumer_cancelled(self, method_frame):
        logger.info('Consumer was cancelled remotely, shutting down: %r',
                    method_frame)
        if self._channel:
            self._channel.close()

    def on_message(self, unused_channel, basic_deliver, properties, body):
        logger.debug('Received message # %s from %s',
                    basic_deliver.delivery_tag, properties.app_id)
        #self.notify_listeners(body)

    def on_cancelok(self, unused_frame):
        logger.info('RabbitMQ acknowledged the cancellation of the consumer')
        self.close_channel()

    def stop_consuming(self):
        if self._channel:
            logger.info('Sending a Basic.Cancel RPC command to RabbitMQ')
            self._channel.basic_cancel(self.on_cancelok, self._consumer_tag)

    def start_consuming(self):
        logger.info('Issuing consumer related RPC commands')
        self.add_on_cancel_callback()
        self._consumer_tag = self._channel.basic_consume(self.on_message, no_ack=True)

    def on_bindok(self, unused_frame):
        logger.info('Queue bound')
        self.start_consuming()

    def close_channel(self):
        logger.info('Closing the channel')
        self._channel.close()

    def open_channel(self):
        logger.info('Creating a new channel')
        self._connection.channel(on_open_callback=self.on_channel_open)

    def run(self):
        self._connection = self.connect()

    def stop(self):
        logger.info('Stopping')
        self._closing = True
        self.stop_consuming()
        logger.info('Stopped')
使用它的代码示例(在WebSocketHandler.open中):


尝试运行此操作时,bind_key会引发异常,因为_通道仍然为None。但在通道和队列建立之前,我还没有找到阻止的方法。是否有任何方法可以通过动态主题列表(在消费者开始运行后可能会更改)来实现这一点?

您实际上有一种方法可以知道队列何时建立-队列上的方法
\u declareok()
。该回调将在
self.queue\u declare
方法完成后执行,而
self.queue\u declare
将在
\u channel.exchange\u declare
完成后执行。您可以沿着链返回到您的run方法:

运行
->
连接
->
打开连接
打开连接。频道
->
打开频道
打开频道。交易所申报
在交易所申报
在频道。队列申报
在队列申报

因此,您只需将调用添加到队列上的
bind\u key
declareok
,这将触发对绑定OK上的
的调用,该调用将调用
start\u consuming
。此时,您的客户机实际上正在侦听消息。如果您希望能够动态地提供主题,只需在
PikaClient
的构造函数中使用它们即可。然后,您可以在队列上的每个内部
上调用
bind\u key
。您还需要添加一个标志,表明您已经开始消费,所以您不会尝试两次

类似这样的情况(假设下面未显示的所有方法保持不变):


初始化客户端后,如何更改主题列表?我考虑过停止客户端并启动一个新实例,但肯定有更好的方法。
self.pc = PikaClient('agents', 'topic')
self.pc.run()
self.pc.bind_key('mytopic.*')
def __init__(self, exchange, exchange_type, topics=None):
    self._topics = [] if topics is None else topics
    self._connection = None
    self._channel = None
    self._closing = False
    self._consumer_tag = None
    self._consuming = False

    self.exchange = exchange
    self.exchange_type = exchange_type
    self.queue = None

    self.event_listeners = set([])

def on_queue_declareok(self, method_frame):
    self.queue = method_frame.method.queue
    for topic in self._topics:
        self.bind_key(topic)

def start_consuming(self):
    if self._consuming:
        return
    logger.info('Issuing consumer related RPC commands')
    self.add_on_cancel_callback()
    self._consumer_tag = self._channel.basic_consume(self.on_message, no_ack=True)
    self._consuming = True