Páginas

sábado, 30 de marzo de 2019

Creat un Hotspot en Ubuntu 18.04

Supongamos que tenemos un acceso Ethernet en nuestro portatil y necesitamos crear un punto de acceso WiFI para un teléfono móvil en la Oficina

1) Activamos el adaptador inalámbrico.
2) Abrimos la ventana de configuración de la WiFI.
3) Accedemos al menú de la parte superior derecha.
4) Seleccionamos la opción Activar punto de acceso inalámbrico (esto nos desconectaría de la WiFI local su fuese el caso, dado que normalmente en el portatil no tendremos dos adaptadores WiFi, en el ejemplo que estamos comentando no nos afecta al tener acceso a internet vía Ethernet).
5) En pantalla nos aparecerá tanto en nombre del SSID como la password de acceso.
6) Si necesitamos nos interesase ajustar el SSID o la password deberíamos editar el fichero

   sudo gedit /etc/NetworkManager/system-connections/Hotspot

7) Ajustando los valores de los parámetros ssid y psk:

[wifi]
mac-address=63:FB:72:12:53:2B
mac-address-blacklist=
mode=ap
ssid=MINUEVOSSID

[wifi-security]
group=ccmp;
key-mgmt=wpa-psk
pairwise=ccmp;
proto=rsn;
psk=MI_PASSWORD

8) Tras salvar el fichero, si queremos que los cambios hagan efecto deberemos reiniciar el servicio NetworkManager:

   sudo systemctl restart NetworkManager

Este método no da de si como para instalar un portal cautivo con una 'landing page' con publicidad o con un formulario de login o que permita a los usuarios autenticarse usando alguna red social, he visto por ahí algunas soluciones: Coova Chilli, Chillispot, WiFiDog y NoCatSplash pero hay que analizarlas con mas detenimiento...

Salu2

martes, 19 de marzo de 2019

Gestionar servicios activos en Ubuntu 18.04 LTS

Dada la gran cantidad de servicios que puedes llegar a tener activos cuando estas constantemente instalando y curioseando con cosas, llega un momento en que se termina notando que el arranque del PC es algo mas lento de lo habitual.

Para sacar la lista de servicios activos lo más cómodo es lanzar el comando:

systemctl list-units --all --type=service --no-pager | grep running

Que genera un listado del tipo:

accounts-daemon.service loaded active running Account Service
acpid.service           loaded active running ACPI event daemon
apache2.service         loaded active running The Apache HTTP Server


En mi caso me interesaba parar los siguientes servicios:

apache2.service
postgresql.service
redis-server.service
sendmail.service
vpnagentd.service

Por lo que he lanzado los comandos:

sudo systemctl disable apache2.service
sudo systemctl disable postgresql.service
sudo systemctl disable redis-server.service
sudo systemctl disable sendmail.service
sudo systemctl disable vpnagentd.service

Y he reiniciado el sistema para verificar que ya no aparecían arrancados.

Si en el futuro necesito que vuelvan a lanzarse al arrancar lanzaré comandos del tipo:

sudo systemctl enable apache2.service
sudo systemctl enable postgresql.service
sudo systemctl enable redis-server.service
sudo systemctl enable sendmail.service
sudo systemctl enable vpnagentd.service

Y reiniciaré el sistema

Salu2 

domingo, 2 de diciembre de 2018

FPGAs libres

¿Qué son las FPGA?

FPGA (field-programmable gate array) son un tipo de chips que contienen una matriz de puertas lógicas donde que podemos configurar:
  • La E/S
  • Las interconexiones entre las puertas lógicas de la matriz
  • La operación de cada una de las puertas lógicas, AND, OR, NOT, XOR y Biestables. No es que en cada elemento de la matriz exista N puertas lógicas combiandas de una manera ingeniosa, lo que se almacena en cada elemento de la matriz es una micromemoria que asocia a cada dirección de entrada una salida. Así si en un elemento de la matriz queremos guardar una puerta AND tendríamos una memoria en la que en la dirección 00 hay almacenado un cero, en la dirección 01 hay almacenado un 0, en la dirección 10 hay almacenado un 0 y en la dirección 11 hay almacenado un 1. La tabla booleana sería:
   DIR. ENTRADA | DATO SALIDA
   00                      | 0
   01                      | 0
   10                      | 0
   11                      | 1

Actualmente estos dispositivos están experimentando un gran auge desde que Clifford Woolf en 2014 liberó sus herramientas open source (Verilog) para reprogramar las FPGA ICE40 de Lattice, lo que ha dado un gran impulso a esta tecnología (hasta la fecha ultra protegida), se están empezando a liberar estos chips, y se está desarrollando una cultura de hardware libre similar a la que GNU supuso en el mundo del software libre de los años 80.

