2つの個別の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()
2つ目のホスト「Host2」もあるので、これも聞きたいです。これを行うために2つの別個のスレッドを作成することを考えましたが、私が読んだことから、pikaはスレッドセーフではありません。もっと良い方法はありますか?または、2つの別々のスレッドを作成し、それぞれが異なるRabbitインスタンス(Host1とHost2)をリッスンするだけで十分でしょうか?
「最善の方法」への答えは、キューの使用パターンと「最善」の意味に大きく依存します。まだ質問にはコメントできないので、考えられる解決策をいくつか提案します。
各例では、交換がすでに宣言されていると想定します。
pika
を使用して、単一のプロセスで別々のホスト上の2つのキューからメッセージを消費できます。
あなたは正しいです- それ自身の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
クラス外の関数である可能性もあります。
または、イベントを消費するキューごとに、常に1つのプロセスをコンシューマーコードで実行することもできます。
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-heavy であり、CPUのコア数> =コンシューマの数である限り、キューを除いて、このアプローチを使用することをお勧めしますほとんどの場合空であり、消費者はこの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の制限が少なくなります*。
pika
について言及したので、pika
はまだ移植されていないため、私はPython 2.xベースのソリューションに限定しました。
ただし、> = 3.3に移行する場合、可能なオプションの1つは、AMQPプロトコル(RabbitMQで話すプロトコル)の1つで asyncio
を使用することです。 asynqp
または aioamqp
。
*-これらは非常に浅いヒントであることに注意してください-ほとんどの場合、選択はそれほど明白ではありません。何が最適かは、キューの「飽和」(メッセージ/時間)、これらのメッセージを受信したときにどのような作業を行うか、コンシューマーを実行する環境などによって異なります。すべての実装をベンチマークする以外に確実な方法はありません
以下は、1つの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()