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 17:54] – [6. Architecture initiale] 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 : le disque finit par saturer, la panne d'une seule machine rend toutes les données inaccessibles, et plusieurs traitements lancés simultanément se disputent les mêmes ressources (CPU, disque, mémoire). Augmenter la capacité signifie changer de machine ou ajouter du matériel, ce qui ne se fait ni rapidement ni indéfiniment. +Cette architecture atteint rapidement ses limites :  
-C'est exactement ce type de limite qu'Hadoop a été conçu pour dépasser, en répartissant à la fois le stockage (HDFS) et le calcul (YARN) sur plusieurs machines. C'est ce que nous allons construire et observer dans ce TD.+le disque finit par saturer, la panne d'une seule machine rend toutes les données inaccessibles, et plusieurs traitements lancés simultanément se disputent les mêmes ressources (CPU, disque, mémoire).  
 +Augmenter la capacité signifie changer de machine ou ajouter du matériel, ce qui ne se fait ni rapidement ni indéfiniment. 
 +C'est exactement ce type de limite qu'Hadoop a été conçu pour dépasser, en répartissant à la fois le stockage (HDFS) et le calcul (YARN) sur plusieurs machines. 
 </WRAP> </WRAP>
 ====== Mission 2 - Préparer les données ====== ====== Mission 2 - Préparer les données ======
Ligne 182: 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 199: 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 221: Ligne 334:
  
 Exécuter : Exécuter :
- 
 <sxh bash> <sxh bash>
-python generer_donnees.py+python3 generer_donnees.py
 </sxh> </sxh>
  
 Observer la taille du fichier : Observer la taille du fichier :
- 
 <sxh bash> <sxh bash>
 ls -lh ventes.csv ls -lh ventes.csv
Ligne 234: Ligne 345:
 </sxh> </sxh>
  
-Répondre : +<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. 
-  - Quelle est la taille du fichier ? +
-  - Combien contient-il de lignes ? +
-  - Est-il suffisamment volumineux pour observer plusieurs blocs HDFS ? +
-  - Comment produire un fichier beaucoup plus volumineux ?+
  
 +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) :
 +<sxh bash>
 +python3 generer_donnees.py --lignes 5000000
 +</sxh>
 +</WRAP>
 ====== Mission 3 - Stocker les données dans HDFS ====== ====== Mission 3 - Stocker les données dans HDFS ======
  
Ligne 717: 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 843: 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.1789487678.txt.gz
  • Dernière modification : il y a 2 mois
  • de jcheron