Entretiens JAVA : Files de Messages

  1. Pourquoi choisir une file de messages dans un projet ? Cas d'utilisation et gestion de la consommation en double

Pourquoi choisir une file de messages ? Cas d'utilisation et gestion de la consommation en doubleValeur fondamentale et scénarios

  • Découplage des systèmes : Les producteurs et consommateurs évoluent indépendamment (comme les événements de modification dans WeChat Work - ajout d'amis, étiquetage, etc., collecte de données de point d'intégration par le client - système d'analyse de point d'intégration).
  • Traitement asynchrone : Opérations chronophages (comme les notifications par SMS, opérations par lots : étiquetage par lots, suppression par lots, calcul de population client, notification des résultats d'envoi de masse groupé) asynchronisées, amélioration de la vitesse de réponse du processus principal.
  • Aplanissement des pics : Gestion des pics de concurrence instantanés (comme l'insertion de messages en base de données, insertion de données de point d'intégration, scénarios de vente flash, augmentation des producteurs), tampon des requêtes via la file de messages.
  • Cohérence transactionnelle : Déduction des stocks après paiement de la commande (messages transactionnels garantissant la cohérence finale).

Solutions pour la consommation en double

  • Conception d'idempotence métier :
  • ID métier unique : comme l'index unique de la base de données, verrou distribué Redis (Redisson).
  • Contrôle par machine d'état : L'état de la commande n'autorise qu'une seule exécution d'opérations spécifiques.
  • Mécanismes natifs de RocketMQ :
  • Clé de message + Vérification : Le producteur définit une clé unique, le consommateur vérifie si cette clé existe dans Redis.
  • Validation manuelle de l'offset : Après consommation réussie, validation manuelle (ACK) pour éviter le retrait en cas de panne.
  1. Analyse détaillée de la structure de stockage des files de messages

Structure de stockage### Structure de stockage de RocketMQ

  • CommitLog
  • Fichier physique de stockage principal de RocketMQ, utilisant un écriture séquentielle
  • Tous les messages de tous les topic sont écrits dans le même CommitLog, améliorant les performances d'écriture et le débit
  • Implémente un mappage mémoire via MappedByteBuffer pour améliorer les performances d'E/S
  • La taille de fichier par défaut est de 1 Go, un nouveau fichier CommitLog est créé automatiquement une fois plein
  • ConsumeQueue
  • File logique maintenue pour chaque topic et queueId
  • Enregistre la position physique, la taille et la valeur de hachage du tag du message dans le CommitLog
  • Chaque enregistrement fait 20 octets fixes, permettant aux consommateurs de localiser rapidement les messages
  • Prend en charge le mécanisme de filtrage des messages basé sur le tag
  • IndexFile
  • Fichier d'index optionnel, prenant en charge la recherche de messages selon la clé ou la plage temporelle
  • Utilise une conception de résolution de conflits par hachage + liste chaînée
  • Permet une localisation rapide de la position des messages pour une clé spécifique

Structure de stockage de Kafka

  • Partition
  • Structure de partition pour un topic, unité de base de traitement parallèle de Kafka
  • Chaque partition correspond à un répertoire log, nommé selon la règle topicName-partitionId
  • Prend en charge l'extension horizontale, augmentation de la capacité de traitement parallèle en augmentant le nombre de partition
  • Segment
  • Chaque partition est composée de plusieurs fichiers segment
  • Contient un fichier de données .log et un fichier d'index .index
  • La taille de segment par défaut est de 1 Go, ajustable via la configuration
  • Prend en charge la stratégie de découpage basée sur le temps ou la taille
  • Écriture séquentielle
  • Les messages sont écrits de manière séquentielle dans la partition
  • Exploite pleinement le mécanisme de cache de pages du système d'exploitation pour réduire les E/S disque
  • Améliore les performances de lecture via la technologie de copie zéro
  • Index épars
  • Le fichier .index maintient la relation entre le décalage du message et sa position physique
  • Utilise un index épars pour réduire la taille du fichier d'index, équilibrant l'efficacité de recherche et les coûts de stockage

Structure de stockage de RabbitMQ

  • Queue
  • La file d'attente est l'unité de stockage principale de RabbitMQ
  • Chaque queue a une structure de stockage indépendante, prenant en charge plusieurs backends de stockage
  • Mécanisme intégré de confirmation des messages, garantissant qu'aucun messsage n'est perdu
  • Persistance des messages
  • Prend en charge la persistance des messages sur disque, gérée via des fichiers comme msg_store et queue_index
  • msg_store stocke le contenu réel des messages
  • queue_index maintient les informations d'index des messages dans la file
  • Mémoire et disque
  • Prend en charge deux modes : stockage en mémoire (hautes performances) et stockage sur disque (haute fiabilité)
  • Configurable pour déterminer quand les messages sont vidés de la mémoire vers le disque
  • Prend en charge le mode lazy queue, gardant les messages sur disque autant que possible
  • File miroir
  • En mode cluster, implémente la réplication de files via le mécanisme mirroring
  • Fournit une haute disponibilité, protégeant contre les pannes uniques
  • Prend en charge les modes synchrone et asynchrone pour la réplication
  1. Garanties de zéro perte et d'absence de duplication de messages

Mécanismes de garantieMécanismes de garantie de zéro perte et d'absence de duplication pour les trois files de messages

Mécanismes de garantie de RocketMQ

Garantie de zéro perte

  • Côté producteur :
  • Mode de synchronisation du vidage sur disque garantit l'écriture des messages sur disque
  • Réplication maître-esclave garantit la sauvegarde des messages sur plusieurs nœuds
  • Mécanisme de nouvelle tentative en cas d'échec d'envoi
  • Côté Broker :
  • L'écriture séquentielle de CommitLog garantit la persistance des données
  • Mécanisme de multiples copies pour éviter les pannes uniques
  • Mécanisme de récupération après panne garantit que les données non vidées ne sont pas perdues
  • Côté consommateur :
  • Mécanisme de confirmation de consommation (ACK) garantit que les messages sont correctement traités
  • Persistance de la position de consommation pour éviter la consommation en double

Garantie d'absence de duplication de messages

  • Conception d'idempotence :
  • Fournit UNIQ_KEY pour garantir l'unicité des messages
  • Le côté consommateur doit implémenter la logique de traitement idempotent
  • Gestion de l'état de consommation :
  • ConsumeQueue enregistre l'état de consommation
  • Prend en charge le mécanisme de saut des messages en double

Mécanismes de garantie de Kafka

Garantie de zéro perte

  • Côté producteur :
  • Configuration acks=all garantit que les messages sont reçus par toutes les répliques
  • Mécanisme de nouvelle tentative pour gérer les échecs d'envoi
  • Support des transactions garantit l'atomicité des messages
  • Côté Broker :
  • Mécanisme de multiples copies (replication.factor)
  • Mécanisme ISR (In-Sync Replicas) garantit la cohérence des données
  • Vidage périodique garantit la persistance des données
  • Côté consommateur :
  • Validation manuelle du décalage garantit que les messages sont correctement traités
  • enable.auto.commit=false désactive la validation automatique

Garantie d'absence de duplication de messages

  • Producteur idempotent :
  • enable.idempotence=true active l'idempotence du producteur
  • producer.id + sequence.number empêche l'envoi en double
  • Support des transactions :
  • transactional.id garantit l'idempotence inter-session
  • Sémantique Exactly-Once garantit

Mécanismes de garantie de RabbitMQ

Garantie de zéro perte

  • Côté producteur :
  • Paramètre mandatory garantit que le message est routé vers une file
  • Mécanisme de confirmation de publication (publisher confirms)
  • Mécanisme transactionnel garantit la fiabilité de l'envoi de messages
  • Côté Broker :
  • Persistance de la file (durable=true)
  • Persistance du message (deliveryMode=2)
  • Files miroir pour éviter la perte de données en cas de panne de nœud
  • Côté consommateur :
  • Mode de confirmation manuelle (autoAck=false)
  • Mécanisme de remise en file d'attente des messages

Garantie d'absence de duplication de messages

  • Idempotence du consommateur :
  • Doit implémenter le traitement idempotent au niveau application
  • Utilisation de l'ID du message pour éviter les doublons
  • File d'attente des lettres mortes :
  • Gère la consommation en double et les messages anormaux
  • Mécanisme transactionnel :
  • Garantit l'atomicité de la publication des messages et des opérations métier

Tableau comparatif

Caractéristique RocketMQ Kafka RabbitMQ
Zéro perte Forte dépendance au vidage sur disque et à la réplication maître-esclave Dépend de acks=all et d'ISR Dépend de la persistance et des mécanismes de confirmation
Pas de duplication Nécessite l'idempotence au niveau application Idempotence du producteur + transactions Nécessite l'idempotence au niveau application
Complexité de configuration Moyenne Élevée Moyenne
Impact sur les performances Moyen Faible Élevé
  1. Problèmes courants des files de messages en production et solutions détaillées (analyse approfondie)

Problèmes courants### 1. Analyse détaillée des problèmes de perte de messages

Analyse des causes fondamentales

  • Côté producteur : Anomalies réseau, absence de réponse du Broker, configuration inappropriée entraînant un échec d'envoi du message
  • Côté Broker : Messages en mémoire non vidés à temps, échec de la réplication maître-esclave, défaillance matérielle
  • Côté consommateur : Non-confirmation correcte après consommation, sortie anormale du consommateur entraînant un retour en arrière des messages

Solutions détaillées pour RocketMQ

Garanties côté producteur:

// Exemple de configuration du producteur
DefaultMQProducer producer = new DefaultMQProducer("GroupeProducteur");
producer.setRetryTimesWhenSendFailed(3);  // 3 tentatives en cas d'échec d'envoi synchrone
producer.setRetryTimesWhenSendAsyncFailed(3);  // 3 tentatives en cas d'échec d'envoi asynchrone
producer.setSendMsgTimeout(3000);  // Délai d'envoi de 3 secondes


Garanties côté Broker:

# Configuration de broker.conf
flushDiskType=SYNC_FLUSH          # Vidage synchrone pour garantir la persistance des données
brokerRole=SYNC_MASTER            # Mode maître-esclave synchrone
haHousekeepingInterval=20000      # Intervalle de vérification de la santé de la connexion HA de 20 secondes
waitTimeMillsInHeartbeatQueue=31000  # Délai d'attente dans la file d'attente des battements de cœur de 31 secondes


Mécanismes de fonctionnement internes:

  1. Lorsque SendMessageProcessor traite les demandes d'envoi de messages, il décide en fonction de flushDiskType s'il faut attendre la fin du vidage sur disque
  2. Après l'écriture de CommitLog, déclenche GroupCommitService pour confirmer le vidage
  3. La réplication maître-esclave est réalisée via HAConnection, le nœud maître attend la confirmation du nœud esclave avant de retourner un succès

Solutions détaillées pour Kafka

Configuration côté producteur:

Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("acks", "all");                    // Attendre la confirmation de toutes les répliques
props.put("retries", 3);                     // 3 tentatives
props.put("enable.idempotence", true);       // Activer l'idempotence
props.put("max.in.flight.requests.per.connection", 1);  // Limite de requêtes par connexion


Configuration côté Broker:

# Configuration de server.properties
min.insync.replicas=2              # Nombre minimum de répliques synchronisées
unclean.leader.election.enable=false  # Interdire aux nœuds non ISR de devenir leader
replication.factor=3               # Facteur de réplication
log.flush.interval.messages=10000  # Vidage sur disque après 10 000 messages
log.flush.interval.ms=1000         # Vidage sur disque chaque seconde


Mécanismes de fonctionnement internes:

  1. La liste ISR (In-Sync Replicas) maintient les répliques disponibles, seule les répliques de la liste ISR peuvent participer à l'élection du leader
  2. HW (High Watermark) marque la limite des messages confirmés, les consommateurs ne peuvent lire que les messages jusqu'à HW
  3. LEO (Log End Offset) suit la position de décalage la plus récente de chaque réplique

Solutions détaillées pour RabbitMQ

Garanties côté producteur:

// Activer le mécanisme de confirmation de publication
channel.confirmSelect();
channel.addConfirmListener(new ConfirmListener() {
    @Override
    public void handleAck(long deliveryTag, boolean multiple) throws IOException {
        // Traitement en cas de confirmation réussie du message
    }
    
    @Override
    public void handleNack(long deliveryTag, boolean multiple) throws IOException {
        // Traitement en cas d'échec de confirmation du message, nécessite renvoi
    }
});


Configuration du Broker:

%% Configuration de rabbitmq.conf
queue_master_locator=min-masters     % Stratégie de sélection du nœud maître de la file
cluster_partition_handling=pause_minority  % Stratégie de traitement de partition de cluster
disk_free_limit.absolute = 1GB       % Seuil d'espace disque


2. Analyse détaillée des problèmes de consommation en double de messages

Scénarios de génération du problème

  • Pics réseau entraînant non-remise de l'ACK au Broker
  • Le consommateur se termine anormalement après traitement
  • Après récupération de panne du Broker, renvoi des messages non confirmés

Solutions de traitement de l'idempotence au niveau application

Basé sur la contrainte unique de la base de données:

@Service
public class ServiceCommande {
    
    @Transactional
    public void traiterCommande(String idCommande, String donneesCommande) {
        try {
            // Utiliser l'ID de commande comme contrainte unique, l'insertion en double lèvera une exception
            Commande commande = new Commande();
            commande.setIdCommande(idCommande);
            commande.setDonnees(donneesCommande);
            commandeRepository.save(commande);  // La contrainte unique de la base de données garantit l'idempotence
            
            // Logique de traitement de la commande
            traiterLogiqueMetier(commande);
        } catch (DataIntegrityViolationException e) {
            // La commande existe déjà, consommation en double, retourner directement succès
            log.info("Commande {} déjà traitée, ignorée...", idCommande);
        }
    }
}


Basé sur la suppression de doublons avec Redis:

@Service
public class ServiceSuppressionDoublons {
    
    @Autowired
    private RedisTemplate redisTemplate;
    
    public boolean estDoublon(String idMessage) {
        String cle = "suppr_doublon:message:" + idMessage;
        Boolean resultat = redisTemplate.opsForValue()
            .setIfAbsent(cle, "1", Duration.ofHours(24));
        return !resultat;  // true signifie déjà existant (doublon), false signifie premier traitement
    }
    
    public void marquerTraite(String idMessage) {
        String cle = "suppr_doublon:message:" + idMessage;
        redisTemplate.opsForValue().set(cle, "1", Duration.ofHours(24));
    }
}


Mécanisme de suppression de doublons de RocketMQ

  • Dans les versions de RocketMQ après 4.3, le mécanisme de messages transactionnels a été introduit, ce qui peut résoudre le problème de "duplication côté producteur", ou en combinaison avec le mécanisme de messages semi-complets, réaliser un effet similaire à Exactly-Once.
  • Mécanisme : Le producteur envoie d'abord un "message semi-complet" (Half Message), invisible pour les consommateurs. Une fois que la transaction locale est exécutée avec succès, il envoie une instruction de "commit", le message devient alors visible.
  • Effet de suppression de doublons : Cette méthode empêche principalement la duplication d'envoi due à des problèmes réseau du producteur, réduisant à la source la génération de messages en double.

3. Analyse détaillée des problèmes d'accumulation de messages

Analyse des causes d'accumulation

  • Capacité de consommation insuffisante
  • Afflux soudain d'un grand nombre de messages
  • Anomalies du consommateur entraînant un arrêt de la consommation
  • Goulots d'étranglement réseau ou système

Solutions de mise à l'échelle

  • Conditions préalables obligatoires à vérifier :
  • Indicateurs de surveillance : longueur de la file de consommation du consommateur, utilisation du CPU, quantité d'accumulation de messages, délai de consommation
  • Seuil de mise à l'échelle : lorsque la capacité de consommation d'un seul nœud atteint la limite (CPU > 70%, file d'attente en croissance continue), démarrer la mise à l'échelle
  • Évaluation des ressources : s'assurer que les nouvelles instances ont suffisamment de ressources (CPU, mémoire, bande passante réseau)
Problèmes et solutions de mise à l'échelle des consommateurs Kafka
  • Problèmes principaux
  • Rééquilibrage entraînant une pause de la consommation : l'ajout de nouveaux consommateurs déclenche une réaffectation des partitions, tous les consommateurs arrêtent de consommer
  • Limite du nombre de partitions : le nombre d'instances de consommateurs ne peut pas dépasser le nombre de partitions du Topic, sinon les instances supplémentaires sont inutilisées
  • Détérioration de l'ordre des messages : la réaffectation des partitions après mise à l'échelle peut entraîner un désordre des messages
  • Risque de consommation en double : pendant le rééquilibrage, des tentatives de reconsommation peuvent être déclenchées

️ Étapes de solution

  1. Vérifier si le nombre de partitions est suffisant
  • Confirmer le nombre actuel de partitions du Topic :
kafka-topics.sh --describe --topic votre-topic


  • Si le nombre de partitions est insuffisant : il faut d'abord étendre les partitions (nécessite de la prudence, peut affecter l'ordre) :
kafka-topics.sh --alter --topic votre-topic --partitions 32


  1. Ajuster la configuration du consommateur
# Augmenter le nombre de threads de consommation
nombre.threads.consommateur=8
# Augmenter la quantité extraite par lot (ajuster selon la taille des messages)
max.enregistrements.extraction=500
# Allonger le délai d'expiration de session, réduire les fausses détections
delai.session.ms=10000


  1. Optimiser le processus de rééquilibrage
# Réduire la fréquence de rééquilibrage
max.intervalle.extraction.ms=300000
# Augmenter l'intervalle de battement de cœur
intervalle.battement.ms=3000


  • Utiliser le protocole Cooperative (Kafka 2.4+) Protocole collant
strategie.affectation.partition=org.apache.kafka.clients.consumer.CooperativeStickyAssignor


  1. Surveiller les effets de la mise à l'échelle
  • Indicateurs clés : délai du consommateur (lag), latence d'extraction, latence de traitement
  • Utiliser les outils de surveillance intégrés de Kafka :
kafka-consumer-groups.sh --describe --group votre-groupe


Problèmes et solutions de mise à l'échelle des consommateurs RabbitMQ
  • Problèmes principaux
  • Mode de consommateurs concurrents entraînant un déséquilibre de charge : plusieurs consommateurs concurrents pour la même file, capacités de traitement inégales
  • Difficulté à garantir l'ordre des messages : traitement simultané des messages par plusieurs consommateurs, impossible de garantir l'ordre
  • Gasillage des ressources : trop de consommateurs entraînant une augmentation du nombre de connexions, mais sans amélioration de la parallélisation
  • Mécanisme de pré-extraction entraînant le blocage des "lents consommateurs" : paramétrage inapproprié de prefetch_count
  • ️ Étapes de solution
  • Ajuster la configuration du consommateur
  • Optimiser la quantité de pré-extraction (essentiel) :
spring.rabbitmq.listener.simple.prefetch=10 # Éviter une valeur trop élevée entraînant le blocage des lents consommateurs


  • Définir un degré de concurrence raisonnable :
spring.rabbitmq.listener.simple.concurrency=5 # Nombre de concurrences initial
spring.rabbitmq.listener.simple.max-concurrency=5 # Nombre de concurrences maximal


  • Scinder les files pour réaliser l'équilibrage de charge
  • Scinder les files par clé métier :
// Répartition par ID de commande
String cleRoutage = "commande." + idCommande;
channel.basicPublish("échangeur", cleRoutage, null, message.getBytes());


  • Créer plusieurs files : diviser la file d'origine en plusieurs sous-files, chaque consommateur traite une file
Problèmes et solutions de mise à l'échelle des consommateurs RocketMQ
  • Problèmes principaux
  • Limite de l'équilibrage de charge au niveau de la file : chaque MessageQueue ne peut être consommée que par un seul consommateur
  • Le nombre de consommateurs dépassant le nombre de files entraîne un gaspillage de ressources : les consommateurs supplémentaires sont inactifs
  • Risque de consommation en double pendant le rééquilibrage : la réaffectation des files peut déclencher des tentatives de retrait
  • Détérioration de l'ordre des messages : la réaffectation des files après mise à l'échelle
  • ️ Étapes de solution
  • Vérifier si le nombre de files est suffisant
  • Si le nombre de files est insuffisant : il faut d'abord étendre le nombre de files du Topic
# Créer un nouveau Topic (temporaire)
mqadmin updateTopic -t topic-temporaire -n localhost:9876 -c ClusterParDefaut -r 32


  • Solution temporaire de mise à l'échelle (en cas d'accumulation sévère) Créer un Topic temporaire : étendre le nombre de files du Topic original à 10 fois Développer un programme de consommation temporaire :