Las ventajas de poder programar internamente un chip son muchas:
  • Cualquier dispositivo con estos chips pasan a ser multipropósito, reconfigurables y actualizable. Se minimiza la obsolescencia y la necesidad de contar con N dispositivos. Un dispositivo puede reprogramarse en milisegundos para ser otra cosa.
  • Se abre un ecosistema de hardware libre (https://opencores.org/) donde cualquiera puede desarrollar o vender 'librerías' de circuitos que permitan configurar un dispositivo para cualquier finalidad: contadores, codificadores, decodificadores, encriptador, desencriptador, procesamiento de imágenes, procesamiento de sonido, etc.
  • Ya existen 'traductores' que permiten pasar desde lenguajes de alto nivel (C/C++) al lenguaje HDL (Hardware Description Languaje) para programar esos circuitos desde un lenguaje conocido. Existen también proyectos como IceStorm que permiten generar HDL mediante una interfac gráfica. El código HDL se traduce a un chorro de bits (bitestream) que es lo que se usa para grabar las FPGA.
  • La liberación de las FPGA pueden suponer una revolución en automatización industrial, IoT, Smart Cities, redes neuronales, procesamiento de lenguaje natural, algoritmos genéticos, robótica, reconocimiento de patrones en el big data, etc.
¿Qué ventajas tienen las FPGA frente a los microcontroladores?

Desde hace unos años tenemos una revolución en marcha con microcontroladores como Arduino o Raspberry Pi (10 euros) que permiten desarrollar pequeños programas asociados a sensores/actuadores en el mundo de IoT y la robótica. Estos dispositivos ha supuesto una auténtica revolución tanto a nivel comercial como a nivel educativo.

Las FPGA ofrecen varias ventajas importantes frente a los microprocesadores:
  • Velocidad. Las FPGAs procesan la información muchísimo mas rápido que los micros actuales, trabajan en velocidades similares a procesadores de niveles superiores. Cualquier micro sigue las instrucciones de un programa escrito en algún tipo de lenguaje ensamblador que es traducido a instrucciones máquina por el microcódigo y ejecutado en el procesador por lo que cualquier instrucción por sencilla que sea implica varios pasos que se ejecutan en varios ciclos de reloj. Una FPGA está 'cableada' para ejecutar ese mismo código sin instrucciones, por lo que es mucho mas rápida. Hoy en día las velocidades de reloj de los procesadores y micros actuales son mas altas que las velocidades de los FPGA pero la inercia del mercado será la de igualar estas velocidades.
  • Tiempo real. Al no depender el funcionamiento del chip de la ejecución de un programa la respuesta a eventos es instantánea. Con un micro debemos recurrir a interrupciones que avisan al procesador que está ejecutando una instrucción de muchos ciclos, que debe atender a otro evento, estas interrupciones son siempre 'escuchadas' al final de una ejecución.
  • Paralelismo. Una FPGA puede programarse para N funcionalidades a la vez. Si la FPGA tiene 64 entradas nada impide que usemos 32 entradas para un procesador de video y 32 entradas para un compresor de audio.
  • Volatilidad. Las FPGA son volátiles, por lo que al desconectar se pierde el circuito por lo que deberemos almacenar el bitstream en algún dispositivo externo que lo cargue de nuevo al arrancar el dispositivo.
  • Diseño de hardware accesible.Con la llegada de las FPGA libres el diseño de hardware se va a universalizar. Los chips actuales son cajas negras, pero desde ahora cualquiera podrá programar compartir y mejorar circuitos con lenguajes como Verilog y HDL.El hardware se genera vía software.
Hoy en día hay muchas empresas realizando inversiones millonarias en el mundo de las FPGA, por ejemplo: Intel, Microsoft y AWS. AWS por ejemplo ofrece FPGAS en la nube donde podemos programar nuestros 'circuitos' y abre el camino a que esos circuitos puedan ser revendidos como librerías a otros interesados.

Referencias:

https://es.wikipedia.org/wiki/Field-programmable_gate_array
https://www.youtube.com/watch?v=K8bM14-R9Ts
https://www.youtube.com/watch?v=GA1lN5dsgao
https://www.youtube.com/watch?v=By8x3gL88T0
https://alhambrabits.com/alhambra/
https://www.arduino.cc/en/Guide/MKRVidor4000
https://alhambrabits.com/software/

sábado, 27 de octubre de 2018

Comando bash para reemplazar una cadena de texto en múltiples ficheros recursivamente

Para reemplazar una cadena de texto en todos los ficheros de un cierto directorio y también el los ficheros de sus subdirectorios con un único comando donde combinamos grep y sed.

Sería el comando:

grep -rl "cadena1" * -R | xargs sed -i 's/cadena1/cadena2/g'

Siendo cadena1 el literal a reemplazar y cadena2 el nuevo literal

Un saludo

sábado, 14 de abril de 2018

Ejemplo de MapReduce con Java sobre HDFS

En esta entrada vamos a realizar una breve descripción de Apache Hadoop, del sistema de archivo distribuído HDFS y del algoritmo de procesamiento MapReduce

1. Introducción a Hadoop.

La utilidad de Hadoop radica en que nos permite procesar rápidamente un volumen inmenso de datos (Big Data), la clave está en que el procesamiento se produce en paralelo en múltiples nodos. Cada nodo procesa parte del trabajo y el resultado de todo ese procesamiento paralelo se combina en el resultado final.

Cuantos mas datos tengamos que procesar y/o cuanto mayor sea la velocidad de respuesta requerida, mas nodos paralelos deberemos tener instalada en la red de servidores Hadoop.

Hadoop se basa en dos 'patas':

1.1) Un sistema de archivos llamado HDFS distribuído en N máquinas o nodos

