RabbitMQ Python——Asynchronous publisher example

# -*- 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/


發佈了101 篇原創文章 · 獲贊 26 · 訪問量 46萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章