web-dev-qa-db-ja.com

RabbitMQ、Pika、および再接続戦略

Pikaを使用してRabbitMQからのデータを処理しています。さまざまな種類の問題が発生したようだったので、切断を処理する方法を確認するために小さなテストアプリケーションを作成することにしました。

私は次のことを行うこのテストアプリを書きました:

  1. ブローカーに接続し、成功するまで再試行します
  2. 接続したら、キューを作成します。
  3. このキューを消費し、結果をpython Queue.Queue(0)に入れます
  4. Queue.Queue(0)からアイテムを取得し、ブローカーキューに戻します。

私が気付いたのは2つの問題でした:

  1. あるホストから別のホスト(VM内)のrabbitmqに接続してスクリプトを実行すると、このスクリプトはエラーを生成せずにランダムな瞬間に終了します。
  2. RabbitMQがインストールされているのと同じホストでスクリプトを実行すると、スクリプトは正常に実行され、実行を続けます。

これは、ネットワークの問題、接続があまり堅牢ではないことがわかったにもかかわらずパケットがドロップされたために説明される可能性があります。

スクリプトがRabbitMQサーバーでローカルに実行され、RabbitMQを強制終了すると、スクリプトは次のエラーで終了します:「ERRORpika SelectConnection:Socket Error on3:104」

そのため、再接続戦略を正常に機能させることができないようです。誰かがコードを見て、私が間違っていることを確認できますか?

ありがとう、

ジェイ

#!/bin/python
import logging
import threading
import Queue
import pika
from pika.reconnection_strategies import SimpleReconnectionStrategy
from pika.adapters import SelectConnection
import time
from threading import Lock