Ya hemos comentado que Hadoop es eficiente cuando opera sobre ficheros extremadamente grandes (petabytes), estos ficheros se trocean en bloques de 128MB que son almacenados en múltiples nodos de la red de servidores. Para minimizar problemas si un noco cae, cada uno de los trozos está disponible en al menos 3 servidores.

Hay un nodo maestro que gestiona qué trozos forman parte de cada fichero y en qué nodos está replicado. Este nodo maestro está también replicado para evitar caídas del sistema.

El sistema de Archivos HDFS corre sobre Java, por lo que puede ejecutar sobre cualquier arquitectura que cuente con una JVM.

1.2) Un algoritmo de procesamiento map-reduce basado en dos operaciones simples

El algoritmo base de Haddop se llama MapReduce. La base de MapReduce es muy simple:

  • Un algoritmo MapReduce recibe un conjunto de pares <Clave,Valor> y genera pares <Clave,Valor>
  • La operación Map recibe un conjunto de pares <Clave,Valor> y genera pares <Clave,Valor>
  • La operación Reduce recibe un conjunto de pares <Clave,Valor> y genera pares <Clave,Valor>

Supongamos que tenemos una ciudad con una red de miles sensores. Los sensores toman la temperatura de su ubicación cada 10 segundos y la envían a un almacén centralizado, los sensores que son nuestros productores de datos, podrían enviar sus lecturas a un topic de Apache Kafka y ese topic lo tendríamos enganchado a un consumidor HDFS mediante un conector.

Cada sensor enviaría un registro en formato JSON como el siguinte:

{"idsensor":"00000001", fechahora: "20180414214500", "longitud": -122.0829009197085, "latitud": 37.4238253802915, "temp": 11.2}

Si por ejemplo nos piden sacar la temperatura media de cada sensor para cada día del año pasado deberíamos programar la siguiente secuencia de procesos mapreduce:

1.2.1) Partimos de un fichero imaginario fichero-temperaturas-madrid-2017.json

El fichero no es otra cosa sino un conjunto de pares <Clave,Valor> donde las claves son los números de línea "numlinea" y los valores el contenido de cada línea "contenidolinea". Partiríamos pues de algo así:

[
{"numlinea": 1, "contenidolinea": {"idsensor":"00000001", "fechahora": "20180414214500", "longitud": -122.0829009197085, "latitud": 37.4238253802915, "temp": 11.2}},
{"numlinea": 2, "contenidolinea": {"idsensor":"00000001", "fechahora": "20180414214500", "longitud": -122.0829009197085, "latitud": 37.4238253802915, "temp": 11.2}},
{"numlinea": 3, "contenidolinea": {"idsensor":"00000001", "fechahora": "20180414214500", "longitud": -122.0829009197085, "latitud": 37.4238253802915, "temp": 11.2}},
....
{"numlinea": 999, "contenidolinea": {"idsensor":"00000001", "fechahora": "20180414214500", "longitud": -122.0829009197085, "latitud": 37.4238253802915, "temp": 11.2}}
]

1.2.2) Programamos una operación MAP

La operación MAP transformará los pares <numlinea, contenidolinea> en pares <idsensorFecha,temp>, que serían del tipo:

[
{"idsensorFecha":"00000001-20180414", "temp": 11.2},
{"idsensorFecha":"00000001-20180414", "temp": 11.3},
{"idsensorFecha":"00000001-20180414", "temp": 11.4},
....
{"idsensorFecha":"99999999-20180414", "temp": 9.0},
{"idsensorFecha":"99999999-20180414", "temp": 9.2},
{"idsensorFecha":"99999999-20180414", "temp": 9.2},
]

1.2.3) Mezcla y ordenación de la salida del MAP

La salida de la operación MAP realizada en cada trozo del fichero de cada uno de los nodos de Hadoop, se mezcla y ordena en un único fichero que contiene las líneas de todos los ficheros ordenadas por la clave "idsensorFecha" y agrupando las temperaturas en una matriz <idsensorFecha,[temp]>, con la pinta siguiente:

[
{"idsensorFecha":"00000001-20180414", "temperaturas": [{"temp": 11.2},{"temp": 11.3}, ...., {"temp": 11.4}]},
        ....
{"idsensorFecha":"99999999-20180414", "temperaturas": [{"temp": 11.2},{"temp": 11.3}, ...., {"temp": 11.4}]}
]

1.2.4) Programamos una operación REDUCE.

La operación REDUCE recibe pares del tipo <idsensorFecha,[temp]> y genera un pares del tipo <idsensorFecha,tempMedia>, donde hemos sacado la media de todas las temperaturas de un sensor en una cierta fecha, generamos por tanto pares del tipo:

[
{"idsensorFecha":"00000001-20180414", "tempMedia": 13.2},
        ....
{"idsensorFecha":"99999999-20180414", "tempMedia": 17.1}
]

2. Instalar Hadoop

Accedemos a la zona de descargas de Apache Hadoop:

http://hadoop.apache.org/releases.html#Download

Nos bajamos la versión ejecutable mas actualizada, en este caso: 

http://apache.rediris.es/hadoop/common/hadoop-3.1.0/hadoop-3.1.0.tar.gz

Vamos ahora a instalar Hadoop en modo autónomo:

  • Descomprimimos en nuestro $HOME
  • Definimos la variable HADOOP_HOME ajustando su valor en el .profile

