1
votes

Configurer correctement ActiveMQ pour éviter les fuites de mémoire Producer

Je ne suis pas un expert ActiveMQ, mais j'ai beaucoup essayé de rechercher des problèmes similaires sur Internet et je suis encore assez confus. J'ai le problème suivant:

Exécution d'une application Web dans Tomcat 8.x, Java 8, Spring Framework 4.3.18. Mon application Web envoie et reçoit des messages avec ActiveMQ, en utilisant la dépendance org.apache.activemq: activemq-spring: 5.11.0 .

Je mets en place une fabrique de connexions ActiveMQ de cette façon:

Class Name                                                                                                  | Shallow Heap | Retained Heap
-------------------------------------------------------------------------------------------------------------------------------------------
java.util.concurrent.ConcurrentHashMap$HashEntry[4] @ 0xe295da78                                            |           32 |    58.160.312
'- table java.util.concurrent.ConcurrentHashMap$Segment @ 0xe295da30                                        |           40 |    58.160.384
   '- [15] java.util.concurrent.ConcurrentHashMap$Segment[16] @ 0xe295d9e0                                  |           80 |    68.573.384
      '- segments java.util.concurrent.ConcurrentHashMap @ 0xe295d9b0                                       |           48 |    68.573.432
         '- sessions org.apache.activemq.state.ConnectionState @ 0xe295d7e0                                 |           40 |    68.575.312
            '- value java.util.concurrent.ConcurrentHashMap$HashEntry @ 0xe295d728                          |           32 |    68.575.344
               '- [1] java.util.concurrent.ConcurrentHashMap$HashEntry[2] @ 0xe295d710                      |           24 |    68.575.368
                  '- table java.util.concurrent.ConcurrentHashMap$Segment @ 0xe295d6c8                      |           40 |    68.575.440
                     '- [12] java.util.concurrent.ConcurrentHashMap$Segment[16] @ 0xe295d678                |           80 |    68.575.616
                        '- segments java.util.concurrent.ConcurrentHashMap @ 0xe295d648                     |           48 |    68.575.664
                           '- connectionStates org.apache.activemq.state.ConnectionStateTracker @ 0xe295d620|           40 |    68.575.808
-------------------------------------------------------------------------------------------------------------------------------------------

La dernière propriété ( maximumActiveSessionPerConnection ) a été définie pour essayer de résoudre le problème suivant (la valeur par défaut semble être 500, ce qui est assez élevé à mon humble avis), mais je ne suis pas sûr que cela ait vraiment aidé, car j'obtiens toujours des erreurs OutOfMemory.

Cette fabrique de connexions est référencée par une fabrique de conteneurs d'écoute:

<int-jms:outbound-channel-adapter id="invoiceEventJmsOutboundChannelAdapter"
    channel="outgoingInvoiceEventJmsChannel" destination-name="outgoingEvent"
    connection-factory="jmsConnectionFactory" explicit-qos-enabled="true" delivery-persistent="true" 
    session-transacted="true" />

<int-jms:outbound-channel-adapter
    id="passwordResetTokenSubmitterJmsOutboundChannelAdapter"
    channel="passwordResetTokenSubmitterJmsChannel"
    destination-name="passwordReset"
    connection-factory="jmsConnectionFactory" explicit-qos-enabled="true"
    delivery-persistent="false" session-transacted="false" />

par un adaptateur entrant Spring Integration 4.3.17:

<int-jms:message-driven-channel-adapter id="invoiceEventJmsInboundChannelAdapter" 
    channel="incomingInvoiceEventJmsChannel"
    connection-factory="jmsConnectionFactory"
    destination-name="incomingEvent"
    max-concurrent-consumers="2"
    transaction-manager="customerTransactionManager"
    error-channel="unexpectedErrorChannel" />

et par deux adaptateurs sortants:

<jms:listener-container factory-id="activationJmsListenerContainerFactory"
    container-type="default" connection-factory="jmsConnectionFactory"
    concurrency="1" transaction-manager="centralTransactionManager">
</jms:listener-container>


0 commentaires

3 Réponses :


0
votes

Nous rencontrons le même problème sur le site d'un client.

En regardant le code ActiveMQ, il y a une classe RemoveTransactionAction dans ConnectionStateTracker qui supprime les entrées en réponse à un message OpenWire RemoveInfo (type 12). Ce message semble être généré par ActiveMQ une fois qu'il a reçu le message.


1 commentaires

J'ai finalement résolu le problème, je pense que dans notre cas, nous avons rencontré le bogue AMQ-6603: issues.apache.org/jira/browse/AMQ-6603 . Je vais ajouter une réponse avec plus de détails.



1
votes

Voici comment j'ai finalement résolu ce problème.

Je pense que le principal problème ici était le bug AMQ-6603 . Donc, la première chose que nous avons faite a été de passer à ActiveMQ 5.15.8. Je pense que cela aurait suffi à réparer la fuite.

Cependant, nous avons également modifié notre configuration un peu après avoir découvert que l'utilisation d'une fabrique de connexions groupées avec une fabrique de conteneurs d'écouteurs est déconseillée. Je pense que la documentation d'ActiveMQ est déroutante et qu'une configuration JMS appropriée est plus compliquée qu'elle ne devrait l'être. Quoi qu'il en soit, si vous lisez la documentation de DefaultMessageListenerContainer , vous lirez:

