Un message qui arrive deux fois
La livraison au moins une fois garantit qu’un message arrive, quitte à arriver plusieurs fois. Un consommateur idempotent donne le même résultat qu’il lise un message une fois ou dix fois.
Un virement de 50 € est écrit dans un journal. Si le consommateur tombe après avoir débité le compte mais avant de noter sa position, il relit le message et débite 50 € une seconde fois.
À la fin de cette leçon
Remarque
Je peux concevoir un consommateur qui reste correct quand un message est relu.
Mots clés
| Mot | Ce que ça veut dire |
|---|---|
| Au moins une fois | un message arrive, parfois en plusieurs exemplaires |
| Idempotent | qui donne le même résultat s’il est répété |
| Doublon | un message reçu une seconde fois |
| Décalage | le numéro d’un événement dans sa partition |
| Validation | l’enregistrement du décalage jusqu’où l’on a lu |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Un réessai peut créer un doublon
Un producteur envoie un événement et attend la confirmation. Si elle n’arrive pas à temps, il ne sait pas si l’événement a été écrit. Le plus sûr est de le renvoyer : c’est un réessai.
Si le premier envoi avait réussi, le journal contient maintenant deux fois le même événement. C’est un doublon.
Créer un topic d’une partition
curl -s -X POST -H "Content-Type: application/json" -d '{"name":"virements","partitions":1,"replicationFactor":1,"configs":{}}' http://kafka-ui:8080/api/clusters/lab/topics -o /dev/null -w "%{http_code}\n"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Envoyer le virement 42
curl -s -X POST -H "Content-Type: application/json" -d '{"partition":0,"key":"v42","value":"payer","headers":{},"keySerde":"String","valueSerde":"String"}' http://kafka-ui:8080/api/clusters/lab/topics/virements/messages -o /dev/null -w "%{http_code}\n"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Le renvoyer, comme après un délai dépassé
curl -s -X POST -H "Content-Type: application/json" -d '{"partition":0,"key":"v42","value":"payer","headers":{},"keySerde":"String","valueSerde":"String"}' http://kafka-ui:8080/api/clusters/lab/topics/virements/messages -o /dev/null -w "%{http_code}\n"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Voir combien d’événements contient la partition
curl -s http://kafka-ui:8080/api/clusters/lab/topics/virements | grep -o '"partition":[0-9]*\|"offsetMax":[0-9]*'Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Relire le journal depuis le début
curl -s -N "http://kafka-ui:8080/api/clusters/lab/topics/virements/messages/v2?seekType=BEGINNING&partitions=0&limit=10" | grep -o '"partition":[0-9]*,"offset":[0-9]*\|"value":"[a-z]*"'Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Valider avant ou après le traitement
Un consommateur note jusqu’où il a lu : c’est la validation de son décalage. Après une panne, il reprend à ce décalage.
S’il valide après avoir traité, une panne entre les deux fait relire le message : au moins une fois. S’il valide avant, la même panne le fait sauter : au plus une fois.
Exemple : où tombe le consommateur
| Moment de la panne | Ce qui arrive au message | Nom du comportement |
|---|---|---|
| Après traiter, avant valider | Il est relu et traité deux fois | Au moins une fois |
| Après valider, avant traiter | Il n’est jamais traité | Au plus une fois |
| Après traiter et valider | Il est traité une seule fois | Cas normal |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Rejouer une panne du consommateur
# Un consommateur traite des débits, puis valide son décalage. Il tombe avant de valider le 2e.
montants = [50, 20, 30]
decalage_valide = 1
traites = [50, 20]
# Au redémarrage, il reprend au dernier décalage validé.
for montant in montants[decalage_valide:]:
traites.append(montant)
total = 0
for montant in traites:
total = total + montant
print("Messages traités :", traites)
print("Total débité :", total, "au lieu de", 100)
Un identifiant rend le traitement idempotent
On ne peut pas empêcher tous les doublons, mais on peut les rendre sans effet. Chaque message porte un identifiant unique. Le consommateur garde la liste des identifiants déjà traités et ignore un identifiant qu’il a déjà vu.
Le traitement devient idempotent : le répéter ne change plus le résultat.
Ignorer un identifiant déjà vu
# Chaque message porte un identifiant. On ne traite jamais deux fois le même.
messages = [["v1", 50], ["v2", 20], ["v2", 20], ["v3", 30]]
deja_vus = {}
total = 0
for message in messages:
identifiant = message[0]
if identifiant in deja_vus:
print("Doublon ignoré :", identifiant)
else:
deja_vus[identifiant] = True
total = total + message[1]
print("Total débité :", total)
# Ajoutez ["v3", 30] à la fin de la liste puis relancez.
Une erreur fréquente
Attention
On croit souvent qu’un message ne peut être lu qu’une seule fois. Une panne ou une confirmation perdue suffit à le faire relire. On obtient l’effet « une seule fois » en acceptant les doublons et en les ignorant.