eadl:bloc5:fm3:td1

Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Les deux révisions précédentes Révision précédente
Prochaine révision
Révision précédente
eadl:bloc5:fm3:td1 [2026/09/15 18:12] – [7. Générer un fichier de ventes] jcheroneadl:bloc5:fm3:td1 [2026/09/17 14:29] (Version actuelle) – jcheron
Ligne 43: Ligne 43:
 ===== 3. Architecture utilisée ===== ===== 3. Architecture utilisée =====
  
-<sxh> +<html><div class='imageB'></html> 
-                         +-----------------+ +<code> 
-                         | ResourceManager | +                         ┌─────────────────┐ 
-                         |      YARN       | +                         │ ResourceManager │ 
-                         +--------+--------+ +                         │      YARN       │ 
-                                  | +                         └────────┬────────┘ 
-              +-------------------+-------------------+ +                                  │ 
-              |                   |                   | +              ┌───────────────────┼───────────────────┐ 
-       +------v------+    +-------v-----+    +--------v------+ +              │                   │                   │ 
-       | NodeManager  |    | NodeManager |    | NodeManager   | +       ┌──────▼──────┐    ┌───────▼─────┐    ┌────────▼──────┐ 
-       |  DataNode 1  |    | DataNode 2 |    |  DataNode 3   | +       │ NodeManager │    │ NodeManager │    │  NodeManager  │ 
-       +--------------+    +-------------+    +---------------+ +       │  DataNode 1 │    │  DataNode 2 │    │  DataNode 3   │ 
-              ^                   ^                   ^ +       └─────────────┘    └─────────────┘    └───────────────┘ 
-              +-------------------+-------------------+ +              ▲                   ▲                   ▲ 
-                                  | +              └───────────────────┼───────────────────┘ 
-                         +--------v--------+ +                                  │ 
-                         |    NameNode     | +                         ┌────────▼────────┐ 
-                         | Métadonnées HDFS| +                         │    NameNode     │ 
-                         +-----------------+ +                         │ Métadonnées HDFS│ 
-</sxh> +                         └─────────────────┘ 
 +</code> 
 +<html></div></html>
 ^ Composant ^ Rôle ^ ^ Composant ^ Rôle ^
 | NameNode | Gère les métadonnées HDFS | | NameNode | Gère les métadonnées HDFS |
Ligne 100: Ligne 101:
 git clone https://github.com/big-data-europe/docker-hadoop.git git clone https://github.com/big-data-europe/docker-hadoop.git
 cd docker-hadoop cd docker-hadoop
 +</sxh>
 +
 +copier le fichier docker-compose.yml sous **docker-compose.yml.back** :
 +
 +Remplacer l'actuel **docker-compose.yml** par :  
 +<sxh yml>
 +version: "3"
 +
 +services:
 +  namenode:
 +    image: bde2020/hadoop-namenode:2.0.0-hadoop3.2.1-java8
 +    container_name: namenode
 +    restart: always
 +    ports:
 +      - 9870:9870
 +      - 9000:9000
 +    volumes:
 +      - hadoop_namenode:/hadoop/dfs/name
 +    environment:
 +      - CLUSTER_NAME=test
 +    env_file:
 +      - ./hadoop.env
 +  datanode1:
 +    image: bde2020/hadoop-datanode:2.0.0-hadoop3.2.1-java8
 +    container_name: datanode1
 +    restart: always
 +    ports:
 +      - "9864:9864"
 +    volumes:
 +      - hadoop_datanode1:/hadoop/dfs/data
 +    environment:
 +      SERVICE_PRECONDITION: "namenode:9870"
 +    env_file:
 +      - ./hadoop.env
 +  datanode2:
 +    image: bde2020/hadoop-datanode:2.0.0-hadoop3.2.1-java8
 +    container_name: datanode2
 +    restart: always
 +    ports:
 +      - "9865:9864"
 +    volumes:
 +      - hadoop_datanode2:/hadoop/dfs/data
 +    environment:
 +      SERVICE_PRECONDITION: "namenode:9870"
 +    env_file:
 +      - ./hadoop.env
 +  datanode3:
 +    image: bde2020/hadoop-datanode:2.0.0-hadoop3.2.1-java8
 +    container_name: datanode3
 +    restart: always
 +    ports:
 +      - "9866:9864"
 +    volumes:
 +      - hadoop_datanode3:/hadoop/dfs/data
 +    environment:
 +      SERVICE_PRECONDITION: "namenode:9870"
 +    env_file:
 +      - ./hadoop.env
 +  resourcemanager:
 +    image: bde2020/hadoop-resourcemanager:2.0.0-hadoop3.2.1-java8
 +    container_name: resourcemanager
 +    restart: always
 +    ports:
 +      - "8088:8088"
 +    environment:
 +      SERVICE_PRECONDITION: "namenode:9000 namenode:9870 datanode1:9864"
 +    env_file:
 +      - ./hadoop.env
 +  nodemanager1:
 +    image: bde2020/hadoop-nodemanager:2.0.0-hadoop3.2.1-java8
 +    container_name: nodemanager
 +    restart: always
 +    ports:
 +      - "8042:8042"
 +    environment:
 +      SERVICE_PRECONDITION: "namenode:9000 namenode:9870 datanode1:9864 resourcemanager:8088"
 +    env_file:
 +      - ./hadoop.env
 +  historyserver:
 +    image: bde2020/hadoop-historyserver:2.0.0-hadoop3.2.1-java8
 +    container_name: historyserver
 +    restart: always
 +    ports:
 +      - "8188:8188"
 +    environment:
 +      SERVICE_PRECONDITION: "namenode:9000 namenode:9870 datanode1:9864 resourcemanager:8088"
 +    volumes:
 +      - hadoop_historyserver:/hadoop/yarn/timeline
 +    env_file:
 +      - ./hadoop.env
 +volumes:
 +  hadoop_namenode:
 +  hadoop_datanode1:
 +  hadoop_datanode2:
 +  hadoop_datanode3:
 +  hadoop_historyserver:
 </sxh> </sxh>
  