HADOOP_HOME=/home/egdepedro/hadoop-3.1.0
export HADOOP_HOME
PATH="$HADOOP_HOME/bin:$PATH"

  • Reiniciamos la sesión o recargamos en bash el contenido del .profile con el comando:

$ . ./.profile

  • Verificamos que todo se ha instalado correctamente lanzando el comando

egdepedro@OBERON:~$ hadoop version
Hadoop 3.1.0
Source code repository https://github.com/apache/hadoop -r 16b70619a24cdcf5d3b0fcf4b58ca77238ccbe6d
Compiled by centos on 2018-03-30T00:00Z
Compiled with protoc 2.5.0
From source with checksum 14182d20c972b3e2105580a1ad6990
This command was run using /home/egdepedro/hadoop-3.1.0/share/hadoop/common/hadoop-common-3.1.0.jar

3. Obtención de datos y definición del ejemplo

Hoy en día muchos organismos y ayuntamientos publican sus datos en una 'iniciativa' conocida como 'Open Data' cuyo objetivo final es que cualquier ciudadano/empresa sea capaz de explotar esos datos de forma productiva. Los datos están siempre convenientemente anonimizados, es decir, no se publica nada sobre nadie que permita identificar al afectado.

Vamos a descargar por ejemplo los datos del Ayuntamiento de Madrid:  https://datos.madrid.es/portal/site/egob/ y descargamos por ejemplo los accidentes de tráfico con bicicleta del 2017. El enlace es: https://datos.madrid.es/egob/catalogo/300110-0-accidentes-bicicleta.csv

Revisando el fichero y limpiando un poco espacios en blanco inútiles he generado el fichero 'accidentes-bicicletas-madrid-2017.csv' que tiene la siguiente estructura:

Fecha;TRAMO HORARIO;Nm Tot Victimas;DISTRITO;Lugar;Numero;Tipo Accidente;Tipo Vehiculo

01/01/2017;DE 6:00 A 6:59;1;ARGANZUELA;CALLE DE TOLEDO;120;CHOQUE CON OBJETO FIJO;BICICLETA           
02/01/2017;DE 21:00 A 21:59;1;SAN BLAS;CALLE DE MEQUINENZA;14;CAÍDA BICICLETA;BICICLETA           
03/01/2017;DE 19:00 A 19:59;1;CENTRO;CALLE DE LA ESCALINATA;8;CAÍDA BICICLETA;BICICLETA           
04/01/2017;DE 21:00 A 21:59;1;CENTRO;CALLE DE LA CAVA DE SAN MIGUEL;13;CAÍDA BICICLETA;BICICLETA           
05/01/2017;DE 8:00 A 8:59;1;MONCLOA-ARAVACA;PUENTE DE LOS FRANCESES;;CAÍDA BICICLETA;BICICLETA           
05/01/2017;DE 12:00 A 12:59;1;LATINA;CARRETERA DE BOADILLA DEL MONTE;27;CAÍDA BICICLETA;BICICLETA         

4. Definimos el caso de uso

Supondremos que alguien nos solicita extraer el 'Número total de muertos por distrito', algo del estilo siguiente:

ARGANZUELA 11
SAN BLAS 15
....

5. Cargamos los datos en una carpeta del HDFS (sistema de ficheros distribuído de Hadoop) que llamaremos 'bicicletas:

egdepedro@OBERON:~/Escritorio$ hadoop fs -mkdir bicicletas
egdepedro@OBERON:~/Escritorio$ hadoop fs -put ./accidentes-bicicletas-madrid-2017.csv bicicletas

Podemos comprobar si se ha subido bien mediante el comando:
 
egdepedro@OBERON:~/Escritorio$ hadoop fs -ls bicicletas
Found 1 items
-rw-r--r--   1 egdepedro egdepedro      83474 2018-04-14 09:59 bicicletas/accidentes-bicicletas-madrid-2017.csv

6. Montamos los programas mapreduce para los dos casos de uso