// Consommer le Topic original, transférer vers le Topic temporaire
@RocketMQMessageListener(topic = "topic-original", consumerGroup = "consommateur-temporaire")
public class ConsommateurTemporaire implements RocketMQListener<String> {
    @Override
    public void onMessage(String message) {
        // Transférer uniformément le message vers les différentes files du Topic temporaire
        rocketMQTemplate.send("topic-temporaire", message);
    }
}



  • Déployer de nouveaux consommateurs : consommer le Topic temporaire, capacité de traitement augmentée de 10 fois
  • Ajuster la configuration du consommateur :

// Augmenter le nombre de threads de consommation
consumer.setConsumeThreadMin(20);
consumer.setConsumeThreadMax(50);
// Activer la consommation par lot
consumer.setConsumeMessageBatchMaxSize(32);


  • S'assurer de l'idempotence : doit implémenter la suppression de doublons, éviter la consommation en double
Bonnes pratiques générales
  • Problèmes à vérifier avant mise à l'échelle
  • La mise à l'échelle est-elle vraiment nécessaire : d'abord rechercher les causes de la lenteur de consommation (comme les SQL lents, problèmes de dépendances externes)
  • Y a-t-il suffisamment de files/partitions : s'assurer que la mise à l'échelle permettra d'utiliser efficacement les nouveaux consommateurs
  • L'idempotence est-elle implémentée : éviter les problèmes de consommation en double causés par la mise à l'échelle
  • Stratégie de mise en œuvre de la mise à l'échelle
  • Mise à l'échelle progressive : ajouter 1 ou 2 instances à la fois, observer la stabilité du système
  • Mise à l'échelle en dehors des heures de pointe : effectuer l'opération de mise à l'échelle pendant les heures creuses
  • Déploiement progressif : d'abord déployer un petit nombre de nouvelles instances, vérifier l'absence de problèmes avant un déploiement complet
  • Vérification après mise à l'échelle
  • Surveiller les indicateurs clés : délai de consommation, longueur de la file, utilisation des ressources système
  • Vérifier l'ordre des messages : s'assurer que la logique métier n'est pas effectée
  • Valider l'idempotence : confirmer l'absence de problèmes de consommation en double
  • Mesures préventives
  • Configurer la mise à l'échelle automatique : ajuster automatiquement le nombre de consommateurs basé sur la longueur de la file et le délai de consommation
  • Établir une surveillance et des alertes : détecter à l'avance les goulots d'étranglement potentiels de consommation
  • Tests de charge réguliers : évaluer la capacité maximale de traitement du système, planifier à l'avance la mise à l'échelle
  • Rappel important : la mise à l'échelle n'est pas une solution universelle, il faut d'abord optimiser la capacité de consommation par point (comme ajuster le pool de threads, optimiser les SQL, réduire les appels de dépendances externes). La mise à l'échelle ne doit être envisagée que lorsque la capacité par point atteint la limite et que les ressources files/partitions sont suffisantes. De plus, l'idempotence doit être implémentée avant la mise à l'échelle, sinon cela peut entraîner de graves problèmes métier.

