Le TP Kafka vous a fait construire, autour du flux des compteurs de passage (topic Counters), plusieurs applications consommatrices écrites « à la main » avec l'API cliente Kafka :
CounterWorker) ;StatsConsumer) ;Ces applications fonctionnent, mais une grande partie de leur code ne traite pas le métier : il gère la répartition du travail, l'état en mémoire, les fenêtres, les reprises après panne.
Votre mission consiste à :
À la fin du TD, vous devez être capables de :
map, filter, keyBy et agrégation ;
┌──────────────┐ ┌───────────────────────┐ ┌────────────────────────────────────────┐
│ Simulateur │ ─────► │ Kafka (1 broker) │ ─────► │ Cluster Flink │
│ (producteur) │ │ │ │ │
│ :8084 │ │ topic Counters (3 P.) │ │ ┌────────────┐ │
└──────────────┘ │ │ │ │ JobManager │ coordonne, UI :8081 │
│ topic point-totals │ ◄───── │ └─────┬──────┘ │
│ :9092 │ │ │ │
└───────────▲───────────┘ │ ┌─────▼────────┐ ┌──────────────┐ │
│ │ │ TaskManager 1│ │ TaskManager 2│ │
┌───────────┴───────────┐ │ │ [slot][slot] │ │ [slot][slot] │ │
│ kafka-ui :8090 │ │ └──────────────┘ └──────────────┘ │
└───────────────────────┘ └────────────────────────────────────────┘
| Composant | Rôle | Adresse |
|---|---|---|
| Simulateur | Produit les mesures des compteurs dans Counters (le même que dans le TP Kafka) | http://localhost:8084 |
| Kafka | 1 broker KRaft : topics Counters (entrée) et point-totals (sortie de la mission 7) | localhost:9092 (hôte), kafka-1:19092 (réseau Docker) |
| kafka-ui | Exploration des topics et des groupes | http://localhost:8090 |
| JobManager Flink | Reçoit les jobs, planifie leur exécution, interface web | http://localhost:8081 |
| TaskManagers Flink (×2) | Exécutent les traitements, 2 slots chacun | — |
| Flink local (IDE) | Mini-cluster lancé dans votre IDE, avec sa propre interface web | http://localhost:8082 |
Ce qui change par rapport au TP Kafka : le cluster Kafka est réduit à un seul broker (facteur de réplication 1) pour laisser de la mémoire à Flink. La réplication et le failover ont été étudiés dans le TP Kafka ; ils ne sont pas l'objet de ce TD.
Ce qui ne change pas : le simulateur, le format des messages, la clé (COUNTER-xxxxx-n), le topic Counters à 3 partitions, kafka-ui et les scripts live.sh, kafka.sh et poison.sh.
docker --version docker compose version java -version mvn -v
td1/
├── docker-compose.yml Kafka, kafka-ui, simulateur, cluster Flink
├── counter-simulator/ le simulateur du TP Kafka
├── flink-jobs/ projet Maven des jobs Flink du TD
│ └── src/main/java/fr/tp/flink/
│ ├── common/ CounterEvent (le même record que dans le TP Kafka), outils
│ ├── Job01Lecture.java
│ ├── Job02Transformations.java
│ ├── Job03Agregation.java
│ ├── Job04Parallelisme.java
│ └── Job05Fenetres.java
└── scripts/
├── live.sh pilote le flux du simulateur
├── kafka.sh outils Kafka en ligne de commande
├── poison.sh injecte un message invalide
└── flink.sh soumet / liste / arrête les jobs sur le cluster Flink
docker compose up -d --build docker compose ps # kafka-init doit être « exited (0) », les autres « running » docker compose logs kafka-init # création des topics Counters et point-totals
Démarrer le flux : 5 points de comptage × 2 sous-points = 10 compteurs, un message par compteur et par seconde, horloge simulée avançant de 60 s à chaque tick :
./scripts/live.sh start 5 2 1000 60 ./scripts/live.sh status
Vérifier dans kafka-ui (http://localhost:8090) : Topics → Counters → Messages.
Format d'un message (clé COUNTER-00001-1) :
{"counterId":"COUNTER-00001","pointCode":"P001","pointName":"Point 1","location":"Site 1",
"subPointNumber":1,"timestamp":"2026-11-15T07:00","entries":5,"exits":3,
"quarterHourEntries":40,"quarterHourExits":32}
L'horloge simulée avance d'une minute par seconde réelle : une heure simulée défile en une minute. Le profil par défaut (PEAK) produit des pointes d'affluence entre 8 h et 10 h et entre 17 h et 19 h (heures simulées).
Cette partie ne reprend pas tout le TP Kafka. Elle réactive uniquement les trois notions sur lesquelles Flink va s'appuyer : clé → partition, groupe de consommateurs et offsets, état d'une application consommatrice.
./scripts/kafka.sh kafka-topics --describe --topic Counters
Relever :
| Élément | Valeur |
|---|---|
| Nombre de partitions | |
| Facteur de réplication | |
| Leader de chaque partition |
Répondre :
Afficher, pour 30 messages, la partition, l'offset et la clé (sans la valeur) :
./scripts/kafka.sh kafka-console-consumer --topic Counters --max-messages 30 \ --formatter-property print.partition=true \ --formatter-property print.offset=true \ --formatter-property print.key=true \ --formatter-property print.value=false
Relancer la commande une seconde fois, puis compléter :
| Partition | Clés observées |
|---|---|
| 0 | |
| 1 | |
| 2 |
Répondre :
Retenez ce principe « même clé → même partition → même consommateur » : Flink propose le même mécanisme à l'intérieur d'un traitement, avec l'opération keyBy (mission 7).
Consommer 20 messages dans un groupe nommé rappel, puis décrire le groupe :
./scripts/kafka.sh kafka-console-consumer --topic Counters --group rappel --max-messages 20 \ --formatter-property print.partition=true --formatter-property print.offset=true ./scripts/kafka.sh kafka-consumer-groups --describe --group rappel
Attendre une dizaine de secondes et décrire à nouveau le groupe.
Répondre :
CURRENT-OFFSET, LOG-END-OFFSET et LAG ?Dans deux terminaux, lancer simultanément :
./scripts/kafka.sh kafka-console-consumer --topic Counters --group rappel --max-messages 60 \ --formatter-property print.partition=true --formatter-property print.value=false --formatter-property print.key=true
Répondre :
./scripts/kafka.sh kafka-consumer-groups --group rappel --reset-offsets --to-earliest --topic Counters --dry-run ./scripts/kafka.sh kafka-consumer-groups --group rappel --reset-offsets --to-earliest --topic Counters --execute ./scripts/kafka.sh kafka-console-consumer --topic Counters --group rappel --max-messages 5 \ --formatter-property print.partition=true --formatter-property print.offset=true
Répondre :
auto.offset.reset et un offset commité ? Quand auto.offset.reset est-il utilisé ?
Extrait simplifié de CounterWorker.processBatch() (TP Kafka, étape 3) :
for (ConsumerRecord<String, String> r : records) {
nextOffsets.put(new TopicPartition(r.topic(), r.partition()), new OffsetAndMetadata(r.offset() + 1));
CounterEvent e;
try {
e = CounterEvent.parse(r.value());
} catch (IllegalArgumentException ex) {
continue; // message illisible ignoré
}
CounterTotals t = totals.computeIfAbsent(r.key(), k -> new CounterTotals()); // état en mémoire
t.add(e, r.partition(), r.offset(), workerId);
touched.put(r.key(), t);
}
flushTotals(touched); // publication dans counter-totals
consumer.commitSync(nextOffsets); // puis commit des offsets
Rappelez-vous aussi : la méthode restore() rechargeait l'état depuis le topic compacté counter-totals à chaque rebalance, et le StatsConsumer calculait lui-même le début de chaque fenêtre de 15 minutes (WindowStats.windowStart()).
Compléter le tableau :
| Préoccupation | Comment le TP Kafka l'a traitée | Problèmes rencontrés |
|---|---|---|
| Répartir le travail entre plusieurs instances | ||
| Conserver un état (cumuls) | ||
| Reprendre après une panne sans perte ni doublon | ||
| Regrouper les messages par fenêtre de temps | ||
| Gérer un message invalide |
Toutes ces préoccupations reviennent dans presque toutes les applications de traitement de flux. Un moteur de traitement de flux comme Flink les prend en charge : vous décrivez le traitement (lire, transformer, regrouper, agréger, écrire), et le moteur s'occupe de le distribuer, de gérer l'état et de reprendre après une panne. Ce TD aborde la description du traitement et sa distribution. L'état et la tolérance aux pannes feront l'objet d'une séance dédiée.
| Terme | Définition |
|---|---|
| Job | Le programme de traitement soumis au cluster ; il tourne en continu tant qu'on ne l'arrête pas |
| Opérateur | Une étape du traitement : source, map, filter, agrégation, sink… |
| DataStream | Un flux (potentiellement infini) d'éléments d'un même type, qui relie les opérateurs |
| Source / Sink | Opérateur d'entrée (ici : Kafka) / de sortie (ici : affichage ou Kafka) |
| Parallélisme | Nombre d'instances d'un opérateur qui s'exécutent en même temps |
| Sous-tâche (subtask) | Une instance d'un opérateur ; un opérateur de parallélisme 3 a 3 sous-tâches |
| JobManager | Reçoit les jobs, découpe le graphe en tâches, les place sur les TaskManagers, supervise |
| TaskManager | Processus (JVM) qui exécute les sous-tâches |
| Slot | Une « place » d'exécution dans un TaskManager ; il borne le parallélisme possible |
Ouvrir l'interface web du cluster : http://localhost:8081.
Relever :
| Élément | Valeur |
|---|---|
| Version de Flink | |
| Nombre de TaskManagers | |
| Nombre total de slots | |
| Slots disponibles |
Repérer dans docker-compose.yml la variable FLINK_PROPERTIES du JobManager et des TaskManagers.
Répondre :
CounterWorker était une application Java autonome, lancée en plusieurs exemplaires. Ici, qui lance les exemplaires du traitement ?
Ouvrir flink-jobs dans l'IDE (projet Maven), puis Job01Lecture.java :
StreamExecutionEnvironment env = Jobs.environment();
KafkaSource<String> source = KafkaSource.<String>builder()
.setBootstrapServers(params.bootstrap())
.setTopics("Counters")
.setGroupId("td1-job01")
.setStartingOffsets(OffsetsInitializer.latest())
.setValueOnlyDeserializer(new SimpleStringSchema())
.build();
DataStream<String> messages = env.fromSource(source, WatermarkStrategy.noWatermarks(), "Kafka : Counters");
DataStream<CounterEvent> events = messages
.map(CounterEvent::parse)
.name("Parsing JSON");
events.print("EVT");
env.execute("Job01 - lecture du topic Counters");
Kafka « Counters » ──► [ source ] ──► [ map : JSON → CounterEvent ] ──► [ sink : print ]
Répondre :
map ?env.execute(…)) ? Pourquoi dit-on que la construction d'un job est « paresseuse » ?OffsetsInitializer.latest() ?Chaque job peut être lancé de deux façons.
Le job s'exécute dans un mini-cluster Flink démarré dans la JVM de l'IDE. C'est le mode le plus rapide pour mettre au point un traitement.
main de Job01Lecture. À la première exécution, modifier la configuration de lancement : Modify options → Add dependencies with “provided” scope to classpath. Les arguments du job se saisissent dans Program arguments.flink-jobs :
mvn -q compile exec:exec -Djob=Job01Lecture mvn -q compile exec:exec -Djob=Job02Transformations -Djob.args="--seuil 18" # avec des arguments
Les lignes EVT> s'affichent dans la console de l'IDE. L'interface web du mini-cluster est sur http://localhost:8082. Arrêter le job en arrêtant le programme.
Pourquoi le scope provided ? Les bibliothèques de Flink sont fournies par le cluster : on ne les embarque pas dans le jar du job. Pour exécuter le job hors du cluster, il faut donc les ajouter au classpath. Voir les commentaires du pom.xml.
Compiler le jar (flink-jobs/target/flink-jobs.jar), puis le soumettre :
cd flink-jobs && mvn -q package && cd .. # ou, sans Maven local : docker compose run --rm build ./scripts/flink.sh run Job01Lecture ./scripts/flink.sh list ./scripts/flink.sh logs # sorties des TaskManagers (Ctrl+C pour quitter)
Cycle de modification sur le cluster : modifier le code → recompiler le jar → arrêter l'ancien job (./scripts/flink.sh cancel-all) → soumettre le nouveau. Un job déjà soumis ne voit pas vos modifications.
Le cluster n'a que 4 slots : pensez à arrêter les jobs dont vous n'avez plus besoin.
Dans l'interface web, ouvrir le job en cours (Running Jobs).
Relever :
| Élément | Valeur |
|---|---|
| Nombre de « boîtes » dans le graphe du job | |
| Noms des opérateurs dans chaque boîte | |
| Parallélisme | |
| Valeurs des colonnes Records Received et Records Sent |
Répondre :
EVT> s'affichent-elles quand le job tourne sur le cluster ? Pourquoi pas dans votre terminal ?Pour chaque expérience, noter ce que vous observez et l'explication.
| # | Action | Question |
|---|---|---|
| a | Arrêter le flux (./scripts/live.sh stop), attendre 20 s, le relancer | L'état du job change-t-il ? Qu'est-ce que cela dit d'un flux « infini » ? |
| b | Décommenter messages.print(“BRUT”), recompiler, resoumettre | Combien de lignes par message ? Les deux sinks reçoivent-ils les mêmes éléments ? Que montre maintenant le graphe ? |
| c | Remplacer latest() par earliest(), recompiler, resoumettre | Que lit-on ? Combien de temps pour « rattraper » le flux ? |
| d | Job en cours : ./scripts/kafka.sh kafka-consumer-groups –list | Le groupe td1-job01 apparaît-il ? Comparez avec le groupe rappel de la mission 2. |
| e | Injecter un message invalide : ./scripts/poison.sh | Quel est l'état du job dans l'interface ? Lire l'onglet Exceptions. Le job redémarre-t-il ? |
Pour l'expérience d : la source Kafka de Flink n'utilise pas le mécanisme des groupes de Kafka pour se répartir les partitions. C'est Flink qui assigne lui-même les partitions à ses sous-tâches, et il ne commite les offsets dans Kafka qu'au moment des checkpoints, qui ne sont pas activés ici (séance « état et tolérance aux pannes »).
Pour l'expérience e : comparez avec le StatsConsumer du TP Kafka (étape 4.4), qui redémarrait et replantait indéfiniment sur le même message.
Ouvrir Job02Transformations.java :
// flatMap : 1 entrée → 0, 1 ou n sorties (ici : un message invalide est ignoré)
DataStream<CounterEvent> events = messages
.flatMap(new ParseOrSkip())
.name("Parsing (invalides ignorés)");
// map : 1 entrée → exactement 1 sortie
DataStream<Passage> passages = events
.map(e -> new Passage(e.pointCode(), e.counterId(), e.timestamp(),
e.entries(), e.exits(), e.entries() - e.exits()))
.name("Projection + solde");
// filter : 1 entrée → 0 ou 1 sortie, du même type
DataStream<Passage> affluence = passages
.filter(p -> p.entries() >= seuil)
.name("Affluence >= " + seuil);
affluence.print("AFFLUENCE");
Kafka ──► [ flatMap : parsing ] ──► [ map : Passage + solde ] ──► [ filter : entries >= seuil ] ──► print
Compléter :
| Opération | Nombre de sorties par entrée | Le type peut-il changer ? | Utilisation dans le job |
|---|---|---|---|
map | |||
filter | |||
flatMap |
Arrêter le job précédent (./scripts/flink.sh cancel-all), puis :
./scripts/flink.sh run Job02Transformations --seuil 15 ./scripts/flink.sh logs
Répondre :
AFFLUENCE apparaissent-elles ? Pourquoi ?./scripts/poison.sh). Le job survit-il ? Où le message ignoré est-il signalé ?AFFLUENCE obtient-on, approximativement, par minute ? Que mesure l'écart ?| # | Modification | À observer / expliquer |
|---|---|---|
| a | Resoumettre avec –seuil 10 puis –seuil 20 | Effet sur le volume de sortie. Pourquoi le seuil est-il un paramètre du job plutôt qu'une constante dans le code ? |
| b | Passer le simulateur en profil constant : ./scripts/live.sh start 5 2 1000 60 CONSTANT (5 entrées, 4 sorties par message), puis revenir à PEAK | Que devient la sortie avec –seuil 15 ? Le job a-t-il changé ? |
| c | Ajouter le champ location (site) au record Passage et l'afficher | Quelles lignes du code faut-il modifier ? |
| d | Ne garder que les messages du point P003 en affluence, en plaçant le filter avant le map (sur CounterEvent) | Le résultat est-il le même ? Quel ordre est le plus économe, et pourquoi ? |
Question de synthèse :
Ouvrir Job03Agregation.java : il calcule le cumul des entrées et des sorties par point de comptage et le publie dans le topic point-totals.
DataStream<PointTotal> totals = events
.map(PointTotal::of) // chaque événement → un total « unitaire » (1 message)
.keyBy(PointTotal::pointCode) // regroupement logique par point
.reduce(PointTotal::plus) // cumul courant de la clé + nouvel élément → nouveau cumul
.name("Cumul par point");
totals.print("TOTAL");
totals.sinkTo(sink); // KafkaSink : topic point-totals, clé = pointCode
keyBy(pointCode)
Kafka ──► [ parsing ] ──► [ map ] ══════ hachage de la clé ═════► [ reduce : cumul par point ] ──┬──► print
(un cumul par clé, en mémoire) └──► Kafka « point-totals »
Répondre :
reduce n'est-il disponible qu'après un keyBy ?reduce émet-il un seul résultat final, ou un résultat à chaque message reçu ? Pourquoi, sur un flux infini, ne peut-il pas y avoir de « résultat final » ?reduce conserve-t-il le cumul de chaque point entre deux messages ?
./scripts/flink.sh cancel-all ./scripts/flink.sh run Job03Agregation ./scripts/flink.sh logs
Dans kafka-ui, ouvrir Topics → point-totals → Messages.
Relever :
| Élément | Valeur |
|---|---|
| Nombre de boîtes dans le graphe du job | |
Type de liaison affiché entre les boîtes (FORWARD, HASH…) | |
| Records Sent de la première boîte, Records Received de la seconde : que transite par la liaison ? | |
Partition de point-totals contenant les cumuls de P001 |
Répondre :
keyBy coupe-t-il la chaîne d'opérateurs (une boîte de plus qu'en mission 5) ?point-totals ?CounterWorker : CounterWorker (TP Kafka) | Job03Agregation (Flink) |
|
|---|---|---|
| Lignes de code du traitement | ≈ 600 | |
| Qui regroupe les messages par clé ? | ||
| Où est conservé l'état (les cumuls) ? | ||
| Comment répartir la charge sur plusieurs machines ? |
| # | Action | À observer / expliquer |
|---|---|---|
| a | Arrêter puis resoumettre Job03Agregation | Les cumuls reprennent-ils là où ils en étaient ? Pourquoi ? |
| b | Resoumettre avec –start earliest | Les cumuls sont-ils justes ? Est-ce une bonne solution si le topic contient un an de données ? |
| c | Cumuler par site au lieu de par point. Relancer le flux avec 10 points (./scripts/live.sh start 10 2 1000 60) pour avoir 2 sites | Quelles lignes modifier ? Combien de clés distinctes ? |
| d | Ajouter un second traitement : le nombre de messages reçus par compteur, avec l'agrégation prédéfinie sum (code ci-dessous) | Pourquoi .returns(…) est-il nécessaire ? |
| e | Question : comment calculer le total de tous les points ? Quelle clé utiliser ? | Combien de sous-tâches feraient alors tout le travail ? Conséquence ? |
Code pour l'expérience d :
events.map(e -> Tuple2.of(e.counterId(), 1L))
.returns(Types.TUPLE(Types.STRING, Types.LONG)) // type des tuples (perdu par la lambda)
.keyBy(t -> t.f0)
.sum(1) // somme du champ n°1 du tuple
.print("NB");
L'expérience a montre la limite du job tel qu'il est écrit : l'état (les cumuls) n'existe qu'en mémoire et disparaît avec le job. Le CounterWorker le restaurait à la main depuis counter-totals. Flink sait sauvegarder cet état et les positions de lecture Kafka de façon cohérente (checkpoints) : c'est l'objet de la séance « état et tolérance aux pannes ».
Job04Parallelisme suit le chemin de chaque compteur : partition Kafka → sous-tâche de la source → keyBy(pointCode) → sous-tâche qui traite le point. Il n'affiche une ligne que lorsqu'il découvre un nouveau chemin.
Partitions Kafka Source (p-source) keyBy(pointCode) Routage (p-agg) P0 ──────────────► [ source #0 ] ──┐ ┌──► [ #0 ] P002, P003… P1 ──────────────► [ source #1 ] ──┼══════ hachage de pointCode ══════┤ P2 ──────────────► [ source #2 ] ──┘ └──► [ #1 ] P001, P004…
Pour chaque configuration : arrêter le job précédent, soumettre, lire les lignes ROUTE (./scripts/flink.sh logs) et le graphe dans l'interface web (cliquer sur une boîte, onglet SubTasks).
./scripts/flink.sh run Job04Parallelisme # tout à 1 ./scripts/flink.sh run -p 2 Job04Parallelisme # parallélisme par défaut du job = 2 ./scripts/flink.sh run Job04Parallelisme --p-source 3 --p-agg 2 ./scripts/flink.sh run Job04Parallelisme --p-source 4 --p-agg 2 ./scripts/flink.sh run -p 5 Job04Parallelisme
Compléter :
| Configuration | Sous-tâches source | Sous-tâches source qui reçoivent des messages | Slots occupés | Points traités par chaque sous-tâche « Routage » |
|---|---|---|---|---|
| défaut (1) | ||||
-p 2 | ||||
–p-source 3 –p-agg 2 | ||||
–p-source 4 –p-agg 2 | ||||
-p 5 |
Répondre :
–p-source 4, pourquoi une sous-tâche source ne reçoit-elle rien ? Quel lien avec la règle du consumer group (mission 2) ?keyBy ? Pourquoi est-ce indispensable pour calculer un cumul juste ?-p 5 ? Pourquoi ?Counters, suffit-il d'augmenter le parallélisme du job ? Quelle autre limite faut-il lever ? (Rappel TP Kafka, étape 3.7 : effet d'un changement du nombre de partitions sur la répartition des clés.)
Expérience complémentaire : resoumettre avec –chainage false et comparer le graphe. Que coûte la séparation des opérateurs en tâches distinctes ?
Le cumul de la mission 7 grossit indéfiniment. Pour suivre l'affluence, on veut plutôt des résultats par tranche de temps : « entrées du point P001 entre 08:00 et 08:15 ». Une fenêtre regroupe les éléments d'une même clé sur un intervalle de temps et produit un résultat à la fermeture de cet intervalle.
events.keyBy(CounterEvent::pointCode)
.window(TumblingProcessingTimeWindows.of(Duration.ofSeconds(10))) // fenêtres fixes de 10 s
.process(new Summarize(...)) // résumé de chaque fenêtre
Mais de quel temps parle-t-on ?
| Horloge | Définition | Dans notre flux |
|---|---|---|
| Temps de traitement | L'heure de la machine qui exécute l'opérateur | L'heure réelle |
| Temps de l'événement | L'heure à laquelle l'événement s'est produit, portée par le message | Le champ timestamp (horloge simulée) |
./scripts/flink.sh cancel-all ./scripts/live.sh start 5 2 1000 60 ./scripts/flink.sh run Job05Fenetres # fenêtres de 10 s, temps de traitement ./scripts/flink.sh logs
Répondre :
./scripts/live.sh start 5 2 500 60) : que devient le nombre de messages par fenêtre ?
./scripts/flink.sh run Job05Fenetres --mode evenement # fenêtres de 15 min de temps simulé
Dans ce mode, le job indique à Flink comment lire l'horodatage de chaque événement, et jusqu'à quel retard l'attendre (WatermarkStrategy dans Job05Fenetres.java). La fenêtre [08:00, 08:15[ se ferme quand le flux montre que le temps des événements a dépassé 08:15.
Répondre :
500 puis 1000 ms) : le contenu des fenêtres change-t-il ? Et leur fréquence d'affichage ?StatsConsumer du TP Kafka (fenêtres de 15 min, temps de l'événement) : qui calcule le début et la fin des fenêtres ? qui décide qu'une fenêtre est terminée ?Soumettre les deux variantes en relisant tout le topic :
./scripts/flink.sh cancel-all ./scripts/flink.sh run Job05Fenetres --start earliest ./scripts/flink.sh run Job05Fenetres --start earliest --mode evenement
Répondre :
Les watermarks (comment Flink mesure l'avancement du temps des événements), les messages en retard et les autres types de fenêtres (glissantes, de session) sont l'objet de la séance « traitements, fenêtres et agrégations ».
Job03Agregation : simulateur, topic, opérateurs Flink (avec leur parallélisme), topic de sortie. Indiquer où les données sont redistribuées.map, filter, flatMap, keyBy, reduce, sum, window : lesquels transforment chaque élément indépendamment ? lequel redistribue les données ? lesquels conservent un état ?Job03Agregation tel qu'il est écrit, qu'un job de production devrait lever.
# Environnement docker compose up -d --build docker compose ps docker compose down # arrêt (ajouter -v pour effacer aussi les données Kafka) # Flux du simulateur ./scripts/live.sh start 5 2 1000 60 # points, sous-points, période (ms), pas de l'horloge simulée (s) [, PEAK|RANDOM|CONSTANT] ./scripts/live.sh status ./scripts/live.sh stop # Kafka ./scripts/kafka.sh kafka-topics --describe --topic Counters ./scripts/kafka.sh kafka-console-consumer --topic point-totals --from-beginning --max-messages 10 --formatter-property print.key=true ./scripts/kafka.sh kafka-consumer-groups --list ./scripts/kafka.sh kafka-consumer-groups --describe --group rappel ./scripts/poison.sh # message invalide dans Counters # Compilation des jobs cd flink-jobs && mvn -q package && cd .. # ou : docker compose run --rm build # Exécution locale (IDE ou Maven), interface web http://localhost:8082 mvn -q compile exec:exec -Djob=Job03Agregation -Djob.args="--start earliest" # Cluster Flink, interface web http://localhost:8081 ./scripts/flink.sh run Job03Agregation [--start earliest] ./scripts/flink.sh run -p 2 Job04Parallelisme --p-source 3 ./scripts/flink.sh list ./scripts/flink.sh cancel <jobId> ./scripts/flink.sh cancel-all ./scripts/flink.sh logs
| Paramètre de job | Jobs | Effet |
|---|---|---|
–start latest ou –start earliest | tous | Lire seulement les nouveaux messages (défaut) ou tout le topic |
–seuil N | Job02 | Seuil d'affluence (défaut 15) |
–p-source N, –p-agg N | Job04 | Parallélisme de la source / de l'opérateur après le keyBy |
–chainage false | Job04 | Désactive le chaînage des opérateurs |
–mode traitement ou –mode evenement | Job05 | Horloge des fenêtres (défaut : traitement) |
–taille N | Job05 | Taille des fenêtres : secondes (traitement, défaut 10) ou minutes (événement, défaut 15) |
–bootstrap hôte:port | tous | Adresse de Kafka (défaut : localhost:9092 depuis l'IDE, kafka-1:19092 sur le cluster) |
En cas de problème
NoResourceAvailableException : plus de slots libres → ./scripts/flink.sh cancel-all../scripts/live.sh status) ? Le job lit-il depuis latest alors que le flux est arrêté ?ClassNotFoundException sur une classe Flink : l'option Add dependencies with “provided” scope to classpath n'est pas cochée.BindException sur le port 8082 : un autre job local tourne déjà.docker compose exec reçoit un chemin du type C:/Program Files/Git/opt/… : utiliser les scripts fournis (ils désactivent la conversion des chemins de Git Bash).