6.1) Creamos un proyecto Maven y definimos las dependencias con el siguiente pom.xml 

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.casa</groupId>
<artifactId>TestMapReduce</artifactId>
<version>1.0</version>
<properties>
<slf4j.version>1.7.25</slf4j.version>
<proyecto.build.jdk_version>1.8</proyecto.build.jdk_version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<hadoop-common.version>3.1.0</hadoop-common.version>
<hadoop-client.version>3.1.0</hadoop-client.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>${proyecto.build.jdk_version}</source>
<target>${proyecto.build.jdk_version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>${hadoop-common.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>${hadoop-client.version}</version>
</dependency>
</dependencies>
</project>

6.2) Para cada cado de uso vamos a crear 3 clases:

Clase Driver. Donde figura el Main que define el trabajo a realizar: 

package org.casa;

import java.io.IOException;

import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.FileInputFormat;
import org.apache.hadoop.mapred.FileOutputFormat;
import org.apache.hadoop.mapred.JobClient;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.mapred.TextInputFormat;
import org.apache.hadoop.mapred.TextOutputFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AccidentesPorDistritoDriver {

private static final Logger LOGGER = LoggerFactory.getLogger(AccidentesPorDistritoDriver.class);

public static void main(String[] args) throws IOException {

LOGGER.info("Se crea el trabajo y su configuración, asignándole un nombre");
final JobClient miClienteHadoop = new JobClient();
final JobConf configTrabajo = new JobConf(AccidentesPorDistritoDriver.class);
configTrabajo.setJobName("AccidentesPorDistrito");

LOGGER.info("Se definen los tipos de los pares <clave,valor> de salida <texto, entero>");
configTrabajo.setOutputKeyClass(Text.class);
configTrabajo.setOutputValueClass(IntWritable.class);

LOGGER.info("Se define las clases mapper y reducer");
configTrabajo.setMapperClass(AccidentesPorDistritoMapper.class);
configTrabajo.setReducerClass(AccidentesPorDistritoReducer.class);

LOGGER.info("Se definen los tipos de los ficheros de entrada y salida (en ambos casos ficheros de texto plano)");
configTrabajo.setInputFormat(TextInputFormat.class);
configTrabajo.setOutputFormat(TextOutputFormat.class);

LOGGER.info("Se definen los directorios de entrada/salida");
FileInputFormat.setInputPaths(configTrabajo, new Path(args[0]));
FileOutputFormat.setOutputPath(configTrabajo, new Path(args[1]));

LOGGER.info("Cargamos el trabajo en el cliente Hadoop");
miClienteHadoop.setConf(configTrabajo);

try {
LOGGER.info("Ejecutamos el trabajo");
JobClient.runJob(configTrabajo);
} catch (Exception e) {
e.printStackTrace();
}
}
}

Clase Mapper. Donde se implementa la rutina de mapeo.

package org.casa;

import java.io.IOException;
import java.util.StringTokenizer;

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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Procesamos las líneas de un fichero CSV que contienen:
 * 
 *   fecha;tramo-horario;num-victimas;distrito;lugar;numero;tipo-accidente;tipo-vehiculo
 *   
 * Extraemos de cada línea el par <distrito, num-victimas>
 */
public class AccidentesPorDistritoMapper extends MapReduceBase implements Mapper<LongWritable, Text, Text, IntWritable> {

private static final Logger LOGGER = LoggerFactory.getLogger(AccidentesPorDistritoMapper.class);

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

LOGGER.info(String.format("Recibimos el par <%s,%s>", numLinea.toString(), txtLinea));
final StringTokenizer s = new StringTokenizer(txtLinea.toString(),";");
s.nextToken();
s.nextToken();
final int numMuertos = Integer.parseInt(s.nextToken());
final String distrito = s.nextToken();

LOGGER.info(String.format("Generamos el par <%s,%d>", distrito, numMuertos));
output.collect(new Text(distrito), new IntWritable(numMuertos));
}
}

Clase Reducer. Donde se implementa la rutina de reduccion.

package org.casa;

import java.io.IOException;
import java.util.Iterator;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.MapReduceBase;
import org.apache.hadoop.mapred.OutputCollector;
import org.apache.hadoop.mapred.Reducer;
import org.apache.hadoop.mapred.Reporter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AccidentesPorDistritoReducer extends MapReduceBase implements Reducer<Text, IntWritable, Text, IntWritable> {

private static final Logger LOGGER = LoggerFactory.getLogger(AccidentesPorDistritoReducer.class);

@Override
public void reduce(Text distrito, Iterator<IntWritable> muertosDistrito, OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException {

LOGGER.info(String.format("Recibimos el par <%s,[VALORES]>", distrito.toString()));
final Text key = distrito;
int totalMuertosDistrito = 0;
int parcialMuertosDistrito = 0;
while (muertosDistrito.hasNext()) {
parcialMuertosDistrito = ((IntWritable)muertosDistrito.next()).get();
totalMuertosDistrito += parcialMuertosDistrito;
}
LOGGER.info(String.format("Generamos el par <%s,%d>", key, totalMuertosDistrito));
output.collect(key, new IntWritable(totalMuertosDistrito));
}
}

6.3) Ejecutamos el caso de uso


  • Nos posicionamos en el directorio raiz del proyecto, donde está el fichero pom.xml
  • Lanzamos el comando maven que genera el jar: mvn clean package
  • Lanzamos el comando Hadoop para que se elecute

    $ hadoop jar TestMapReduce-1.0.jar org.casa.AccidentesPorDistritoDriver bicicletas/accidentes-bicicletas-madrid-2017.csv salida

  • Obtendremos una consola como:

