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