Solutions de contrôle de débit

Basé sur le seau de jetons:

@Component
public class LimiteurDeTauxMessages {
    
    private final Map<String, RateLimiter> limiteurs = new ConcurrentHashMap<>();
    
    public boolean tenterAcquérir(String topic) {
        RateLimiter limiter = limiteurs.computeIfAbsent(topic, 
            k -> RateLimiter.create(1000.0));  // 1000 messages par seconde
        return limiter.tryAcquire();
    }
    
    public void consommerAvecLimiteDeTaux(List<MessageExt> msgs, Consumer<List<MessageExt>> processeur) {
        List<MessageExt> msgsAutorisees = msgs.stream()
            .filter(msg -> tenterAcquérir(msg.getTopic()))
            .collect(Collectors.toList());
        
        if (!msgsAutorisees.isEmpty()) {
            processeur.accept(msgsAutorisees);
        }
    }
}


4. Analyse détaillée des problèmes d'anomalies réseau et de connexion

Diagnostic des anomalies de connexion

Configuration de détection de battements de cœur:

RocketMQ:

# Configuration des battements de cœur côté client
heartbeatBrokerInterval=30000           # Intervalle de battements de cœur de 30 secondes
persistConsumerOffsetInterval=5000      # Intervalle de persistance de la position du consommateur
clientCallbackExecutorThreads=8         # Nombre de threads de rappel