class Broker(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.logging = logging.getLogger(__name__)
        self.to_broker = Queue.Queue(0)
        self.from_broker = Queue.Queue(0)
        self.parameters = pika.ConnectionParameters(Host='sandbox',heartbeat=True)
        self.srs = SimpleReconnectionStrategy()
        self.properties = pika.BasicProperties(delivery_mode=2)

        self.connection = None
        while True:
            try:
                self.connection = SelectConnection(self.parameters, self.on_connected,  reconnection_strategy=self.srs)
                break
            except Exception as err:
                self.logging.warning('Cant connect. Reason: %s' % err)
                time.sleep(1)

        self.daemon=True
    def run(self):
        while True:
            self.submitData(self.from_broker.get(block=True))
        pass
    def on_connected(self,connection):
        connection.channel(self.on_channel_open)
    def on_channel_open(self,new_channel):
        self.channel = new_channel
        self.channel.queue_declare(queue='sandbox', durable=True)
        self.channel.basic_consume(self.processData, queue='sandbox')    
    def processData(self, ch, method, properties, body):
        self.logging.info('Received data from broker')
        self.channel.basic_ack(delivery_tag=method.delivery_tag)
        self.from_broker.put(body)
    def submitData(self,data):
        self.logging.info('Submitting data to broker.')
        self.channel.basic_publish(exchange='',
                    routing_key='sandbox',
                    body=data,
                    properties=self.properties)
if __name__ == '__main__':
    format=('%(asctime)s %(levelname)s %(name)s %(message)s')
    logging.basicConfig(level=logging.DEBUG, format=format)
    broker=Broker()
    broker.start()
    try:
        broker.connection.ioloop.start()
    except Exception as err:
        print err
16
jay_t

スクリプトの主な問題は、メインスレッド(ioloopが実行されている場所)と「ブローカー」スレッド(ループ内でsubmitDataを呼び出す)の両方からの単一チャネルと相互作用していることです。これは 安全ではありません です。

また、SimpleReconnectionStrategyは何の役にも立たないようです。接続が中断されても、再接続は発生しません。これはPikaのバグだと思います: https://github.com/pika/pika/issues/12

コードをリファクタリングして、希望どおりに機能するようにしましたが、別の問題が発生しました。 Pikaには配信の失敗を検出する方法がないようです。つまり、接続が切断されるとデータが失われる可能性があります。これはそのような明白な要件のようです! basic_publishが失敗したことを検出する方法がないのはなぜですか?トランザクションやadd_on_return_callback(どれも不格好で複雑すぎるように見えた)を含むあらゆる種類のものを試しましたが、何も思いつきませんでした。本当に方法がない場合、pikaは、RabbitMQに送信されたデータの損失を許容できる状況、またはRabbitMQからのみ消費する必要があるプログラムでのみ役立つようです。

これは信頼できませんが、参考までに、マルチスレッドの問題を解決するコードを次に示します。

import logging
import pika
import Queue
import sys
import threading
import time
from functools import partial
from pika.adapters import SelectConnection, BlockingConnection
from pika.exceptions import AMQPConnectionError
from pika.reconnection_strategies import SimpleReconnectionStrategy

log = logging.getLogger(__name__)

DEFAULT_PROPERTIES = pika.BasicProperties(delivery_mode=2)


class Broker(object):

    def __init__(self, parameters, on_channel_open, name='broker'):
        self.parameters = parameters
        self.on_channel_open = on_channel_open
        self.name = name

    def connect(self, forever=False):
        name = self.name
        while True:
            try:
                connection = SelectConnection(
                    self.parameters, self.on_connected)
                log.debug('%s connected', name)
            except Exception:
                if not forever:
                    raise
                log.warning('%s cannot connect', name, exc_info=True)
                time.sleep(10)
                continue

            try:
                connection.ioloop.start()
            finally:
                try:
                    connection.close()
                    connection.ioloop.start() # allow connection to close
                except Exception:
                    pass

            if not forever:
                break

    def on_connected(self, connection):
        connection.channel(self.on_channel_open)


def setup_submitter(channel, data_queue, properties=DEFAULT_PROPERTIES):
    def on_queue_declared(frame):
        # PROBLEM pika does not appear to have a way to detect delivery
        # failure, which means that data could be lost if the connection
        # drops...
        channel.confirm_delivery(on_delivered)
        submit_data()

    def on_delivered(frame):
        if frame.method.NAME in ['Confirm.SelectOk', 'Basic.Ack']:
            log.info('submission confirmed %r', frame)
            # increasing this value seems to cause a higher failure rate
            time.sleep(0)
            submit_data()
        else:
            log.warn('submission failed: %r', frame)
            #data_queue.put(...)

    def submit_data():
        log.info('waiting on data queue')
        data = data_queue.get()
        log.info('got data to submit')
        channel.basic_publish(exchange='',
                    routing_key='sandbox',
                    body=data,
                    properties=properties,
                    mandatory=True)
        log.info('submitted data to broker')

    channel.queue_declare(
        queue='sandbox', durable=True, callback=on_queue_declared)


def blocking_submitter(parameters, data_queue,
        properties=DEFAULT_PROPERTIES):
    while True:
        try:
            connection = BlockingConnection(parameters)
            channel = connection.channel()
            channel.queue_declare(queue='sandbox', durable=True)
        except Exception:
            log.error('connection failure', exc_info=True)
            time.sleep(1)
            continue
        while True:
            log.info('waiting on data queue')
            try:
                data = data_queue.get(timeout=1)
            except Queue.Empty:
                try:
                    connection.process_data_events()
                except AMQPConnectionError:
                    break
                continue
            log.info('got data to submit')
            try:
                channel.basic_publish(exchange='',
                            routing_key='sandbox',
                            body=data,
                            properties=properties,
                            mandatory=True)
            except Exception:
                log.error('submission failed', exc_info=True)
                data_queue.put(data)
                break
            log.info('submitted data to broker')


def setup_receiver(channel, data_queue):
    def process_data(channel, method, properties, body):
        log.info('received data from broker')
        data_queue.put(body)
        channel.basic_ack(delivery_tag=method.delivery_tag)

    def on_queue_declared(frame):
        channel.basic_consume(process_data, queue='sandbox')

    channel.queue_declare(
        queue='sandbox', durable=True, callback=on_queue_declared)


if __name__ == '__main__':
    if len(sys.argv) != 2:
        print 'usage: %s RABBITMQ_Host' % sys.argv[0]
        sys.exit()

    format=('%(asctime)s %(levelname)s %(name)s %(message)s')
    logging.basicConfig(level=logging.DEBUG, format=format)

    Host = sys.argv[1]
    log.info('connecting to Host: %s', Host)
    parameters = pika.ConnectionParameters(Host=host, heartbeat=True)
    data_queue = Queue.Queue(0)
    data_queue.put('message') # prime the pump

    # run submitter in a thread

    setup = partial(setup_submitter, data_queue=data_queue)
    broker = Broker(parameters, setup, 'submitter')
    thread = threading.Thread(target=
         partial(broker.connect, forever=True))

    # uncomment these lines to use the blocking variant of the submitter
    #thread = threading.Thread(target=
    #    partial(blocking_submitter, parameters, data_queue))

    thread.daemon = True
    thread.start()

    # run receiver in main thread
    setup = partial(setup_receiver, data_queue=data_queue)
    broker = Broker(parameters, setup, 'receiver')
    broker.connect(forever=True)
19
millerdev