Ligne 118: Ligne 215:
 <sxh> <sxh>
 namenode namenode
-datanode+datanode1 
 +datanode2 
 +datanode3
 resourcemanager resourcemanager
 nodemanager nodemanager
 historyserver historyserver
 +</sxh>
 +
 +Vérifier les hosts créés :
 +
 +<sxh bash;gutter:false>
 +docker exec -it namenode hdfs dfsadmin -report
 </sxh> </sxh>
  
Ligne 130: Ligne 235:
 | ResourceManager | [[http://localhost:8088|http://localhost:8088]] | | ResourceManager | [[http://localhost:8088|http://localhost:8088]] |
 | NodeManager | [[http://localhost:8042|http://localhost:8042]] | | NodeManager | [[http://localhost:8042|http://localhost:8042]] |
 +| DataNode1| [[http://localhost:9864|http://localhost:9864]] |
 +| DataNode2| [[http://localhost:9865|http://localhost:9865]] |
 +| DataNode3| [[http://localhost:9866|http://localhost:9866]] |
  
 <WRAP info round> <WRAP info round>
Ligne 162: Ligne 270:
 On considère l’architecture suivante : On considère l’architecture suivante :
  
-<sxh gutter:false>+<html><div class='imageB'></html> 
 +<code>
 Serveur unique Serveur unique
 │ │
Ligne 169: Ligne 278:
 ├── clics_j1.csv ├── clics_j1.csv
 └── application.log └── application.log
-</sxh> +</code> 
 +<html></div></html>
 <WRAP help round> <WRAP help round>
 Cette architecture atteint rapidement ses limites :  Cette architecture atteint rapidement ses limites : 
Ligne 184: Ligne 293:
  
 <sxh python> <sxh python>
 +import argparse
 import csv import csv
 import random import random
 from datetime import date, timedelta from datetime import date, timedelta
  
 +parser = argparse.ArgumentParser()
 +parser.add_argument("--lignes", type=int, default=200000, help="Nombre de lignes à générer")
 +args = parser.parse_args()
 categories = ["informatique", "maison", "sport", "livres"] categories = ["informatique", "maison", "sport", "livres"]
 villes = ["Paris", "Lyon", "Lille", "Nantes", "Bordeaux"] villes = ["Paris", "Lyon", "Lille", "Nantes", "Bordeaux"]
- 
 with open("ventes.csv", "w", newline="") as fichier: with open("ventes.csv", "w", newline="") as fichier:
     writer = csv.writer(fichier)     writer = csv.writer(fichier)
Ligne 201: Ligne 313:
         "montant"         "montant"
     ])     ])
- 
     date_depart = date(2026, 1, 1)     date_depart = date(2026, 1, 1)
  
-    for i in range(200000):+    for i in range(args.lignes):
         jour = date_depart + timedelta(days=random.randint(0, 30))         jour = date_depart + timedelta(days=random.randint(0, 30))
         commande_id = f"C{i:07d}"         commande_id = f"C{i:07d}"
Ligne 234: Ligne 345:
 </sxh> </sxh>
  
-Par défaut, HDFS découpe les fichiers en blocs de 128 Mo. Le fichier généré ici est volontairement petit (quelques Mo au plus) : il tiendra donc dans un seul bloc, et c'est normal. L'objectif de cette étape est seulement de vérifier que le fichier est bien généré et de comprendre son contenu — l'observation des blocs se fera en Mission 4, sur un fichier généré en plus grande quantité.+<WRAP info round> 
 +Par défaut, HDFS découpe les fichiers en blocs de 128 Mo. Le fichier généré ici est volontairement petit (quelques Mo au plus) : il tiendra donc dans un seul bloc, et c'est normal.  
 + 
 +L'objectif de cette étape est seulement de vérifier que le fichier est bien généré et de comprendre son contenu — l'observation des blocs se fera en Mission 4, sur un fichier généré en plus grande quantité.
 Pour générer un fichier plus volumineux (utile pour la Mission 4) : Pour générer un fichier plus volumineux (utile pour la Mission 4) :
 <sxh bash> <sxh bash>
 python3 generer_donnees.py --lignes 5000000 python3 generer_donnees.py --lignes 5000000
 </sxh> </sxh>
 +</WRAP>
 ====== Mission 3 - Stocker les données dans HDFS ====== ====== Mission 3 - Stocker les données dans HDFS ======
  
Ligne 715: Ligne 830:
 Les fichiers Parquet doivent être relus avec Spark plutôt qu’avec ''cat''. Les fichiers Parquet doivent être relus avec Spark plutôt qu’avec ''cat''.
  
-====== Mission 10 - Synthèse de l’architecture ======+====== Mission 10 - MapReduce sur nos propres données ======
  
-Compléter :+Le ''wordcount'' précédent utilise un exemple fourni par Hadoop, sur un fichier qui n'a rien à voir avec notre contexte e-commerce. Nous allons maintenant écrire notre propre traitement MapReduce, sur ''ventes.csv'', afin de calculer le **chiffre d'affaires total par catégorie**. 
 + 
 +<WRAP tip round> 
 +Vous allez retrouver exactement le même problème que celui résolu par Spark en Mission 9 (''groupBy("categorie").agg(sum("montant"))''). L'objectif est de comparer les deux approches : MapReduce "à la main" contre Spark. 
 +</WRAP> 
 + 
 +===== 19. MapReduce ===== 
 + 
 +==== 19.1 Rappel du format des données ====
  
 <sxh> <sxh>
-Fichier de ventes +date,commande_id,client_id,categorie,ville,montant 
-       | +2026-01-05,C0000001,CL00234,informatique,Paris,120.50 
-       v +2026-01-12,C0000002,CL01998,maison,Lyon,45.90 
-   _________ +...
-   _________ +
-       | +
-       v +
-Découpage en blocs +
-       | +
-       v +
-   _________ +
-   _________ +
-       | +
-       v +
-Attribution de ressources +
-       | +
-       v +
-   _________ +
-       | +
-       v +
-Résultats stockés dans HDFS+
 </sxh> </sxh>
  
-Associer chaque composant à son rôle :+----
  
-<sxh> +==== 19.2 Écrire le mapper ==== 
-HDFS + 
-YARN +Sur la machine hôte, créer ''mapper.py'' : 
-NameNode + 
-DataNode +<sxh python> 
-ResourceManager +#!/usr/bin/env python3 
-Spark +import sys 
-MapReduce+ 
 +for line in sys.stdin: 
 +    line = line.strip() 
 + 
 +    if line.startswith("date"):  # on saute l'en-tête 
 +        continue 
 + 
 +    champs = line.split(",") 
 +    date_vente, commande_id, client_id, categorie, ville, montant = champs 
 + 
 +    # clé = catégorie, valeur = montant 
 +    print(f"{categorie}\t{montant}")
 </sxh> </sxh>
  
-====== Travail à rendre ======+----
  
-Un fichier ''compte_rendu.md'' contenant :+==== 19.3 Écrire le reducer ====
  
-===== Partie 1 - Architecture =====+Créer ''reducer.py'' :
  
-  * un schéma de l’architecture Hadoop ; +<sxh python> 
-  * le rôle de chaque composant ; +#!/usr/bin/env python3 
-  * la différence entre Hadoop, HDFS et YARN.+import sys
  
-===== Partie 2 - Blocs =====+totaux = {}
  
-  * la taille du fichier ; +for line in sys.stdin: 
-  * le nombre de blocs ; +    categorie, montant = line.strip().split("\t") 
-  * le facteur de réplication ; +    montant = float(montant) 
-  * les DataNodes utilisés ; +    totaux[categorie] = totaux.get(categorie, 0) + montant
-  * un schéma de répartition des blocs.+
  
-===== Partie 3 - Tolérance aux pannes =====+for categorie, total in sorted(totaux.items()): 
 +    print(f"{categorie}\t{total:.2f}") 
 +</sxh>
  
-  * état du cluster avant l’arrêt d’un DataNode ; +----
-  * état du cluster après l’arrêt ; +
-  * résultat de la lecture du fichier ; +
-  * explication du comportement observé.+
  
-===== Partie 4 - YARN =====+==== 19.4 Copier les scripts dans le conteneur ====
  
-  * liste des nœuds ; +<sxh bash> 
-  * observation d’une application ; +docker cp mapper.py docker-hadoop-namenode-1:/tmp/mapper.py 
-  * rôle du ResourceManager ; +docker cp reducer.py docker-hadoop-namenode-1:/tmp/reducer.py 
-  * rôle des NodeManagers ; +</sxh>
-  * explication de la notion de conteneur.+
  
-===== Partie 5 - Traitement =====+Dans le conteneur ''namenode'', rendre les scripts exécutables :
  
-  * commande de soumission ; +<sxh bash> 
-  * résultat obtenu ; +chmod +x /tmp/mapper.py /tmp/reducer.py 
-  * emplacement des résultats dans HDFS ; +</sxh>
-  * schéma du parcours des données.+
  
-===== Partie 6 - Conclusion =====+----
  
-Répondre en une dizaine de lignes :+==== 19.5 Lancer le traitement avec Hadoop Streaming ====
  
-> En quoi HDFS et YARN répondent-ils à des problèmes différents, et pourquoi sont-ils complémentaires pour exécuter un traitement distribué ?+<sxh bash> 
 +hadoop jar /opt/hadoop-3.2.1/share/hadoop/tools/lib/hadoop-streaming-*.jar \ 
 +    -input /data/ecommerce/raw/ventes/ventes.csv \ 
 +    -output /data/ecommerce/processed/ca_par_categorie_mr \ 
 +    -mapper /tmp/mapper.py \ 
 +    -reducer /tmp/reducer.py \ 
 +    -file /tmp/mapper.py \ 
 +    -file /tmp/reducer.py 
 +</sxh> 
 + 
 +<WRAP important round> 
 +Le répertoire de sortie ne doit pas déjà exister dans HDFS. Le supprimer si besoin : 
 + 
 +<sxh bash> 
 +hdfs dfs -rm -r /data/ecommerce/processed/ca_par_categorie_mr 
 +</sxh> 
 +</WRAP> 
 + 
 +<WRAP info round> 
 +Le chemin exact du jar hadoop-streaming peut varier selon l'image utilisée. Le rechercher si besoin avec : 
 +<sxh bash> 
 +find / -name "hadoop-streaming*.jar" 2>/dev/null 
 +</sxh> 
 +</WRAP> 
 + 
 +---- 
 + 
 +==== 19.6 Observer le résultat ==== 
 + 
 +<sxh bash> 
 +hdfs dfs -cat /data/ecommerce/processed/ca_par_categorie_mr/part-00000 
 +</sxh> 
 + 
 +Pendant l'exécution, consulter également : 
 + 
 +<sxh bash> 
 +yarn application -list 
 +</sxh> 
 + 
 +Répondre : 
 + 
 +  - Combien de reducers ont été utilisés ? Comment le savez-vous ? 
 +  - Ce traitement est-il apparu comme une application YARN, comme le wordcount précédent ? 
 +  - Le résultat obtenu est-il cohérent avec celui que vous avez obtenu avec Spark (Mission 9) ? 
 + 
 +---- 
 + 
 +==== 19.7 À vous de jouer — montant moyen par ville ==== 
 + 
 +On souhaite maintenant calculer le **montant moyen des ventes par ville**. 
 + 
 +<WRAP important round> 
 +**Piège classique** : on ne peut pas calculer une moyenne globale en faisant la moyenne des moyennes calculées par chaque mapper. Pourquoi ? 
 +</WRAP> 
 + 
 +Indice : le reducer doit recevoir à la fois la somme des montants et le nombre de ventes pour chaque ville, puis calculer la moyenne lui-même — pas l'inverse. 
 + 
 +==== Instructions ==== 
 + 
 +  - Modifier ''mapper.py'' pour qu'il émette ''(ville, montant)'' au lieu de ''(categorie, montant)''. 
 +  - Modifier ''reducer.py'' pour qu'il calcule une **moyenne** et non plus une somme. 
 +  - Relancer le traitement avec un nouveau répertoire de sortie : ''/data/ecommerce/processed/moyenne_par_ville''. 
 +  - Observer le résultat. 
 + 
 +==== Squelette du reducer à compléter ==== 
 + 
 +<sxh python> 
 +#!/usr/bin/env python3 
 +import sys 
 + 
 +sommes = {} 
 +compteurs = {} 
 + 
 +for line in sys.stdin: 
 +    ville, montant = line.strip().split("\t") 
 +    montant = float(montant) 
 +    # TODO : mettre à jour sommes[ville] et compteurs[ville] 
 + 
 +for ville in sorted(sommes): 
 +    moyenne = None  # TODO : calculer la moyenne 
 +    print(f"{ville}\t{moyenne:.2f}") 
 +</sxh> 
 + 
 +---- 
 + 
 +==== 19.8 Questions de synthèse ==== 
 + 
 +  - Pourquoi ne peut-on pas faire la moyenne des moyennes obtenues par chaque mapper pour obtenir la moyenne globale ? 
 +  - Combien de lignes le mapper a-t-il produites en sortie ? Combien le reducer en a-t-il produites ? 
 +  - Si vous deviez calculer le CA par catégorie **et** par ville en même temps, comment modifieriez-vous la clé émise par le mapper ? 
 +  - En quoi ce traitement est-il plus verbeux que l'équivalent Spark (''groupBy().agg()'') que vous allez écrire en Mission 9 ? Citez au moins deux différences concrètes. 
 +  - Où sont physiquement stockés les fichiers ''part-00000'' produits en sortie ? Quel composant Hadoop les a répartis sur le cluster ? 
 + 
 +<WRAP tip round> 
 +Comparez vos résultats MapReduce avec ceux de la mission 9 obtenus en Spark. 
 +</WRAP>
  
 ====== Commandes utiles ====== ====== Commandes utiles ======
Ligne 841: Ligne 1049:
 </sxh> </sxh>
  
-====== Schéma final à retenir ====== 
- 
-<sxh gutter:false> 
-                         YARN 
-             +--------------------------+ 
-             | ResourceManager          | 
-             | NodeManagers             | 
-             | Allocation des ressources| 
-             +------------+-------------+ 
-                          | 
-                          v 
-                       Spark 
-                 ou MapReduce 
-                          | 
-                          v 
-                         HDFS 
-             +-------------------------+ 
-             | NameNode                | 
-             | DataNodes               | 
-             | Blocs et réplication    | 
-             +-------------------------+ 
-</sxh> 
- 
-**HDFS stocke. YARN organise les ressources. Spark ou MapReduce traitent.** 
  • eadl/bloc5/fm3/td1.1789488725.txt.gz
  • Dernière modification : il y a 2 mois
  • de jcheron