Kafka:

# Configuration de connexion côté client
connections.max.idle.ms=540000          # Temps d'inactivité maximal de connexion de 9 minutes
request.timeout.ms=30000                # Délai d'expiration de la requête de 30 secondes
session.timeout.ms=45000                # Délai d'expiration de session de 45 secondes
heartbeat.interval.ms=3000              # Intervalle de battements de cœur de 3 secondes


RabbitMQ:

ConnectionFactory factory = new ConnectionFactory();
factory.setRequestedHeartbeat(60);      # Intervalle de battements de cœur de 60 secondes
factory.setConnectionTimeout(60000);    # Délai d'expiration de connexion de 60 secondes
factory.setHandshakeTimeout(10000);     # Délai d'expiration de la poignée de main de 10 secondes


Gestion du pool de connexions

Optimisation du pool de connexions RocketMQ:

public class ProducteurMQOptimise {
    
    private DefaultMQProducteur producteur;
    
    public void initialiser() {
        producteur = new DefaultMQProducteur("groupe_producteur_optimise");
        producteur.setSendMessageThreadPoolNums(16);    # Nombre de threads d'envoi
        producteur.setPullMessageThreadPoolNums(64);    # Nombre de threads d'extraction
        producteur.setClientCallbackExecutorThreads(8); # Nombre de threads de rappel
        producteur.demarrer();
    }
    