N'utilisez pas org.springframework.jms.connection.CachingConnectionFactory de Spring en combinaison avec une mise à l'échelle dynamique. Idéalement, ne l'utilisez pas du tout avec un conteneur d'écouteur de messages, car il est généralement préférable de laisser le conteneur d'écouteur lui-même gérer la mise en cache appropriée au cours de son cycle de vie. De plus, l'arrêt et le redémarrage d'un conteneur d'écouteur ne fonctionneront qu'avec une connexion indépendante, mise en cache localement, et non avec une connexion en cache externe.

Il en va de même pour ActiveMQ PooledConnectionFactory alors. Cependant, la documentation ActiveMQ dit à la place:

Le MessagListenerContainer de Spring doit être utilisé pour la consommation de messages. Cela fournit toute la puissance des MDB - consommation JMS efficace et mise en commun des écouteurs de messages - mais sans nécessiter un conteneur EJB complet.

Vous pouvez utiliser l'activemq-pool org.apache.activemq.pool.PooledConnectionFactory pour une mise en commun efficace des connexions et des sessions pour votre collection de consommateurs, ou vous pouvez utiliser Spring JMS org.springframework.jms.connection.CachingConnectionFactory pour obtenir le même effet.

Ainsi, la documentation ActiveMQ suggère le contraire. J'ai ouvert le bug AMQ-7140 pour cela. Pour cette raison, j'injecte maintenant une PooledConnectionFactory (ou une Spring CachingConnectionFactory ) aux clients JMS uniquement, et l'ActiveMQ non mis en cache fabrique de connexions (construite avec ) lors de la construction d'usines de conteneurs d'écouteurs avec Spring ou avec Spring Integration et j'ai plutôt défini leurs attributs liés à la concurrence et au niveau de cache.

La difficulté supplémentaire était que nous transmettions un gestionnaire de transactions construit localement aux usines de conteneurs d'écoute afin de synchroniser la validation du message JMS avec la validation de la base de données. Cela conduit en effet la fabrique de conteneurs d'écouteur à désactiver complètement son mécanisme de mise en cache de connexion par défaut, sauf si vous définissez également un niveau de cache explicite (voir org.springframework.jms.listener.DefaultMessageListenerContainer.setCacheLevel (int) Javadoc) .

Je termine cette réponse en disant qu'il était difficile d'arriver à la solution, surtout parce que je n'ai eu aucun retour de la part d'un développeur ActiveMQ, que ce soit sur le liste de diffusion ou sur le suivi des problèmes, même si à mon humble avis, cela peut être considéré comme un problème de sécurité. Cette inactivité, associée au manque d'alternatives, me fait réfléchir à la question de savoir si je devrais toujours envisager JMS pour mon prochain projet.


1 commentaires

On dirait que je suis confronté à un problème identique ou très similaire ... Je ne sais pas si je peux voir ce qui doit vraiment être fait pour le résoudre ... stackoverflow.com/questions/61406593/…



0
votes

TL; DR

Désactivez anonymousProducers sur votre PooledConnectionFactory .


Nous avons eu un problème très similaire avec un service manquant de mémoire lors de l'utilisation de transactions JMS et une PooledConnectionFactory configurée avec un maximum de 8 connexions.

Cependant, nous n'utilisions pas DefaultMessageListenerContainer ou Spring, et nous n'envoyions que sur un seul producteur.

Ce producteur était responsable de l'envoi d'un grand nombre de messages à partir d'un travail par lots, et nous avons constaté que lorsque la connexion échouait, il laissait ces messages sur le ConnectionStateTracker attaché à l'ancienne connexion. Après un certain nombre de basculements, ces messages s'accumulaient sur les anciennes connexions au point où nous serions à court de tas.

Il semble que le seul moyen d'effacer ces messages de la mémoire soit de fermer le producteur après avoir validé la transaction JMS. Cela supprime l'instance ProducerState de SessionState sur le ConnectionStateTracker .

L'appel à RemoveTransactionAction que SimonD a mentionné dans sa réponse (qui se produit automatiquement après la validation de la transaction JMS) supprime simplement TransactionState du ConnectionState , mais laisse toujours le producteur et ses messages sur l'objet SessionState .

Malheureusement appeler close () sur le producteur ne fonctionne pas directement avec PooledConnectionFactory , car par défaut, il utilise des producteurs anonymes - appeler la méthode close () sur un producteur anonyme n'a aucun effet. Vous devez d'abord appeler setUseAnonymousProducers (false) sur PooledConnectionFactory pour que la fermeture du producteur ait un effet.

Il convient également de souligner que vous devez appeler close () sur le producteur - appeler close () sur la session ne résultera pas en la fermeture du producteur, malgré ce que suggère le JavaDoc pour ActiveMQSession . Au lieu de cela, il appelle la méthode dispose () sur le producteur.


1 commentaires

Merci pour le partage, intéressant à savoir. Malheureusement, lorsque vous utilisez Spring, vous n'avez généralement pas de contrôle direct sur ces composants de bas niveau (et vous ne devriez probablement pas avoir à le faire), donc bien que vous puissiez certainement désactiver les producteurs anonymes au niveau de l'usine, fermer le producteur n'est pas cela droit. Je suis simplement curieux de savoir si vous avez consulté AMQ-6603 .