2018-04-14 13:19:17,464 INFO casa.AccidentesPorDistritoDriver: Se crea el trabajo y su configuración, asignándole un nombre
2018-04-14 13:19:17,689 INFO casa.AccidentesPorDistritoDriver: Se definen los tipos de los pares <clave,valor> de salida <texto, entero>
2018-04-14 13:19:17,691 INFO casa.AccidentesPorDistritoDriver: Se define las clases mapper y reducer
2018-04-14 13:19:17,694 INFO casa.AccidentesPorDistritoDriver: Se definen los tipos de los ficheros de entrada y salida (en ambos casos ficheros de texto plano)
2018-04-14 13:19:17,697 INFO casa.AccidentesPorDistritoDriver: Se definen los directorios de entrada/salida
2018-04-14 13:19:18,098 INFO casa.AccidentesPorDistritoDriver: Cargamos el trabajo en el cliente Hadoop
2018-04-14 13:19:18,098 INFO casa.AccidentesPorDistritoDriver: Ejecutamos el trabajo
....
....
2018-04-14 13:19:19,103 INFO casa.AccidentesPorDistritoMapper: Recibimos el par <0,01/01/2017;DE 6:00 A 6:59;1;ARGANZUELA;CALLE DE TOLEDO;120;CHOQUE CON OBJETO FIJO;BICICLETA>
2018-04-14 13:19:19,103 INFO casa.AccidentesPorDistritoMapper: Generamos el par <ARGANZUELA,1>
2018-04-14 13:19:19,104 INFO casa.AccidentesPorDistritoMapper: Recibimos el par <93,02/01/2017;DE 21:00 A 21:59;1;SAN BLAS;CALLE DE MEQUINENZA;14;CAÍDA BICICLETA;BICICLETA>
2018-04-14 13:19:19,104 INFO casa.AccidentesPorDistritoMapper: Generamos el par <SAN BLAS,1>
2018-04-14 13:19:19,104 INFO casa.AccidentesPorDistritoMapper: Recibimos el par <183,03/01/2017;DE 19:00 A 19:59;1;CENTRO;CALLE DE LA ESCALINATA;8;CAÍDA BICICLETA;BICICLETA>
2018-04-14 13:19:19,104 INFO casa.AccidentesPorDistritoMapper: Generamos el par <CENTRO,1>
2018-04-14 13:19:19,104 INFO casa.AccidentesPorDistritoMapper: Recibimos el par <273,04/01/2017;DE 21:00 A 21:59;1;CENTRO;CALLE DE LA CAVA DE SAN MIGUEL;13;CAÍDA BICICLETA;BICICLETA>
2018-04-14 13:19:19,104 INFO casa.AccidentesPorDistritoMapper: Generamos el par <CENTRO,1>
....
....
2018-04-14 13:19:19,685 INFO casa.AccidentesPorDistritoReducer: Recibimos el par <ARGANZUELA,[VALORES]>
2018-04-14 13:19:19,686 INFO casa.AccidentesPorDistritoReducer: Generamos el par <ARGANZUELA,[VALORES]>
2018-04-14 13:19:19,687 INFO casa.AccidentesPorDistritoReducer: Recibimos el par <BARAJAS,[VALORES]>
2018-04-14 13:19:19,688 INFO casa.AccidentesPorDistritoReducer: Generamos el par <BARAJAS,[VALORES]>
2018-04-14 13:19:19,688 INFO casa.AccidentesPorDistritoReducer: Recibimos el par <CARABANCHEL,[VALORES]>
2018-04-14 13:19:19,688 INFO casa.AccidentesPorDistritoReducer: Generamos el par <CARABANCHEL,[VALORES]>
2018-04-14 13:19:19,688 INFO casa.AccidentesPorDistritoReducer: Recibimos el par <CENTRO,[VALORES]>
2018-04-14 13:19:19,690 INFO casa.AccidentesPorDistritoReducer: Generamos el par <CENTRO,[VALORES]>
....
....
2018-04-14 13:19:19,946 INFO mapreduce.Job: Job job_local296713609_0001 completed successfully
2018-04-14 13:19:19,953 INFO mapreduce.Job: Counters: 30
....
....

  • Obtendremos, el la carpeta salida, un fichero 'part-00000' con un contenido ordenado como el siguiente:

ARGANZUELA 60
BARAJAS 18
CARABANCHEL 44
CENTRO 149
CHAMARTIN 24
CHAMBERI 56
...

REFERENCIAS:

https://www.guru99.com/create-your-first-hadoop-program.html
https://datos.madrid.es/portal/site/egob/

lunes, 19 de marzo de 2018

Pruebas con ActiveMQ

Instalación de ActiveMQ

1) Descargamos el software 'apache-activemq-5.15.3-bin.tar.gz' desde http://activemq.apache.org/download.html
2) Descomprimimos el fichero tar zxvf apache-activemq-5.15.3-bin.tar.gz en $HOME
3) Levantamos el servidor. El script a lanzar será uno de los siguientes dependiendo de nuestro sistema: /bin/linux-x86-64/activemq o /bin/linux-x86-32/activemq):

   $ cd $HOME
   $ ./apache-activemq-5.15.3/bin/linux-x86-64/activemq start
   Starting ActiveMQ Broker...

4) Paramos el servidor:

   $ cd $HOME
   $ ./apache-activemq-5.15.3/bin/linux-x86-64/activemq stop
   Stopping ActiveMQ Broker...
   Stopped ActiveMQ Broker.

5) Consolta de gestión http://127.0.0.1:8161/admin/ (admin/admin)

Introducción a JMS

Un canal de intercambio de mensajes es un lugar común donde ciertas aplicaciones publican mensajes que son consumidos por otras aplicaciones.

En un sistema JMS participan 4 componentes:
  • publicador. Aplicación que genera mensajes y los publica en el canal.
  • consumidor. Aplicación que consume los mensajes de un canal y los procesa
  • mensaje. Información que se pasan entre publicador y consumidor, ambos conocen el formato del mensaje.
  • canal. Lugar por donde se intercambian los mensajes
JMS funciona de forma asíncrona, el publicador y el consumidor no necesitan estar conectados a la vez, ya que el servidor que alberga los canales actúa de intermediario, guardando y distribuyendo los mensajes a medida que el consumidor pueda atenderlos.