    public EnvoyerResult envoyer(Message msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
        return producteur.envoyer(msg, 3000);  # Délai d'expiration de 3 secondes
    }
}


5. Analyse détaillée des problèmes de goulot d'étranglement de performance

Paramètres d'optimisation JVM

Paramètres JVM du Broker RocketMQ:

-Xms4g -Xmx4g                          # Mémoire heap de 4G
-XX:+UseG1GC                           # Utiliser le ramasse-miettes G1
-XX:G1HeapRegionSize=16m               # Taille de région G1
-XX:MetaspaceSize=256m                 # Taille de l'espace méta
-XX:MaxDirectMemorySize=4g             # Taille de la mémoire directe


Paramètres JVM du Broker Kafka:

-Xms4g -Xmx4g                          # Mémoire heap de 4G
-server                                # Mode serveur
-XX:+UseG1GC                           # Utiliser le ramasse-miettes G1
-XX:MaxGCPauseMillis=20                # Durée maximale de pause GC de 20ms
-XX:InitiatingHeapOccupancyPercent=35  # Pourcentage d'utilisation du heap déclenchant le GC


Optimisation des E/S disque

Optimisation disque RocketMQ:

# Configuration de store.conf
storePathRootDir=/data/rocketmq/store   # Chemin de stockage
storePathCommitLog=/data/rocketmq/store/commitlog  # Chemin CommitLog
mappedFileSizeCommitLog=1073741824      # Taille de fichier CommitLog de 1GB
deleteWhen=04                           # Suppression des fichiers expirés à 4h du matin
fileReservedTime=72                     # Conservation des fichiers pendant 72 heures


