web-dev-qa-db-ja.com

RabbitMQで遅延キューを作成する方法は?

Python、Pika、RabbitMQで遅延(または駐車)キューを作成する最も簡単な方法は何ですか?同様の questions を見ましたが、Pythonにはありません。

これは、アプリケーションを設計するときに便利なアイデアだと思います。再キューする必要があるメッセージを調整できるからです。

処理できるメッセージよりも多くのメッセージを受信する可能性が常にあります。HTTPサーバーの速度が遅いか、データベースに過度のストレスがかかっている可能性があります。

また、メッセージの損失に対する許容度がゼロであるシナリオで何かがうまくいかなかったときに非常に便利であり、処理できないメッセージを再キューイングすることでそれを解決できる場合もあります。また、メッセージが何度もキューに入れられるという問題も発生する可能性があります。パフォーマンスの問題を引き起こす可能性があり、スパムを記録します。

41
eandersson

これは、アプリケーションを開発するときに非常に役立ちました。単純にメッセージを再キューイングする代わりになるので。これはコードの複雑さを簡単に減らすことができ、RabbitMQの多くの強力な隠し機能の1つです。

手順

まず、メインキュー用と遅延キュー用の2つの基本チャネルを設定する必要があります。最後の例では、不要なフラグをいくつか追加していますが、コードの信頼性は向上しています。といった confirm deliverydelivery_modeおよびdurable。これらの詳細については、RabbitMQ manual をご覧ください。

チャネルを設定したら、メインチャネルにバインディングを追加します。これを使用して、遅延チャネルからメインキューにメッセージを送信できます。

channel.queue_bind(exchange='amq.direct',
                   queue='hello')

次に、有効期限が切れたメッセージをメインキューに転送するように遅延チャネルを構成する必要があります。

delay_channel.queue_declare(queue='hello_delay', durable=True,  arguments={
  'x-message-ttl' : 5000,
  'x-dead-letter-exchange' : 'amq.direct',
  'x-dead-letter-routing-key' : 'hello'
})
  • x-message-ttl(Message-Time To Live)

    これは通常、特定の期間後にキュー内の古いメッセージを自動的に削除するために使用されますが、2つのオプション引数を追加することでこの動作を変更し、代わりにこのパラメーターにメッセージが遅延キューに留まる時間をミリ秒単位で決定させることができます。

  • x-dead-letter-routing-key

    この変数を使用すると、メッセージを完全に削除するデフォルトの動作の代わりに、期限切れになったメッセージを別のキューに転送できます。

  • x-dead-letter-exchange

    この変数は、hello_delayからhelloキューへのメッセージの転送に使用されたExchangeを決定します。

遅延キューへの公開

すべての基本的なPikaパラメーターの設定が完了したら、基本的なパブリッシュを使用して遅延キューにメッセージを送信するだけです。

delay_channel.basic_publish(exchange='',
                      routing_key='hello_delay',
                      body="test",
                      properties=pika.BasicProperties(delivery_mode=2))

スクリプトを実行すると、RabbitMQ管理モジュールで作成された次のキューが表示されます。 enter image description here

import pika

connection = pika.BlockingConnection(pika.ConnectionParameters(
               'localhost'))

# Create normal 'Hello World' type channel.
channel = connection.channel()
channel.confirm_delivery()
channel.queue_declare(queue='hello', durable=True)

# We need to bind this channel to an exchange, that will be used to transfer 
# messages from our delay queue.
channel.queue_bind(exchange='amq.direct',
                   queue='hello')

# Create our delay channel.
delay_channel = connection.channel()
delay_channel.confirm_delivery()

# This is where we declare the delay, and routing for our delay channel.
delay_channel.queue_declare(queue='hello_delay', durable=True,  arguments={
  'x-message-ttl' : 5000, # Delay until the message is transferred in milliseconds.
  'x-dead-letter-exchange' : 'amq.direct', # Exchange used to transfer the message from A to B.
  'x-dead-letter-routing-key' : 'hello' # Name of the queue we want the message transferred to.
})

delay_channel.basic_publish(exchange='',
                      routing_key='hello_delay',
                      body="test",
                      properties=pika.BasicProperties(delivery_mode=2))

print " [x] Sent"
86
eandersson

RabbitMQ公式プラグインを使用できます:x-delayed-message

まず、 ezファイルYour_rabbitmq_root_path/pluginsにダウンロードしてコピーします

次に、プラグインを有効にします(サーバーを再起動する必要はありません):

rabbitmq-plugins enable rabbitmq_delayed_message_exchange

最後に、次のような「x-delay」ヘッダーを使用してメッセージを公開します。

headers.put("x-delay", 5000);

注意:

メッセージの安全性は保証されません。rabbitmq-serverのダウンタイム中にメッセージの有効期限が切れると、残念ながらメッセージは失われます。したがって、このスキームを使用するときは、注意してください