En JMS hay dos tipos de canales: Queue (colas) y Topic
  • Queue (cola). Uno o varios publicadores generan mensajes para uno o varios consumidores y cada mensaje le llega a un único consumidor. Los publicadores van dejando sus mensajes en la cola. Los mensajes son recuperados en orden FIFO por el consumidor que solicita un mensaje. Si no hay ningún consumidor solicitando mensajes, la cola los guarda. Si un mensaje es procesado por un consumidor no le llega a los demás consumidores.
  • Topic. Uno o varios publicadores generan mensajes para uno o varios consumidores y cada mensaje le llega a todos los consumidores 'online'. Los publicadores van dejando sus mensajes en el 'topic', los mensajes que llegan al 'topic' se reenvían a todos los suscriptores online (cada suscriptor levanta un listener). Todos los mensajes publicados en el topic mientras un suscriptor permanezca offline se perderán para ese suscriptor.
Ejemplo Cola JMS

package activemq;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Productor implements Runnable {

private static final Logger LOGGER = LoggerFactory.getLogger(Productor.class);

private ConnectionFactory connectionFactory;
private Connection connection;
private Session session;
private Queue queue;
private MessageProducer messageProducer;
private String clientId;

public void run() {
try {
sendMensaje();
closeConnection();
} catch (JMSException e) {
e.printStackTrace();
}
}

public Productor(String clientId, String queueName) throws JMSException {

// Creamos la factoria de conexiones
this.connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_BROKER_URL);

// Creamos una conexión
this.clientId = clientId;
this.connection = connectionFactory.createConnection();
this.connection.setClientID(this.clientId);

// Creamos una sesion
this.session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

// Creamos la cola a la que enviaremos los mensajes (si la cola ya existe no se crearía)
this.queue = session.createQueue(queueName);

// Creamos el productor de mensajes
messageProducer = session.createProducer(this.queue);
}

private void closeConnection() throws JMSException {

// Cerramos la conexion
connection.close();
}

private void sendMensaje() throws JMSException {

// Creamos un mensaje JMS de tipo TextMessage
final String text = "Productor ["+clientId+"] genera el mensaje ["+ System.currentTimeMillis()+"]";
final TextMessage textMessage = session.createTextMessage(text);

// Enviamos el mensaje a la cola definida
messageProducer.send(textMessage);

LOGGER.info(text);
}
}

package activemq;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Consumidor implements Runnable {

private static final Logger LOGGER = LoggerFactory.getLogger(Consumidor.class);

private ConnectionFactory connectionFactory;
private Connection connection;
private Session session;
private Queue queue;
private MessageConsumer messageConsumer;
private String clientId;

public void run() {
try {
getTexto(250);
closeConnection();
} catch (JMSException e) {
e.printStackTrace();
}
}

public Consumidor(String clientId, String queueName) throws JMSException {

// Creamos la factoria de conexiones
this.connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_BROKER_URL);

//  Creamos una conexión
this.clientId = clientId;
connection = connectionFactory.createConnection();
connection.setClientID(clientId);

// Creamos una sesion
this.session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);

// Creamos la cola de la que recibiremos los mensajes (si la cola ya existe no se crearía)
this.queue = session.createQueue(queueName);

// Creamos el consumidor de mensajes
messageConsumer = session.createConsumer(queue);

// Iniciamos la conexión para poder recibir mensajes
connection.start();
}

private void closeConnection() throws JMSException {

// Cerramos la conexion
connection.close();
}

private void getTexto(int timeout) throws JMSException {

// Solicitamos un mensaje de la cola y esperamos timeout milisegundos la llegada de uno si no hubiese nada en la cola
Message message = messageConsumer.receive(timeout);

String texto = null;

// Verificamos si se ha recibido un mensaje
if (message != null) {

// Casteamos el mensaje al tipo previamente acordado (en este caso un TextMessage)
TextMessage textMessage = (TextMessage) message;

// Recuperamos el texto del TextMessage
texto = textMessage.getText();

// Confirmamos al broker (a nuestro ActiveMQ) que se ha procesado correctamente el mensaje
message.acknowledge();

}
LOGGER.info("Consumidor ["+clientId+"] procesa el mensaje ["+ texto+"]");
}

}

package activemq;

import javax.jms.JMSException;

public class TestActiveMQ {

public static void main(String[] args) throws JMSException, InterruptedException {
testColas();
}
public static void testColas() throws JMSException, InterruptedException {

//Levantamos 5 productores de mensajes en threads independientes cada uno envía su mensaje a la cola
Thread tp;
for (int i=0;i<5;i++) {
tp = new Thread(new Productor("p"+i,"miCola"), "tp"+i);
tp.start();
}

//Levantamos 8 consumidores de mensajes en threads independientes, cada uno procesa un mensaje de la cola
//observaremos en el log que los tres últimos consumidores no obtienen nada de la cola.
Thread tc;
for (int i=0;i<8;i++) {
tc = new Thread(new Consumidor("c"+i,"miCola"), "tc"+i);
tc.start();
}
}
}

Ejemplo Topic JMS