Optimisation disque Kafka:

# Configuration de server.properties
log.dirs=/data/kafka/logs               # Répertoire de stockage des journaux
num.recovery.threads.per.data.dir=2     # Nombre de threads de récupération par répertoire de données
log.retention.hours=168                 # Conservation des journaux pendant 7 jours
log.segment.bytes=1073741824            # Taille de segment de 1GB
log.retention.check.interval.ms=300000  # Intervalle de vérification de la stratégie de conservation de 5 minutes


6. Analyse détaillée de la surveillance et des alertes

Indicateurs de surveillance clés

Indicateurs de surveillance RocketMQ:

  • rocketmq_producteur_tps: TPS du producteur
  • rocketmq_consommateur_tps: TPS du consommateur
  • rocketmq_broker_commitlog_disk_ratio: Taux d'utilisation disque CommitLog
  • rocketmq_consommateur_diff: Quantité d'accumulation de consommation

Indicateurs de surveillance Kafka:

  • kafka_server_broker_topic_metrics_messages_in_total: Total des messages entrants
  • kafka_server_broker_topic_metrics_bytes_in_total: Total des octets entrants
  • kafka_consommateur_groupe_retard: Quantité de retard du groupe de consommateurs
  • kafka_server_replica_manager_under_replicated_partitions: Nombre de partitions non suffisamment répliquées

Indicateurs de surveillance RabbitMQ:

  • rabbitmq_file_messages_ready: Nombre de messages prêts dans la file
  • rabbitmq_file_messages_unacknowledged: Nombre de messages non confirmés
  • rabbitmq_channel_consommateurs: Nombre de consommateurs
  • rabbitmq_node_disk_free: Espace disque restant du nœud

Exemple d'implémentation de surveillance

@Component
public class CollecteurIndicateursMQ {
    
    private final MeterRegistry meterRegistry;
    
    public CollecteurIndicateursMQ(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }
    
    public void enregistrerDelaiMessage(String topic, long delaiMs) {
        Timer.builder("delai.message")
            .tag("topic", topic)
            .register(meterRegistry)
            .record(delaiMs, TimeUnit.MILLISECONDS);
    }
    
    public void enregistrerRetardConsommateur(String groupeConsommateur, String topic, long retard) {
        Gauge.builder("retard.consommateur")
            .tag("groupe", groupeConsommateur)
            .tag("topic", topic)
            .register(meterRegistry, retard);
    }
    
    public void incrementerCompteErreurs(String typeErreur) {
        Counter.builder("erreurs.mq")
            .tag("type", typeErreur)
            .register(meterRegistry)
            .increment();
    }
}


7. Analyse détaillée du dépannage et de la récupération

Techniques d'analyse des journaux

Dépannage des pannes RocketMQ:

# Voir les erreurs clés dans les journaux du Broker
grep -i "error\|exception\|warn" rocketmqlogs/broker.log | tail -100

# Voir la traçabilité des messages
grep "CLE_UNIQUE" rocketmqlogs/store.log | grep "votre_cle_message"

# Vérifier les connexions réseau
netstat -anp | grep :9876  # port namesrv
netstat -anp | grep :10911 # port broker


Dépannage des pannes Kafka:

# Voir les journaux Kafka
tail -f /opt/kafka/logs/server.log

# Voir l'état du groupe de consommateurs
./kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group votre-groupe

# Voir les détails du topic
./kafka-topics.sh --bootstrap-server localhost:9092 --describe --topic votre-topic


Mécanismes de récupération automatique

Implémentation de la vérification de santé:

@Component
public class VerificateurSanteMQ {
    
