Recuerda que puedes descargarte algunos de los ejemplos en la pestaña de Código Fuente

jueves, 28 de febrero de 2013

Hadoop: Introducción al Desarrollo en Java (Parte II): El Mapper (Ejemplo Word Count)

El Mapper implementa el método map, es la parte del programa que se va a ejecutar en el lugar en el que se encuentran los bloques de datos, hará las operaciones necesarias con ellos, seleccionará sólo los datos que nos interesan y los emitirá como datos intermedios antes de que se sigan procesando
 
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

// Esta clase tiene que extender de la clase Mapper.
// Espera 4 tipos de datos: los 2 primeros definen 
// los tipos del key/value de entrada y los 2 últimos 
// definen los tipos del key/value de salida.
public class WordCountMapper extends 
 Mapper <LongWritable, Text, Text, IntWritable> {

// Una buena práctica es la reutilización de objetos. 
// Cuando necesitamos utilizar constantes, crear una 
// variable estática fuera del map.
// De esta forma, cada vez que el método map se llama,
// no se creará una nueva instancia de ese tipo.
 private final static IntWritable cuenta = new IntWritable(1);
 private Text palabra = new Text();

// La función que obligatoriamente tiene que 
// implementarse en el Mapper es la map, que va
// a recibir como parámetros: primero el tipo de
// la key, luego el tipo del value y finalmente un
// objeto Context que se usará para escribir los 
// datos intermedios
 public void map(LongWritable key, Text values, Context context) 
   throws IOException, InterruptedException{
// En el objeto "values" estamos recibiendo cada
// línea del fichero que estamos leyendo. Primero
// tenemos que pasarlo a String para poder 
// operar con él
  String linea = values.toString();
  
// Cada línea va a contener palabras separadas por
// "un separador", separador que se considera como
// una expresión regular y a partir del cual dividimos
// la línea. Vamos recorriendo elemento a elemento. 
  for(String word : linea.split(" ")){
   if (word.length() > 0){
//  Le damos el valor a nuestro objeto creado para la
//  reutilización (claramente, a 'palabra', ya que 
//  'cuenta' es una constante final static).
//  Con el write escribimos los datos intermedios, que
//  son como key la palabra y como valor un 1.
    palabra.set(word);
    context.write(palabra, cuenta);
   }
  }
 }
}

Ahora os muestro el código con la old API que vemos que también tiene algunas diferencias. Además, aprovecho para que os fijéis en la parte output.collect, ahí se está creando cada vez una nueva instancia al objeto IntWritable y mostrar que es bastante útil intentar reaprovechar los objetos:
 
import java.io.IOException;
import org.apache.hadoop.io.IntWritable; 
import org.apache.hadoop.io.LongWritable; 
import org.apache.hadoop.io.Text; 
import org.apache.hadoop.mapred.MapReduceBase; 
import org.apache.hadoop.mapred.Mapper;
import org.apache.hadoop.mapred.OutputCollector; 
import org.apache.hadoop.mapred.Reporter;

public class WordCountMapper extends MapReduceBase implements 
   Mapper <LongWritable, Text, Text, IntWritable> {

    public void map(LongWritable key, Text value, 
     OutputCollector<Text, IntWritable> output, Reporter reporter) 
     throws IOException {

        String s = value.toString();
        for (String word : s.split("\\W+")) {
           if (word.length() > 0) {
              output.collect(new Text(word), new IntWritable(1));
           }
        }
    }
}


Las diferencias principales que observamos son:

  • En la new API la clase sólo extiende de Mapper, mientras que en la old API necesita extender de MapReduceBase e implementar Mapper.
  • La new API recibe 3 atributos: los 2 tipos del par key/value y el context. La old API recibía 4, los 2 tipos de la key/value, un OutputCollector que es donde se escribían los datos intermedios y un objeto Reporter que servía para devolver cierta información al Driver. En la new API este paso de información se puede hacer con el Context.

Ver también:


martes, 26 de febrero de 2013

Hadoop: Introducción al desarrollo en Java (Parte I)

En esta entrada voy a explicar una introducción al desarrollo de un programa MapReduce en Java.

Este blog lo estoy desarrollando a partir de la versión 1.0.4. Esta versión permite usar tanto la nueva API de Hadoop como la vieja API (me referiré a ellas como "new API" -releases 1.x- y "old API" -releases 0.20-x). En esta entrada la voy a aprovechar para mostrar las diferencias entre estas dos APIs pero las próximas entradas y desarrollos ya estarán hechas únicamente con la nueva API.

Para explicar una introducción al desarrollo de un programa MapReduce utilizaré el "Hola Mundo" de Hadoop, que es el "Word Count", básicamente se coge un fichero, se cuenta las veces que aparece una palabra y la salida será un listado de palabras (keys) con el número de veces que aparece (values).

Para desarrollar este tipo de programas necesitamos básicamente tres clases:
También antes de empezar a explicar código, hay que tener en cuenta cuáles son los tipos de datos que vamos a utilizar, ya que las clases hay que definirlas con los tipos de entrada y de salida.

En el WordCount el Mapper va a recibir como key elementos de tipo numérico ya que, como ya he explicado en otros artículos, normalmente va a ser el offset del fichero y ese valor no lo vamos a utilizar.
El value que recibe el map es cada una de las líneas del fichero, es decir, valores de tipo texto.
El map va a retornar una lista de pares key/value, donde la key es la palabra localizada (de tipo texto) y el value es un número (en este caso siempre va a devolver un 1 por cada palabra que encuentre).

