Apache Flink
• Stack tecnológico desarrollado inicialmente como proyecto de I+D Stratosphere por grupos de investigación de Berlín. Apache Incubator en Abril 2014 y Apache Top Level en Diciembre 2014.
• Motor de procesamiento en memoria
• Procesamiento de streaming puro. Batch es un tipo concreto de Streaming.
• API similar a Spark.
• Soporte nativo de iteraciones.
• Híbrido mediante Arquitectura Kappa.
• Ecosistema creciendo.
Ejemplos
• Batch
• Stream
Librerías
Ingesta y almacenamiento
de datos
Motores de procesamiento
Gestores de aplicaciones
y recursos
Clasificación
YARN
Comparación con otras tecnologías
API low-level high-level high-level
Data Transfer batch batch pipelined & batch
Memory Management
disk-based JVM-managed Active managed
Iterationsfile system
cachedin-memory
cached streamed
Fault tolerance task level task level job level
Good at massive scale out data explorationheavy backend &
iterative jobs
Libraries many external built-in & externalevolving built-in &
external
Batch processing
Streaming “true” mini batches “true”
API low-level high-level high-level
Fault tolerance tuple-level ACKs RDD-based (lineage)coarse
checkpointing
State not built-in external internal
Exactly once at least once exactly once exactly once
Windowing not built-in restricted flexible
Latency low medium low
Throughput medium high high
Streaming processing
Tecnologías
Streaming Si Micro-batches Si Si
Gestión de estado No Si. Escribe a disco. Si. Escribe a disco. Si. BD embebida.
Semánticaat least once
exactly-once con Trident
exactly once exactly once at least once
Ventana deslizante
Sí en 1.0 Si. Por tiempoSi. Por tiempo y nº
eltosNo
Ventana no-deslizante
Sí en 1.0Si. Por tiempo (micro-batch)
Si. Por tiempo y nº eltos.
Si. Por tiempo
Latencia < segundos segundos < segundos < segundos
Rendimiento ¿Medio? Alto Alto Alto
LenguajesJava, Scala, Ruby,
Python, Javascript, Perl
Scala, Java, Python, R Java, Scala, Python Java, Scala
Madurez ¿Alta? Media Baja Baja
1. Instalar Java 1.8
sudo add-apt-repository ppa:openjdk-r/ppa
sudo apt-get update
sudo apt-get install openjdk-8-jdk
Editar etc/environment para añadir la variable JAVA_HOME
JAVA_HOME=“/usr/lib/jvm/java-8-openjdk-XXX”
2. Instalar Scala 2.11
sudo apt-get remove scala-library scala
sudo wget www.scala-lang.org/files/archive/scala-2.11.8.deb
sudo dpkg -i scala-2.11.8.deb
[Si da errores] sudo apt-get install libjansi-java
[Si da errores] sudo apt-get -f install
sudo apt-get update
sudo apt-get install scala
3. Instalar mavensudo apt-get install maven
4. Descargar y descomprimir Flink 1.0.2
wget http://apache.rediris.es/flink/flink-1.0.2/flink-1.0.2-bin-hadoop27-scala_2.11.tgz
tar -zxvf flink-1.0.2-bin-hadoop27-scala_2.11.tgz
5. Probar Scala-Flink interactivo
bin/start-scala-shell.sh local
Preparación del entorno (1/2)
6. Crear un esqueleto maven
mvn archetype:generate \
-DarchetypeGroupId=org.apache.flink \
-DarchetypeArtifactId=flink-quickstart-java \
-DarchetypeVersion=1.0.0 \
-DgroupId=org.apache.flink.quickstart \
-DartifactId=flink-java-project \
-Dversion=0.1 \
-Dpackage=org.apache.flink.quickstart \
-DinteractiveMode=false
cd flink-java-project
mvn clean install
mvn clean package
7. Instalar IDE. Importar proyecto en el IDE
[Intelli J] Import project → Import project from external model [existing sources]→ Maven
8. Ejecutar WordCount para validar instalación y abrir localhost:8081 para ver la app de admin
9. Desplegar en cluster
mvn clean package → en /target obtenemos el .jar
flink-1.0.1/bin/start-local.sh → arrancar cluster local, existen otras opciones
[opcional] flink-1.0.1/ tail log flink-*-jobmanager* → ver si todo está OK
flink-1.0.1/bin/flink run –c MainClassName ../target/flink-java-project-0.1.jar → lanzar job
Preparación del entorno (2/2)
mvn archetype:generate \
-DarchetypeGroupId=org.apache.flink \
-DarchetypeArtifactId=flink-quickstart-java \
-DarchetypeVersion=1.0.0 \
-DgroupId=org.apache.flink.quickstart \
-DartifactId=flink-java-project \
-Dversion=0.1 \
-Dpackage=org.apache.flink.quickstart \
-DinteractiveMode=false
http://dataartisans.github.io/flink-training/devSetup/handsOn.html
• Client• Master (Job Manager)• Worker (Task Manager)
Arquitectura
Client
Job Manager
Task Manager
Task Manager
Task Manager
• Local. Similar al Driver de Spark• Construye el grafo de transformaciones (DAF) y lo
optimiza• Pasa el grafo al Job Manager • Recoge los resultados
Job Manager
Client
case class Path (from: Long, to: Long)val tc = edges.iterate(10) {
paths: DataSet[Path] =>val next = paths
.join(edges)
.where("to")
.equalTo("from") {(path, edge) =>
Path(path.from, edge.to)}.union(paths).distinct()
next} Optimizer
Type extraction
Data Sourceorders.tbl
Filter
Map DataSourcelineitem.tbl
JoinHybrid Hash
buildHT probe
hash-part [0] hash-part [0]
GroupRed
sort
forward
Client
• Paralelizacion: Crea el grafo de ejecución• Scheduling: Asigna las tasks a los task managers• State: Supervisa la ejecución
Job Manager
Job Manager
Data
Sourceorders.tbl
Filter
MapDataSour
celineitem.tbl
JoinHybrid Hash
build
HT
prob
e
hash-part [0] hash-part [0]
GroupRed
sort
forwar
d
Task Manager
Task Manager
Task Manager
Task Manager
Data
Sourceorders.tbl
Filter
MapDataSour
celineitem.tbl
JoinHybrid Hash
build
HT
prob
e
hash-part [0] hash-part [0]
GroupRed
sort
forwar
d
Data
Sourceorders.tbl
Filter
MapDataSour
celineitem.tbl
JoinHybrid Hash
build
HT
prob
e
hash-part [0] hash-part [0]
GroupRed
sort
forwar
d
Data
Sourceorders.tbl
Filter
MapDataSour
celineitem.tbl
JoinHybrid Hash
build
HT
prob
e
hash-part [0] hash-part [0]
GroupRed
sort
forwar
d
Data
Sourceorders.tbl
Filter
MapDataSour
celineitem.tbl
JoinHybrid Hash
build
HT
prob
e
hash-part [0] hash-part [0]
GroupRed
sort
forwar
d
• Las operaciones se dividen en tasks según el grado de paralelismo definido
• Cada instancia paralela de una operación ejecuta la operación en un task slot separado
• El scheduler puede ejecutar diversas tasks de diferentes operaciones en el mismo task slot
Task Manager
Slot
Task ManagerTask Manager
Slot
Slot
Task Manager
13
Gelly
Table
ML
SA
MO
A
DataSet (Java/Scala/Python) DataStream (Java/Scala)
Hadoop
M/R
Local Remote Yarn Tez Embedded
Data
flow
Da
taflo
w (
WiP
)
MR
QL
Table
Ca
sca
din
g (
WiP
)
Streaming dataflow runtime
DataSet
• Lista distribuida de elementos en memoria similar a RDD de Spark
• Los elementos pueden ser simples (String, Long, Integer, Boolean) o compuestos
(Array, Tuple, Pojo)
• Se crea a partir de un ExecutionEnviorment o tras aplicar transformaciones a otro
DataSet
• Existen diferentes subclases de DataSet con sus peculiaridades
• Data Source → Orígenes de datos desde lo que un ExecutionEnviorment puede
generar un Dataset (p.e. fichero)
• Data Sink → Destino de datos donde se puede volcar un DataSet (p.e. fichero)
DataSet
Data Source DataSet Data Sink
DataSet
• API proporcionada por el ExecutionEnviorment
• Genera objetos DataSeta partir de una fuente de datos (fichero, base de datos, etc.)
• Se puede añadir Data Source nuevos
• Estructura de datos básica en Flink para batch processing
• Proporciona el API para aplicar transformaciones sobre un DataSet y generar nuevos DataSets(o clases derivadas)
• API proporcionada por la clase DataSet
• Permite exportar el contenido de un DataSethacia otro sistema (fichero, base de datos, etc.)
• Se puede añadir Data Sinks nuevos
object WordCount {
def main(args: Array[String]) {
// Crear el ExecutionEnvironment
val env = ExecutionEnvironment.getExecutionEnvironment
// Crear un Dataset con un DataSource
val text = env.fromElements("hola", "qué tal", "Apache Flink")
// Transformaciones sobre un DataSet
val counts = text.flatMap { _.toLowerCase.split(“ ") }
.map { (_, 1) }
.groupBy(0)
.sum(1)
// Usar un DataSink para conseguir resultados
counts.print()
}
}
DataSet
DataSet API
DataSet: yDataSet: x
val x = env.fromElements(“b”, “a”, “c”)
val y = x.map( e =>(e,1))
x.print()
y.print()
Devuelve un nuevo DataSet aplicando una función a cada uno de los elementos del DataSet original
DataSet API
DataSet: yDataSet: x
val x = env.fromElements(1, 2, 3)
val y = x.filter( e => e%2==1)
x.print()
y.print()
Devuelve un nuevo DataSet que solo incluye los elementos que cumplen la condición
DataSet API
DataSet: yDataSet: x
Devuelve un nuevo DataSet aplicando una función a cada uno de los elementos del DataSet original y después apilando los resultados.
val x = env.fromElements(1, 2, 3)
val y = x.flatMap( e => Array (e, e*100, 42))
x.print()
y.print()
x = sc.parallelize([1,2,3,4])
y = x.collect()
DataSet API
DataSet: x
val x = env.fromElements(1, 2, 0)
x.count()
Devuelve un Long con el nº de elementos del DataSet
3
COUNT
x = sc.parallelize([1,2,3,4])
y = x.collect()
DataSet API
DataSet: x
val x = env.fromElements(1, 2, 0)
x.print()
Imprime por la salida estándar (System.out) los elementos del DataSet
120
• ¿En cuántas líneas del fichero LICENSE aparece la palabra “contributor”?
• Tips
• val mydataset = env.readTextFile(“LICENSE”)
• Función string.contains(string2)de Scala
• Si se quiere tener en cuenta apariciones tanto en minúscula como en mayúscula: string.toLowerCase()
Ejercicio 1 DataSet API
Tipos de Datos para DataSet
• Tipos básicos de Java– String, Long, Integer, Boolean,…
– Arrays
• Tipos compuestos– Tuples
– Pojo
– Scala Case class
• No todos los tipos pueden ser usados como claves– Tienen que ser comparables
23
Tuples
• La manera más fácil y eficiente de encapsular datos en Flink
• Tuple1 hasta Tuple25
Tuple2<String, String> person = new Tuple2<String, String>("Max", "Mustermann”);
Tuple3<String, String, Integer> person =
new Tuple3<String, String, Integer>("Max", "Mustermann", 42);
Tuple4<String, String, Integer, Boolean> person =
new Tuple4<String, String, Integer, Boolean>("Max", "Mustermann", 42, true);
// el primer campo es el 0
String firstName = person.f0;
String secondName = person.f1;
Integer age = person.f2;
Boolean fired = person.f3;
24
Pojo
• Cualquier clase Java que– Tenga un constructor vacío por defecto
– Todos sus campos son accesibles (public o getter/setter)
public class Person {
public int id;
public String name;
public Person() {};
public Person(int id, String name) {…};
}
DataSet<Person> d = env.fromElements(new Person(1, ”Ruben”));
• Permite definir claves por nombreDataSet<Person> p = …
// agrupar por el campo “name”
d.groupBy(“name”).groupReduce(…);
25
Scala Case Classes
• Soporte nativo para case classes de Scala
case class Person(id: Int, name: String)
d: DataSet[Person] = env.fromElements(new Person(1, “Ruben”)
• Permite definer claves por nombre
// agrupar por el campo “name”
d.groupBy(“name”).groupReduce(…)
26
DataSet: x
val x = env.fromElements((1,5), (2,5), (1, 4))
val y =x.groupBy(0)
x.print()
y.sum(1).print()
GroupedDataSet: y
(1,5), (2,5), (1, 4)
(1,9), (2,5)
Devuelve un nuevo GroupedDataSet agrupando los elementos por la clave especificada. La clave puede ser un campo de un Tuple/Pojo/Case o una función que genere la clave.
DataSet API
DataSet APIGroupedDataset / DataSet: x DataSet: y
Devuelve un DataSet de un único elemento combinando todos los elementos del DataSet original mediante una función asociativa. Se puede aplicar en GroupedDataset, generando en este caso un DataSet con un elemento por cada clave.
val x = env.fromElements(1,2,3,4)
val y =x.reduce(_ + _)
x.print()
y.print()
DataSet APIGroupedDataset / DataSet: x DataSet: y
val x = env.fromElements((“hola”,1), (“adiós”, 1), (“hola”, 3))
val y =x.groupBy(0).reduceGroup(elements => elements.length)
x.print()
y.print()
REDUCEGROUP
Devuelve un DataSet de un único elemento combinando todos los elementos del DataSet original mediante una función que recibe todos elementos en forma de iterable. Se puede aplicar en GroupedDataset, generando en este caso un DataSet con un elemento por cada clave.
12
((“hola”,1), (“adiós”, 1), (“hola”, 3)
• Ejecutar y analizar el código el ejemplo de WordCount desde el IDE
• Seguir los pasos de la slide “Preparar Entorno (2/2)”
• Ejecutar el Main del archivo WordCount.java desde el IDE
• Analizar clase LineSplitter Lógica concreta de un FlatMap
• ¿Cómo implementarías (sin usar la función SUM) la cuenta de palabras? Lógica concreta de un ReduceGroupe
Ejercicio 2 DataSet API
WordCount: FlatMap
public static class LineSplitter
implements FlatMapFunction<String, Tuple2<String, Integer>> {
@Override
public void flatMap(String value, Collector<Tuple2<String, Integer>> out){
// normalizer y partir la linea por palabras
String[] tokens = value.toLowerCase().split("\\W+");
// emitir un par (palabra, 1) por cada palabra
for (String token : tokens) {
if (token.length() > 0) {
out.collect(new Tuple2<String, Integer>(token, 1));
}
}
}
}
WordCount: FlatMap: Interface
public static class LineSplitter
implements FlatMapFunction<String, Tuple2<String, Integer>> {
@Override
public void flatMap(String value, Collector<Tuple2<String, Integer>> out){
// normalizer y partir la linea por palabras
String[] tokens = value.toLowerCase().split("\\W+");
// emitir un par (palabra, 1) por cada palabra
for (String token : tokens) {
if (token.length() > 0) {
out.collect(new Tuple2<String, Integer>(token, 1));
}
}
}
}
32
WordCount: FlatMap: tipos
public static class LineSplitter
implements FlatMapFunction<String, Tuple2<String, Integer>> {
@Override
public void flatMap(String value, Collector<Tuple2<String, Integer>> out){
// normalizar y partir la linea por palabras
String[] tokens = value.toLowerCase().split("\\W+");
// emitir un par (palabra, 1) por cada palabra
for (String token : tokens) {
if (token.length() > 0) {
out.collect(new Tuple2<String, Integer>(token, 1));
}
}
}
}
33
WordCount: FlatMap: collector
public static class LineSplitter
implements FlatMapFunction<String, Tuple2<String, Integer>> {
@Override
public void flatMap(String value, Collector<Tuple2<String, Integer>> out){
// normalizer y partir la linea por palabras
String[] tokens = value.toLowerCase().split("\\W+");
// emitir un par (palabra, 1) por cada palabra
for (String token : tokens) {
if (token.length() > 0) {
out.collect(new Tuple2<String, Integer>(token, 1));
}
}
}
}
WordCount: GroupReduce
public static class SumWords implements
GroupReduceFunction<Tuple2<String, Integer>, Tuple2<String, Integer>> {
@Override
public void reduce(Iterable<Tuple2<String, Integer>> values,
Collector<Tuple2<String, Integer>> out) {
int count = 0;
String word = null;
for (Tuple2<String, Integer> tuple : values) {
word = tuple.f0;
count++;
}
out.collect(new Tuple2<String, Integer>(word, count));
}
}
WordCount: GroupReduce: Interface
public static class SumWords implements
GroupReduceFunction<Tuple2<String, Integer>, Tuple2<String, Integer>> {
@Override
public void reduce(Iterable<Tuple2<String, Integer>> values,
Collector<Tuple2<String, Integer>> out) {
int count = 0;
String word = null;
for (Tuple2<String, Integer> tuple : values) {
word = tuple.f0;
count++;
}
out.collect(new Tuple2<String, Integer>(word, count));
}
}
WordCount: GroupReduce: tipos
public static class SumWords implements
GroupReduceFunction<Tuple2<String, Integer>, Tuple2<String, Integer>> {
@Override
public void reduce(Iterable<Tuple2<String, Integer>> values,
Collector<Tuple2<String, Integer>> out) {
int count = 0;
String word = null;
for (Tuple2<String, Integer> tuple : values) {
word = tuple.f0;
count++;
}
out.collect(new Tuple2<String, Integer>(word, count));
}
}
WordCount: GroupReduce: collector
public static class SumWords implements
GroupReduceFunction<Tuple2<String, Integer>, Tuple2<String, Integer>> {
@Override
public void reduce(Iterable<Tuple2<String, Integer>> values,
Collector<Tuple2<String, Integer>> out) {
int count = 0;
String word = null;
for (Tuple2<String, Integer> tuple : values) {
word = tuple.f0;
count++;
}
out.collect(new Tuple2<String, Integer>(word, count));
}
}
Desde Fichero
• readTextFile(path, [TextInputFormat]): Lee línea a línea y las devuelve como
String
• readCsvFile(path, [CsvInputFormat ]):Lee línea a línea ficheros con campos
separados por comas (u otro char). Genera un DataSet de tuplas o objetos case classes.
• readHadoopFile(FileInputFormat, Key, Value, path
,[FileInputFormat ]) : Crea un JobConf y lee el fichero del path especificado
usando el FileInputFormat. Se devuelven como Tuple2<Key, Value>
Data Sources para DataSet
Desde Colecciones
• fromCollection(Seq): Crear un DataSet a partir de un objeto Seq. Todos los elementos
de la colección tienen que ser del mismo tipo.
• fromCollection(Iterator): Crear un DataSet a partir de un objeto Iterator. El tipo
de dato es el devuelto por el iterador.
• fromElements(elements: _*): Crea un DataSet a partir de la secuencia de
elementos introducidos directamente.
Genéricos
• readFile(inputFormat, path): Lee desde fichero pero con un format genérico que
se especifica.
• createInput(inputFormat) : Para crear un nuevo data Source (por ejemplo
Kafka).
Data Sources para DataSet
ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
// Leer desde fichero local o Sistema de ficheros distribuido
DataSet<String> localLines = env.readTextFile(”/path/to/my/textfile");
// leer un CSV con 3 campos
DataSet<Tuple3<Integer, String, Double>> csvInput =
env.readCsvFile(“/the/CSV/file")
.types(Integer.class, String.class, Double.class);
// leer un CSV de 5 campos pero se queda solo con el primero y el cuarto
DataSet<Tuple2<String, Double>> csvInput = env.readCsvFile(“/the/CSV/file")
.ignoreFirstLine()
.includeFields("10010")
.types(String.class, Double.class);
// leer un CSV y parsearlo como una clase POJO
DataSet<Film> films = env.readCsvFile(“the/CSV/file“)
.pojoType(
Film.class,
"name",
"year",
"nominations",
"genre1");
Data Sources: ficheros
ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
// leer de elementos introducidos directamente
DataSet<String> names = env.fromElements(“Some”, “Example”, “Strings”);
// leer desde una colección
List<String> list = new ArrayList<String>();
list.add(“Some”);
list.add(“Example”);
list.add(“Strings”);
DataSet<String> names = env.fromCollection(list);
Data Sources: colecciones
Text• writeAsText(“/path/to/file”)
• writeAsCSV(“/path/to/file”, “;”)
• output(outputFormat)
CSV• writeAsCsv(“/path/to/file”)
Devolver resultados al cliente• print()
• collect()
• count()
Data Sinks para DataSet
// Reduce → Recibe 2 parámetros para aplicar de forma asociativa
val text:DataSet[Tuple2[String, Int]] = env.fromElements(Tuple2("uno",1),Tuple2 ("dos",2),Tuple2 ("uno",11))
text.groupBy(0).reduce( (x, y) => (x._1, x._2 + y._2)).print()
// ReduceGroup -> Recibe iterador como parámetro
val text:DataSet[Tuple2[String, Int]] = env.fromElements(Tuple2("uno",1),Tuple2 ("dos",2),Tuple2 ("uno",11))
text.groupBy(0).reduceGroup(x => x.length).print()
// Pasar funciones a las transformaciones
val numeros:DataSet[Int] =
env.fromElements(1,2,4,6,20)
numeros.map(new MiMap).print()
numeros.reduce(new MiReduce).print()
class MiMap extends MapFunction[Int, Int] {
def map(n: Int): Int = {
return n*2
}
}
class MiReduce extends ReduceFunction[Int] {
def reduce(a: Int, b:Int): Int = {
return a+b
}}
// Parsear un Dataset con una case class
case class Persona (id: Int, nombre: String)
val d: DataSet[Persona] = env.fromElements(new Persona(1, “Rubén”)
// usar un campo como clave
d.groupBy(“nombre”).groupReduce(…)
DataSet API en Scala
Transformación Descripción
MapDado un elemento, genera otro elemento
data.map { x => x.toInt }
FlatMapDado un elemento produce 0, 1 o más elementos
data.flatMap { str => str.split(" ") }
MapPartition
Recibe un iterador y produce un nº arbitrario de resultados. Se ejecuta por cada partición.
data.mapPartition { in => in map { (_, 1) } }
Filter
Recibe un elemento y lo devuelve si la evaluación de la función es True sobre ese elemento
data.filter { _ > 1000 }
ReduceCombina elementos en uno único mediante una función asociativa. Se puede aplicar en GroupedDatasets
data.reduce { _ + _ }
ReduceGroupReduce grupos de elementos en uno o varios elementos.
data.reduceGroup { elements => elements.sum }
Aggregate
Agrega grupos de elementos en uno único. Se puede aplicar en DataSets y GroupedDasets
val input: DataSet[(Int, String, Double)] = // [...]
val output: DataSet[(Int, String, Doublr)] =
input.aggregate(SUM, 0).aggregate(MIN, 2);
Hay atajos para algunas funciones de agregación habituales
val input: DataSet[(Int, String, Double)] = // [...] val output:
DataSet[(Int, String, Doublr)] = input.sum(0).min(2)
Transformaciones para DataSet
Transformación Descripción
Distinct Devuelve los elementos distintos de un DataSet
data.distinct()
Join Une dos datasets creando todos los pares de elementos que son iguales en sus claves. Hay otras opciones de join con Join Hints
// Los elementos son tuplas y se usa el campo 0 del Dataset 1 con el 1 del DataSet 2
val result = input1.join(input2).where(0).equalTo(1)
OuterJoin Ejecutar left, right and full outer joins
keys.val joined = left.leftOuterJoin(right).where(0).equalTo(1)
{ (left, right) => val a = if (left == null) "none" else left._1 (a, right) }
Union Produce la union de dos DataSets.
data.union(data2)
Cross Producto cartestiano entre dos DataSets creando todos los pares de elementos.
val data1: DataSet[Int] = // [...]
val data2: DataSet[String] = // [...]
val result: DataSet[(Int, String)] = data1.cross(data2)
CoGroup Versión de 2 dimensiones del Reduce. Agrupa el input de uno o más campos y luego une los grupos. La función de transformación se aplica a los pares de grupos
data1.coGroup(data2).where(0).equalTo(1).with( cogroupfunction)
First-n Devuelve N elementos del DataSet.
data1.first(3)
Sort Partition Ordena una partición.val in: DataSet[(Int, String)] = // [...] val result = in.sortPartition(1,
Order.ASCENDING).mapPartition { ... }
Transformaciones para DataSet
• Usar el fichero “pictures.csv”
https://cs.uwaterloo.ca/~s255khan/files/pictures.csv
• Modelar cada película con una Case class / Pojo/ Tuple
• Calcular con el API de DataSet
• Media de nominaciones de las películas
• Media en Metacritic, agrupado por géneros, de las películas
• Duración media de las películas ganadoras por décadas
• ¿Cuántas películas ganadoras incluyen al menos una de las palabras de su título en la sinopsis?
• ¿Cuántas películas ganadoras incluyen todas las palabras de su título en la sinopsis?
• ¿Cuál es la desviación estándar del rating de las películas ganadoras en el siglo XXI?
name year nominations rating duration genre1 genre2 release metacritic synopsis
Birdman 2014 9 7.8 119 Comedy Drama November 88Illustrated upon the progress of his latest Broadway play a former popular actors struggle tocope with his current life as a wasted actor is shown.
12 Years a Slave 2013 9 8.1 134 Biography Drama November 97In the antebellum United States Solomon Northup a free black man from upstate New York isabducted and sold into slavery.
Ejercicio 3 DataSet API
48
Gelly
Table
ML
SA
MO
A
DataSet (Java/Scala/Python) DataStream (Java/Scala)
Hadoop
M/R
Local Remote Yarn Tez Embedded
Data
flow
Da
taflo
w (
WiP
)
MR
QL
Table
Ca
sca
din
g (
WiP
)
Streaming dataflow runtime
DataStream
Arquitectura de un sistema de streaming processing
Capa de adquisición
Cola de mensajes
Capa de procesamiento
Almacenamiento en memoria
Capa de acceso
Almacenamiento larga duración
Origen
Capa de procesamiento: semántica
At most once: cada mensaje se procesa como máximo una vez. Seasegura que ningún mensaje es procesado más de una vez, pero podríapasar que algún mensaje no se procesase.
La más sencilla, no se requiere implementar ninguna lógica.
At least once: cada mensaje se procesa al menos una vez. Se aseguraque todos los mensajes recibidos son procesados, pero podría pasarque algún mensaje se procesase más de una vez.
• El sistema tiene que mantener un registro de los mensajes que se envió ala capa de procesamiento y enviar un ACK de recibimiento.
Exactly once: cada mensaje se procesa exactamente una vez. Ningúnmensaje se queda sin procesar y ningún mensaje se procesa más deuna vez.
• La más compleja. El sistema debe mantener un registro de los mensajesenviados a la capa de procesamiento pero también detectar losduplicados.
Para un tiempo tn concreto, ¿depende la salida del sistema solo de lainformación recibida en tn (stateless) o también de la recibida en tn-1
(statefull)?
Ejemplos:
• Lanzar una alarma cuando el PM10 sobrepase el valor de 25
• Identificar los trending topics en Twitter
• Contar el nº de páginas vistas por hora por cada usuario
Los frameworks para streaming processing suelen disponer de APIs paragestionar estados Flink lo tiene automático y manual
Gestión del estadoEjemplo statefull
Capa de procesamiento: estado
Noción del tiempo
• Event time: tiempo en el que el dato se generó
• Stream/Ingestion time (system timestmap): tiempo en el que eldato entró en el sistema
• Skew: diferencia entre el event time y el stream time
Capa de procesamiento: eventos
Ventana de datos (window): porción finita del streaming de datossobre la que poder ejecutar los algoritmos.
• Lo habitual es por tiempo, aunque podría ser por nº de elementos u otrosmecanismos de disparo.
• Existen diferentes tipos de ventanas: deslizante (sliding) y no deslizantes(tumbling)
Capa de procesamiento: ventanas
Ventana deslizante
Sliding Window: técnica para el procesamiento de datos en continuoque divide el flujo en grupos finitos basándose en dos parámetros.
• Longitud de la ventana (window length): Tiempo de datos que se tendráen cuenta en el cálculo (desde tactual hasta tactual – longitud_ventana)
• Intervalo: cada cuánto se recomputa el cálculo sobre los datos de laventana
Ejemplo: Actualizar cada 1 segundo (intervalo) el valor de la mayor compra delos últimos 2 segundos (longitud de la ventana)
Ventana no deslizante
Tumbling window: técnica para el procesamiento de datos en continuoque divide el flujo en grupos finitos basándose solamente en lalongitud de la ventana.
• La longitud de la ventana puede ser por tiempo. Equivalente a ventanadeslizante donde longitud_ventana = intervalo
• La longitud de la ventana puede ser por nº de elementos. Hasta que no sereciba ese nº de elementos fijado no se produce ningún resultado.
DataStream
• Lista distribuida de infinitos elementos NO micro-batch
• Se crea a partir de un StreamExecutionEnvironment o tras aplicar transformaciones a otro DataStream
• Mismos conceptos de DataSource y Data Sinks
• Las funciones de agregación solo tienen sentido sobre ventanas
• Las funciones sobre Windows cuyo stream no es Clave-Valor (KeyedDataStream) NO son paralelas
• Conceptos de ventanas (Tamaño y Trigger)
Tumbling time window
.timeWindow(Time.minutes(1))
Sliding time window
.timeWindow(Time.minutes(1), Time.seconds(30))
Tumbling count window
.countWindow(100)
Sliding count window
.countWindow(100, 10)
Trigger
.trigger(new miDisparador())
DataStream
Heredan
Generan
DataStream
StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
String url = "wss://stream.meetup.com/2/rsvps";
DataStream<MeetupRSVGevent> events = env.addSource(new
MeetupStreamingSource(url));
events.filter(new FilterNullsEvents())
.keyBy("venue")
.timeWindow(Time.seconds(5))
.apply(new PojoCountPeople()).print();
env.execute("Meetup Madrid DataStream");
Data Sources para DataStream
Desde fichero
• readTextFile(path)
• readFile(path)
• readFileStream ()
Desde Socket
• socketTextStream
Desde Colección
• fromCollection(Seq)
• fromCollection(Iterator)
• fromElements(elements: _*)
Genérico
• addSource () Para Kafka addSource(new FlinkKafkaConsumer09<>)
Data Sinks para DataStreams
A fichero
• writeAsText()
• writeAsCsv(...)
• writeUsingOutputFormat()
Imprimir
• print()
• printToErr()
A Socket
• writeToSocket
Genérico
• addSink para volcar a Kafka addSink(new FlinkKafkaConsumer09[](…)
Transformación Descripción
MapDataStream → DataStream
Dado un element genera otrodataStream.map { x => x * 2 }
FlatMapDataStream → DataStream
Dado un element genera 0,1 o más elementosdataStream.flatMap { str => str.split(" ") }
FilterDataStream → DataStream
Evalua una función boleana sobre el element y lo devuelve si es TruedataStream.filter { _ != 0 }
KeyByDataStream → KeyedStream
Particionado por clave para ejecutar operaciones en paralelodataStream.keyBy("someKey")
dataStream.keyBy(0)
ReduceKeyedStream → DataStream
Aplica la función asociativa con el anterior resultado y el nuevo elemento
keyedStream.reduce { _ + _ }
FoldKeyedStream → DataStream
Combina el resultado anterior con el Nuevo element, utilizando un valor inicial
val result: DataStream[String] = keyedStream.fold("start",
(str, i) => { str + "-" + i })
Transformaciones para DataStream
Transformación Descripción
AggregationsKeyedStream → DataStream
Agregaciones. Min devuelve el mínimo valor y MinBy el elemento que contiene el mínimo valorkeyedStream.sum(0) keyedStream.sum("key")
keyedStream.min(0) keyedStream.min("key")
keyedStream.max(0) keyedStream.max("key")
keyedStream.minBy(0) keyedStream.minBy("key")
keyedStream.maxBy(0) keyedStream.maxBy("key")
WindowKeyedStream → WindowedStream
Agrupa los datos (en KeyedStream) según la definición de la ventana.
dataStream.keyBy(0).window(TumblingEventTimeWindows.of(Ti
me.seconds(5)))
WindowAllDataStream → AllWindowedStream
Lo mismo pero sobre DataStream (no KeyedStream). Puede no ser paralelodataStream.windowAll(TumblingEventTimeWindows.of(Time.sec
onds(5)))
ApplyWindowedStream → DataStreamAllWindowedStream → DataStream
Aplica una función sobre toda la ventana
windowedStream.apply { WindowFunction}
Transformaciones para DataStream
Transformación Descripción
Window ReduceWindowedStream → DataStream
Aplica una función Reduce sobre una ventana.
windowedStream.reduce { _ + _ }
Window FoldWindowedStream → DataStream
Función de asociación usando un valor inicial sobre una ventana
val result: DataStream[String] =
windowedStream.fold("start", (str, i) => { str + "-" + i })
Aggregations on windowsWindowedStream → DataStream
Agregaciones sobre ventanas. Min devuelve el mínimo valor y MinBy el elemento que contiene el mínimo valorwindowedStream.sum(0) windowedStream.sum("key")
windowedStream.min(0) windowedStream.min("key")
windowedStream.max(0) windowedStream.max("key")
windowedStream.minBy(0) windowedStream.minBy("key")
windowedStream.maxBy(0) windowedStream.maxBy("key")
Transformaciones para DataStream
Transformation Description
UnionDataStream* → DataStream
Unión de 2 o más DataStreams creando uno DataStream único con todos loselementos.
dataStream.union(otherStream1, otherStream2, ...)
Window JoinDataStream,DataStream → DataStream
Join two data streams on a given key and a common window.
dataStream.join(otherStream) .where(0).equalTo(1)
.window(TumblingEventTimeWindows.of(Time.seconds(3)))
.apply { ... }
Window CoGroupDataStream,DataStream → DataStream
Cogroups two data streams on a given key and a common window.
dataStream.coGroup(otherStream) .where(0).equalTo(1)
.window(TumblingEventTimeWindows.of(Time.seconds(3)))
.apply {}
Extract TimestampsDataStream → DataStream
Extraer el timpstmap de un registro para usar ventanas que trabajan con semánticade tiempos
stream.assignTimestamps { timestampExtractor }
Transformaciones para DataStream
• Descargar el proyecto esqueleto MeetupMadrid Flink e importarlo en el IDE
https://onedrive.live.com/redir?resid=1F231B643ABC3A9C!11283&authkey=!AFsdm9PIjlkSrmM&ithint=file%2czip
• Esqueleto para procesar datos en tiempo real de la red social Meetup. Se leen las respuestas RSVG
• Analizar el código
o La clase MeetupStreamingSource lee datos de un WebSocket y los añade como una fuente de datos a Flink (SourceFunction)
o Se parsean los eventos (JSON) como un tipo de dato MeetupRSVGevent (Java)
• Ejecutar el Main
• Revisar la Web Admin (localhost:8081)
Ejercicio 4a DataStream API
• Sobre el flujo de datos entrante:
• Eliminar los objetos mal formados (alguno de sus campos a null)
• Contar los usuarios que han confirmado a cada evento en los últimos 10 segundos
• Contar los usuarios que han confirmado a cada evento en los últimos 20 segundos actualizando el resultado cada 5 segundos
• Contar los usuarios por países cada 5 segundos
• Calcular los Trending Topics (palabras semánticamente significativas más repetidos de los topic_name) teniendo en cuenta la información del último minuto y actualizando el resultado cada 10 segundos
Ejercicio 4b DataStream API