web-dev-qa-db-fra.com

RabbitMQ: producteur rapide et consommateur lent

J'ai une application qui utilise RabbitMQ comme file d'attente pour envoyer/recevoir un message entre deux composants: l'expéditeur et le destinataire. L'expéditeur envoie un message très rapidement. Le destinataire reçoit le message, puis effectue une tâche très fastidieuse (principalement l’écriture dans une base de données pour des données de très grande taille). Étant donné que le destinataire met beaucoup de temps à terminer la tâche, puis à récupérer le message suivant dans la file d'attente, l'expéditeur continuera à remplir rapidement la file d'attente. Ma question est donc la suivante: cela entraînera-t-il un débordement de la file de messages? 

Le consommateur de message se présente comme suit:

public void onMessage() throws IOException, InterruptedException {
    channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
    String queueName = channel.queueDeclare("allDataCase", true, false, false, null).getQueue();
    channel.queueBind(queueName, EXCHANGE_NAME, "");

    QueueingConsumer consumer = new QueueingConsumer(channel);
    channel.basicConsume(queueName, true, consumer);

    while (true) {
        QueueingConsumer.Delivery delivery = consumer.nextDelivery();
        String message = new String(delivery.getBody());
        System.out.println(" [x] Received '" + message + "'");

        JSONObject json = new JSONObject(message);
        String caseID = json.getString("caseID");
        //following takes very long time            
        dao.saveToDB(caseID);
    }
}

Chaque message reçu par le consommateur contient un identifiant de cas. Pour chaque ID de cas, cela économisera une grande quantité de données dans la base de données, ce qui prend beaucoup de temps. Actuellement, un seul consommateur est configuré pour RabbitMQ car le producteur/consommateur utilise la même file d'attente pour la publication/l'abonnement de caseID. Alors, comment puis-je accélérer le débit du consommateur afin que celui-ci puisse rattraper le producteur et éviter le débordement de messages dans la file d'attente? Devrais-je utiliser le multithreading dans la partie consommateur pour accélérer le taux de consommation? Ou devrais-je utiliser plusieurs consommateurs pour consommer le message entrant simultanément? Ou existe-t-il un moyen asynchrone permettant au consommateur de consommer le message de manière asynchrone sans attendre sa fin? Toutes les suggestions sont les bienvenues. 

14
tonga

"Cela entraînera-t-il un débordement de la file d'attente de messages?"

Oui. RabbitMQ entrera dans un état de "contrôle de flux" pour éviter une consommation excessive de mémoire lorsque la longueur de la file d'attente augmente. Il commencera également à conserver les messages sur le disque, plutôt que de les conserver en mémoire.

"Alors, comment puis-je accélérer le débit du consommateur afin que celui-ci Puisse rattraper le producteur et éviter le débordement de messages dans la file d'attente "

Vous avez 2 options:

  1. Ajouter plus de consommateurs. N'oubliez pas que votre base de données sera désormais manipulée par plusieurs processus simultanés si vous choisissez cette option. Assurez-vous que le DB peut résister à la pression supplémentaire.
  2. Augmentez la valeurQOSdu canal consommateur. Cela extraira plus de messages de la file d'attente et les mettra en mémoire tampon sur le consommateur. Cela augmentera le temps de traitement global; si 5 messages sont mis en mémoire tampon, le 5ème message mettra le temps de traitement des messages 1 à 5 à compléter.

"Devrais-je utiliser le multithreading dans la partie consommateur pour accélérer le taux de consommation ?"

Non, sauf si vous avez une solution bien conçue. L'ajout de parallélisme à une application va engendrer beaucoup de surcharge du côté du consommateur. Vous pouvez finir par épuiser le ThreadPool ou limiter l’utilisation de la mémoire.

Lorsque vous traitez avec AMQP, vous devez vraiment prendre en compte les exigences métier de chaque processus afin de concevoir la solution optimale. Dans quelle mesure les messages entrants sont-ils sensibles au facteur temps? Doivent-ils être conservés dans DB ASAP ou est-ce important pour vos utilisateurs de savoir si ces données sont disponibles immédiatement?

Si les données ne doivent pas nécessairement être immédiatement conservées, vous pouvez modifier votre application afin que le ou les consommateurs suppriment simplement les messages de la file d'attente et les enregistrent dans une collection mise en cache, dans Redis par exemple. Introduisez un deuxième processus qui lit et traite ensuite les messages mis en cache de manière séquentielle. Cela garantira que la longueur de votre file d'attente n'augmente pas suffisamment pour permettre un contrôle du flux, tout en évitant que votre base de données ne soit bombardée de demandes d'écriture, généralement plus coûteuses que les demandes de lecture. Votre client (s) supprime maintenant simplement les messages de la file d'attente pour être traités ultérieurement par un autre processus.

14
Paul Mooney

S'il est vrai que l'ajout de plus de consommateurs peut accélérer les choses, le vrai problème sera de sauvegarder dans la base de données.

Il y a déjà beaucoup de réponses ici qui parlent d'ajouter des consommateurs (threads et/ou machines) et de changer la QoS, donc je ne vais pas répéter cela. Au lieu de cela, vous devriez sérieusement envisager d’utiliser le motif Aggregator pour regrouper les messages en un groupe de messages, puis insérer par lots le groupe dans votre base de données en une seule fois.

Votre code actuel pour chaque message ouvre probablement une connexion, insère les données et ferme la connexion (ou retourne au pool). Pire, il peut même utiliser des transactions. 

En utilisant le modèle d'agrégation, vous mettez essentiellement les données en mémoire tampon avant de les vider.

Maintenant, écrire un bon agrégateur est délicat. Vous devrez décider comment vous souhaitez mettre en mémoire tampon (chaque ouvrier a son propre tampon ou un tampon central comme Redis). L'intégration printanière a un agrégateur, je crois.

1
Adam Gent

Vous avez beaucoup de façons d'augmenter vos performances. 

  1. Vous pouvez créer une file d'attente de travail avec plus de producteurs. Vous créez ainsi un système d'équilibrage de charge simple. n'utilisez pas d'échange ---> file d'attente mais seulement file d'attente. Lire cet article RabbitMQ Dispatching Non Round Round

  2. Lorsque vous recevez un message, vous pouvez créer un poolthread pour insérer les données dans votre base de données, mais dans ce cas, vous devez gérer l'échec.

Mais je pense que le principal problème est la base de données et non RabbitMQ. Avec une configuration optimisée, une file d'attente multi-threading et une file d'attente de travail, vous pouvez disposer d'une solution évolutive et rapide.

Faites le moi savoir 

1
Gabriele

"Alors, comment puis-je accélérer le débit du consommateur afin que celui-ci puisse rattraper le producteur et éviter le débordement de messages dans la file d'attente?" C'est la réponse "utiliser plusieurs consommateurs pour consommer le message entrant simultanément", utiliser le multi-thread pour s'exécuter en parallèle, ces consommateurs mettant en œuvre le principe ne partageant rien, http://www.eaipatterns.com/CompetingConsumers.html

1
voutrin

Comme réponse, je suggère: les deux.

Vous pouvez tirer parti de la présence de plusieurs destinataires, ainsi que de la configuration de chaque destinataire pour exécuter la tâche dans un thread distinct, permettant ainsi au destinataire d’accepter le message suivant dans la file d’attente.

Bien sûr, cette approche suppose que le résultat de chaque opération (l'écriture sur la base de données, si j'ai bien compris) n'influence en aucune manière le résultat des opérations ultérieures en réponse à d'autres messages.

0
mbera