    @Scheduled(fixedRate = 30000)  # Vérification toutes les 30 secondes
    public void verifierSante() {
        try {
            # Vérifier l'état de la connexion MQ
            if (!estMQSain()) {
                # Déclencher une alerte
                serviceAlerte.envoyerAlerte("Connexion MQ non saine");
                
                # Essayer de se reconnecter
                seReconnecterMQ();
            }
        } catch (Exception e) {
            log.error("Échec de la vérification de santé", e);
        }
    }
    
    private boolean estMQSain() {
        # Implémenter la logique spécifique de vérification de santé
        try {
            # Envoyer un message de test et attendre la réponse
            Message messageTest = new Message("TOPIC_VERIFICATION_SANTE", "ping".getBytes());
            EnvoyerResult resultat = producteur.envoyer(messageTest, 3000);
            return resultat.getEnvoyerStatut() == EnvoyerStatut.ENVOI_OK;
        } catch (Exception e) {
            return false;
        }
    }
}


Ces solutions détaillées couvrent divers problèmes typiques rencontrés dans les systèmes de files de messages en production. Grâce à des configurations appropriées, implémentations de code et moyens de surveillance, on peut garantir efficacement la stabilité et la fiabilité des systèmes de messages.

  1. Différences fondamentales entre RabbitMQ, Kafka et RocketMQ

Différences fondamentales1. Positionnement du modèle de base Kafka : Système de publication-abonnement basé sur des flux de journaux distribués, le noyau est le modèle Topic + Partition, réalisant l'extension horizontale via les partitions RabbitMQ : Modèle de file de messages traditionnel, utilisant l'architecture Exchange + Queue, prenant en charge diverses règles de routage (direct, topic, broadcast, etc.) RocketMQ : Fusion des modèles de file et de publication-abonnement, utilisant l'architecture Topic + Queue, prenant en charge le filtrage par Tag et les messages séquentiels 2. Comparaison des composants clés

Composant Kafka RabbitMQ RocketMQ
Centre de coordination ZooKeeper/KRaft Aucun NameServer (léger)
Stockage des messages Fichiers Segment (écriture séquentielle) Fichiers indépendants par file CommitLog + ConsumeQueue indexé
Unité de message Topic Exchange Topic
Partition physique Partition Queue Queue (similaire aux Partition de Kafka)
Mode de consommation Pull (groupe de consommateurs) Push/Pull Push/Pull (commutable)

Deux, différences clés dans la logique d'implémentation sous-jacente

  1. Mécanisme de stockage Kafka : Conçu pour les E/S disques séquentielles, les messages sont ajoutés aux fichiers Segment, exploitant le PageCache et la technologie de copie zéro pour améliorer les performances, adapté au stockage à long terme et à la compression par lots RabbitMQ : Stockage hybride mémoire + disque, utilisant la base de données mnesia, la persistance nécessite un paramétrage manuel, adapté aux scénarios de consommation rapide RocketMQ : Écriture séquentielle de CommitLog + index ConsumeQueue, prend en charge le vidage synchrone/asynchrone, équilibre performance et fiabilité
  2. Mécanismes de haute disponibilité Kafka : Réalise la synchronisation multi-répliques via le mécanisme ISR (In-Sync Replicas), cohérence forte des données entre répliques, prend en charge le basculement automatique en cas de panne RabbitMQ : Utilise les files miroir (Mirrored Queue) ou les files Quorum (protocole Raft), configuration plus complexe RocketMQ : Architecture maître-esclave (Master-Slave), prend en charge le protocole Dledger pour réaliser la cohérence Raft, basculement automatique du nœud maître en cas de panne
  3. Garantie de l'ordre des messages Kafka : Garanti uniquement l'ordre au sein d'une partition, sans ordre entre partitions, adapté aux scénarios de traitement séquentiel au sein d'une partition RabbitMQ : Ordre au sein d'une seule file, mais impossible de garantir un ordre global en cas de multiples consommateurs RocketMQ : Ordre strict au sein de la file, prend en charge les messages globalement séquentiels, adapté aux scénarios comme les commandes e-commerce
  4. Mécanismes transactionnels et de fiabilité
Caractéristique Kafka RabbitMQ RocketMQ
Support des transactions Transactions de base (0.11+) Pas de transactions natives Messages transactionnels complets (deux phases)
Fiabilité des messages At-least-once (nécessite configuration) At-most-once/Exact-once Exactly-once (en mode transactionnel)
Mécanisme de persistance Vidage asynchrone (par défaut) Vidage synchrone, confirmation ACK Vidage synchrone/asynchrone, multi-répliques
Messages différés Non supporté nativement Via TTL+DLX Support natif (18 niveaux prédéfinis)

Trois, comparaison des performances et des scénarios d'application

  1. Indicateurs de performance
