web-dev-qa-db-ja.com

RabbitMQでの遅延メッセージ

RabbitMQ経由でメッセージを送信することは可能ですか?たとえば、30分後にクライアントセッションを期限切れにしたいのですが、30分後に処理されるメッセージを送信します。

35
alex

次の2つの方法があります。

古いアプローチ:各メッセージ/キュー(ポリシー)にTTL(存続時間)ヘッダーを設定し、それを処理するDLQを導入します。 ttlが期限切れになると、リスナーが処理できるように、メッセージはDLQからメインキューに移動します。

最新のアプローチ:最近、RabbitMQがRabbitMQ遅延メッセージプラグインを思い付きました。 RabbitMQ-3.5.8。

タイプx-delayed-messageとの交換を宣言してから、メッセージの遅延時間をミリ秒単位で表すカスタムヘッダーx-delayを使用してメッセージを公開できます。メッセージはx-delayミリ秒後にそれぞれのキューに配信されます

byte[] messageBodyBytes = "delayed payload".getBytes("UTF-8");
Map<String, Object> headers = new HashMap<String, Object>();
headers.put("x-delay", 5000);
AMQP.BasicProperties.Builder props = new 
AMQP.BasicProperties.Builder().headers(headers);
channel.basicPublish("my-exchange", "", props.build(), messageBodyBytes);

詳細: git

19
lambodar

RabbitMQ v2.8のリリースで、スケジュールされた配信が利用可能になりましたが、間接的な機能として http://www.javacodegeeks.com/2012/04/rabbitmq-scheduled-message-delivery.html

13
Jonathan Oliver

Normanの回答のおかげで、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});
});
9
walv

このブログ投稿 は、デッドレター交換とメッセージttlを使用して同様のことを行うことを説明しています。

以下のコードは、CoffeeScriptとNode.JSを使用してRabbitにアクセスし、同様のものを実装しています。

amqp   = require 'amqp'
events = require 'events'
em     = new events.EventEmitter()
conn   = amqp.createConnection()

key = "send.later.#{new Date().getTime()}"
conn.on 'ready', ->
  conn.queue key, {
    arguments:{
      "x-dead-letter-exchange":"immediate"
    , "x-message-ttl": 5000
    , "x-expires": 6000
    }
  }, ->
    conn.publish key, {v:1}, {contentType:'application/json'}

  conn.exchange 'immediate'

  conn.queue 'right.now.queue', {
      autoDelete: false
    , durable: true
  }, (q) ->
    q.bind('immediate', 'right.now.queue')
    q.subscribe (msg, headers, deliveryInfo) ->
      console.log msg
      console.log headers
7
Norman H

コメントを追加するのに十分な評判がないので、新しい回答を投稿します。これは http://www.javacodegeeks.com/2012/04/rabbitmq-scheduled-message-delivery.html ですでに説明されているものへの追加です。

メッセージにttlを設定する代わりに、キューレベルで設定できます。また、メッセージを別のキューにリダイレクトするためだけに新しい交換を作成することを回避できます。サンプルJavaコード:

プロデューサー:

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import Java.util.HashMap;
import Java.util.Map;

public class DelayedProducer {
    private final static String QUEUE_NAME = "ParkingQueue";
    private final static String DESTINATION_QUEUE_NAME = "DestinationQueue";

    public static void main(String[] args) throws Exception{
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();

        Map<String, Object> arguments = new HashMap<String, Object>();
        arguments.put("x-message-ttl", 10000);
        arguments.put("x-dead-letter-exchange", "");
        arguments.put("x-dead-letter-routing-key", DESTINATION_QUEUE_NAME );
        channel.queueDeclare(QUEUE_NAME, false, false, false, arguments);

        for (int i=0; i<5; i++) {
            String message = "This is a sample message " + i;
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
            System.out.println("message "+i+" got published to the queue!");
            Thread.sleep(3000);
        }

        channel.close();
        connection.close();
    }
}

消費者:

import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.QueueingConsumer;

public class Consumer {
   private final static String DESTINATION_QUEUE_NAME = "DestinationQueue";

    public static void main(String[] args) throws Exception{
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        QueueingConsumer consumer = new QueueingConsumer(channel);
        boolean autoAck = false;
        channel.basicConsume(DESTINATION_QUEUE_NAME, autoAck, consumer);

        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println(" [x] Received '" + message + "'");
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }

    }
}
7
Sateesh

それは現在不可能です。有効期限のタイムスタンプをデータベースなどに保存し、それらのタイムスタンプを読み取ってメッセージをキューに入れるヘルパープログラムを用意する必要があります。

遅延メッセージは、多くの状況で役立つため、頻繁に要求される機能です。ただし、クライアントセッションを期限切れにする必要がある場合は、メッセージングは​​理想的なソリューションではなく、別のアプローチの方が適していると思います。

6
Alessandro

あなたがコンシューマを制御しているとすると、次のようにコンシューマの遅延を実現できますか?

キューのn番目のメッセージの遅延が常にn + 1番目のメッセージよりも小さいことが確実である場合(これは多くのユースケースで当てはまる可能性があります):プロデューサーは、このジョブを実行する必要がある時間を伝えるタスクでtimeInformationを送信します。 (currentTime + delay)。消費者:

1)タスクからscheduleTimeを読み取ります

2)currentTime> cheduledTimeの場合。

それ以外の場合、delayed = scheduleTime-currentTime

遅延で示される時間スリープ

コンシューマーは常に並行性パラメーターで構成されます。したがって、他のメッセージは、コンシューマーがジョブを完了するまでキューで待機します。そのため、このソリューションは、特に大きな遅延時間の場合には扱いにくく見えますが、うまく機能する可能性があります。

0
Swami