PRÁTICA E LABORATÓRIO I 1
Prática e Laboratório I
Big data
Além dos módulos (Hadoop, 2016) Hadoop Common, Hadoop Distributed File
System (HDFS™), Hadoop YARN e Hadoop MapReduce, têm-se diversos
projetos relacionados ao Hadoop, tais como Ambari™, HBase™, Hive™, Pig™ e
Spark™.
A administração do cluster Hadoop pode ser realizada com o Apache Ambari, que,
utilizando uma interface Web, exibe um painel (dashboard), pelo qual o administrador
do cluster consegue:
PRÁTICA E LABORATÓRIO I 2
Como dito anteriormente, Hadoop possui seus módulos principais e um conjunto de
projetos associados. Dessa forma, entendido o mecanismo MapReduce de Hadoop,
deve-se conhecer projetos que fazem parte do trabalho de análise de dados em grande
escala (Big Data Analytics), pelo qual se descobrem padrões e correlações entre dados,
dentre outras possibilidades que tornem mais fácil e mais correta a tomada de decisão
por parte do cliente. Alguns projetos do ecossistema Hadoop serão mostrados nesta
disciplina.
PRÁTICA E LABORATÓRIO I 3
Uma vez executada a máquina virtual, são exibidos o IP a Porta (127.0.0.1:8888) para
poder acessar o ambiente gráfico da Sandbox.
PRÁTICA E LABORATÓRIO I 5
Posicionando o cursor sobre o link SSH CLIENT, obtém-se o login/senha do usuário
root.
O usuário raj_ops (senha raj_ops) será utilizado no Bitvise para acessar o sistema
remoto.
PRÁTICA E LABORATÓRIO I 6
E, no primeiro acesso, deve-se clicar em Accept and Save.
,
Figura 8: Primeiro acesso do usuário raj_ops.
Uma vez configurado o ambiente virtual, o próximo passo é saber como analisar dados
em ambiente Big Data (Big Data Analytics). Nesta disciplina, serão estudados os
seguintes componentes do ecossistema Hadoop: Pig, Hive, Hbase, Spark, Zeppelin e
a linguagem de programação Scala.
PRÁTICA E LABORATÓRIO I 7
Tarefas Administrativas
[raj_ops@sandbox ~]$ su
Digite a nova senha (Please set the password for admin:) e confirme (Please
retype the password for admin:).
PRÁTICA E LABORATÓRIO I 8
Ainda na janela de console, configure a hora local para Brazil/East:
PRÁTICA E LABORATÓRIO I 10
Altere a timezone como feito no Linux (Brazil/East):
PRÁTICA E LABORATÓRIO I 11
Figura 15: Timezone alterada para Brazil/East (UTC-02:00 BRST).
Apache Pig
Apache Pig (2016) é uma plataforma para análise de grandes massas de dados de
forma paralela. Originalmente desenvolvido pela Yahoo!, hoje é um projeto de alto
nível da Apache. (WHITE, 2015).
A camada de linguagem de Pig consiste de uma linguagem chamada Pig Latin, que
permite ao usuário deste projeto construir, de maneira muito fácil, programas
MapReduce nesta linguagem de script.
PRÁTICA E LABORATÓRIO I 12
A partir de scripts Pig Latin, pode-se extrair dados de uma fonte (ou mais de uma:
join), transformá-los de acordo com regras de negócios desejadas e carregá-los em
uma base diferente no sistema distribuído de Hadoop (HDFS). Este processo de extrair,
transformar e carregar é conhecido como modelo de transação ETL (Extract,
Transform and Load).
Remoção Remoção
de dados de dados
inválidos duplicados
Com Pig, pode-se obter dados de uma base muito grande, gerando-se, em
consequência, um espaço amostral menor.
10 TB 10 MB
PRÁTICA E LABORATÓRIO I 13
A análise de dados com Pig envolve três etapas:
Para executar códigos Pig Latin por meio do ambiente interativo Grunt do Pig, a
partir de um terminal, digite pig e pressione ENTER:
PRÁTICA E LABORATÓRIO I 14
O Grunt é carregado:
Definições
José,Sistemas de Informação,6.5,9.0,7.5
Letícia,Comunicação Social,8.0,9.5,F
Luiz,Sistemas de Informação,8.0,8.0,F
No caso, tem-se que: José, Letícia, Luiz, 8.0 são átomos (atom);
A coleção de tuplas
é uma Bag.
José,Sistemas de Informação,6.5,9.0,7.5
Letícia,Comunicação Social,8.0,9.5,F
Luiz,Sistemas de Informação,8.0,8.0,F
PRÁTICA E LABORATÓRIO I 16
2) Envie-o para o HDFS.
Observe que não é obrigatório ter o nome da relação (alunos) igual ao nome do
arquivo (notas.txt). Mas não se pode utilizar o caractere ponto para nomear relações,
somente caracteres alfanuméricos e o caractere sublinhado (underscore): “_”.
Ao término da execução do DUMP, Pig exibe as tuplas da bag alunos.
PRÁTICA E LABORATÓRIO I 17
Figura 21: Resultado da execução de DUMP alunos.
PRÁTICA E LABORATÓRIO I 18
Em ambos os casos, Pig retorna com o seguinte:
(Letícia,Comunicação Social,8.0,9.5,F)
(Luiz,Sistemas de Informação,8.0,8.0,F)
PRÁTICA E LABORATÓRIO I 19
Apache
Hive
Apache Hive foi desenvolvido pelo Facebook para funcionar como data warehouse
sobre o sistema Hadoop/HDFS. (WHITE, 2015).
Hive gera jobs MapReduce e utiliza a linguagem HiveQL (ou HQL), bem próxima do
SQL, e que, por isso, diminui a curva de aprendizado.
De forma similar ao Pig, torna mais fácil a análise de dados armazenados em HDFS,
porém HiveQL é declarativa (a execução se dá em uma só operação), enquanto que
Pig Latin é procedural (a execução se dá em uma série de passos).
Tal qual SQL, para HiveQL não importa se a digitação se dará em caixa baixa ou alta,
a não ser nos casos de comparação entre strings.
Hive é uma ferramenta que analisa dados estruturados. Sendo assim, provê-se um
esquema (schema) para carregar os dados do HDFS para uma tabela do Hive.
Dito isto, para analisar dados com Hive deve-se:
PRÁTICA E LABORATÓRIO I 20
A. Criar uma tabela, especificando seu nome, os campos da tabela e respectivos
tipos, caractere de delimitação dos campos e caractere de finalização da linha.
Por exemplo:
CREATE TABLE teste (c1 INT, c2 FLOAT, c3 STRING) ROW FORMAT
DELIMITED FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n';
BOOLEAN: TRUE/FALSE
PRÁTICA E LABORATÓRIO I 21
TIMESTAMP: suporta o timestamp (data e hora) tradicional do UNIX,
com precisão opcional de nanossegundos. Utiliza o
formato: yyyy-mm-dd hh:mm:ss[.f...]
DATE: descreve uma data (sem hora), no formato: yyyy-mm-dd
BINARY: declarar um campo como binary, faz com que Hive não
tente interpretá-lo. Não há correlação dos tipos BINARY e
BLOB (tipo existente em outros sistemas).
Uma lista completa dos tipos definidos por Hive encontra-se em LanguageManual UDF
– Apache Hive (2016).
PRÁTICA E LABORATÓRIO I 22
HBase é um banco de dados distribuído e orientado a colunas que funciona no HDFS.
É uma aplicação Hadoop quando se necessita manipular (leitura/escrita) grandes
massas de dados em tempo real. (APACHE HBASE REFERENCE GUIDE, 2016).
Por não ser um banco de dados relacional, não suporta SQL. Porém, possui capacidade
de armazenamento muito superior aos bancos de dados relacionais em ambiente
distribuído (cluster) em commodity hardware. (WHITE, 2015).
Aplicações armazenam dados em tabelas, que possuem linhas e colunas. As linhas são
ordenadas e possuem uma coleção de famílias (de colunas). Uma família de colunas,
que organiza as colunas, é representada por um prefixo, o caractere dois-pontos (:) e
o nome da coluna. Desta forma, as colunas pbg:curso e pbg:disciplina pertencem à
mesma família: pbg. Novas famílias podem ser adicionadas à tabela em função da
necessidade. As famílias de colunas devem ser especificadas no início da definição do
esquema da tabela, porém, membros de uma nova família podem se adicionados por
demanda a famílias preexistentes. Por exemplo, no caso da família pbg, caso fosse
necessário, poderiam ser inseridos membros qualificados como turma (pbg:turma).
Colunas armazenam informações na forma de pares chave-valor.
Em resumo:
Em HBase, uma tabela é ordenada pela chave primária (row key), cuja informação,
que identifica a linha (row key), é um array de bytes. Como consequência, a chave
primária pode conter desde strings até representações binárias de estruturas de dados
serializadas. Todo acesso à tabela é realizado por meio desta chave.
As células (encontro entre linhas e colunas) possuem versões. Por padrão, HBase
associa um valor do tipo timestamp no momento da inserção de um valor à célula.
PRÁTICA E LABORATÓRIO I 24
Column family: aluno Column family: identificação
Regiões
Da mesma forma que HDFS possui namenode e datanodes e YARN possui resource
manager e node managers, HBase Master gerencia os chamados RegionServers,
nós em que estão localizadas as regiões. Então, o HBase Master tem por função
associar/desassociar regiões a regionservers registrados no master, efetuar
recuperações de falhas etc., auxiliado por Zookeeper. Os RegionServers possuem
uma ou mais regiões e endereçam requisições de leitura/escrita de clientes, gerenciam
a divisão de regiões e informam ao HBase Master sobre as novas regiões.
PRÁTICA E LABORATÓRIO I 25
Colocando em prática
PRÁTICA E LABORATÓRIO I 26
Figura 25: Janela de execução de operação em background.
PRÁTICA E LABORATÓRIO I 27
O HBase também pode ser inspecionado acessando-se a URL
http://127.0.0.1:16010, como mostrado abaixo:
Após a exibição de como obter ajuda (help), sair do HBase (exit) e da versão em
execução (version), é exibido o prompt do HBase para execução de comandos:
hbase(main):001:0>
PRÁTICA E LABORATÓRIO I 28
Comandos do Hbase
1) Comandos Gerais
- Comando: help
PRÁTICA E LABORATÓRIO I 29
- Comando: status
ERROR: Can't get master address from Zookeeper; znode data == null
PRÁTICA E LABORATÓRIO I 30
Figura 30: Mensagem de erro na execução de comando do HBase.
hbase(main):003:0> status
- Comando: version
PRÁTICA E LABORATÓRIO I 31
hbase(main):004:0> version
1.1.2.2.5.0.0-1245, r53538b8ab6749cbb6fdc0fe448b89aa82495fb3f, Fri Aug 26
01:32:27 UTC 2016
- Comando: table_help
- Comando: whoami
hbase(main):006:0> whoami
raj_ops (auth:SIMPLE)
groups: raj_ops
- Comando: alter
Descrição: altera uma tabela. Por exemplo, para alterar a tabela cadastro,
inserindo a família de coluna 'historico', faz-se como a seguir:
Por exemplo:
PRÁTICA E LABORATÓRIO I 32
Done.
Done.
- Comando: create
Por exemplo:
- Comando: describe
PRÁTICA E LABORATÓRIO I 33
Descrição: descreve as características da tabela.
Por exemplo:
Done.
cadastro
ATION_SCOPE => '0', VERSIONS => '1', COMPRESSION => 'NONE', MIN_VERSIONS
=> '
0', TTL => 'FOREVER', KEEP_DELETED_CELLS => 'FALSE', BLOCKSIZE => '65536',
IN
PLICATION_SCOPE => '0', COMPRESSION => 'NONE', VERSIONS => '1', TTL =>
'FOREV
PRÁTICA E LABORATÓRIO I 34
ER', MIN_VERSIONS => '0', KEEP_DELETED_CELLS => 'FALSE', BLOCKSIZE =>
'65536'
ONS => '0', TTL => 'FOREVER', KEEP_DELETED_CELLS => 'FALSE', BLOCKSIZE =>
'65
Done.
cadastro
ATION_SCOPE => '0', VERSIONS => '1', COMPRESSION => 'NONE', MIN_VERSIONS
=> '
PRÁTICA E LABORATÓRIO I 35
0', TTL => 'FOREVER', KEEP_DELETED_CELLS => 'FALSE', BLOCKSIZE => '65536',
IN
ONS => '0', TTL => 'FOREVER', KEEP_DELETED_CELLS => 'FALSE', BLOCKSIZE =>
'65
- Comando: disable
- Comando: disabel_all
Por exemplo, para desabilitar todas as tabelas que iniciem por 'cad':
disable_all 'cad*'
- Comando: is_disabled
Descrição: retorna true (se a tabela estiver desabilitada) ou false (caso contrário).
Por exemplo:
- Comando: drop_all
- Comando: enable
- Comando: enable_all
- Comando: is_enabled
Descrição: retorna true (se a tabela estiver habilitada) ou false (caso contrário).
- Comando: exists
Por exemplo:
PRÁTICA E LABORATÓRIO I 37
Table arquivo does not exist
- Comando: list
Descrição: lista todas as tabelas no HBase. Pode ser especificado um filtro.
Por exemplo:
hbase(main):033:0> list
TABLE
ATLAS_ENTITY_AUDIT_EVENTS
SYSTEM.CATALOG
atlas_titan
cadastro
iemployee
TABLE
cadastro
=> ["cadastro"]
PRÁTICA E LABORATÓRIO I 38
3) DML (Data Manipulation Language)
- Comando: count
=> 0
- Comando: delete
Por exemplo:
ROW COLUMN+CELL
ROW COLUMN+CELL
- Comando: deleteall
- Comando: get
Por exemplo:
PRÁTICA E LABORATÓRIO I 40
Outra opção é especificar o timestamp (quantidade de segundos decorridos desde
1/1/1970):
- Comando: put
Por exemplo:
- Comando: scan
Por exemplo:
PRÁTICA E LABORATÓRIO I 41
2 row(s) in 0.3300 seconds
- Comando: truncate
Por exemplo:
- Disabling table...
- Truncating table...
cadastro
PRÁTICA E LABORATÓRIO I 42
MIN_VERSIONS => '0', KEEP_DELETED_CELLS => 'FALSE', BLOCKSIZE => '65536',
IN_
MEMORY => 'false', BLOCKCACHE => 'true'}
{NAME => 'identificacao', DATA_BLOCK_ENCODING => 'NONE', BLOOMFILTER =>
'ROW'
, REPLICATION_SCOPE => '0', COMPRESSION => 'NONE', VERSIONS => '1', TTL
=> 'F
OREVER', MIN_VERSIONS => '0', KEEP_DELETED_CELLS => 'FALSE', BLOCKSIZE
=> '65
536', IN_MEMORY => 'false', BLOCKCACHE => 'true'}
3 row(s) in 0.0460 seconds
ROW COLUMN+CELL
PRÁTICA E LABORATÓRIO I 43
Scala (THE SCALA PROGRAMMING LANGUAGE, 2016; SCALA
DOCUMENTATION, 2016) é uma linguagem funcional, orientada a objetos e de fácil
integração com Java. Essa integração fica evidenciada pelo fato de todas as classes
do pacote java.lang serem importadas por padrão. As demais classes, entretanto,
devem ser importadas de forma explícita.
Como esta linguagem será utilizada pelo Spark, vamos aproveitar a estrutura fornecida
por esse framework para estudar a linguagem Scala. Para tanto, execute o comando
spark-shell a partir do prompt do Linux.
Antes de começar, importe o pacote sys.process de tal forma que possamos executar
comandos de sistema (sempre entre aspas, terminando com um ponto de
exclamação). Por exemplo, para “limpar” a tela com o comando “clear”, faz-se como
abaixo:
scala> import sys.process._
scala> "clear" !
PRÁTICA E LABORATÓRIO I 44
Figura 32: Executando comandos de sistema.
Variáveis
val: uma vez recebido um valor, a variável não pode mais ser alterada.
Como uma variável declarada por val não pode ser alterada, fazer
scala> val a = 1
scala> a = 2
a = 2;
Uma variável pode ter seu valor avaliado dentro de uma string por meio do interpolador
“s”. Por exemplo:
scala> val a = 1
a = 1 e b = 1.0
Neste caso, “$a” foi substituída por “1” e “$b” por “1.0”.
Observe o caractere interpolador “s” antes das aspas que abrem a string (sem espaço
em branco). Caso não haja “s” antes das aspas, a string é impressa tal como está
escrita (sem avaliação das variáveis):
PRÁTICA E LABORATÓRIO I 45
scala> println (tipos)
a = $a e b = $b
Tipos de Dados
- Strings: são sequências de caracteres delimitadas por aspas duplas. Por exemplo,
abaixo a função println “escreve” a string teste:
teste
import java.util.Date
Não é preciso explicitar o tipo da variável, pois Scala tem alta capacidade de inferência.
Por exemplo:
PRÁTICA E LABORATÓRIO I 46
val a = 1 => Scala define a como Int
Operadores aritméticos
+ : adição
- : subtração
* : multiplicação
/ : divisão
% : resto da divisão
7 4
1 3
7/4
7%4
a: Int = 1 a: Int = 3
a: Double = 1.75
PRÁTICA E LABORATÓRIO I 47
Operadores Relacionais
== : igual a
!= : diferente de
Operadores Lógicos
&& : e
|| : ou
! : não
Outros Operadores
= : atribuição.
scala> var x = 1
x: Int = 1
scala> x += 4
Estrutura de controle: if
Exemplo:
scala> val a = 0
Exemplo:
PRÁTICA E LABORATÓRIO I 49
{
println (a * 5)
10
15
20
25
Exemplo:
10
Interpolador
O interpolador “s” avalia uma variável dentro de uma string. Por exemplo:
scala> val a = 1
a = 1 e b = 1.0
PRÁTICA E LABORATÓRIO I 50
Já o interpolador “f” avalia uma variável ou expressão, e pode formata-la conforme o
especificador de formato escolhido (tal como utilizado no comando printf da linguagem
C). Por exemplo, se o valor da variável “b” deve ser impresso com três casas decimais:
scala> val a = 1
a = 1 e b = 1.000
Função
Exemplo:
10
Exemplo:
PRÁTICA E LABORATÓRIO I 51
Uma função que não retorna valor é uma função do tipo Unit (equivalente ao void de
Java). Neste caso, a função é chamada de procedure, e o tipo de retorno da função
pode ser declarado como Unit: def msg (m : String) : Unit ou pode ser omitido
(como no exemplo).
Exemplo:
Alô, você!
Para conter mais de uma sentença, o corpo da função tem que estar delimitado por
chaves.
Exemplo:
var soma = 0
for (x <- 1 to n)
soma += x
return soma
scala> somaTodosNums(10)
res5: Int = 5
PRÁTICA E LABORATÓRIO I 52
Funções de primeira classe e funções de alta ordem
Uma função é dita de primeira classe se ela pode ser armazenada em uma variável,
passada como parâmetro de uma função ou retornada como resultado do
processamento de uma outra função.
Funções que recebem outras funções por parâmetro ou cujo valor de retorno é uma
função são ditas funções de alta ordem.
Exemplo: seja a função anterior “somaTodosNums”, vamos criar uma variável “stn”
que receba essa função e que opere o mesmo valor (10):
scala> stn(10)
res2: Int = 55
Outro exemplo: criar a função “opera”, que recebe por parâmetro a função “fc” (recebe
um inteiro e retorna um inteiro), e um valor do tipo inteiro, “x”. A função “fc” deve
receber – por parâmetro – o valor “x”.
scala> opera(dobro,2)
res5: Int = 4
scala> opera(triplo,2)
res6: Int = 6
Funções Anônimas
Funções anônimas ou funções lambda são funções que não possuem nome.
Declara-se uma função anônima como:
scala> res8(4)
res9: Int = 9
Explicitando o nome da variável que irá armazenar a função anônima, fica como:
PRÁTICA E LABORATÓRIO I 54
res9: Int = 15
Também se pode passar uma função anônima como parâmetro de uma função,
decidindo-se, futuramente, a sua funcionalidade.
Exemplo:
scala> opera(x=>x+1,2)
res10: Int = 3
scala> opera(x=>x*2,2)
res11: Int = 4
Neste caso:
2) Dada uma função qualquer que receba uma função anônima e um valor inteiro
como parâmetros, o valor de retorno da função é igual ao valor de retorno da função
anônima sobre o valor passado como parâmetro: f(n)
O caractere underscore ( _ ) pode ser utilizado no lugar de "x => x". Assim, a função
opera pode ser escrita de duas formas:
scala> opera(_+1,2)
res0: Int = 3
Classe
Classes podem ser declaradas por meio da palavra-chave “class”. Podem ser definidos
parâmetros, que devem ser passados no momento em que a classe está sendo
instanciada, em substituição ao método construtor.
Os métodos são funções e, por isso, iniciam sua declaração pela palavra-chave “def”.
Métodos e campos possuem, por padrão, visibilidade public, mas podem ser protected
ou private.
private var d = 0
if (b != 0) {
d=a/b
PRÁTICA E LABORATÓRIO I 56
true
false
scala> num.soma()
res8: Int = 12
scala> num.subtracao()
res9: Int = 8
scala> num.multiplicacao()
res10: Int = 20
scala> num.divisao()
scala> num.getDivisao()
res12: Int = 5
num.d
PRÁTICA E LABORATÓRIO I 57
Case class
Case classes são classes que possuem os métodos equals, hashCode e toString
predefinidos, além de não precisarem da palavra-chave “new” para serem
instanciadas.
Exemplo: crie as classes abaixo no Scala.
scala> SomaCC(1,2).soma()
res8: Int = 3
scala> Soma(1,2).soma()
Soma(1,2).soma()
PRÁTICA E LABORATÓRIO I 58
No caso da classe Soma, deve-se utilizar a palavra-chave “new” para instanciar o
objeto e, por meio dele, executar o método “soma()”.
s: Soma = $iwC$$iwC$Soma@278a64c0
scala> s.soma()
res2: Int = 3
Array
Arrays são coleções de dados de mesmo tipo. Variáveis do tipo Array são variáveis
indexadas, com o primeiro índice igual a 0 (zero). Podem conter elementos duplicados
e são modificáveis.
PRÁTICA E LABORATÓRIO I 59
scala> var arr = Array("Pos BigData" , "Prática e Laboratório I")
scala> println(arr(0))
Pos BigData
Tuplas
Exemplo:
PRÁTICA E LABORATÓRIO I 60
Os elementos da tupla podem ser acessados especificando-se o nome da variável
seguido do caractere ponto, do caractere sublinhado (underscore) e da posição em
que se encontra o elemento desejado (com o primeiro índice igual a 1).
Listas
Listas são similares a arrays, com a diferença que listas são imutáveis, ou seja, não
podem sofrer alterações após criadas. Pode-se criar novas listas a partir de listas
existentes, mas não alterar uma lista já definida.
Exemplo:
Uma lista também pode ser definida em termos da função “cons” (::). Lembre-se que
Scala é funcional, e tudo em Scala é uma função. Em termos práticos:
lista = elemento1 :: lista2
E assim sucessivamente, até que seja encontrado o elemento “Nil”, o que finaliza a
lista:
PRÁTICA E LABORATÓRIO I 61
Agrupando-se tudo, tem-se a seguinte sintaxe:
Função: ::
Função: apply
PRÁTICA E LABORATÓRIO I 62
Retorna o elemento posicionado no índice especificado.
novaLista: Int = 2
Função: distinct
Função: drop
Função: dropRight
Função: dropWhile
PRÁTICA E LABORATÓRIO I 63
Função: exists
Função: filter
Função: flatten
Outro exemplo:
PRÁTICA E LABORATÓRIO I 64
Função: forall
Função: head
primeiroElemento: Int = 1
Função: indexOf
posicao: Int = 1
posicao: Int = -1
Função: init
PRÁTICA E LABORATÓRIO I 65
scala> val novaLista = lista.init
Função: intersect
Função: isEmpty
Função: last
ultimoElemento: Int = 3
Função: lastIndexOf
PRÁTICA E LABORATÓRIO I 66
scala> val posição = lista.lastIndexOf(2,3)
posicao: Int = 1
posicao: Int = -1
Função: map
Retorna uma nova lista após a aplicação da função especificada em todos os elementos
da lista original.
scala> nomes.map(_.toUpperCase)
Observação:
Função: flatMap
scala> nomes.flatMap(_.toUpperCase)
Função: max
PRÁTICA E LABORATÓRIO I 67
maiorElemento: Int = 3
Função: min
menorElemento: Int = 1
Função: mkString
Retorna uma String formada pelos elementos da lista, separados pelo elemento
separador especificado. Pode ser especificada uma sequência de escape como
separador (por exemplo, a tabulação: ‘\t’).
str: String = 1 2 3 3
Função: reverse
Função: sorted
PRÁTICA E LABORATÓRIO I 68
scala> val listaNaoOrdenada = List(1,4,8,2,6,0,7,4,9)
Função: sum
soma: Int = 9
Função: tail
Função: take
Função: takeRight
Função: toArray
Função: toString
PRÁTICA E LABORATÓRIO I 70
Apache
Spark
Um DAG é um grafo acíclico (não há ciclos, ou seja, loops). Isto significa dizer que,
partindo-se de um nó A para um nó B, não se consegue retornar ao nó A. Ele é
PRÁTICA E LABORATÓRIO I 71
direcionado porque o percurso realizado no grafo possui direção, ou seja, partir do nó
A para o nó B não produz o mesmo resultado que partir do nó B em direção ao nó A.
Dessa forma, os nós de um sistema que executa seus jobs utilizando DAG podem rodar
de forma paralela.
RDD
Toda a aplicação Spark consiste de um driver que executa a função “main” e as várias
operações paralelas no cluster. A principal abstração fornecida por Spark foi uma base
de dados distribuída tolerante a falhas, RDD (Resilient Distributed Dataset), a qual é
uma coleção de elementos particionados, armazenados nos nós executores, os quais
operam sobre esses dados de forma paralela.
PRÁTICA E LABORATÓRIO I 72
Executor 1 Executor 2
Executor 3 Executor 4
Suponha haver três RDDs, cujas partições sejam representadas pelas formas:
quadrado: , círculo: e triângulo: .
Então, pelo o esquema abaixo pode-se ver como seria uma possível distribuição dos
dados de cada um dos RDDs pelos nós executores do cluster:
PRÁTICA E LABORATÓRIO I 73
RDDs são criados a partir de dados presentes no HDFS (ou em outra fornte de
armazenamento suportado por Hadoop, tal como Amazon S3, HBase etc.). Também
podem ser criados a partir de coleções (Collections) de Scala.
Os RDDs são imutáveis, então, uma vez criados, não se pode alterá-los, e sim criar
novos RDDs a partir de RDDs existentes (operação conhecida por transformação).
Os RDDs são processados pelo Spark, que gera novos RDDs a partir da transformação
dos dados por meio de funções.
f1(x) f2(x)
RDDa RDDb RDDc
- Transformação: não retornam um único valor, mas um novo RDD. Nada é avaliado
quando a função de transformação é invocada: apenas um novo RDD é retornado a
partir de um RDD antigo. São funções de transformação, por exemplo: map, filter,
flatMap, groupByKey e reduceByKey.
- Ação: esta operação avalia e retorna um novo valor. Quando uma função de ação é
invocada, todas as transformações são processadas, e um valor é retornado. São
funções de ação, por exemplo: collect, count, first, foreach e reduce.
As operações sobre os RDDs vão, então, formando um grafo (DAG) de tarefas a serem
executadas em blocos distribuídos de memória. A execução das ações é realizada de
forma tardia (lazy), com os resultados intermediários sendo mantidos em memória.
Caso haja falha na execução de uma dada tarefa, basta que Spark reprocesse o grafo
para que a tarefa seja cumprida, o que garante resiliência ao RDD. O acesso ao disco
é realizado em último caso, e somente se a memória disponível no sistema não mais
comportar a quantidade de dados a serem armazenados. O mecanismo de realizar
PRÁTICA E LABORATÓRIO I 74
transformações e só processá-las invocando-se uma função de ação, explica a
diferença de tempo de processamento entre Spark e Hadoop, pois isso evita a alta
latência requerida por Hadoop com suas operações de disco.
Criando RDDs
Observe que a execução de “val data = Array(1, 2, 3, 4, 5)” cria um array de Int,
contendo 5 elementos inteiros.
Outra forma de criar RDDs, é a partir de um arquivo texto, carregado via método
textFile de SparkContext. Este método recebe como parâmetro uma URI contendo o
nome do arquivo (podendo incluir, também, o local: hdfs://, s3n:// etc.), e retorna
o conteúdo do arquivo como uma coleção de linhas (String). Por exemplo, crie o
arquivo “enderecos.txt” com o conteúdo abaixo e o transfira para o HDFS:
PRÁTICA E LABORATÓRIO I 75
001;Rua A;97;Bairro1
002;Rua X;12;Bairro2
003;Rua T;147;Bairro1
Para criar o RDD “enders”, invoque o método textFile de SparkContext (via objeto sc),
passando a URI abaixo como parâmetro:
hdfs://sandbox.hortonworks.com:8020/user/raj_ops/enderecos.txt
Juntando tudo:
Agora, crie o RDD caractLinha, que conterá a quantidade de caracteres, linha a linha,
do RDD enders:
scala> enders.collect
Ao passo que o RDD caractLinha é um novo RDD, obtido pela transformação do RDD
enders:
PRÁTICA E LABORATÓRIO I 76
scala> caractLinha.collect
Para saber quantas linhas possui o RDD enders, aplica-se a ação count:
scala> enders.count
res34: Long = 3
totalCaracteres: Int = 61
Como dito anteriormente, com Spark pode-se encadear uma sequência de estágios
(com o caractere ponto como elemento de separação das chamadas de função). Então,
os passos anteriores para obter a quantidade de caracteres do arquivo “enderecos.txt”,
poderiam ser executados na sequência mostrada abaixo:
totalCaracteres: Int = 61
Transformações
PRÁTICA E LABORATÓRIO I 77
1) map(func)
Retorna um novo RDD formado pelos retornos da função func aplicada a cada
elemento do RDD original.
resulta em:
Array[(String, Int)] = Array((RJ,16), (SP,4), (ES,8), (MG,4))
2) filter(func)
Retorna um novo RDD formado pelos elementos do RDD origem que satisfazem a
função func (elementos que fazem a func retornar true).
resulta em:
res0: Array[(String, Double)] = Array((Margarina,3.99), (Pão,0.3))
3) flatMap(func)
Similar à map, mas cada item pode ser mapeado para 0 ou mais itens de saída. A
função func deve retornar uma sequência, em vez de um único item. Por exemplo
resulta em:
res129: Array[String] = Array(RJ, SP, SP, MG, RJ, ES, RJ)
PRÁTICA E LABORATÓRIO I 78
4) union(otherDataset)
Retorna um novo RDD contendo os elementos de ambos os RDDs. Por exemplo, dados
os RDDs:
Executar
scala> est1.union(est2).collect
resulta em:
res2: Array[(String, Int)] = Array((BA,1), (SE,2), (GO,3), (DF,4))
5) intersection(otherDataset)
Retorna um novo RDD contendo somente os elementos presentes em ambos os RDDs.
Por exemplo, dados os RDDs:
Executar
scala> est1.intersection(est2).collect
resulta em:
res1: Array[(String, Int)] = Array((SC,2))
6) distinct([numTasks])
Retorna um novo RDD contendo os elementos do RDD origem, sem repetição. Por
exemplo
PRÁTICA E LABORATÓRIO I 79
resulta em:
res5: Array[(String, Int)] = Array((PI,1), (MT,2))
7) groupByKey([numTasks])
Atua sobre um RDD contendo pares (K,V). Retorna um RDD de pares (K,Iterable<V>).
Por exemplo
resulta em:
res37: Array[(String, Iterable[Int])] = Array((RJ,CompactBuffer(2, 6, 8)),
(SP,CompactBuffer(2, 2)), (ES,CompactBuffer(8)), (MG,CompactBuffer(4)))
8) reduceByKey(func, [numTasks])
Atua sobre um RDD contendo pares (K,V). Retorna um RDD de pares (K,V) onde os
valores para cada chave são agregados utilizando a função de redução, a qual pode
ser do tipo (V,V) => V. Por exemplo:
9) sortByKey([ascending], [numTasks])
PRÁTICA E LABORATÓRIO I 80
Atua sobre um RDD de pares (K,V) onde K implementa Ordered. Retorna um RDD de
pares (K,V) ordenado pela chave. Se o parâmetro descending não for especificado, ou
se for especificado true, a ordenação é crescente:
resulta em:
res70: Array[(String, Int)] = Array((AM,8), (PB,1), (PB,6), (PE,2), (RJ,4), (RS,2),
(TO,8))
resulta em:
res71: Array[(String, Int)] = Array((TO,8), (RS,2), (RJ,4), (PE,2), (PB,1), (PB,6),
(AM,8))
Ações
Funções de ação avaliam e retornam um novo valor. Quando uma função de ação é
invocada, todas as transformações são processadas e um valor é retornado.
1) reduce(func)
Agrega os elementos do RDD por meio de func (a qual utiliza dois argumentos e
retorna somente um). A função func deve ser comutativa e associativa, para que possa
ser computada em paralelo.
res0: Int = 15
PRÁTICA E LABORATÓRIO I 81
scala> sc.parallelize(List(1, 2, 3, 4, 5)).reduce( _ + _ )
res1: Int = 15
2) collect()
Retorna todos os elementos do RDD como um array ao Driver. Esta função é muito
útil após a execução de filter ou outra operação que retorne um pequeno subconjunto
de dados.
3) count()
res3: Long = 5
4) first()
res4: String = A
5) take(n)
6) takeOrdered(n, [ordering])
7) saveAsTextFile(path)
Salva os elementos do RDD como um arquivo texto (ou conjunto de arquivos texto)
no HDFS ou outro sistema de arquivos suportado por Hadoop. Spark invoca a função
toString para cada elemento, de forma a realizar sua conversão em uma linha de texto
a ser armazenada no arquivo.
Found 4 items
drwx------ - raj_ops hdfs 0 2016-11-07 01:20 .Trash
-rw-r--r-- 1 raj_ops hdfs 64 2016-11-06 04:04 enderecos.txt
-rw-r--r-- 1 raj_ops hdfs 64 2016-11-06 14:38 enderecos1.txt
drwxr-xr-x - raj_ops hdfs 0 2016-11-07 01:21 outputDir
res13: Int = 0
Found 5 items
PRÁTICA E LABORATÓRIO I 83
-rw-r--r-- 1 raj_ops hdfs 0 2016-11-07 01:21 outputDir/_SUCCESS
res14: Int = 0
8) saveAsSequenceFile(path)
São mais compactos do que arquivos texto (textFile) e podem ser utilizados como
contêineres de vários arquivos pequenos, o que é vantajoso, uma vez que Hadoop não
lida bem com arquivos pequenos. Esta função está disponível atualmente para Scala
e Java.
scala> sc.parallelize(List((1,"A"),(2,"B"),(3,"C"),(4,"D"),(5,"E"))).
saveAsSequenceFile("hdfs://sandbox.hortonworks.com:8020/
user/raj_ops/seq")
PRÁTICA E LABORATÓRIO I 84
9) saveAsObjectFile(path)
scala> sc.parallelize(List("A","B","C","D","E")).saveAsObjectFile
("outputObjDir/*")
hdfs://sandbox.hortonworks.com:8020/user/raj_ops/outputObjDir/*
scala> of.count
res42: Long = 5
11) foreach(func)
scala> of.foreach(println)
10) countByKey()
PRÁTICA E LABORATÓRIO I 85
scala> sc.parallelize(List(("HIGIENE",10.00),("LIMPEZA",3.50),
("LIMPEZA",5.50),("HORTIFRUTI",2.99),("HIGIENE",7.50))).
countByKey
PRÁTICA E LABORATÓRIO I 86
Apache Spark é uma ferramenta poderosa para analisar e processar grandes massas
de dados em ambiente big data. Conta com a excelente e intuitiva linguagem Scala na
execução de funções de transformação e ação. Como foi mostrado anteriormente, o
Spark possui duas unidades básicas: o Driver e os Executores (dados a serem
transformados).
Dataset
A nova interface de Spark SQL, Dataset, disponível em Scala e Java, é uma coleção de
dados distribuída, e provê os benefícios do RDD: uso de funções lambda e a
consistência de dados e memória (strong typing), com os benefícios do motor de
execução otimizado de Spark SQL.
Ao contrário da API simples dos RDDs de Spark, Dataset conta com mais informação
sobre os dados e sobre o processamento sendo executado sobre eles. Internamente,
PRÁTICA E LABORATÓRIO I 87
Spark SQL utiliza essa informação para realizar otimizações adicionais sobre o
processamento.
E, uma vez construído, um Dataset pode ser manipulado por meio de funções de
transformação e ação:
scala> ds.collect
scala> ds.map(_._2).collect
DataFrame
PRÁTICA E LABORATÓRIO I 88
Essas operações são conhecidas como “transformações não tipadas”, em contraste
com as “transformações tipadas”, que utilizam os Datasets fortemente tipados de
Scala/Java.
Obs.: Para realizar conversões implícitas, tal como converter objetos comuns (em
Scala) em DataFrames, deve-se importar sqlContext.implicits. Por exemplo, na
execução abaixo, o método toDF falha se implicits não estiver importado:
import sqlContext.implicits._
P.S.: A Sandbox é otimizada para conter as instalações necessárias para que o código
seja executado (por exemplo, sqlContext é instanciado na execução de spark-
shell). Num ambiente em que sqlContext tenha que ser instanciada pelo programador.
import sqlContext.implicits._
import sqlContext.implicits._
PRÁTICA E LABORATÓRIO I 89
rows: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[15] at
parallelize at <console>:39
+-----+---+
| letra|cod|
+-----+---+
| A | 65|
| B | 66|
+-----+---+
scala> df.printSchema
root
|-- letra: string (nullable = true)
scala> df.select(df("letra")).show
+-----+
| letra|
+-----+
| A |
| B |
+-----+
+-----+---+
| letra|cod|
+-----+---+
| A | 65|
+-----+---+
Obs.2: Pode-se representar os nomes das colunas utilizando uma das três formas
abaixo:
PRÁTICA E LABORATÓRIO I 90
scala> df.filter( df("cod") === 65).show
+-----+---+
| letra|cod|
+-----+---+
| A | 65|
+-----+---+
+-----+
| letra|
+-----+
| B |
+-----+
Dica: Para listar as funções disponíveis de Spark SQL (SPARK SQL FUNCTIONS,
2016), digite uma letra no prompt do Scala e dê um toque na tecla TAB. Por
exemplo:
scala> h
Para saber sobre a função, digite o nome da função e dê dois toques na tecla TAB:
scala> hex
PRÁTICA E LABORATÓRIO I 91
Utilizando a função hex para exibir os códigos dos caracteres na base hexadecimal:
scala> df.select(hex(df("cod"))).show
+----------+
| hex(cod) |
+----------+
| 41 |
| 42 |
+----------+
Para utilizar a função MD5, converte-se o valor da coluna para binário e, em seguida,
gera-se o código MD5 sobre o retorno da função “bin”:
scala> m
markPartiallyConstructed max
md5 mean
min minute
monotonicallyIncreasingId monotonically_increasing_id
month months_between
mozilla mx4j
math management
scala> md5
scala> df.select(md5(bin(df("cod")))).show
+--------------------------+
PRÁTICA E LABORATÓRIO I 92
| md5(bin(cod)) |
+--------------------------+
|59e711d152de7bec7... |
|3e267ff3c8b6621e5... |
+--------------------------+
rdd: org.apache.spark.rdd.RDD[org.apache.spark.sql.Row] =
ParallelCollectionRDD[1] at parallelize at <console>:33
scala> rdd.collect.foreach(println)
[A,65]
[B,66]
schema: org.apache.spark.sql.types.StructType =
StructType(StructField(letra,StringType,true), StructField(cod,IntegerType,true))
scala> df.registerTempTable("ascii")
scala> df.printSchema
root
|-- letra: string (nullable = true)
|-- cod: integer (nullable = true)
scala> df.show
PRÁTICA E LABORATÓRIO I 93
+-----+---+
|letra |cod|
+-----+---+
|A | 65|
|B | 66|
+-----+---+
scala> val result = sql("SELECT * FROM ascii")
scala> result.show
+-----+---+
|letra |cod|
+-----+---+
|A | 65|
|B | 66|
+-----+---+
16/11/13 14:33:22 INFO ParseDriver: Parsing command: SELECT * FROM ascii order
by cod desc
16/11/13 14:33:22 INFO ParseDriver: Parse Completed
result1: org.apache.spark.sql.DataFrame = [letra: string, cod: int]
scala> result1.show
+-----+---+
|letra |cod|
+-----+---+
|A | 65|
|B | 66|
+-----+---+
PRÁTICA E LABORATÓRIO I 94
Apache
Zeppelin
Para iniciar os trabalhos com Apache Zeppelin, acesse a URL fornecida na execução
da Sandbox (http://127.0.0.1:8888). Na janela inicial, clique em Quick Links (figura
3). Coloque, agora, o cursor do mouse sobre o link Zeppelin na janela ADVANCED
HDP QUICK LINKS.
PRÁTICA E LABORATÓRIO I 95
O link mostra o acesso ao Apache Zeppelin (http://127.0.0.1:9995):
PRÁTICA E LABORATÓRIO I 96
A partir da janela inicial, pode-se:
PRÁTICA E LABORATÓRIO I 97
Importando um notebook
Como indica a janela Import new note, a importação do notebook pode ser
local (Choose a JSON here) ou remota (Add from URL):
PRÁTICA E LABORATÓRIO I 98
Após selecionar o arquivo que contém o notebook salvo, clicar em Open faz com que
o nome do notebook importado seja exibido no campo Import AS, no caso, o
notebook de nome Pbg, salvo no arquivo note.json.
Para utilizar um notebook, basta dar um clique no seu nome. Por exemplo, carregue
o notebook Hello World Tutorial:
O notebook é carregado:
Clique no link Create new note (figura 39). Surgirá a janela correspondente. Digite no
campo Note Name o nome do notebook (Pbg). Para finalizar, clique no botão
Create Note.
- Seção de resultados.
Veja que a seção de resultados não possui mais informação alguma. Isto é útil quando
se comete algum erro de digitação e a exibição do resultado incorreto atrapalha de
alguma forma, salientando que uma nova execução limpa a seção de resultados.
a) Fazer as importações:
%sql
Colocar o curso do mouse sobre uma determinada área mostra a quantidade daquela
área:
%sql
Analisando o gráfico de pizza, observa-se que a informação foi alterada de 4.00 para
85.20, resultante do custo total por seção:
Seq("açougue","alcatra",35.00,10)
DEITEL, H. M.; Deitel, P. J. Java Como programar. 8. ed. São Paulo: Pearson
Prentice Hall, 2010.
WHITE, T. Hadoop - The Definitive Guide. 4rd. ed. Sebastopol: O´Reilly, 2015.