Messagerie et streaming d'événements
Architecture d'Apache Kafka : topics, partitions, réplication, consumer groups et KRaft
Intermédiaire13 min de lectureMis à jour
Cette page a été traduite de l'anglais avec l'aide de l'IA, puis vérifiée par rapport à l'original. Lire la version anglaise
Kafka n'est pas une file d'attente dotée de fonctionnalités en plus : c'est un log partitionné et répliqué que les consommateurs lisent à leur propre rythme. Ce guide construit ce modèle pas à pas — enregistrements, partitions, offsets, segments, rétention et compaction, réplicas leader et followers, ensemble des réplicas synchronisés, consumer groups et rééquilibrage, et le quorum de métadonnées KRaft qui a remplacé ZooKeeper — pour que les réglages des producteurs, l'exploitation et les choix de conception prennent tout leur sens par la suite.
Ce que vous allez apprendre
- Expliquer pourquoi Kafka est un log et non une file, et ce que cela change pour les consommateurs
- Décrire partitions, offsets, segments, rétention et compaction, et leurs interactions
- Expliquer la réplication leader/follower, l'ensemble des réplicas synchronisés, acks et min.insync.replicas
- Décrire les consumer groups, les commits d'offsets et le rééquilibrage, ainsi que leur effet sur l'ordre et le parallélisme
- Expliquer le rôle du quorum de contrôleurs KRaft et comment il a remplacé ZooKeeper
- Choisir le nombre de partitions, les clés et le facteur de réplication d'un nouveau topic
Apache Kafka est né pour transporter des logs d'activité à l'échelle de LinkedIn, avant de devenir la colonne vertébrale par défaut de l'event streaming. On le présente souvent comme « une file de messages qui passe à l'échelle », ce qui pousse à l'utiliser comme tel — à tort. Kafka est un commit log distribué : une séquence d'enregistrements en ajout seul (append-only), découpée en partitions réparties sur des brokers, répliquée pour la sûreté, conservée pendant une durée configurée et lue par des consommateurs qui mémorisent eux-mêmes leur position. Tout le reste découle de cette conception. Ce guide porte sur Kafka 3.x en mode KRaft et traite ZooKeeper comme de l'histoire ancienne.
Enregistrements, topics et partitions
Un enregistrement (record) se compose d'une clé, d'une valeur, d'un horodatage et d'en-têtes facultatifs. Pour Kafka, clé et valeur sont des tableaux d'octets ; la sérialisation est entièrement l'affaire du client. Un topic est une catégorie nommée d'enregistrements, découpée en une ou plusieurs partitions. Chaque partition est une séquence ordonnée et immuable : un enregistrement ajouté à la partition 3 reçoit l'offset suivant de la partition 3, un nombre sur 64 bits qui ne change jamais et ne se répète jamais au sein de cette partition.
Trois conséquences façonnent toute conception Kafka :
- L'ordre existe au sein d'une partition, et nulle part ailleurs. Deux enregistrements de partitions différentes n'ont aucun ordre défini.
- Le parallélisme est borné par le nombre de partitions. Une partition est une unité de stockage sur un broker et une unité de consommation pour un seul consommateur d'un groupe.
- La clé détermine la partition. Le partitionneur par défaut hache la clé et prend le résultat modulo le nombre de partitions : tous les enregistrements de
customer-4711atterrissent donc dans la même partition et sont consommés dans l'ordre. Les enregistrements à clé nulle sont répartis entre les partitions par lots.
topic "orders" with 3 partitions
partition 0 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | ← offsets; new records append on the right
partition 1 | 0 | 1 | 2 | 3 |
partition 2 | 0 | 1 | 2 | 3 | 4 | 5 |
key "customer-4711" → hash → partition 1, always
La position d'un enregistrement est entièrement identifiée par le triplet (topic, partition, offset), et c'est ce triplet qu'un consommateur stocke pour marquer sa progression. Il n'y a ni acquittement par enregistrement, ni état « non acquitté », ni file de relivraison ; il n'y a que « le consumer group a lu jusqu'ici ».
Segments, rétention et compaction
Sur disque, une partition est un répertoire de fichiers segments. Le segment actif reçoit les ajouts ; lorsqu'il atteint log.segment.bytes (1 Go par défaut) ou log.roll.ms, il est fermé et un nouveau démarre. Chaque segment est accompagné d'un index d'offsets et d'un index temporel, si bien qu'un consommateur qui demande « l'offset 1 234 567 » ou « le premier enregistrement après 9 h » est servi en quelques seeks plutôt que par un parcours complet. Les écritures sont des ajouts séquentiels qui passent par le page cache du système d'exploitation, et les lectures de données récentes sont servies depuis ce cache ; c'est cela, bien plus que du code astucieux, qui permet à Kafka d'écouler des centaines de mégaoctets par seconde et par broker sur des disques ordinaires.
Les enregistrements sont supprimés par la rétention, pas par la consommation. Deux politiques existent, par topic :
cleanup.policy | Règle | Usage |
|---|---|---|
delete (par défaut) | Supprime les segments entiers plus anciens que retention.ms (7 jours par défaut) ou au-delà de retention.bytes | Flux d'événements, logs, tout ce qui est borné dans le temps |
compact | Conserve au moins le dernier enregistrement de chaque clé ; les enregistrements plus anciens de même clé sont supprimés en arrière-plan | Changelogs, état courant par entité, configuration, topic interne des offsets |
compact,delete | Les deux : compaction, et suppression des données plus anciennes que la rétention | Changelogs qui ne doivent pas garder indéfiniment des clés mortes depuis longtemps |
La compaction transforme un topic en instantané clé-valeur que l'on peut reconstruire en le relisant depuis le début ; c'est ainsi que les state stores de Kafka Streams et de nombreux services de « vues matérialisées » se rétablissent après un redémarrage. Un enregistrement à valeur nulle est un tombstone : il marque la clé comme supprimée et disparaît lui-même après delete.retention.ms. La compaction ne touche que les segments fermés et ne s'exécute que lorsque la proportion de données obsolètes le justifie : un topic compacté contient donc toujours quelques doublons, et les lecteurs doivent s'y attendre.
C'est la rétention qui rend le log rejouable. Un nouveau consumer group peut partir de l'offset le plus ancien et traiter une semaine d'historique ; un groupe qui a découvert un bug peut revenir à la veille et tout retraiter. Aucune file n'offre cela, et c'est la meilleure raison de choisir Kafka.
Brokers, réplication et ensemble des réplicas synchronisés
Un broker est un processus serveur Kafka. Chaque partition a un facteur de réplication — trois est la norme en production — et ses réplicas vivent sur autant de brokers. L'un d'eux est le leader : toutes les requêtes de production et, par défaut, toutes les requêtes de fetch pour cette partition lui sont adressées. Les autres sont des followers qui récupèrent en continu les données du leader pour rester à jour, exactement comme des consommateurs.
Le leader garde trace des followers qui sont à jour. Un follower qui a rattrapé la fin du log du leader dans le délai replica.lag.time.max.ms (30 secondes par défaut) fait partie de l'ensemble des réplicas synchronisés (ISR). L'ISR compte pour deux raisons :
- Un enregistrement est commité lorsque tous les réplicas de l'ISR l'ont écrit. Les consommateurs ne peuvent lire que des enregistrements commités, jusqu'au high watermark.
- Si le leader tombe, le contrôleur élit un nouveau leader parmi l'ISR, ce qui garantit qu'aucun enregistrement commité n'est perdu. Avec
unclean.leader.election.enable=false(la valeur par défaut), un réplica désynchronisé n'est jamais choisi : une partition dont l'ISR est vide devient indisponible plutôt que de perdre des données.
Le réglage acks du producteur détermine combien de temps il attend :
acks | Le producteur rend la main quand | Risque |
|---|---|---|
0 | L'enregistrement a été confié au réseau | Perte à la moindre panne ; même une erreur du broker n'est pas remontée |
1 | Le leader l'a écrit | Perte si le leader tombe avant que les followers l'aient récupéré |
all (par défaut depuis 3.0) | Chaque membre de l'ISR l'a écrit | Aucun en cas de panne de broker — tant que l'ISR compte plus d'un membre |
C'est pour cette dernière réserve que min.insync.replicas existe. Avec un facteur de réplication de 3 et min.insync.replicas=2, le leader refuse les écritures en acks=all lorsque moins de deux réplicas sont synchronisés : on sacrifie de la disponibilité pour garantir qu'un enregistrement acquitté existe sur au moins deux brokers. Le trio courant en production est facteur de réplication 3, min.insync.replicas à 2 et acks=all : un broker peut être arrêté pour maintenance sans bloquer les producteurs, et aucun enregistrement acquitté ne dépend d'une seule machine.
Depuis Kafka 2.4, on peut demander aux consommateurs de lire depuis le réplica le plus proche (replica.selector.class côté broker et client.rack côté consommateur), ce qui réduit le trafic inter-zones dans les déploiements cloud.
Consumer groups et offsets
Un consumer group est un ensemble d'instances de consommateurs partageant un même group.id. Kafka attribue chaque partition des topics auxquels le groupe est abonné à exactement un de ses membres ; un groupe qui compte plus de membres que de partitions laisse les membres en trop inactifs. Deux groupes indépendants qui lisent le même topic reçoivent chacun tous les enregistrements — c'est ainsi que Kafka offre à la fois le partage du travail (au sein d'un groupe) et le publish-subscribe (entre groupes).
La progression de chaque groupe est un ensemble d'offsets commités, un par partition, stockés dans le topic interne __consumer_offsets (compacté, avec pour clé le groupe, le topic et la partition). Un offset commité désigne « le prochain enregistrement à lire » : commiter l'offset 42 signifie que les enregistrements 0 à 41 sont traités. Les consommateurs commitent périodiquement ou explicitement ; au redémarrage ou lors d'une réattribution, le nouveau propriétaire de la partition repart du dernier offset commité. Tout ce qui se trouve entre le dernier commit et le crash est relu : c'est la livraison at-least-once, et la raison pour laquelle le guide sur les consommateurs s'attarde autant sur le moment où commiter.
group "billing" partition 0: committed 1,204 partition 1: committed 988 partition 2: committed 1,377
group "analytics" partition 0: committed 1,190 partition 1: committed 980 partition 2: committed 1,377
log end offsets partition 0: 1,210 partition 1: 991 partition 2: 1,377
lag(billing) = (1,210 − 1,204) + (991 − 988) + 0 = 9 records
Le consumer lag — offset de fin de log moins offset commité, additionné sur toutes les partitions — est la métrique la plus importante d'une application consommatrice. Un lag qui grimpe signifie que les consommateurs ne suivent pas ou se sont arrêtés.
Rééquilibrage
Lorsqu'un membre rejoint ou quitte le groupe, ou que le nombre de partitions du topic change, le groupe se rééquilibre (rebalance) : le group coordinator (un broker choisi en hachant l'identifiant du groupe) pilote un protocole par lequel les membres s'accordent sur une nouvelle attribution. Lors d'un rééquilibrage eager classique, chaque membre abandonne toutes ses partitions et en reçoit de nouvelles, ce qui met tout le groupe en pause pendant plusieurs secondes ; l'assignor cooperative sticky, disponible depuis la 2.4, ne déplace que les partitions qui doivent l'être. Kafka 4.0 a introduit un nouveau protocole de groupe (KIP-848) qui confie la logique d'attribution au coordinateur et rend les rééquilibrages incrémentaux par conception.
Un membre est considéré comme mort lorsqu'il cesse d'envoyer des heartbeats pendant session.timeout.ms, ou lorsqu'il n'appelle pas poll() dans le délai max.poll.interval.ms. C'est ce second cas qui fait mal : un consommateur qui met six minutes à traiter un lot est exclu du groupe, ses partitions sont réattribuées, son travail non commité est refait ailleurs, et quand il finit par commiter, il échoue sur une erreur de rééquilibrage. Dimensionner ensemble la taille des lots et les timeouts est une tâche d'exploitation récurrente.
Métadonnées du cluster : le contrôleur KRaft
Il faut bien que quelqu'un sache quels brokers existent, où vivent les partitions, qui est leader de chacune et quels réplicas sont synchronisés, et que quelqu'un prenne les décisions lorsqu'un broker disparaît. Ce quelqu'un, c'est le contrôleur.
Jusqu'à Kafka 2.8, cet état vivait dans ZooKeeper, un service de coordination distinct ; un broker était élu contrôleur via un verrou ZooKeeper et propageait les changements de métadonnées aux autres. Cela fonctionnait, mais il fallait exploiter et sécuriser deux systèmes distribués, le nombre de partitions qu'un cluster pouvait gérer était limité parce qu'un basculement de contrôleur devait tout recharger depuis ZooKeeper, et des divergences subtiles apparaissaient entre ce que contenait ZooKeeper et ce que croyaient les brokers.
KRaft (Kafka Raft) le remplace par les propres mécanismes de Kafka. Un petit quorum de nœuds contrôleurs — trois ou cinq — exécute le protocole de consensus Raft sur un log de métadonnées interne, __cluster_metadata. Le leader élu de ce quorum est le contrôleur actif ; les autres sont des standbys à chaud qui ont déjà toutes les métadonnées en mémoire, si bien que le basculement est quasi instantané. Les brokers récupèrent le log de métadonnées comme n'importe quel consommateur et appliquent les changements dans l'ordre. Les contrôleurs peuvent tourner sur des nœuds dédiés (process.roles=controller) ou, pour les petits clusters et le développement, dans les mêmes processus que les brokers (process.roles=broker,controller).
| Mode ZooKeeper (historique) | Mode KRaft | |
|---|---|---|
| Stockage des métadonnées | Ensemble ZooKeeper | Log Raft interne sur le quorum de contrôleurs |
| Basculement du contrôleur | De quelques secondes à quelques minutes, rechargement depuis ZooKeeper | Quelques millisecondes, le standby a l'état en mémoire |
| Partitions par cluster | Des dizaines de milliers | Des millions |
| Systèmes à exploiter | Deux | Un |
| Statut | Déprécié en 3.5, supprimé en 4.0 | Prêt pour la production depuis 3.3, seul mode en 4.0 |
Pour un développeur d'applications, KRaft ne change rien à l'API cliente. Pour un exploitant, il retire un système entier du déploiement : bootstrap.servers et un quorum de contrôleurs, voilà toute l'histoire.
Au-delà du log : Connect et Streams
Deux composants sont livrés avec Kafka et réutilisent tout ce qui précède. Kafka Connect est un framework de connecteurs source et sink qui copient des données entre Kafka et des systèmes externes — bases de données via le change data capture, stockage objet, moteurs de recherche — en prenant en charge offsets, reprises et montée en charge. Kafka Streams est une bibliothèque Java qui traite les enregistrements de topic à topic avec des jointures, des fenêtres et des state stores adossés à des topics changelog compactés, le tout dans votre propre application, sans cluster supplémentaire. Tous deux expliquent pourquoi on parle de Kafka comme d'une plateforme plutôt que d'un broker, et tous deux reposent sur la sémantique de log décrite ici : partitions rejouables, ordre par clé et compaction.
Concevoir un topic
Mettre ce modèle en pratique, c'est répondre à quelques questions avant de lancer kafka-topics.sh --create.
- Clé. Choisissez la clé selon l'ordre dont vous avez besoin : un identifiant de commande garde les événements d'une commande dans l'ordre ; un identifiant client garde dans l'ordre tout ce qui concerne un client, mais concentre les clients très actifs sur une seule partition. Une clé nulle maximise la répartition et renonce à l'ordre.
- Partitions. Partez du débit cible divisé par ce qu'un consommateur peut traiter, arrondissez au multiple supérieur du nombre de brokers et gardez de la marge — ajouter des partitions plus tard redistribue les clés. Un très grand nombre de partitions coûte de la mémoire et des descripteurs de fichiers, et allonge les élections de leader.
- Facteur de réplication et
min.insync.replicas. 3 et 2 pour tout ce qui compte ; 1 uniquement pour des données de développement jetables. - Rétention. Assez longue pour reconstruire un consommateur aval à partir de zéro ou pour couvrir la plus longue panne à laquelle vous voulez survivre ; la compaction si le topic représente un état plutôt qu'un historique.
- Schéma. Kafka se moque du contenu des octets : un schema registry avec Avro, Protobuf ou JSON Schema, plus une règle de versionnement pour les évolutions compatibles, fait toute la différence entre un contrat durable et un tas de JSON non documenté.
Erreurs courantes
- Attendre une sémantique de file. Pas d'acquittement par enregistrement, pas de dead-letter queue, pas de relivraison ; un enregistrement en échec doit être géré par le consommateur (l'ignorer, réessayer ou l'écrire dans un topic d'erreurs). Les équipes qui veulent ces fonctionnalités au-dessus de Kafka les construisent elles-mêmes ou utilisent une file.
- Une seule partition « pour garder l'ordre ». Cela marche jusqu'au jour où un consommateur ne suit plus, et le seul remède est alors un repartitionnement qui change l'ordre de toute façon. Ordonnez par clé dès le départ.
- Ajouter des partitions à un topic à clés en production. Les clés sont ensuite hachées vers d'autres partitions ; l'ordre par clé est rompu au moment du changement, et tout état compacté qui supposait la localité des clés devient incohérent.
- Facteur de réplication 3 avec
min.insync.replicasà 1. L'écriture sur un seul broker est acquittée comme durable. Réglez-le à 2. - Ignorer le lag. Un consumer lag qui grimpe est le premier signe d'un consommateur bloqué, d'un groupe sous-dimensionné ou d'un poison record. Mettez une alerte dessus.
- Considérer le topic de métadonnées ou les contrôleurs comme facultatifs. En mode KRaft, perdre la majorité des contrôleurs bloque les élections de leader et les changements de métadonnées dans tout le cluster. Faites tourner trois ou cinq contrôleurs sur des domaines de panne distincts.
Points clés à retenir
- Kafka est un log partitionné, répliqué et conservé ; les consommateurs suivent leurs offsets, et rien n'est supprimé parce qu'il a été lu.
- L'ordre comme le parallélisme se jouent par partition, et la clé de l'enregistrement détermine la partition : le choix de la clé est la décision de conception centrale.
- Segments et rétention (delete ou compact) régissent le stockage ; la compaction garde la dernière valeur par clé et fait d'un topic un instantané reconstructible.
- La réplication par leader avec un ensemble de réplicas synchronisés,
acks=alletmin.insync.replicas=2sur un facteur de réplication de 3, constitue la configuration de durabilité standard. - Les consumer groups se partagent les partitions et commitent leurs offsets dans
__consumer_offsets; les rééquilibrages et les timeouts de poll sont les endroits où les applications consommatrices dérapent le plus souvent. - Les contrôleurs KRaft conservent les métadonnées du cluster dans un log Raft interne ; ZooKeeper est déprécié en 3.x et a disparu en 4.0.
Sources et lectures complémentaires
Questions fréquentes
- Un topic Kafka est-il ordonné ?
- Une partition est ordonnée ; un topic de plus d'une partition ne l'est pas. Les enregistrements qui partagent une clé vont dans la même partition (avec le partitionneur par défaut et un nombre de partitions fixe), donc l'ordre est garanti par clé. Pour un ordre global, il faut une seule partition, ce qui plafonne le débit à ce que peuvent absorber un seul broker et un seul consommateur.
- Combien de partitions un topic doit-il avoir ?
- Assez pour répartir la charge sur les brokers et donner au plus gros consumer group le parallélisme dont il a besoin, puisqu'une partition ne peut être lue que par un seul consommateur au sein d'un groupe. Partez d'un petit multiple du nombre de brokers — six ou douze pour un cluster de trois brokers — et gardez en tête qu'on peut ajouter des partitions mais pas en retirer, et qu'en ajouter change la partition vers laquelle une clé est envoyée.
- Kafka supprime-t-il un enregistrement une fois consommé ?
- Non. Les enregistrements disparaissent selon la politique de rétention — au-delà d'un âge configuré ou quand la partition dépasse une taille donnée — ou sont remplacés par un enregistrement plus récent de même clé en cas de compaction. La consommation ne fait qu'avancer l'offset d'un consumer group. C'est ce qui permet à plusieurs groupes de lire les mêmes données et à n'importe quel groupe de revenir en arrière.
- Ai-je encore besoin de ZooKeeper ?
- Pas pour un nouveau cluster. Le mode KRaft, où un quorum de contrôleurs Kafka stocke les métadonnées du cluster dans un log interne, est prêt pour la production depuis Kafka 3.3 et c'est le seul mode de Kafka 4.0. Les clusters existants basés sur ZooKeeper en 3.x doivent être migrés selon la procédure documentée avant de passer en 4.x.