# -*- coding: utf-8 -*- import logging import pika import json LOG_FORMAT = ('%(levelname) -10s %(asctime)s %(name) -30s %(funcName) ' '-35s %(lineno) -5d: %(message)s') LOGGER = logging.getLogger(__name__) class ExamplePublisher(object): EXCHANGE = 'message' EXCHANGE_TYPE = 'topic' PUBLISH_INTERVAL = 1 QUEUE = 'text' ROUTING_KEY = 'example.text' def __init__(self,ampq_url): ''' Connect to RabbitMQ :param str amqp_url: The URL for connecting to RabbitMQ ''' self._connection = None self._channel = None self._deliveries = [] self._acked = 0 self._nacked = 0 self._message_number = 0 self._stopping = False self._url = ampq_url self._closing = False def connect(self): LOGGER.info("Connecting to %s",self._url) ''' class pika.adapters.select_connection.SelectConnection(parameters=None, on_open_callback=None, stop_ioloop_on_close=True) ''' return pika.SelectConnection(pika.URLParameters(self._url),self.on_connection_open) def on_connection_open(self,unused_connection): LOGGER.info("Connection opened") self.add_on_connection_close_callback() self.open_channel() def open_channel(self): LOGGER.info('Creating a new channel') """This method will open a new channel with RabbitMQ by issuing the Channel.Open RPC command. When RabbitMQ confirms the channel is open by sending the Channel.OpenOK RPC reply, the on_channel_open method will be invoked. The channel method in `connection`, then it will call `channel.Channel` see the original code """ self._connection.channel(on_open_callback = self.on_channel_open) def on_channel_open(self,channel): LOGGER.info('Channel opened') self._channel = channel self.add_on_channel_close_callback() self.setup_exchange(self.EXCHANGE) def setup_exchange(self,exchange_name): ''' Setup the exchange on RabbitMQ ''' LOGGER.info('Declaring exchange %s', exchange_name) self._channel.exchange_declare(self.on_exchange_declareok, exchange=exchange_name, type=self.EXCHANGE_TYPE,durable=True) def on_exchange_declareok(self,unused_frame): ''' Setup the queue on RabbitMQ ''' LOGGER.info("Exchange declared") self.setup_queue(self.QUEUE) def setup_queue(self,queue_name): ''' Setup the queue on RabbitMQ ''' LOGGER.info("Declaring queue %s",queue_name) self._channel.queue_declare(self.on_queue_declareok,queue_name,durable=True) def on_queue_declareok(self,method_frame): LOGGER.info('Binding %s to %s with %s', self.EXCHANGE, self.QUEUE, self.ROUTING_KEY) self._channel.queue_bind(self.on_bindok,self.QUEUE,self.EXCHANGE,self.ROUTING_KEY) def on_bindok(self,unused_frame): LOGGER.info("Queue bound") self.start_publishing() def add_on_connection_close_callback(self): LOGGER.info("Adding connection close callback") self._connection.add_on_close_callback(self.on_connection_closed) ''' Add a callback notification when the connection has closed. The callback will be passed the connection, the reply_code (int) and the reply_text (str), if sent by the remote server. ''' def on_connection_closed(self,connection,reply_code,reply_text): self._channel = None if self._closing: self._connection.ioloop.stop() else: LOGGER.warning('Connection closed, reopening in 5 seconds: (%s) %s', reply_code, reply_text) self._connection.add_timeout(5, self.reconnect) def reconnect(self): self._connection.ioloop.stop() self._connection = self.connect() self._connection.ioloop.start() def add_on_channel_close_callback(self): LOGGER.info('Adding channel close callback') self._channel.add_on_close_callback(self.on_channel_closed) def on_channel_closed(self, channel, reply_code, reply_text): LOGGER.warning('Channel was closed: (%s) %s', reply_code, reply_text) if not self._closing: self._connection.close() def start_publishing(self): LOGGER.info('Issuing consumer related RPC commands') self.enable_delivery_confirmations() self.schedule_next_message() def enable_delivery_confirmations(self): ''' Send the Confirm.Select RPC method to RabbitMQ to enable delivery confirmations on the channel. The only way to turn this off is to close the channel and create a new one. When the message is confirmed from RabbitMQ, the on_delivery_confirmation method will be invoked passing in a Basic.Ack or Basic.Nack method from RabbitMQ that will indicate which messages it is confirming or rejecting. ''' LOGGER.info('Issuing Confirm.Select RPC command') self._channel.confirm_delivery(self.on_delivery_confirmation) def on_delivery_confirmation(self,method_frame): confirmation_type = method_frame.method.NAME.split('.')[1].lower() LOGGER.info('Received %s for delivery tag: %i', confirmation_type, method_frame.method.delivery_tag) if confirmation_type == 'ack': self._acked += 1 elif confirmation_type == 'nack': self._nacked += 1 self._deliveries.remove(method_frame.method.delivery_tag) LOGGER.info('Published %i messages, %i have yet to be confirmed, ' '%i were acked and %i were nacked', self._message_number, len(self._deliveries), self._acked, self._nacked) def schedule_next_message(self): """If we are not closing our connection to RabbitMQ, schedule another message to be delivered in PUBLISH_INTERVAL seconds. """ if self._stopping: return LOGGER.info('Scheduling next message for %0.1f seconds', self.PUBLISH_INTERVAL) self._connection.add_timeout(self.PUBLISH_INTERVAL, self.publish_message) def publish_message(self): if self._stopping: return message = {u'مفتاح': u' قيمة', u'键': u'值', u'キー': u'値'} properties = pika.BasicProperties(app_id='example-publisher', content_type='text/plain', headers=message) self._channel.basic_publish(self.EXCHANGE, self.ROUTING_KEY, json.dumps(message, ensure_ascii=False), properties) self._message_number += 1 self._deliveries.append(self._message_number) LOGGER.info('Published message # %i', self._message_number) self.schedule_next_message() def run(self): self._connection = self.connect() self._connection.ioloop.start() def close_channel(self): LOGGER.info('Closing the channel') if self._channel: self._channel.close() def close_connection(self): LOGGER.info('Closing connection') self._closing = True self._connection.close() def stop(self): LOGGER.info('Stopping') self._stopping = True self.close_channel() self.close_connection() self._connection.ioloop.start() LOGGER.info('Stopped') def main(): logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) # Connect to localhost:5672 as guest with the password guest and virtual host "/" (%2F) example = ExamplePublisher('amqp://guest:guest@localhost:5672/%2F?connection_attempts=3&heartbeat_interval=3600') try: example.run() except KeyboardInterrupt: example.stop() if __name__ == '__main__': main()
本文参考:https://pika.readthedocs.org/en/latest/examples/asynchronous_publisher_example.html
在Channel queue_declare API中有这么一句话Declare queue, create if needed. This method creates or checks a queue. When creating a new queue the client can specify various properties that control the durability of the queue and its contents, and the level of sharing for the queue.
这句话的意思就是只有当消息队列是新建的时候才能使用durable=True参数,否则会出现莫名其妙的错误。
BlockingConnection和SelectConnection所使用的API是不同的,前者使用BlockingChannel,后者使用Channel。
Pika的API文档:https://pika.readthedocs.org/en/0.9.12/