Python и RabbitMQ-лучший способ прослушивания событий из нескольких каналов?
У меня есть два отдельных экземпляра RabbitMQ. Я пытаюсь найти лучший способ, чтобы слушать события от обоих.
например, я могу использовать события на одном со следующим:
credentials = pika.PlainCredentials(user, pass)
connection = pika.BlockingConnection(pika.ConnectionParameters(host="host1", credentials=credentials))
channel = connection.channel()
result = channel.queue_declare(Exclusive=True)
self.channel.queue_bind(exchange="my-exchange", result.method.queue, routing_key='*.*.*.*.*')
channel.basic_consume(callback_func, result.method.queue, no_ack=True)
self.channel.start_consuming()
У меня есть второй хост, "host2", который я хотел бы послушать. Я думал о создании двух отдельных потоков для этого, но из того, что я прочитал, пика не является потокобезопасным. Есть ли лучший способ? Или создаст два отдельных потока, каждый из которых будет слушать другого кролика экземпляр (host1 и host2) будет достаточным?
2 ответов
ответ на вопрос" каков наилучший способ "сильно зависит от вашего шаблона использования очередей и что вы подразумеваете под"лучшим". Поскольку я пока не могу комментировать вопросы, я просто попытаюсь предложить некоторые возможные решения.
в каждом примере я собираюсь предположить, что exchange уже объявлен.
темы
вы можете использовать сообщения из двух очередей на отдельных хостах в одном процессе, используя pika
.
вы правы - как свои собственные государства FAQ, pika
не является потокобезопасным, но его можно использовать многопоточным способом, создавая соединения с хостами RabbitMQ на поток. Выполнение этого примера в потоках с помощью threading
модуль выглядит следующим образом:
import pika
import threading
class ConsumerThread(threading.Thread):
def __init__(self, host, *args, **kwargs):
super(ConsumerThread, self).__init__(*args, **kwargs)
self._host = host
# Not necessarily a method.
def callback_func(self, channel, method, properties, body):
print("{} received '{}'".format(self.name, body))
def run(self):
credentials = pika.PlainCredentials("guest", "guest")
connection = pika.BlockingConnection(
pika.ConnectionParameters(host=self._host,
credentials=credentials))
channel = connection.channel()
result = channel.queue_declare(exclusive=True)
channel.queue_bind(result.method.queue,
exchange="my-exchange",
routing_key="*.*.*.*.*")
channel.basic_consume(self.callback_func,
result.method.queue,
no_ack=True)
channel.start_consuming()
if __name__ == "__main__":
threads = [ConsumerThread("host1"), ConsumerThread("host2")]
for thread in threads:
thread.start()
Я объявил callback_func
как метод чисто использовать ConsumerThread.name
при печати текста сообщения. С таким же успехом это может быть функция вне ConsumerThread
класса.
процессы
в качестве альтернативы, вы всегда можете просто запустить один процесс с кодом потребителя для каждой очереди, которую вы хотите использовать события.
import pika
import sys
def callback_func(channel, method, properties, body):
print(body)
if __name__ == "__main__":
credentials = pika.PlainCredentials("guest", "guest")
connection = pika.BlockingConnection(
pika.ConnectionParameters(host=sys.argv[1],
credentials=credentials))
channel = connection.channel()
result = channel.queue_declare(exclusive=True)
channel.queue_bind(result.method.queue,
exchange="my-exchange",
routing_key="*.*.*.*.*")
channel.basic_consume(callback_func, result.method.queue, no_ack=True)
channel.start_consuming()
а затем бегите мимо:
$ python single_consume.py host1
$ python single_consume.py host2 # e.g. on another console
если работа, которую вы делаете с сообщениями из очередей,CPU-тяжелый и пока количество ядер в вашем процессоре >= количество потребителей, обычно лучше использовать этот подход-если ваши очереди не пусты большую часть времени, и потребители не будут использовать этот процессор время.*
асинхронные
Другой альтернативой является привлечение некоторой асинхронной структуры (например Twisted
) и запуск всего этого в одном потоке.
вы больше не можете использовать BlockingConnection
в асинхронном коде; к счастью,pika
имеет адаптер для Twisted
:
from pika.adapters.twisted_connection import TwistedProtocolConnection
from pika.connection import ConnectionParameters
from twisted.internet import protocol, reactor, task
from twisted.python import log
class Consumer(object):
def on_connected(self, connection):
d = connection.channel()
d.addCallback(self.got_channel)
d.addCallback(self.queue_declared)
d.addCallback(self.queue_bound)
d.addCallback(self.handle_deliveries)
d.addErrback(log.err)
def got_channel(self, channel):
self.channel = channel
return self.channel.queue_declare(exclusive=True)
def queue_declared(self, queue):
self._queue_name = queue.method.queue
self.channel.queue_bind(queue=self._queue_name,
exchange="my-exchange",
routing_key="*.*.*.*.*")
def queue_bound(self, ignored):
return self.channel.basic_consume(queue=self._queue_name)
def handle_deliveries(self, queue_and_consumer_tag):
queue, consumer_tag = queue_and_consumer_tag
self.looping_call = task.LoopingCall(self.consume_from_queue, queue)
return self.looping_call.start(0)
def consume_from_queue(self, queue):
d = queue.get()
return d.addCallback(lambda result: self.handle_payload(*result))
def handle_payload(self, channel, method, properties, body):
print(body)
if __name__ == "__main__":
consumer1 = Consumer()
consumer2 = Consumer()
parameters = ConnectionParameters()
cc = protocol.ClientCreator(reactor,
TwistedProtocolConnection,
parameters)
d1 = cc.connectTCP("host1", 5672)
d1.addCallback(lambda protocol: protocol.ready)
d1.addCallback(consumer1.on_connected)
d1.addErrback(log.err)
d2 = cc.connectTCP("host2", 5672)
d2.addCallback(lambda protocol: protocol.ready)
d2.addCallback(consumer2.on_connected)
d2.addErrback(log.err)
reactor.run()
этот подход был бы еще лучше, чем больше очередей вы потребляете и тем меньше CPU-привязан к работе, выполняемой потребителями есть.*
Python 3
раз уж вы упомянули pika
, Я ограничился Python 2.решения на основе x, потому что pika
еще не портировали.
но если вы хотите перейти к >=3.3, один из возможных вариантов-использовать asyncio
С одним из протоколов AMQP (протокол, в котором вы говорите с RabbitMQ), например asynqp
или aioamqp
.
* - обратите внимание, что они очень мелкие советы-в большинстве случаев выбор не так очевиден; что будет лучше для вас, зависит от очередей "насыщенность" (сообщения/время), какую работу вы делаете при получении этих сообщений, в какой среде вы запускаете своих потребителей и т. д.; нет никакого способа быть уверенным, кроме как проверить все реализации
Ниже приведен пример того, как я использую один экземпляр rabbitmq для прослушивания 2 очередей одновременно:
import pika
import threading
threads=[]
def client_info(channel):
channel.queue_declare(queue='proxy-python')
print (' [*] Waiting for client messages. To exit press CTRL+C')
def callback(ch, method, properties, body):
print (" Received %s" % (body))
channel.basic_consume(callback, queue='proxy-python', no_ack=True)
channel.start_consuming()
def scenario_info(channel):
channel.queue_declare(queue='savi-virnet-python')
print (' [*] Waiting for scenrio messages. To exit press CTRL+C')
def callback(ch, method, properties, body):
print (" Received %s" % (body))
channel.basic_consume(callback, queue='savi-virnet-python', no_ack=True)
channel.start_consuming()
def manager():
connection1= pika.BlockingConnection(pika.ConnectionParameters
(host='localhost'))
channel1 = connection1.channel()
connection2= pika.BlockingConnection(pika.ConnectionParameters
(host='localhost'))
channel2 = connection2.channel()
t1 = threading.Thread(target=client_info, args=(channel1,))
t1.daemon = True
threads.append(t1)
t1.start()
t2 = threading.Thread(target=scenario_info, args=(channel2,))
t2.daemon = True
threads.append(t2)
t2.start()
for t in threads:
t.join()
manager()