web-dev-qa-db-fra.com

Message retardé dans RabbitMQ

Est-il possible d'envoyer un message via RabbitMQ avec un certain retard? Par exemple, je souhaite expirer une session client après 30 minutes et j'envoie un message qui sera traité après 30 minutes.

27
alex

Avec la sortie de RabbitMQ v2.8, la livraison programmée est désormais disponible, mais en tant que fonctionnalité indirecte: http://www.javacodegeeks.com/2012/04/rabbitmq-scheduled-message-delivery.html

11
Jonathan Oliver

Il existe deux approches que vous pouvez essayer:

Ancienne approche: Définissez l'en-tête TTL (durée de vie) dans chaque message/file d'attente (stratégie), puis introduisez une requête DLQ pour le gérer. une fois le ttl expiré, vos messages passeront de DLQ à la file d'attente principale afin que votre auditeur puisse les traiter.

Dernière approche: Récemment, RabbitMQ a conçu Plugin de message différé RabbitMQ, avec lequel vous pouvez obtenir le même support, disponible depuis RabbitMQ-3.5.8.

Vous pouvez déclarer un échange avec le type x-delay-message, puis publier des messages avec l'en-tête personnalisé x-delay exprimant en millisecondes le délai du message. Le message sera remis aux files d'attente respectives après x-delay millisecondes.

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);

Plus ici: git

9
lambodar

Grâce à la réponse de Norman, j'ai pu l'implémenter dans NodeJS.

Tout est assez clair dans le code. J'espère que cela fera gagner du temps à quelqu'un.

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});
});
7
walv

Il semble que cet article de blog décrit l'utilisation de l'échange de lettres mortes et du message ttl pour faire quelque chose de similaire.

Le code ci-dessous utilise CoffeeScript et Node.JS pour accéder à Rabbit et implémenter quelque chose de similaire.

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
6
Norman H

Comme je n'ai pas assez de réputation pour ajouter un commentaire, poster une nouvelle réponse. Ceci est juste un ajout à ce qui a déjà été discuté sur http://www.javacodegeeks.com/2012/04/rabbitmq-scheduled-message-delivery.html

Sauf qu'au lieu de définir ttl sur les messages, vous pouvez le définir au niveau de la file d'attente. De plus, vous pouvez éviter de créer un nouvel échange dans le seul but de rediriger les messages vers une autre file d'attente. Voici un exemple de code Java:

Producteur:

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();
    }
}

Consommateur:

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);
        }

    }
}
6
Sateesh

Ce n'est actuellement pas possible. Vous devez stocker vos horodatages d'expiration dans une base de données ou quelque chose de similaire, puis disposer d'un programme d'assistance qui lit ces horodatages et met un message en file d'attente.

Les messages différés sont une fonctionnalité souvent demandée, car ils sont utiles dans de nombreuses situations. Cependant, si votre besoin est d'expiration des sessions client, je pense que la messagerie n'est pas la solution idéale pour vous et qu'une autre approche pourrait fonctionner mieux.

5
Alessandro

Supposons que vous ayez le contrôle du consommateur, vous pouvez obtenir le délai sur le consommateur comme ceci ??:

Si nous sommes sûrs que le nième message de la file d'attente a toujours un délai plus court que le n + 1 er message (cela peut être le cas pour de nombreux cas d'utilisation): le producteur envoie timeInformation dans la tâche, indiquant l'heure à laquelle ce travail doit être exécuté. (currentTime + delay). Le consommateur: 

1) Lit l'horaire planifié de la tâche

2) si heure courante> heure programmée continue. 

Else delay = scheduleTime - currentTime

sommeil pendant le temps indiqué par délai

Le consommateur est toujours configuré avec un paramètre de concurrence. Ainsi, les autres messages attendent simplement dans la file d'attente jusqu'à ce qu'un consommateur termine le travail. Donc, cette solution pourrait bien fonctionner même si elle semble gênante, surtout en cas de retard important.

0
Swami