El Reducer va a recibir una key que será la palabra (dada en el Mapper) de tipo texto y como valores una lista de números. Y finalmente emitirá la palabra que estamos contanto de tipo texto como key, y como value el resultado de las veces que aparece esa palabra, es decir, de tipo número.

En la clase map y en la clase reduce los tipos de entrada no tienen por qué ser los mismos que los de salida. Pero es muy importante tener en cuenta los tipos de la salida del map deben ser los mismos que los de entrada del reduce.

Con el fin de que este artículo no quede demasiado largo, lo he dividido en varios artículos e iré explicando el código escribiendo los comentarios correspondientes.

Para ejecutar y probar el programa en Eclipse podéis leer este artículo.

Continuar con:
Introducción al desarrollo en Java (Parte II): El Mapper (Ejemplo Word Count)
Introducción al desarrollo en Java (Parte III): El Reducer (Ejemplo Word Count)
Introducción al desarrollo en Java (Parte IV): El Driver (Ejemplo Word Count)
Introducción al desarrollo en Java (Parte V): Métodos setup() y cleanup()

jueves, 21 de febrero de 2013

Configuración de Eclipse con Hadoop (Local y Pseudo-Distribuído)

En esta entrada voy a explicar cómo configurar eclipse para poder trabajar con Hadoop.

Por decirlo de alguna forma, hay dos formas de trabajar:
Una realizando una aplicación para ejecutarla en modo pseudo-distribuído a través de los demonios que hemos configurado e instalado (como hemos visto en esta entrada).
Y la otra instalándole al eclipse un plugin que nos permitirá trabajar en modo local, sin necesidad de lanzar los demonios.

Si no tenemos ya el eclipse instalado, descargamos la última versión disponible (actualmente Juno) en http://www.eclipse.org/downloads/





Crear una aplicación y ejecutarla en modo local


Descargamos el plugin de hadoop para eclipse en.
http://wiki.apache.org/hadoop/EclipsePlugIn

Guardamos el jar descargado en {ruta_eclipse}/eclipse/plugins
Arrancamos el eclipse, seleccionamos un workspace. Ahora, si vamos a Window-Open Perspective-Other, podremos seleccionar la vista MapReduce.

Primero hay que configurar Hadoop en el eclipse en Eclipse-Preferencias-Hadoop, ponemos la ruta de donde habíamos instalado hadoop (/usr/local/hadoop/hadoop-1.0.4)




Ahora ya podemos crear nuevas aplicaciones de tipo MapReduce.



Una vez creada la nueva aplicación, el plugin nos permite añadir clases de tipo Mapper, Reduccer y el Driver:


Además, a la hora de crear un nuevo Driver, si le indicamos cuál es el Mapper y el Reducer lo creará con las configuraciones y las relaciones a estas clases hechas.



Hay que tener cuidado que este plugin va a crear las clases con los encabezados y tipos de la "old API", si vamos a querer desarrollar con la "new API" vamos a tener que cambiarlos a mano, tanto los paquetes importados, como los tipos y los encabezados.

También otra cosa en la que hay que tener cuidado, que al desarrollar en Eclipse y en modo local hay tener cuidad con el paso de parámetros entre el Driver y el Mapper o el Reducer.




Crear una aplicación y ejecutarla en modo pseudo-distribuído


Para lanzar Jobs MapReduce hay que seguir todos estos pasos.


Si hemos instalado el plugin de Hadoop para Eclipse, crearíamos una nueva aplicación MapReduce (tal y como hemos visto en la parte de creación y ejecución para modo local).
Si no hemos instalado el plugin de Hadoop, crearíamos una aplicación Java estándar y tendríamos que añadir al build path las librerías que se encuentran en /usr/local/hadoop/hadoop-1.0.4/lib más las que se llaman hadoop-***.jar que se encuentran en la raíz /usr/local/hadoop/hadoop-1.0.4






La instalación de Hadoop la había hecho en el directorio /usr/local/hadoop, así que en ese nivel he creado este sistema de carpetas:
 /usr/local/hadoop/training/jars -> Donde depositaré mis aplicaciones
 /usr/local/hadoop/training/docs -> Donde depositaré ficheros sobre los que quiera trabajar posteriormente en HDFS.

Después de haber desarrollado nuestra aplicación, con el botón derecho sobre el proyecto vamos a Export, seleccionamos Jar File y como destino  /usr/local/hadoop/training/jars/nombreAplicacion.jar



Y por último, a través del terminal arrancar todos los demonios, y lanzar la aplicación a través de los comandos hadoop.

Ahora sólo queda que si la aplicación da algún tipo de error, volveremos al eclipse, corregiremos los cambios y tendremos que volver a exportar el nuevo jar.


Ejecución de Jobs HDFS

Si vamos usar Hadoop sin necesidad de lanzar un Job MapReduce (por ejemplo, si sólo estamos haciendo operaciones HDFS), hay una forma más fácil de lanzarlo.

Tras haber creado la nueva aplicación (bien sea a través del plugin, o a siendo una aplicación Java estándar a la que le hemos incluído las librerías), vamos a las propiedades del proyecto-Java Build Path-Libraries  y luego pulsando sobre "Add External Class Folder" y añadimos la carpeta conf de Hadoop en la ruta /usr/local/hadoop/hadoop-1.0.4/conf



También en este caso tendríamos arrancar el clúster Hadoop a partir de la línea de comandos del terminal.

Pero a partir de ahora, cuando desarrollemos con el Eclipse este tipo de aplicaciones, podremos ejecutarlas en modo pseudo-distribuído sin necesidad de exportar el Jar y haciendo simplemente un Run As Java Application (si es sin el plugin) y con el plugin valdría tanto como Java Application como Run On Hadoop