RabbitMQ,Pika和重新连接策略

时间:2012-02-29 23:21:18

标签: python rabbitmq pika

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

我写了这个测试应用程序,其中包含以下内容:

  1. 连接到Broker,重试直到成功
  2. 连接时创建队列。
  3. 使用此队列并将结果放入python Queue.Queue(0)
  4. 从Queue.Queue(0)获取项目并将其生成回经纪人队列。
  5. 我注意到的是2个问题:

    1. 当我从一台连接到另一台主机上的rabbitmq的主机(在vm中)运行我的脚本时,这些脚本随机退出而不会产生错误。
    2. 当我在安装RabbitMQ的同一主机上运行我的脚本时,它运行正常并继续运行。
    3. 这可能是因为网络问题,数据包丢失,但我发现连接不是很强大。

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

1 个答案:

答案 0 :(得分:19)

您的脚本的主要问题是它与主线程(运行ioloop的地方)和“Broker”线程(在循环中调用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)