rabbitmq-delayed-message-exchange でお楽しみください。

15
flycee

参考までに、Spring 3.2.xでこれを行う方法。

<rabbit:queue name="delayQueue" durable="true" queue-arguments="delayQueueArguments"/>

<rabbit:queue-arguments id="delayQueueArguments">
  <entry key="x-message-ttl">
    <value type="Java.lang.Long">10000</value>
  </entry>
  <entry key="x-dead-letter-exchange" value="finalDestinationTopic"/>
  <entry key="x-dead-letter-routing-key" value="finalDestinationQueue"/>
</rabbit:queue-arguments>


<rabbit:fanout-exchange name="finalDestinationTopic">
  <rabbit:bindings>
    <rabbit:binding queue="finalDestinationQueue"/>
  </rabbit:bindings>
</rabbit:fanout-exchange>
8
Ryan Walls

NodeJSの実装。

コードからすべてがかなり明確です。それが誰かの時間を節約することを願っています。

var ch = channel;
ch.assertExchange("my_intermediate_exchange", 'fanout', {durable: false});
ch.assertExchange("my_final_delayed_exchange", 'fanout', {durable: false});

// setup intermediate queue which will never be listened.
// all messages are TTLed so when they are "dead", they come to another exchange
ch.assertQueue("my_intermediate_queue", {
      deadLetterExchange: "my_final_delayed_exchange",
      messageTtl: 5000, // 5sec
}, function (err, q) {
      ch.bindQueue(q.queue, "my_intermediate_exchange", '');
});

ch.assertQueue("my_final_delayed_queue", {}, function (err, q) {
      ch.bindQueue(q.queue, "my_final_delayed_exchange", '');

      ch.consume(q.queue, function (msg) {
          console.log("delayed - [x] %s", msg.content.toString());
      }, {noAck: true});
});
4
walv

シナリオとニーズに応じて、次のアプローチをお勧めします。

0
henrylilei

Rabbitキュー内のメッセージは2つの方法で遅延させることができます-QUEUE TTLを使用-Message TTLを使用して、キュー内のすべてのメッセージを一定時間遅延させる場合TTL。さまざまな時間で各メッセージを遅延させる必要がある場合は、メッセージTTLを使用します。python3およびpikaモジュールを使用して説明しました。 delay_queue(「消費者が消費するのを待っている実際のキューではない」)にメッセージを発行します。delayed_queueのメッセージが期限切れになると、メッセージはexchange 'amq.direct'を使用して実際のキューにルーティングされます

def delay_publish(self, messages, queue, headers=None, expiration=0):
    """
    Connect to RabbitMQ and publish messages to the queue
    Args:
        queue (string): queue name
        messages (list or single item): messages to publish to rabbit queue
        expiration(int): TTL in milliseconds for message
    """
    delay_queue = "".join([queue, "_delay"])
    logging.info('Publishing To Queue: {queue}'.format(queue=delay_queue))
    logging.info('Connecting to RabbitMQ: {Host}'.format(
        Host=self.rabbit_Host))
    credentials = pika.PlainCredentials(
       RABBIT_MQ_USER, RABBIT_MQ_PASS)
    parameters = pika.ConnectionParameters(
       rabbit_Host, RABBIT_MQ_PORT,
        RABBIT_MQ_VHOST, credentials, heartbeat_interval=0)
    connection = pika.BlockingConnection(parameters)

    channel = connection.channel()
    channel.queue_declare(queue=queue, durable=True)

    channel.queue_bind(exchange='amq.direct',
                       queue=queue)
    delay_channel = connection.channel()
    delay_channel.queue_declare(queue=delay_queue, durable=True,
                                arguments={
                                    'x-dead-letter-exchange': 'amq.direct',
                                    'x-dead-letter-routing-key': queue
                                })

    properties = pika.BasicProperties(
        delivery_mode=2, headers=headers, expiration=str(expiration))

    if type(messages) not in (list, Tuple):
        messages = [messages]

    try:
        for message in messages:
            try:
                json_data = json.dumps(message)
            except Exception as err:
                logging.error(
                    'Error Jsonify Payload: {err}, {payload}'.format(
                        err=err, payload=repr(message)), exc_info=True
                )
                if (type(message) is dict) and ('data' in message):
                    message['data'] = {}
                    message['error'] = 'Payload Invalid For JSON'
                    json_data = json.dumps(message)
                else:
                    raise

            try:
                delay_channel.basic_publish(
                    exchange='', routing_key=delay_queue,
                    body=json_data, properties=properties)
            except Exception as err:
                logging.error(
                    'Error Publishing Data: {err}, {payload}'.format(
                        err=err, payload=json_data), exc_info=True
                )
                raise

    except Exception:
        raise

    finally:
        logging.info(
            'Done Publishing. Closing Connection to {queue}'.format(
                queue=delay_queue
            )
        )
        connection.close()
0