package activemq;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Publicador implements Runnable {

private static final Logger LOGGER = LoggerFactory.getLogger(Publicador.class);

private ConnectionFactory connectionFactory;
private Connection connection;
private Session session;
private Topic topic;
private MessageProducer messageProducer;
private String clientId;

public void run() {
try {
sendMensaje();
closeConnection();
} catch (JMSException e) {
e.printStackTrace();
}
}

public Publicador(String clientId, String topicName) throws JMSException {

// Creamos la factoria de conexiones
this.connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_BROKER_URL);

// Creamos una conexión
this.clientId = clientId;
this.connection = connectionFactory.createConnection();
this.connection.setClientID(this.clientId);

// Creamos una sesion
this.session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

// Creamos el topic a la que enviaremos los mensajes (si el topic ya existe no se crearía)
this.topic = session.createTopic(topicName);

// Creamos el productor de mensajes
messageProducer = session.createProducer(this.topic);
}

private void closeConnection() throws JMSException {

// Cerramos la conexion
connection.close();
}

private void sendMensaje() throws JMSException {

// Creamos un mensaje JMS de tipo TextMessage
final String text = "Productor ["+clientId+"] genera el mensaje ["+ System.currentTimeMillis()+"]";
final TextMessage textMessage = session.createTextMessage(text);

// Enviamos el mensaje al topic definido
messageProducer.send(textMessage);

LOGGER.info(text);
}
}

package activemq;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Suscriptor implements Runnable, MessageListener {

private static final Logger LOGGER = LoggerFactory.getLogger(Suscriptor.class);

private ConnectionFactory connectionFactory;
private Connection connection;
private Session session;
private Topic topic;
private MessageConsumer messageConsumer;
private String suscriptorId;
private String topicName;

public void run() {
LOGGER.info("Suscriptor ["+this.suscriptorId+"] iniciando la escucha del topic ["+this.topicName+"]...");
}

public Suscriptor(String suscriptorId, String topicName) throws JMSException {

// Creamos la factoria de conexiones
this.connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_BROKER_URL);

//  Creamos una conexión
this.suscriptorId = suscriptorId;
connection = connectionFactory.createConnection();
connection.setClientID(this.suscriptorId);

// Creamos una sesion
this.session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);

// Creamos el topic del que recibiremos los mensajes (si el topic ya existe no se crearía)
this.topicName = topicName;
this.topic = session.createTopic(this.topicName);

// Creamos el consumidor de mensajes
this.messageConsumer = session.createConsumer(this.topic);
this.messageConsumer.setMessageListener(this);
this.connection.start();
}

// Método que será invocado cuando llegue un mensaje
public void onMessage(Message message) {

String texto = null;

// Casteamos el mensaje al tipo previamente acordado (en este caso un TextMessage)
TextMessage textMessage = (TextMessage) message;

// Recuperamos el texto del TextMessage
try {
texto = textMessage.getText();
LOGGER.info("Suscriptor ["+this.suscriptorId+"] procesa el mensaje ["+ texto+"]");
} catch (JMSException e) {
e.printStackTrace();
}
}
}

package activemq;

import javax.jms.JMSException;

public class TestActiveMQ {

public static void main(String[] args) throws JMSException, InterruptedException {
testTopic();
}

public static void testTopic() throws JMSException, InterruptedException {

//Levantamos 3 consumidores de mensajes en threads independientes, cada uno procesa todo
//lo que llegue al topic. Conectamos antes los suscriptores dado que en caso de levantar
//primero los publicadores no llegaría nada a los suscriptores y además la cola se vería
//vacía dado que no había nadie escuchando
Thread tc;
for (int i=0;i<3;i++) {
tc = new Thread(new Suscriptor("sus"+i,"miTopic"), "tsus"+i);
tc.start();
}

//Levantamos 2 productores de mensajes en threads independientes cada uno envía su mensaje al topic
Thread tp;
for (int i=0;i<2;i++) {
tp = new Thread(new Publicador("pub"+i,"miTopic"), "tpub"+i);
tp.start();
}
}
}

Referencias:
https://www.codenotfound.com/jms-point-to-point-messaging-example-activemq-maven.html
https://www.codenotfound.com/jms-publish-subscribe-messaging-example-activemq-maven.html


miércoles, 21 de febrero de 2018

Resumen Typescript

Hola

En esta entrada dejo un repaso bastante completo al lenguaje de programación Typescript.

Todos los ejemplos de este resumen están basados en la documentación disponible en:

     https://www.typescriptlang.org/docs/handbook/basic-types.html
     https://www.tutorialspoint.com/typescript/index.htm

Para ejecutar cualquiera de los ejemplos deberíamos lanzar:

     tsc 001-tipos-basicos.ts     // Compila a javascript
     node 001-tipos-basicos.js    // Ejecuta el código en consola

La lista completa de ejemplos es:

001-tipos-basicos.ts
002-constantes-variables.ts
003-String.ts
004-Arrays.ts
005-estructuras.ts
006-operadores.ts
007-interfaces.ts
008-clases.ts
009-clasesDos.ts
010-funciones.ts
011-recursividad.ts
012-callbacks.ts
013-secuencial.ts
014-promesas.ts
015-generics.ts
016-generics2.ts

El enlace al zip con todos los ejemplos es el siguiente:

     https://mega.nz/#!lZomnICb!XvUZiEsC1BBlkt517LHQU4Gp4A5x9sZWC-3D0t5WuUQ