Indicateur Kafka RabbitMQ RocketMQ
Débit TPS de millions (maximal) TPS de milliers TPS de dizaines de milliers (équilibré)
Latence des messages Niveau milliseconde (latence élevée pour la consommation par lot) Niveau microseconde (latence la plus faible) Niveau milliseconde (optimisation de faible latence)
Capacité d'accumulation Extrêmement forte (stockage à long terme) Faible (pression mémoire élevée) Forte (accumulation de milliards de messages)
Capacité d'extension Extension à grande échelle native principalement Extension verticale Capacité d'extension horizontale较强
  1. Scénarios d'application typiques Kafka : Systèmes de collecte de journaux (ELK), traitement de flux de big data (Flink/Spark Streaming), suivi du comportement utilisateur, etc., scénarios à haut débit RabbitMQ : Découplage d'applications d'entreprise (commandes et systèmes de stock), transmission financière à haute fiabilité, scénarios nécessitant des règles de routage complexes RocketMQ : Systèmes de transactions e-commerce (cohérence des commandes), paiements financiers (transactions distribuées), activités de vente flash (haute concurrence + messages séquentiels) Quatre, recommandations de sélection Pour un débit maximal : Choisissez Kafka, adapté à la collecte de journaux, l'analyse de big data, etc. Pour une haute fiabilité et un routage complexe : Choisissez RabbitMQ, adapté aux applications financières et d'entreprise Pour équilibrer débit et fiabilité : Choisissez RocketMQ, adapté aux scénarios e-commerce, financiers, etc. Considérations de besoins spécifiques : Besoin de messages transactionnels → RocketMQ Besoin de latence microseconde → RabbitMQ Besoin d'accumulation massive de messages → Kafka ou RocketMQ Besoin de support multi-protocole → RabbitMQ (AMQP, MQTT, STOMP, etc.) En résumé : Kafka excelle par son haut débit et les flux de journaux distribués, adapté aux scénarios de big data ; RabbitMQ brille par sa haute fiabilité et son routage flexible, idéal pour les applications d'entreprise ; RocketMQ équilibre débit et fiabilité, performant en termes de support transactionnel et de messages séquentiels, choix idéal pour les scénarios e-commerce et financiers.
  2. Implémentation de l'idempotence et du zéro perte

  • Idempotence :
  • Index unique de la base de données : Intercepter les clés métier en double.
  • Jetons d'idempotence Redis : Verrouillage SETNX avant consommation, traitement si succès.
  • Zéro perte :
  • Répliques Dledger : Basculement automatique du nœud principal basé sur le protocole Raft.
  • Mécanisme de point de contrôle : Enregistrer l'horodatage de vidage sur disque, rejouer le journal en cas de récupération de panne.
  1. Raisons de choix de RocketMQ plutôt que Kafka

  • Besoins transactionnels et séquentiels : Scénarios financiers dépendant des messages transactionnels et d'un ordre strict.
  • Support d'entreprise : Alibaba Cloud fournit une surveillance de bout en bout et une auto-guérison des pannes.
  • Filtrage flexible : Prend en charge le filtrage par expressions SQL (comme a > 5 AND b = 'hello').
  1. Mécanismes pour garantir l'absence de duplication de messages

  • Suppression de doublons côté producteur : SequenceId global + cache local.
  • Idempotence du consommateur : Verrou optimiste de la base de données (contrôle par numéro de version).
  1. Gestion de l'accumulation de messages

  • Extension des Consumer : Augmenter le nombre d'instances pour correspondre au nombre de files.
  • Consommation par lot : Ajuster maxBatchSize pour augmenter le débit.
  1. Causes racines de la duplication des messages

  • Pics réseau entraînant des tentatives de retrait du producteur.
  • Délai de traitement du consommateur déclenchant un rejet par le Broker.
  • Après basculement maître-esclave, les messages non synchronisés sont renvoyés.
  1. Processus de rééquilibrage et déclencheurs

  • Conditions de déclenchement : Arrivée/départ de consommateurs, modification du nombre de files.
  • Stratégie d'affectation : Affectation équitable des files (comme 10 files → 3 consommateurs affectés 3-4-3).
  1. Stockage de fichiers et mappage CommitLog

  • CommitLog : Messages ajoutés séquentiellement à un fichier de 1 Go, le nom du fichier indique le décalage de départ.
  • ConsumeQueue : Chaque enregistrement contient le décalage CommitLog, la taille du message et le hachage du Tag, localisation des messages via le décalage logique.
  1. Fonctionnalités du NameServer

  • Gestion de routage : Maintenir la table de mappage Thème → Broker, les clients la récupèrent périodiquement (toutes les 30 secondes).
  • Élimination des pannes : Un Broker sans battement de cœur depuis 30 secondes est retiré de la table de routage.
  1. Fonctionnalités clés de RocketMQ

  1. Haut débit : Écriture séquentielle + mappage mémoire (Mmap) réduisant les pertes d'E/S.
  2. Filtrage flexible : Prend en charge le filtrage par expressions SQL et par hachage de Tag.
  3. Haute disponibilité multi-répliques : Dledger basé sur le protocole Raft réalisant un basculement automatique maître-esclave.
  4. Surveillance d'entreprise : Dashboard intégré et intégration d'indicateurs Prometheus.

Conclusion RocketMQ réalise le zéro perte par la combinaison de messages transactionnels + vidage synchrone + réplication maître-esclave + validation manuelle (ACK), combinée à une conception d'idempotence et à une optimisation du rééquilibrage garantissant la fiabilité des messages. Son modèle de stockage hybride et son support transactionnel d'entreprise le rendent supérieur à Kafka dans les scénarios nécessitant une forte cohérence comme la finance et le e-commerce. Le déploiement réel nécessite d'équilibrer performance et fiabilité selon les SLA métier (comme le vidage synchrone réduisant le débit).

Étiquettes: Java Files de messages RocketMQ Kafka rabbitmq

Publié le 11 juillet à 16h39