使用pika和TornadoConnection消费多个主题

时间:2015-06-02 16:12:59

标签: python rabbitmq tornado pika

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

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中):

self.pc = PikaClient('agents', 'topic')
self.pc.run()
self.pc.bind_key('mytopic.*')

尝试运行此命令时,bind_key会抛出异常,因为_channel仍为None。但是,在建立通道和队列之前,我还没有找到阻止的方法。是否有任何方法可以使用动态主题列表(在消费者开始运行后可能会改变)?

1 个答案:

答案 0 :(得分:0)

您确实有办法知道队列何时建立 - 方法on_queue_declareok()。一旦self.queue_declare方法完成,将执行该回调,self.queue_declare完成后执行_channel.exchance_declare等。您可以一直关注链接到您的run方法:

run - > connect - > on_connection_open - > _connection.channel - > on_channel_open - > _channel.exchange_declare - > on_exchange_declareok - > _channel.queue_declare - > on_queue_declareok

因此,您只需将您的通话添加到bind_keyon_queue_declareok,这将触发对on_bindok的调用,该调用将调用start_consuming。此时,您的客户端实际上正在侦听消息。如果您希望能够动态提供主题,只需将它们放在PikaClient的构造函数中即可。然后,您可以在每个bind_key内拨打on_queue_declareok。您还需要添加一个标记,表明您已经开始消费,因此您不要尝试两次。

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

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