【问题标题】:RabbitMQ, Pika and reconnection strategyRabbitMQ、Pika 和重连策略
【发布时间】:2012-03-19 11:27:42
【问题描述】:

我正在使用 Pika 处理来自 RabbitMQ 的数据。 由于我似乎遇到了不同类型的问题,我决定编写一个小型测试应用程序来看看我如何处理断开连接。

我编写了这个测试应用程序,它执行以下操作:

  1. 连接到 Broker,重试直到成功
  2. 连接后创建队列。
  3. 使用此队列并将结果放入 python Queue.Queue(0)
  4. 从 Queue.Queue(0) 中获取项目并将其重新生成到代理队列中。

我注意到两个问题:

  1. 当我从一个连接到另一台主机(在 vm 内)上的 rabbitmq 的主机上运行我的脚本时,该脚本会随机退出而不会产生错误。
  2. 当我在安装了 RabbitMQ 的同一主机上运行我的脚本时,它运行良好并继续运行。

这可能是由于网络问题造成的,尽管我发现连接不是很可靠,但数据包被丢弃。

当脚本在 RabbitMQ 服务器上本地运行并且我杀死 RabbitMQ 时,脚本退出并出现错误:“ERROR pika SelectConnection: Socket Error on 3: 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

【问题讨论】:

    标签: python rabbitmq pika


    【解决方案1】:

    您的脚本的主要问题是它正在与来自主线程(ioloop 正在运行的地方)和“代理”线程(循环调用submitData)的单个通道进行交互。这是not safe

    另外,SimpleReconnectionStrategy 似乎没有做任何有用的事情。如果连接中断,它不会导致重新连接。我相信这是 Pika 中的一个错误:https://github.com/pika/pika/issues/120

    我试图重构您的代码以使其按我认为的那样工作,但遇到了另一个问题。 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)
    

    【讨论】:

    • 感谢您花时间浏览代码并找到与之相关的所有问题。我目前正在使用barryp.org/software/py-amqplib,这是一个更基本/更简单的库,但完全适合我的需求。结合 gevent 我有一些非常好的结果。这些天我不再为 Pika 烦恼了。
    • 发布后可以使用Channel.confirm_delivery()等待ack,一旦连接关闭,就会超时,然后你就会知道消息没有传递给broker
    • “Pika 似乎没有办法检测传送失败,这意味着如果连接断开,数据可能会丢失” - 这是不正确。如果您使用发布者确认,您将知道您的消息何时送达。在某些时候,我会提供代码来展示这一点,但您可以使用 java tutorial 作为该概念的示例。
    猜你喜欢
    • 1970-01-01
    • 2014-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多