๐ฅ Niveau 3 : Avancรฉ
๐จ Distributed Messaging pour Data Engineers
Bienvenue dans ce module avancรฉ oรน tu vas maรฎtriser les systรจmes de messaging distribuรฉs. Tu apprendras les fonctionnalitรฉs avancรฉes de Kafka, les alternatives comme RabbitMQ et Pulsar, et le Change Data Capture avec Debezium โ des compรฉtences essentielles pour construire des architectures data temps rรฉel !
Prรฉrequis
| Niveau | Compรฉtence |
|---|---|
| โ Requis | Avoir suivi le module 24_kafka_streaming (Kafka, Spark SSS, Watermarks) |
| โ Requis | Maรฎtriser topics, partitions, offsets, consumer groups |
| โ Requis | Connaรฎtre kafka-python et confluent-kafka (producers/consumers) |
| โ Requis | Maรฎtriser Spark Structured Streaming (readStream, writeStream, foreachBatch) |
| โ Requis | Connaissances en Docker et Kubernetes (M14-M16, M27) |
| โ Requis | Bases de donnรฉes relationnelles et SQL (pour Debezium CDC) |
| ๐ก Recommandรฉ | Expรฉrience avec des pipelines streaming en production |
๐ฏ Objectifs du module
ร la fin de ce module, tu seras capable de :
- Configurer les fonctionnalitรฉs avancรฉes de Kafka (Quotas, Tiered Storage, Transactions)
- Comprendre et choisir entre Kafka, RabbitMQ et Pulsar
- Implรฉmenter le Change Data Capture complet avec Debezium
- Concevoir des architectures de messaging robustes
- Gรฉrer les patterns avancรฉs : exactly-once, dead letter queues, event sourcing
Rappel : Ce qu'on a vu en M24 vs Ce qu'on approfondit ici
| Module M24 (Intermediate) | Ce module M29 (Advanced) |
|---|---|
| Architecture Lambda vs Kappa | โ |
| Topics, Partitions, Offsets, Consumer Groups | Tiered Storage, Quotas, Monitoring |
| Producers / Consumers (kafka-python, confluent-kafka) | Transactions Kafka, Exactly-Once (EOS) |
| Schema Registry basics (Avro) | Schema Registry avancรฉ (compatibilitรฉ, รฉvolution) |
| Spark Structured Streaming complet | โ |
| Windowing, Watermarks, foreachBatch | โ |
| Faust (aperรงu) | โ |
| Debezium (mentionnรฉ) | Debezium CDC en profondeur |
| โ | RabbitMQ (alternative queue-based) |
| โ | Apache Pulsar (alternative multi-tenant) |
| โ | Patterns : DLQ, Saga, Event Sourcing, CQRS |
Schรฉma : รcosystรจme Messaging
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ DISTRIBUTED MESSAGING LANDSCAPE โ
โ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โ
โ โ KAFKA โ โ RABBITMQ โ โ PULSAR โ โ
โ โ Log-based โ โ Queue-based โ โ Multi-tenant โ โ
โ โ High throughputโ โ Flexible routingโ โ Geo-replicationโ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โ
โ โ โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ DEBEZIUM (CDC) โ โ
โ โ Capture changes from databases โ Stream to messaging systems โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โน๏ธ Le savais-tu ?
Kafka traite plus de 7 trillions de messages par jour chez LinkedIn, son crรฉateur.
RabbitMQ a รฉtรฉ crรฉรฉ en 2007 et implรฉmente le protocole AMQP, un standard ouvert pour le messaging.
Apache Pulsar a รฉtรฉ dรฉveloppรฉ par Yahoo! pour gรฉrer leurs 100 milliards de messages quotidiens avec une architecture multi-tenant native.
Debezium (du latin "from the beginning") capture chaque changement depuis le dรฉbut du log de la base de donnรฉes โ c'est la base du Change Data Capture.
๐ Kafka at LinkedIn
1. Kafka Avancรฉ
Cette section couvre les fonctionnalitรฉs avancรฉes de Kafka pour la production ร grande รฉchelle. Tu connais dรฉjร les bases (M24), on passe directement aux sujets avancรฉs.
1.1 Quotas et Throttling
Les quotas permettent de limiter les ressources consommรฉes par les clients pour รฉviter qu'un client ne monopolise le cluster.
Types de quotas
| Quota | Description | Unitรฉ |
|---|---|---|
| producer_byte_rate | Dรฉbit max en รฉcriture | bytes/sec |
| consumer_byte_rate | Dรฉbit max en lecture | bytes/sec |
| request_percentage | % CPU du broker | % |
| controller_mutation_rate | Taux de mutations (create/delete) | mutations/sec |
Configurer les quotas
# Quota par user
kafka-configs.sh --bootstrap-server localhost:9092 \
--alter --add-config 'producer_byte_rate=1048576,consumer_byte_rate=2097152' \
--entity-type users --entity-name data-pipeline-user
# Quota par client-id
kafka-configs.sh --bootstrap-server localhost:9092 \
--alter --add-config 'producer_byte_rate=5242880' \
--entity-type clients --entity-name etl-producer
# Quota par user + client-id (plus spรฉcifique)
kafka-configs.sh --bootstrap-server localhost:9092 \
--alter --add-config 'producer_byte_rate=10485760' \
--entity-type users --entity-name spark-user \
--entity-type clients --entity-name spark-producer
# Quota par dรฉfaut pour tous les users
kafka-configs.sh --bootstrap-server localhost:9092 \
--alter --add-config 'producer_byte_rate=1048576' \
--entity-type users --entity-default
# Voir les quotas configurรฉs
kafka-configs.sh --bootstrap-server localhost:9092 \
--describe --entity-type users --entity-name data-pipeline-user
Quotas dans le code Python
from confluent_kafka import Producer
# Le client doit spรฉcifier son client.id pour รชtre identifiรฉ par les quotas
producer = Producer({
'bootstrap.servers': 'localhost:9092',
'client.id': 'etl-producer', # Identifiant pour les quotas
'acks': 'all',
})
# Si le quota est dรฉpassรฉ, Kafka throttle automatiquement le client
# Le producer recevra des dรฉlais dans les rรฉponses
1.2 Tiered Storage (KIP-405)
Le Tiered Storage permet de stocker les donnรฉes anciennes sur un stockage moins cher (S3, GCS, Azure Blob) tout en gardant les donnรฉes rรฉcentes sur disque local.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ TIERED STORAGE โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ LOCAL TIER (Hot Data) โ โ
โ โ โโโโโโโ โโโโโโโ โโโโโโโ โโโโโโโ โโโโโโโ โ โ
โ โ โSeg 5โ โSeg 6โ โSeg 7โ โSeg 8โ โSeg 9โ โ Donnรฉes rรฉcentes โ โ
โ โ โโโโโโโ โโโโโโโ โโโโโโโ โโโโโโโ โโโโโโโ (SSD local, rapide) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ Offload automatique โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ REMOTE TIER (Cold Data) โ โ
โ โ โโโโโโโ โโโโโโโ โโโโโโโ โโโโโโโ โ โ
โ โ โSeg 1โ โSeg 2โ โSeg 3โ โSeg 4โ โ Donnรฉes anciennes โ โ
โ โ โโโโโโโ โโโโโโโ โโโโโโโ โโโโโโโ (S3/GCS/Azure, รฉconomique) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Configuration Tiered Storage
# server.properties (broker)
# Activer le tiered storage
remote.log.storage.system.enable=true
# Plugin de stockage (exemple S3)
remote.log.storage.manager.class.name=org.apache.kafka.tiered.storage.s3.S3RemoteStorageManager
remote.log.storage.manager.class.path=/opt/kafka/plugins/tiered-storage-s3.jar
# Configuration S3
remote.log.storage.s3.bucket=my-kafka-tiered-storage
remote.log.storage.s3.region=eu-west-1
# Rรฉtention locale (donnรฉes chaudes)
local.retention.ms=86400000 # 1 jour en local
# Rรฉtention totale (incluant remote)
retention.ms=2592000000 # 30 jours au total
# Activer le tiered storage sur un topic existant
kafka-configs.sh --bootstrap-server localhost:9092 \
--alter --entity-type topics --entity-name events \
--add-config 'remote.storage.enable=true,local.retention.ms=86400000,retention.ms=2592000000'
Avantages du Tiered Storage
| Avantage | Description |
|---|---|
| Coรปt rรฉduit | Stockage S3 ~10x moins cher que SSD |
| Rรฉtention illimitรฉe | Garder des annรฉes de donnรฉes |
| Cluster plus petit | Moins de disque local nรฉcessaire |
| Replay facilitรฉ | Relire des donnรฉes anciennes pour reprocessing |
1.3 Transactions et Exactly-Once Semantics (EOS)
En M24, tu as vu les garanties de livraison (at-most-once, at-least-once, exactly-once). Ici, on va implรฉmenter exactly-once avec les transactions Kafka.
Rappel des garanties
| Niveau | Description | Risque |
|---|---|---|
| At-most-once | Fire & forget | Perte de messages |
| At-least-once | Retry jusqu'ร ACK | Doublons possibles |
| Exactly-once | Transactions + idempotence | Aucun (mais plus complexe) |
Producer Idempotent (pas de doublons)
L'idempotence garantit qu'un message n'est รฉcrit qu'une seule fois mรชme en cas de retry rรฉseau.
from confluent_kafka import Producer
# Producer idempotent โ PAS de duplicatas mรชme avec retries
producer = Producer({
'bootstrap.servers': 'localhost:9092',
'enable.idempotence': True, # โ Active l'idempotence
'acks': 'all', # Requis pour idempotence
'retries': 2147483647, # Retries infinis (best practice)
'max.in.flight.requests.per.connection': 5, # Max 5 avec idempotence
})
# Comment รงa marche ?
# 1. Le producer assigne un Producer ID (PID) et un Sequence Number ร chaque message
# 2. Le broker dรฉtecte les doublons en comparant (PID, Sequence)
# 3. Si un retry envoie le mรชme message, le broker le reconnaรฎt et ignore le doublon
Transactions complรจtes (multi-topics atomique)
Les transactions permettent d'รฉcrire sur plusieurs topics/partitions de maniรจre atomique : tout rรฉussit ou tout รฉchoue.
from confluent_kafka import Producer, KafkaException
# Producer transactionnel
producer = Producer({
'bootstrap.servers': 'localhost:9092',
'transactional.id': 'etl-pipeline-001', # ID unique et STABLE
'enable.idempotence': True, # Implicitement activรฉ
'acks': 'all',
})
# Initialiser les transactions (une seule fois au dรฉmarrage)
producer.init_transactions()
try:
# Dรฉmarrer une transaction
producer.begin_transaction()
# รcrire sur PLUSIEURS topics (atomique)
producer.produce('orders-processed', key='order-1', value='{"status": "done"}')
producer.produce('audit-log', key='order-1', value='{"action": "order_processed"}')
producer.produce('metrics', key='counter', value='{"orders_processed": 1}')
# Commit la transaction โ TOUT ou RIEN
producer.commit_transaction()
print("โ
Transaction committed successfully")
except KafkaException as e:
# Abort en cas d'erreur โ aucun message n'est visible
producer.abort_transaction()
print(f"โ Transaction aborted: {e}")
Pattern Read-Process-Write (Exactly-Once complet)
Le pattern le plus puissant : lire, traiter, รฉcrire, et commiter les offsets dans la mรชme transaction.
from confluent_kafka import Consumer, Producer, KafkaException
# Consumer avec isolation transactionnelle
consumer = Consumer({
'bootstrap.servers': 'localhost:9092',
'group.id': 'etl-group',
'isolation.level': 'read_committed', # โ Ne lit que les messages committรฉs
'enable.auto.commit': False, # โ Commit manuel dans la transaction
})
producer = Producer({
'bootstrap.servers': 'localhost:9092',
'transactional.id': 'etl-processor-001',
'enable.idempotence': True,
})
producer.init_transactions()
consumer.subscribe(['raw-events'])
while True:
msg = consumer.poll(1.0)
if msg is None:
continue
if msg.error():
continue
try:
# 1. Dรฉmarrer la transaction
producer.begin_transaction()
# 2. Traiter le message
processed = process_message(msg.value())
# 3. รcrire le rรฉsultat
producer.produce('processed-events', value=processed)
# 4. Commit les offsets DANS la transaction
producer.send_offsets_to_transaction(
consumer.position(consumer.assignment()),
consumer.consumer_group_metadata()
)
# 5. Commit atomique : รฉcriture + offset ensemble
producer.commit_transaction()
except Exception as e:
producer.abort_transaction()
print(f"Transaction failed: {e}")
1.4 Schema Registry Avancรฉ
En M24, tu as vu les bases du Schema Registry avec Avro. Approfondissons les modes de compatibilitรฉ et l'รฉvolution de schรฉmas.
Modes de compatibilitรฉ dรฉtaillรฉs
| Mode | Nouveau consumer lit ancien | Ancien consumer lit nouveau | Changements autorisรฉs |
|---|---|---|---|
| BACKWARD | โ Oui | โ Non | Ajouter champs optionnels, supprimer champs |
| FORWARD | โ Non | โ Oui | Ajouter champs, supprimer champs optionnels |
| FULL | โ Oui | โ Oui | Ajouter/supprimer champs optionnels uniquement |
| NONE | โ | โ | Tout (โ ๏ธ dangereux en production) |
# Configurer la compatibilitรฉ globale
curl -X PUT http://localhost:8081/config \
-H "Content-Type: application/json" \
-d '{"compatibility": "BACKWARD"}'
# Configurer par sujet (override global)
curl -X PUT http://localhost:8081/config/orders-value \
-H "Content-Type: application/json" \
-d '{"compatibility": "FULL"}'
# Tester la compatibilitรฉ AVANT de publier un nouveau schรฉma
curl -X POST http://localhost:8081/compatibility/subjects/orders-value/versions/latest \
-H "Content-Type: application/json" \
-d '{"schema": "{...}"}'
# Rรฉponse: {"is_compatible": true} ou {"is_compatible": false}
รvolution de schรฉma โ Exemple pratique
# Version 1 du schรฉma
schema_v1 = '''
{
"type": "record",
"name": "Order",
"fields": [
{"name": "order_id", "type": "string"},
{"name": "amount", "type": "double"}
]
}
'''
# Version 2 โ Ajouter un champ optionnel (BACKWARD compatible)
schema_v2 = '''
{
"type": "record",
"name": "Order",
"fields": [
{"name": "order_id", "type": "string"},
{"name": "amount", "type": "double"},
{"name": "currency", "type": "string", "default": "EUR"}
]
}
'''
# โ
Les nouveaux consumers peuvent lire les anciens messages (currency = "EUR" par dรฉfaut)
2. RabbitMQ โ L'Alternative Queue-Based
RabbitMQ est un message broker traditionnel implรฉmentant le protocole AMQP. Contrairement ร Kafka (log-based), RabbitMQ est queue-based avec un routage flexible.
Kafka vs RabbitMQ โ Comparaison dรฉtaillรฉe
| Aspect | Kafka | RabbitMQ |
|---|---|---|
| Modรจle | Log distribuรฉ (append-only) | Message queue (FIFO) |
| Persistance | Toujours sur disque | Optionnelle (mรฉmoire ou disque) |
| Ordre | Garanti par partition | Garanti par queue |
| Replay | โ Natif (offsets) | โ Messages supprimรฉs aprรจs ACK |
| Routage | Topics + Partitions | Exchanges (fanout, direct, topic, headers) |
| Throughput | Trรจs รฉlevรฉ (millions/sec) | รlevรฉ (dizaines de milliers/sec) |
| Latence | Millisecondes | Sub-milliseconde |
| Use case principal | Event streaming, analytics | Task queues, RPC, notifications |
Architecture RabbitMQ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ RABBITMQ ARCHITECTURE โ
โ โ
โ Producer โโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโ โ
โ โ EXCHANGE โ โ Routing logic (type: fanout/direct/topic) โ
โ โโโโโโโโโฌโโโโโโโโ โ
โ โ Bindings (routing rules) โ
โ โโโโโโโโโโโโโผโโโโโโโโโโโโ โ
โ โ โ โ โ
โ โผ โผ โผ โ
โ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โ
โ โ Queue 1 โ โ Queue 2 โ โ Queue 3 โ โ Messages stockรฉs ici โ
โ โโโโโโฌโโโโโ โโโโโโฌโโโโโ โโโโโโฌโโโโโ โ
โ โ โ โ โ
โ โผ โผ โผ โ
โ Consumer 1 Consumer 2 Consumer 3 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Types d'Exchanges
| Exchange | Routage | Use case |
|---|---|---|
| Direct | routing_key exact match | Logs par niveau (errorโerror_queue) |
| Fanout | Broadcast ร toutes les queues liรฉes | Notifications, cache invalidation |
| Topic | Pattern matching (*.error, logs.#) | Logs multi-critรจres (app.module.level) |
| Headers | Match sur headers du message | Routage complexe multi-attributs |
Installation
# Docker
docker run -d --name rabbitmq \
-p 5672:5672 \
-p 15672:15672 \
rabbitmq:3-management
# Management UI: http://localhost:15672 (guest/guest)
Producer Python (pika)
import pika
import json
# Connexion
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
# Dรฉclarer un exchange de type topic
channel.exchange_declare(exchange='data_events', exchange_type='topic', durable=True)
# Publier un message
message = {'event_type': 'order_created', 'order_id': 'ORD-001', 'amount': 99.99}
channel.basic_publish(
exchange='data_events',
routing_key='orders.created',
body=json.dumps(message),
properties=pika.BasicProperties(delivery_mode=2, content_type='application/json')
)
connection.close()
Consumer Python
import pika
import json
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.queue_declare(queue='order_processor', durable=True)
channel.queue_bind(exchange='data_events', queue='order_processor', routing_key='orders.*')
def callback(ch, method, properties, body):
message = json.loads(body)
print(f"Received: {message}")
ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue='order_processor', on_message_callback=callback)
channel.start_consuming()
Quand utiliser RabbitMQ vs Kafka ?
| โ RabbitMQ | โ Kafka |
|---|---|
| Task queues (jobs async) | Event streaming temps rรฉel |
| RPC (request/reply) | Log aggregation |
| Routage complexe (exchanges) | Replay de donnรฉes historiques |
| Faible latence critique (<1ms) | Trรจs haut throughput (millions/sec) |
3. Apache Pulsar โ Le Challenger Multi-Tenant
Apache Pulsar combine les avantages de Kafka (log-based) et RabbitMQ (queuing) avec une architecture cloud-native.
Kafka vs Pulsar
| Aspect | Kafka | Pulsar |
|---|---|---|
| Architecture | Brokers = Storage + Compute | Sรฉparation Brokers / BookKeeper |
| Multi-tenancy | Limitรฉ | Natif (tenants, namespaces) |
| Geo-replication | MirrorMaker (externe) | Natif et synchrone |
| Queuing | Non natif | Natif (shared subscriptions) |
Types de Subscriptions Pulsar
| Type | Description | รquivalent |
|---|---|---|
| Exclusive | 1 seul consumer | Kafka standard |
| Failover | Failover automatique | โ |
| Shared | Load balanced (round-robin) | RabbitMQ |
| Key_Shared | Ordre par clรฉ | Kafka partitions |
Producer/Consumer Python
import pulsar
import json
client = pulsar.Client('pulsar://localhost:6650')
# Producer
producer = client.create_producer('persistent://public/default/orders')
producer.send(json.dumps({'order_id': 'ORD-001'}).encode('utf-8'))
# Consumer (shared = load balanced)
consumer = client.subscribe(
'persistent://public/default/orders',
subscription_name='order-processor',
consumer_type=pulsar.ConsumerType.Shared
)
msg = consumer.receive()
print(json.loads(msg.data()))
consumer.acknowledge(msg)
client.close()
4. Debezium โ Change Data Capture en Profondeur
Debezium est une plateforme open-source de Change Data Capture (CDC) qui capture les changements dans les bases de donnรฉes et les streame vers Kafka.
En M24, Debezium รฉtait mentionnรฉ dans le quiz. Ici, on l'implรฉmente en profondeur.
Pourquoi le CDC ?
| Approche traditionnelle | CDC avec Debezium |
|---|---|
| Batch ETL (SELECT * toutes les heures) | Streaming temps rรฉel |
| Query la DB source (charge CPU/IO) | Lit le transaction log (lรฉger) |
| Dรฉtection des DELETEs difficile | Capture TOUS les changements |
| Latence รฉlevรฉe (heures) | Latence sub-seconde |
Architecture Debezium
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ DEBEZIUM ARCHITECTURE โ
โ โ
โ โโโโโโโโโโโโโโโ โ
โ โ PostgreSQL โ Transaction Log (WAL) โ
โ โ (source) โโโโโโโโโโโโโโโโโโโโ โ
โ โโโโโโโโโโโโโโโ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ MySQL โโโโโถโ KAFKA CONNECT โโโโโถโ KAFKA TOPICS โ โ
โ โ (source) โ โ + Debezium โ โ (change events) โ โ
โ โโโโโโโโโโโโโโโ โ Connectors โ โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ โข dbserver.schema โ โ
โ โโโโโโโโโโโโโโโ โ .table โ โ
โ โ MongoDB โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ (source) โ โ โ
โ โโโโโโโโโโโโโโโ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Consumers โ โ
โ โ โข Data Warehouse โ โ
โ โ โข Elasticsearch โ โ
โ โ โข Microservices โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Connecteurs Debezium
| Base de donnรฉes | Mรฉthode de capture | Maturitรฉ |
|---|---|---|
| PostgreSQL | Logical replication (pgoutput) | โญโญโญโญโญ |
| MySQL/MariaDB | Binary log (binlog) | โญโญโญโญโญ |
| MongoDB | Oplog / Change Streams | โญโญโญโญโญ |
| SQL Server | CDC tables | โญโญโญโญ |
| Oracle | LogMiner | โญโญโญโญ |
Docker Compose complet
version: '3.8'
services:
zookeeper:
image: confluentinc/cp-zookeeper:7.5.0
environment:
ZOOKEEPER_CLIENT_PORT: 2181
kafka:
image: confluentinc/cp-kafka:7.5.0
depends_on: [zookeeper]
ports: ["9092:9092"]
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
postgres:
image: postgres:15
ports: ["5432:5432"]
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: source_db
command: ["postgres", "-c", "wal_level=logical"] # CRUCIAL
connect:
image: debezium/connect:2.5
depends_on: [kafka, postgres]
ports: ["8083:8083"]
environment:
BOOTSTRAP_SERVERS: kafka:29092
GROUP_ID: debezium-connect
CONFIG_STORAGE_TOPIC: connect_configs
OFFSET_STORAGE_TOPIC: connect_offsets
STATUS_STORAGE_TOPIC: connect_statuses
Enregistrer le connecteur
curl -X POST http://localhost:8083/connectors \
-H "Content-Type: application/json" \
-d '{
"name": "postgres-connector",
"config": {
"connector.class": "io.debezium.connector.postgresql.PostgresConnector",
"database.hostname": "postgres",
"database.port": "5432",
"database.user": "postgres",
"database.password": "postgres",
"database.dbname": "source_db",
"topic.prefix": "cdc",
"table.include.list": "public.orders",
"plugin.name": "pgoutput",
"slot.name": "debezium_slot",
"transforms": "unwrap",
"transforms.unwrap.type": "io.debezium.transforms.ExtractNewRecordState",
"transforms.unwrap.drop.tombstones": "false",
"transforms.unwrap.delete.handling.mode": "rewrite",
"transforms.unwrap.add.fields": "op,source.ts_ms"
}
}'
Format des messages Debezium
| Champ | Description |
|---|---|
| before | รtat AVANT le changement (null pour INSERT) |
| after | รtat APRรS le changement (null pour DELETE) |
| op | Opรฉration : c=create, u=update, d=delete, r=read (snapshot) |
| source | Mรฉtadonnรฉes (table, transaction ID, LSN) |
Consumer Python CDC
from confluent_kafka import Consumer
import json
consumer = Consumer({
'bootstrap.servers': 'localhost:9092',
'group.id': 'cdc-processor',
'auto.offset.reset': 'earliest',
})
consumer.subscribe(['cdc.public.orders'])
while True:
msg = consumer.poll(1.0)
if msg is None:
continue
event = json.loads(msg.value())
op = event.get('__op')
data = {k: v for k, v in event.items() if not k.startswith('__')}
if op in ('c', 'r'): # INSERT ou SNAPSHOT
print(f"INSERT: {data}")
elif op == 'u': # UPDATE
print(f"UPDATE: {data}")
elif op == 'd': # DELETE
print(f"DELETE: id={data.get('id')}")
Outbox Pattern
Le Outbox Pattern garantit la cohรฉrence entre les mises ร jour DB et l'envoi d'รฉvรฉnements.
-- Table outbox
CREATE TABLE outbox (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
aggregate_type VARCHAR(255) NOT NULL,
aggregate_id VARCHAR(255) NOT NULL,
event_type VARCHAR(255) NOT NULL,
payload JSONB NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
-- Dans une TRANSACTION
BEGIN;
UPDATE orders SET status = 'shipped' WHERE id = 1;
INSERT INTO outbox (aggregate_type, aggregate_id, event_type, payload)
VALUES ('Order', '1', 'OrderShipped', '{"order_id": 1}');
COMMIT;
-- Debezium capture l'INSERT dans outbox โ Kafka
Use Cases CDC
| Use Case | Description |
|---|---|
| Sync Data Warehouse | Rรฉplication temps rรฉel vers Snowflake, BigQuery |
| Cache invalidation | Invalider Redis quand la DB change |
| Search indexing | Sync vers Elasticsearch |
| Microservices events | Outbox pattern |
| Audit log | Compliance, RGPD |
5. Patterns de Messaging Distribuรฉ
5.1 Dead Letter Queue (DLQ)
MAX_RETRIES = 3
while True:
msg = consumer.poll(1.0)
headers = dict(msg.headers() or [])
retry_count = int(headers.get('retry_count', b'0'))
try:
process_message(msg.value())
consumer.commit(msg)
except Exception as e:
if retry_count >= MAX_RETRIES:
# Envoyer vers DLQ
producer.produce('orders-dlq', key=msg.key(), value=msg.value(),
headers=[('error', str(e))])
else:
# Retry
producer.produce('orders-retry', key=msg.key(), value=msg.value(),
headers=[('retry_count', str(retry_count + 1))])
consumer.commit(msg)
5.2 Saga Pattern
Create Order โโโถ Reserve Stock โโโถ Process Payment โโโถ Ship Order
โ โ โ
โผ โผ โผ
Cancel Order โโโ Release Stock โโโ Refund Payment (compensation)
5.3 Event Sourcing
events = [
{'type': 'OrderCreated', 'order_id': 1, 'amount': 100},
{'type': 'PaymentReceived', 'order_id': 1},
{'type': 'OrderShipped', 'order_id': 1},
]
def rebuild_state(events):
state = {}
for e in events:
if e['type'] == 'OrderCreated':
state = {'id': e['order_id'], 'status': 'created'}
elif e['type'] == 'PaymentReceived':
state['status'] = 'paid'
elif e['type'] == 'OrderShipped':
state['status'] = 'shipped'
return state
5.4 CQRS
Commands โโโถ Events (Kafka) โโโถ Projector โโโถ Read Model (optimized)
โ โ
โผ โผ
Write DB Query API
(PostgreSQL) (Elasticsearch)
6. Exercices Pratiques
Exercice 1 : Kafka Transactions
Implรฉmenter un producer transactionnel qui รฉcrit sur 2 topics atomiquement.
Exercice 2 : RabbitMQ Task Queue
Crรฉer une task queue avec prioritรฉs et DLQ.
Exercice 3 : Pipeline CDC Complet
Dรฉployer PostgreSQL + Kafka + Debezium et sync vers un data warehouse.
Exercice 4 : Comparatif Performance
Comparer Kafka vs RabbitMQ (100K messages, throughput, latence).
Exercice 5 : Outbox Pattern
Implรฉmenter le pattern Outbox avec Debezium.
๐ Ressources
- Apache Kafka Docs
- RabbitMQ Docs
- Apache Pulsar Docs
- Debezium Docs
- Kafka: The Definitive Guide โ Neha Narkhede
- Designing Data-Intensive Applications โ Martin Kleppmann
โก๏ธ Prochaine รฉtape
๐ Module suivant : 30_spark_scala_deep_dive โ Spark & Scala Deep Dive
๐ Fรฉlicitations ! Tu as terminรฉ le module Distributed Messaging.