Você está na página 1de 119

Prática e Laboratório I

PRÁTICA E LABORATÓRIO I 1
Prática e Laboratório I

Big data

Um cluster Hadoop é uma plataforma de software que processa – de forma eficiente


– um grande volume de informação utilizando um grupo (cluster) de computadores
("nós" do cluster) trabalhando em paralelo com os dados sendo distribuídos pelos nós
de forma replicada. Assim, caso ocorra uma falha em um nó, o dado replicado que
está em outro nó será copiado para o nó em que a falha ocorreu.

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™.

Os dados a serem processados em ambiente Hadoop devem estar contidos num


sistema de arquivos distribuído, como é o caso do HDFS, que gerencia o
armazenamento de arquivos em máquinas fisicamente separadas com tolerância às
falhas dos nós com nenhuma perda de dados. Tais arquivos podem ser manipulados
por meio de comandos shell HDFS.

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:

- Monitorar a saúde e o status do cluster;


- Obter métricas de funcionamento do sistema de cada host, enviando-as
para o coletor de métricas;
- Ser alertado pelo framework de alerta de Ambari quando a situação inspira
atenção, como, por exemplo, quando um nó “cai”, há pouco espaço
remanescente em disco etc.

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.

Como boa prática, as execuções serão realizadas em ambiente de teste, utilizando a


máquina virtual Hortonworks Sandbox (versão 2.5) no ambiente de virtualização
Oracle VM VirtualBox (versão 5.1.6). Uma vez importada, tem-se como mostrado
na figura 1.

Figura 1: VirtualBox com a máquina virtual Hortonworks.

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.

Figura 2: Informação de IP e porta

Digite no campo URL de um navegador Web o IP da porta, informado


(http://127.0.0.1:8888); Em seguida, clique em QUICK LINKS.

Figura 3: Execução do ambiente gráfico da Hortonworks Sandbox.


PRÁTICA E LABORATÓRIO I 4
É exibida a janela ADVANCED HDP QUICK LINKS com diversos links:

Figura 4: Janela ADVANCED HDP QUICK LINKS.

Posicionando o cursor sobre o link AMBARI, obtém-se o login/senha do usuário


raj_ops (por meio do qual também serão executados os jobs):

Figura 5: Obtendo login/senha do usuário não root.

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.

Figura 6: Obtendo o login/senha do usuário root.

O usuário raj_ops (senha raj_ops) será utilizado no Bitvise para acessar o sistema
remoto.

Figura 7: Acessando o sistema remoto com o Bitvise.

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.

Em seguida, são exibidas as janelas de console e gerenciamento de arquivos.

Figura 9: Terminal console e janela SFTP do Bitvise.

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

1) Na janela de console, efetue login como superusuário (su):

[raj_ops@sandbox ~]$ su

2) Execute o comando para alterar a senha do usuário admin do Ambari:

[raj_ops@sandbox ~]$ ambari-admin-password-reset

Digite a nova senha (Please set the password for admin:) e confirme (Please
retype the password for admin:).

Ao pressionar ENTER, o serviço ambari-server é parado e a senha alterada. Ao final,


este serviço será iniciado.

Figura 10: Alterando a senha do administrado do Ambari.

PRÁTICA E LABORATÓRIO I 8
Ainda na janela de console, configure a hora local para Brazil/East:

[root@sandbox etc]# ls /usr/share/zoneinfo/

Africa Chile GB Indian MST PRC UTC

America CST6CDT GB-Eire Iran MST7MDT PST8PDT WET

Antarctica Cuba GMT iso3166.tab Navajo right W-SU

Arctic EET GMT0 Israel NZ ROC zone.tab

Asia Egypt GMT-0 Jamaica NZ-CHAT ROK Zulu

Atlantic Eire GMT+0 Japan Pacific Singapore

Australia EST Greenwich Kwajalein Poland Turkey

Brazil EST5EDT Hongkong Libya Portugal UCT

Canada Etc HST MET posix Universal

CET Europe Iceland Mexico posixrules US

[root@sandbox etc]# ls /usr/share/zoneinfo/Brazil/

Acre DeNoronha East West

[root@sandbox etc]# mv /etc/localtime /etc/localtime.bak

[root@sandbox etc]# ln -s /usr/share/zoneinfo/Brazil/East /etc/localtime

[root@sandbox etc]# ls -l loc*

lrwxrwxrwx 1 root root 31 Nov 27 11:57 localtime -> /usr/share/zoneinfo/Brazil/East

-rw-r--r-- 1 root root 118 Jun 2 10:44 localtime.bak

Verifique se a hora e a time zone estão corretas:

[root@sandbox etc]# date


PRÁTICA E LABORATÓRIO I 9
Sun Nov 27 12:00:58 BRST 2016

3) Acesse em um navegador web a URL: http://127.0.0.1:8080 e efetue login


como usuário admin:

Figura 11: Acessando o Ambari como usuário administrador.

Verifique se a hora local está configurada para Brazil/East:

Figura 12: Acessando a seção de configurações do Ambari.

PRÁTICA E LABORATÓRIO I 10
Altere a timezone como feito no Linux (Brazil/East):

Figura 13: Verificando a timezone configurada no Ambari.

Figura 14: Alterando a timezone para 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 infraestrutura de Pig consiste em um compilador que produz sequências


de programas MapReduce, os quais são executados em ambiente paralelo, tal qual
Hadoop. (HADOOP, 2016).

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

Figura 16: ETL com Pig.

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

Figura 17: Diminuição da base a analisar com Pig.

PRÁTICA E LABORATÓRIO I 13
A análise de dados com Pig envolve três etapas:

1. Carregar os dados para o HDFS por meio da operação de LOAD;

2. Executar transformações sobre os dados carregados (FILTER, JOIN e GROUP)


por meio de tarefas Map e Reduce;

3. Visualizar os dados na tela (DUMP) ou salvá-los (STORE) em um arquivo no


HDFS.

Iniciando o Shell Interativo Grunt

Para executar códigos Pig Latin por meio do ambiente interativo Grunt do Pig, a
partir de um terminal, digite pig e pressione ENTER:

Figura 18: Executando Pig.

PRÁTICA E LABORATÓRIO I 14
O Grunt é carregado:

Figura 19: Execução do ambiente interativo de Grunt.

Definições

Em Pig, são definidos os seguintes elementos na organização dos dados:

- Átomo (Atom): valor de um campo;

- Campo (Field): contém um determinado valor;

- Tupla (Tuple): conjunto de campos (equivale a um registro de uma tabela em um


bancos de dados);

- Bag: coleção de tuplas (equivale a uma tabela em um banco de dados);

- Mapa (Map): conjunto de pares chave-valor.


PRÁTICA E LABORATÓRIO I 15
Como exemplo, seja o arquivo notas.txt com o seguinte conteúdo:

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 primeira posição (a que contém os nomes dos alunos) é um campo (field).

A informação: José Sistemas de Informação 8.0 9.0 F

É uma tupla (tuple).

A coleção de tuplas

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

é uma Bag.

Executando Código Pig Latin

1) Crie o arquivo notas.txt (vi notas.txt) com o seguinte conteúdo:

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.

[raj_ops@sandbox ~]$ hdfs dfs -put notas.txt.

3) Carregue o arquivo notas.txt no Grunt:

grunt> alunos = LOAD 'notas.txt' USING PigStorage(',');

Obs.: PigStorage(',') especifica o caractere de delimitação: vírgula.

4) Visualizar o arquivo carregado no Grunt:

grunt> DUMP alunos;

Figura 20: Executando código Pig Latin no Grunt.

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.

Para saber quem faltou à terceira prova, faz-se como abaixo:

grunt> faltosos = FILTER alunos BY $4 == 'F';


grunt> DUMP faltosos;

Se desejado, pode-se nomear os campos, visando facilitar a identificação dos mesmos.


O exemplo anterior poderia ser reescrito como:

grunt> alunos = LOAD 'notas.txt' USING PigStorage(',') AS


(nome,curso,p1,p2,p3);
grunt> faltosos = FILTER alunos BY p3 == 'F';
grunt> DUMP faltosos;

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)

Figura 22: Resultado da execução de DUMP faltosos.

Em vez de exibir os resultados no terminal, pode-se salvar os resultados no HDFS por


intermédio do comando STORE. Exemplo abaixo:

grunt> alunos = LOAD 'notas.txt' USING PigStorage(',') AS


(nome,curso,p1,p2,p3);

grunt> faltosos = FILTER alunos BY p3 == 'F';

grunt> STORE faltosos INTO 'faltasP3’ USING PigStorage('\t');

O resultado contendo os faltosos à terceira prova (P3) serão armazenados no arquivo


part-r-00000, no diretório faltasP3, com os campos sendo separados pelo caractere
tabulação: '\t'.

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).

Pode-se executar comandos em HiveQL por meio do Hive shell, terminando-se o


comando com um caractere ponto e vírgula.

HiveQL é um subconjunto do SQL-92 muito similar ao SQL padrão, porém com


diferenças, dentre outras, oriundas da limitação do HDFS em alterar dados lá
armazenados. HiveQL não suporta operações de alteração (update) ou remoção
(delete).

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';

B. Carregar os dados do arquivo no HDFS para a tabela Hive:


LOAD DATA LOCAL INPATH './dados.txt' OVERWRITE INTO TABLE
teste;

C. Executar cláusulas HQL (Sempre terminando com “;”).


SELECT * FROM teste;

TIPOS DE DADOS (LANGUAGE MANUAL TYPES - APACHE HIVE, 2016)

TINYINT: -128 a 127


SMALLINT: -32768 a 32767
INT/INTEGER: -2.147.483.648 a 2.147.483.647
BIGINT: -9,223,372,036,854,775,808 a
9,223,372,036,854,775,807 (+/- 9.2x1018)

FLOAT: +/- 1.4E-45 a 3.4E+38


DOUBLE: +/- 4.9E-324 a 1.797E308

DECIMAL: precisão de 38 dígitos

STRING: sequência de caracteres delimitados por aspas simples ou


duplas
VARCHAR: sequência de, no máximo, 65.355 caracteres
CHAR: sequência de, no máximo, 255 caracteres

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).

O HBase foi desenvolvido em 2006 e se tornou um projeto Apache em 2010.

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:

 Tabela: coleção de linhas;


 Linha: coleção de famílias;
 Família: coleção de colunas;
 Coluna: coleção de pares (chave-valor).
PRÁTICA E LABORATÓRIO I 23
A tabela abaixo exemplifica o modelo de dados do HBase, em que cada linha possui
duas famílias: aluno e professor.

Column family: aluno Column family: professor

Aluno: matrícula Aluno: nome Professor: nome Professor: matrícula

1 19375-0 Luiz Silva José Oliveira 900185-9

2 24561-2 Ana Lima Tiago Abreu 900173-2

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.

Como o modelo de dados de HBase, é orientado a famílias de colunas, ou seja,


todos os membros de uma mesma família seguem o mesmo padrão de acesso, se
houver a necessidade de se armazenar uma informação muito grande em relação aos
demais membros da tabela. Por exemplo, imagens, que possuem muitos megabytes,
se comparadas com textos (que, em geral, possuem alguns kilobytes), podem ser
armazenadas em uma família diferente das demais informações, o que torna o acesso
mais rápido.

PRÁTICA E LABORATÓRIO I 24
Column family: aluno Column family: identificação

Aluno: Aluno: Identificação:


matrícula nome imagem

1 19375-0 Luiz Silva 19375-0.png

2 24561-2 Ana Lima 24561-2.png

Regiões

Uma tabela é particionada horizontalmente pelo HBase em regiões, sendo desta


forma que o HBase distribui os dados. Cada região possui um subconjunto de linhas
da tabela. Uma região é identificada pela tabela a qual pertence. Inicialmente, uma
tabela-região compreende uma região simples, porém, conforme a tabela cresce, a
região é dividida em duas outras regiões com aproximadamente o mesmo tamanho.

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

Verifique no Ambari se o HBase está em execução. Se o serviço estiver parado


(Stopped), será preciso iniciá-lo (Start).

Figura 23: Iniciando o HBase no Ambari.

Confirme a iniciação do serviço:

Figura 24: Confirmando a operação de iniciação do HBase.

PRÁTICA E LABORATÓRIO I 26
Figura 25: Janela de execução de operação em background.

Pronto! O serviço está em execução (Started).

Figura 26: HBase em execução.

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:

Figura 27: Inspecionando o HBase via navegador.

Agora, numa janela do terminal, conecte-se a uma instância em execução do HBase


por meio do comando “hbase shell”:

[raj_ops@sandbox ~]$ hbase shell


HBase Shell; enter 'help<RETURN>' for list of supported commands.
Type "exit<RETURN>" to leave the HBase Shell
Version 1.1.2.2.5.0.0-1245, r53538b8ab6749cbb6fdc0fe448b89aa82495fb3f, Fri, Aug
26 01:32:27 UTC 2016

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

Descrição: fornece vários comandos do HBase. Alguns desses comandos são


listados abaixo:

Figura 28: Exibição do help do HBase.

PRÁTICA E LABORATÓRIO I 29
- Comando: status

Descrição: fornece informações sobre o status do cluster.

Figura 29: Exibindo opções do comando status do HBase.

Obs.: caso seja exibida a mensagem de erro abaixo, vá ao Ambari e verifique se o


HBase Master (e/ou o RegionServer) está parado:

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.

Caso esteja, proceda a execução do HBase (ou do RegionServer), como já explicado


anteriormente.

Se o servidor estiver em execução, o comando status retorna o seguinte:

hbase(main):003:0> status

1 active master, 0 backup masters, 1 servers, 0 dead, 9.0000 average load

- Comando: version

Descrição: exibe a versão do HBase.

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

Descrição: exibe diversas informações sobre comandos referentes a tabelas.

- Comando: whoami

Descrição: exibe o nome do usuário.

hbase(main):006:0> whoami
raj_ops (auth:SIMPLE)
groups: raj_ops

2) DDL (Data Definiton Language)

- 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:

alter 'cadastro' , NAME => 'historico'

Para excluir a família de coluna 'historico':

alter 'cadastro' , NAME => 'historico' , METHOD => 'delete'

Por exemplo:

hbase(main):003:0> alter 'cadastro' , 'historico'

Updating all regions with the new schema...

1/1 regions updated.

PRÁTICA E LABORATÓRIO I 32
Done.

0 row(s) in 11.7770 seconds

Obs.: também poderia ser executado:

alter 'cadastro' , NAME => 'historico'

hbase(main):001:0> alter 'cadastro' , 'delete' => 'historico'

Updating all regions with the new schema...

0/1 regions updated.

1/1 regions updated.

Done.

0 row(s) in 6.2380 seconds

O comando acima também poderia ser executado como:

alter 'cadastro' , NAME => 'historico' , METHOD => 'delete'

- Comando: create

Descrição: cria uma tabela, especificando-se uma ou mais famílias de colunas.

Por exemplo:

hbase(main):001:0> create 'cadastro' , 'aluno' , 'identificacao'

Obs.: também poderia ser executado:

create 'cadastro' , {NAME => 'aluno'} , {NAME => 'identificacao'}

- Comando: describe

PRÁTICA E LABORATÓRIO I 33
Descrição: descreve as características da tabela.

Por exemplo:

hbase(main):003:0> alter 'cadastro' , 'historico'

Updating all regions with the new schema...

1/1 regions updated.

Done.

0 row(s) in 11.7770 seconds

Obs.: também poderia ser executado:

alter 'cadastro' , NAME => 'historico'

hbase(main):004:0> describe 'cadastro'

Table cadastro is ENABLED

cadastro

COLUMN FAMILIES DESCRIPTION

{NAME => 'aluno', DATA_BLOCK_ENCODING => 'NONE', BLOOMFILTER => 'ROW',


REPLIC

ATION_SCOPE => '0', VERSIONS => '1', COMPRESSION => 'NONE', MIN_VERSIONS
=> '

0', TTL => 'FOREVER', KEEP_DELETED_CELLS => 'FALSE', BLOCKSIZE => '65536',
IN

_MEMORY => 'false', BLOCKCACHE => 'true'}

{NAME => 'historico', DATA_BLOCK_ENCODING => 'NONE', BLOOMFILTER =>


'ROW', RE

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'

, IN_MEMORY => 'false', BLOCKCACHE => 'true'}

{NAME => 'identificacao', DATA_BLOCK_ENCODING => 'NONE', BLOOMFILTER =>


'ROW'

, REPLICATION_SCOPE => '0', VERSIONS => '1', COMPRESSION => 'NONE',


MIN_VERSI

ONS => '0', TTL => 'FOREVER', KEEP_DELETED_CELLS => 'FALSE', BLOCKSIZE =>
'65

536', IN_MEMORY => 'false', BLOCKCACHE => 'true'}

3 row(s) in 0.1330 seconds

hbase(main):001:0> alter 'cadastro' , 'delete' => 'historico'

Updating all regions with the new schema...

0/1 regions updated.

1/1 regions updated.

Done.

0 row(s) in 6.2380 seconds

hbase(main):002:0> desc 'cadastro'

Table cadastro is ENABLED

cadastro

COLUMN FAMILIES DESCRIPTION

{NAME => 'aluno', DATA_BLOCK_ENCODING => 'NONE', BLOOMFILTER => 'ROW',


REPLIC

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

_MEMORY => 'false', BLOCKCACHE => 'true'}

{NAME => 'identificacao', DATA_BLOCK_ENCODING => 'NONE', BLOOMFILTER =>


'ROW'

, REPLICATION_SCOPE => '0', VERSIONS => '1', COMPRESSION => 'NONE',


MIN_VERSI

ONS => '0', TTL => 'FOREVER', KEEP_DELETED_CELLS => 'FALSE', BLOCKSIZE =>
'65

536', IN_MEMORY => 'false', BLOCKCACHE => 'true'}

2 row(s) in 0.1010 seconds

- Comando: disable

Descrição: coloca a tabela em estado desabilitado.

- Comando: disabel_all

Descrição: desabilita todas as tabelas que satisfizerem o filtro especificado.

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:

hbase(main):013:0> is_disabled 'cadastro'


PRÁTICA E LABORATÓRIO I 36
false
0 row(s) in 0.0320 seconds
- Comando: drop

Descrição: exclui a tabela, que deve estar desabilitada.

- Comando: drop_all

Descrição: exclui todas as tabelas que satisfizerem o filtro especificado.

- Comando: enable

Descrição: habilita a tabela.

- Comando: enable_all

Descrição: habilita todas as tabelas que satisfizerem o filtro especificado.

- Comando: is_enabled

Descrição: retorna true (se a tabela estiver habilitada) ou false (caso contrário).

- Comando: exists

Descrição: informa se a tabela especificada existe ou não.

Por exemplo:

hbase(main):015:0> exists 'cadastro'

Table cadastro does exist

0 row(s) in 0.0240 seconds

hbase(main):016:0> exists 'arquivo'

PRÁTICA E LABORATÓRIO I 37
Table arquivo does not exist

0 row(s) in 0.0320 seconds

- 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

5 row(s) in 0.0050 seconds

=> ["ATLAS_ENTITY_AUDIT_EVENTS", "SYSTEM.CATALOG", "atlas_titan",


"cadastro", "iemployee"]

hbase(main):034:0> list 'ca.*'

TABLE

cadastro

1 row(s) in 0.0190 seconds

=> ["cadastro"]

PRÁTICA E LABORATÓRIO I 38
3) DML (Data Manipulation Language)

- Comando: count

Descrição: Obtém o total de linhas de uma tabela.

hbase(main):039:0> count 'cadastro'

0 row(s) in 0.7620 seconds

=> 0

- Comando: delete

Descrição: apaga o valor de uma célula. Devem ser especificados os


identificadores da tabela, linha e coluna.

Por exemplo:

hbase(main):047:0> put 'cadastro' , 1, 'aluno:nome' ,'JL'

0 row(s) in 0.1160 seconds

hbase(main):048:0> put 'cadastro' , 2, 'aluno:nome' ,'LR'

0 row(s) in 0.0920 seconds

hbase(main):049:0> scan 'cadastro'

ROW COLUMN+CELL

1 column=aluno:nome, timestamp=1480817574239, value=JL

2 column=aluno:nome, timestamp=1480817592995, value=LR

2 row(s) in 0.2040 seconds


PRÁTICA E LABORATÓRIO I 39
hbase(main):050:0> delete 'cadastro' , 1 , 'aluno:nome'

0 row(s) in 0.0460 seconds

hbase(main):051:0> scan 'cadastro'

ROW COLUMN+CELL

2 column=aluno:nome, timestamp=1480817592995, value=LR

1 row(s) in 0.1420 seconds

- Comando: deleteall

Descrição: apaga todas as células que pertencerem à linha de uma tabela


especificada. Opcionalmente, pode-se informar uma coluna.

- Comando: get

Descrição: obtém o valor de uma célula ou o conteúdo de uma linha.

Por exemplo:

hbase(main):002:0> get 'cadastro' , 2


COLUMN CELL
aluno:nome timestamp=1480817592995, value=LR
1 row(s) in 0.3910 seconds

Pode-se especificar também a coluna:

hbase(main):006:0> get 'cadastro' , 2 , {COLUMN => 'aluno'}


COLUMN CELL
aluno:nome timestamp=1480817592995, value=LR
1 row(s) in 0.7320 seconds

PRÁTICA E LABORATÓRIO I 40
Outra opção é especificar o timestamp (quantidade de segundos decorridos desde
1/1/1970):

hbase(main):007:0> get 'cadastro' , 2 , {TIMESTAMP => 1480817592995}


COLUMN CELL
aluno:nome timestamp=1480817592995, value=LR
1 row(s) in 0.5040 seconds

hbase(main):008:0> get 'cadastro' , 2 , {COLUMN => 'aluno' , TIMESTAMP =>


1480817592995}
COLUMN CELL
aluno:nome timestamp=1480817592995, value=LR
1 row(s) in 0.0250 seconds

- Comando: put

Descrição: insere um valor em uma célula de uma coluna posicionada em uma


linha e pertencente a uma tabela especificada.

Por exemplo:

hbase(main):047:0> put 'cadastro' , 1, 'aluno:nome' ,'JL'

0 row(s) in 0.1160 seconds

- Comando: scan

Descrição: lista o conteúdo de uma tabela.

Por exemplo:

hbase(main):002:0> scan 'cadastro'


ROW COLUMN+CELL
1 column=aluno:nome, timestamp=1480865533123, value=JL
2 column=aluno:nome, timestamp=1480868508798, value=LR

PRÁTICA E LABORATÓRIO I 41
2 row(s) in 0.3300 seconds

- Comando: truncate

Descrição: exclui e recria a tabela especificada.

Por exemplo:

hbase(main):003:0> truncate 'cadastro'

Truncating 'cadastro' table (it may take a while):

- Disabling table...

- Truncating table...

0 row(s) in 7.1440 seconds

hbase(main):004:0> describe 'cadastro'

Table cadastro is ENABLED

cadastro

COLUMN FAMILIES DESCRIPTION


{NAME => 'aluno', DATA_BLOCK_ENCODING => 'NONE', BLOOMFILTER => 'ROW',
REPLIC
ATION_SCOPE => '0', COMPRESSION => 'NONE', VERSIONS => '1', TTL =>
'FOREVER',
MIN_VERSIONS => '0', KEEP_DELETED_CELLS => 'FALSE', BLOCKSIZE => '65536',
IN
_MEMORY => 'false', BLOCKCACHE => 'true'}
{NAME => 'cont', DATA_BLOCK_ENCODING => 'NONE', BLOOMFILTER => 'ROW',
REPLICA
TION_SCOPE => '0', COMPRESSION => 'NONE', VERSIONS => '1', TTL =>
'FOREVER',

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

hbase(main):005:0> scan 'cadastro'

ROW COLUMN+CELL

0 row(s) in 0.3560 seconds

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.

Figura 31: Executando o ambiente de interação do framework Spark.

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

Podem ser declaradas de duas formas:

val: uma vez recebido um valor, a variável não pode mais ser alterada.

var: a variável pode ser alterada após receber um valor.

Como uma variável declarada por val não pode ser alterada, fazer

scala> val a = 1

scala> a = 2

gera a seguinte mensagem de erro:

<console>:32: error: reassignment to val

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

scala> val b = 1.0

scala> val tipos = s"a = $a e b = $b"

scala> println (tipos)

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):

scala> val tipos = "a = $a e b = $b"

PRÁTICA E LABORATÓRIO I 45
scala> println (tipos)

a = $a e b = $b

Tipos de Dados

Os tipos de dados em Scala iniciam com letra maiúscula:

- Strings: são sequências de caracteres delimitadas por aspas duplas. Por exemplo,
abaixo a função println “escreve” a string teste:

scala> println ("teste")

teste

Outro exemplo: concatenando strings com o operador “+”:

scala> import java.util.Date

import java.util.Date

scala> println ("Data: " + new Date)

Data: Tue Nov 01 00:16:04 UTC 2016

- Char: caractere delimitado por aspas simples (apóstrofe)

- Byte: -128 a 127

- Short: -32768 a 32767

- Int: -2147483648 a 2147483647

- Long: -9223372036854775808 a 9223372036854775807

- Float: ponto flutuante de simples precisão

- Double: ponto flutuante de dupla precisão

- Boolean: valor lógico true ou false

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

val b: Int = 1 => b: Int define b como Int

Operadores aritméticos

+ : adição

- : subtração

* : multiplicação

/ : divisão

% : resto da divisão

Obs.: na divisão inteira de 7 por 4, tem-se o quociente igual a 3 e o resto da divisão


igual a 4:

7 4

1 3
7/4

7%4

scala> val a = 7 / 4 scala> val a = 7 % 4

a: Int = 1 a: Int = 3

A divisão real de 7.0 por 4 produz como resultado 1.75:

scala> val a = 7.0 / 4

a: Double = 1.75

PRÁTICA E LABORATÓRIO I 47
Operadores Relacionais

== : igual a

!= : diferente de

> : maior que

>= : maior ou igual a

< : menor que

<= : menor ou igual a

Operadores Lógicos

&& : e

|| : ou

! : não

Outros Operadores

= : atribuição.

+= : adiciona o valor à direita à variável à esquerda, e atribui à variável o


valor resultante.

-= : subtrai o valor à direita da variável à esquerda, e atribui à variável o


valor resultante.

*= : multiplica o valor à direita à variável à esquerda, e atribui à variável o


valor resultante.

/= : divide o valor da variável à esquerda pelo valor à direita, e atribui à


variável o valor resultante.

%= : calcula o resto da divisão da variável à esquerda pelo valor à direita, e


atribui à variável o valor resultante.
PRÁTICA E LABORATÓRIO I 48
Obs.: Exemplificando com o operador “+=”:

scala> var x = 1

x: Int = 1

scala> x += 4

scala> println (x)

Estrutura de controle: if

Sintaxe: if (condição) { bloco1 } else { bloco2}

Exemplo:

scala> val a = 0

scala> if (a == 0) { println ("O valor de a não pode ser nulo!")}

Obs.: se o bloco contiver somente um comando, as chaves podem ser dispensadas.


Assim, executar

scala> if (a == 0) {println ("O valor de a não pode ser nulo!")}

produz o mesmo que:

scala> if (a == 0) println ("O valor de a não pode ser nulo!")

Estrutura de controle: for

Sintaxe: for (var <- início to fim) { bloco }

Exemplo:

scala> for ( a <- 1 to 5)

PRÁTICA E LABORATÓRIO I 49
{

println (a * 5)

10

15

20

25

Sintaxe: for (var <- coleção) { bloco }

Exemplo:

scala> for (a <- Vector(1,2,3,4,5)) println (a * 2)

10

Interpolador

O interpolador “s” avalia uma variável dentro de uma string. Por exemplo:

scala> val a = 1

scala> val b = 1.0

scala> val tipos = s"a = $a e b = $b"

scala> println (tipos)

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

scala> val b = 1.0

scala> val tipos = f"a = $a%d e b = $b%1.3f"

scala> println (tipos)

a = 1 e b = 1.000

Função

A declaração de uma função tem a seguinte sintaxe, e se inicia pela palavra-chave


def:

def nomeDaFunção () : tipoDaFunção = valorDeRetorno

Exemplo:

scala> def num10 () : Int = 10

scala> println (num10)

10

Podem ser especificados parâmetros:

def nomeDaFunção (par1 : tipo1 , par2 : tipo2) : ...

Exemplo:

scala> def soma (a : Int , b : Int) : Int = a + b

scala> println ( sum (2 , 3) )

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:

scala> def msg (m : String) = { println (m)}

msg: (m: String)Unit

scala> msg("Alô você!")

Alô, você!

Para conter mais de uma sentença, o corpo da função tem que estar delimitado por
chaves.
Exemplo:

scala> def somaTodosNums (n : Int) : Int = {

var soma = 0

for (x <- 1 to n)

soma += x

return soma

somaTodosNums: (n: Int)Int

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> val stn = somaTodosNums(_)

stn: Int => Int = <function1>

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”.

Em seguida, definem-se as funções dobro e triplo, que calculam, respectivamente, o


dobro e o triplo do valor passado como parâmetro.

Para finalizar, invoca-se a função “opera”, passando como parâmetro a função


desejada (dobro ou triplo) e um valor numérico do tipo Int.
O retorno da função “opera” depende de qual função é passada como parâmetro.

scala> def opera (fc : Int => Int , x : Int) = fc(x)

opera: (fc: Int => Int, x: Int)Int

scala> def dobro (n : Int) : Int = n * 2


PRÁTICA E LABORATÓRIO I 53
dobro: (n: Int)Int

scala> def triplo (n : Int) : Int = n * 3

triplo: (n: Int)Int

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:

(parâmetro : Tipo) => retorno da função

Exemplo: a função anônima deve retornar o triplo de um valor:

scala> (n : Int) => n * 3

res8: Int => Int = <function1>

Pode-se executar a função por meio da variável que Scala produziu:

scala> res8(4)

res9: Int = 9

Explicitando o nome da variável que irá armazenar a função anônima, fica como:

scala> val triplo = (n : Int) => n * 3

triplo: Int => Int = <function1>

scala> triplo (5)

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> def opera(f:(Int => Int), n:Int) = f(n)

opera: (f: Int => Int, n: Int)Int

scala> opera(x=>x+1,2)

res10: Int = 3

scala> opera(x=>x*2,2)

res11: Int = 4

Neste caso:

1) A função anônima opera um inteiro, produzindo um valor de retorno inteiro: f:(Int


=> Int)

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)

3) Agora, deve-se definir a funcionalidade a executar:


3.1) No primeiro caso, incrementar o valor da variável em 1: x=>x+1

3.2) No segundo caso, multiplicar o valor da variável por 2: x=>x*2

O caractere underscore ( _ ) pode ser utilizado no lugar de "x => x". Assim, a função
opera pode ser escrita de duas formas:

- Sem o underscore: opera( x=>x + 1 , 2 )


PRÁTICA E LABORATÓRIO I 55
- Com o underscore: opera( _ + 1 , 2 )

scala> def opera(f:(Int => Int), n:Int) = f(n)

opera: (f: Int => Int, n: Int)Int

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.

Os argumentos do construtor (entre parênteses, após o nome da classe) são private.


Exemplo:

scala> class Numeros (a : Int , b : Int) {

private var d = 0

def soma() : Int = a + b

def subtracao() : Int = a - b

def multiplicacao() : Int = a * b

def divisao() : Boolean = {

if (b != 0) {

d=a/b
PRÁTICA E LABORATÓRIO I 56
true

false

def getDivisao() : Int = a /b

defined class Numeros

scala> val num = new Numeros (10,2)

num: Numeros = $iwC$$iwC$Numeros@138725bc

scala> num.soma()

res8: Int = 12

scala> num.subtracao()

res9: Int = 8

scala> num.multiplicacao()

res10: Int = 20

scala> num.divisao()

res11: Boolean = true

scala> num.getDivisao()

res12: Int = 5

Observação: Como a visibilidade da variável “d” é private, não é possível acessar o


valor dessa variável fora da classe (por meio do objeto “num”):
scala> num.d

<console>:32: error: variable d in class Numeros cannot be accessed in Numeros

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> case class SomaCC (x : Int , y : Int) {

def soma() : Int = x + y

scala> class Soma (x : Int , y : Int) {

def soma() : Int = x + y

Definidas as classes no Scala, obtenha a soma entre os números 1 e 2, sem utilizar a


palavra-chave “new”.
Primeiro, utilizando a case class SomaCC:

scala> SomaCC(1,2).soma()

res8: Int = 3

Agora, utilizando a classe Soma:

scala> Soma(1,2).soma()

<console>:26: error: not found: value 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()”.

scala> val s = new Soma(1,2)

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.

Declaram-se arrays de duas formas:

1) Sem especificação de tipo:

scala> var arr = new Array[String](2)

scala> arr (0) = "Pos BigData"

scala> arr (1) = "Prática e Laboratório I"

2) Com especificação de tipo:

scala> var arr : Array[String] = new Array[String](2)

scala> arr (0) = "Pos BigData"

scala> arr (1) = "Prática e Laboratório I"

3) Com iniciação do Array pela definição de seus elementos:

PRÁTICA E LABORATÓRIO I 59
scala> var arr = Array("Pos BigData" , "Prática e Laboratório I")

Para testar as declarações

scala> println("Disciplina: " + arr(1))

Disciplina: Prática e Laboratório I

scala> println(arr(0))

Pos BigData

A iteração do array pode ser realizada pelo comando “for”:

scala> val elementos = Array(1,2,3,4,5)

elementos: Array[Int] = Array(1, 2, 3, 4, 5)

scala> for (e <- elementos) println (e)

Tuplas

Tuplas são coleções de elementos de mesmo tipo ou de diferentes tipos de dado.

Exemplo:

scala> val aluno = (1, "José")

aluno: (Int, String) = (1,José)

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).

scala> println ("Ordem do aluno na chamada: " + aluno._1)

Ordem do aluno na chamada: 1

scala> println ("Nome do aluno: " + aluno._2)

Nome do aluno: José

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:

scala> val listagem : List[Int] = List(1,2,3,4,5)

listagem: List[Int] = List(1, 2, 3, 4, 5)

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 lista2 pode ser definida como:

lista2 = elemento2 :: lista3

E assim sucessivamente, até que seja encontrado o elemento “Nil”, o que finaliza a
lista:

lista3 = elemento3 :: Nil

PRÁTICA E LABORATÓRIO I 61
Agrupando-se tudo, tem-se a seguinte sintaxe:

Lista = elemento1 :: (elemento2 :: (elemento3 :: Nil))

Sendo assim, a declaração

val listagem : List[Int] = List(1,2,3,4,5)

pode ser substituída por:

val listagem = 1 :: ( 2 :: ( 3 :: ( 4 :: ( 5 :: Nil ) ) ) )

scala> val listagem = 1 :: (2 :: (3 :: (4 :: (5 :: Nil ))))

listagem: List[Int] = List(1, 2, 3, 4, 5)

Observe o balanceamento dos parênteses:

val listagem = 1 :: ( 2 :: ( 3 :: ( 4 :: ( 5 :: Nil ) ) ) )

Abaixo, algumas funções aplicáveis a listas. Os exemplos serão baseados na seguinte


variável (do tipo List):
scala> val lista = List(1,2,3,3)

Função: ::

Retorna uma nova lista contendo o elemento especificado no início da lista.

scala> val novaLista = 0 :: lista

novaLista: List[Int] = List(0, 1, 2, 3, 3)

Função: apply

PRÁTICA E LABORATÓRIO I 62
Retorna o elemento posicionado no índice especificado.

scala> val novaLista = lista.apply(1)

novaLista: Int = 2

Função: distinct

Retorna uma lista sem elementos duplicados.

scala> val novaLista = lista.distinct

novaLista: List[Int] = List(1, 2, 3)

Função: drop

Retorna os elementos a partir da posição especificada até o final. A primeira posição


é 0 (zero).

scala> val novaListagem = lista.drop(2)

novaListagem: List[Int] = List(3, 3)

Função: dropRight

Retorna todos os elementos da lista, exceto os n últimos.

scala> val novaLista = lista.dropRight(3)

novaLista: List[Int] = List(1)

Função: dropWhile

Exclui da lista todos os elementos da lista original enquanto a condição especificada


for verdadeira.

scala> val novaLista = lista.dropWhile(x => x < 2)

novaLista: List[Int] = List(2, 3, 3)

PRÁTICA E LABORATÓRIO I 63
Função: exists

Retorna “true” se a lista contém o elemento especificado; retorna “false” caso


contrário.

scala> val existe = lista.exists (x => x == 1)

existe: Boolean = true

scala> val existe = lista.exists (x => x == 5)

existe: Boolean = false

Função: filter

Retorna todos os elementos que fazem a função especificada retornar true.

scala> val novaListagem = lista.filter (x => x % 2 == 1)

novaListagem: List[Int] = List(1, 3, 3)

Função: flatten

A partir de uma lista de string, retorna uma lista de Char.

scala> val nomes = List("josé", "Luiz", "letícia")

nomes: List[String] = List(josé, Luiz, letícia)

scala> val novaLista = nomes.flatten

novaLista: List[Char] = List(j, o, s, é, L, u, i, z, l, e, t, í, c, i, a)

Outro exemplo:

scala> val listaDeListas = List(List(1,2,3), List(4,5,6))

listaDeListas: List[List[Int]] = List(List(1, 2, 3), List(4, 5, 6))

scala> val listaSimples = listaDeListas.flatten

listaSimples: List[Int] = List(1, 2, 3, 4, 5, 6)

PRÁTICA E LABORATÓRIO I 64
Função: forall

Testa se a função especificada retorna true para todos os elementos.

scala> val novaListagem = lista.forall (x => x != 2)

novaListagem: Boolean = false

scala> val novaListagem = lista.forall (x => x != 6)

novaListagem: Boolean = true

Função: head

Retorna o primeiro elemento da lista.

scala> val primeiroElemento = lista.head

primeiroElemento: Int = 1

Função: indexOf

Retorna a posição em que se encontra o elemento especificado. Deve-se especificar,


também, a posição inicial de busca (a primeira posição tem índice 0). Se não encontrar,
retorna “-1”.

scala> val posicao = lista.indexOf (2,0)

posicao: Int = 1

scala> val posicao = lista.indexOf (2,3)

posicao: Int = -1

Função: init

Retorna todos os elementos, com exceção do último.

PRÁTICA E LABORATÓRIO I 65
scala> val novaLista = lista.init

novaLista: List[Int] = List(1, 2, 3)

Função: intersect

Retorna o resultado da interseção entre duas listas.

scala> val outraLista2 = List(1,3,4)

outraLista2: List[Int] = List(1, 3, 4)

scala> val novaListagem = lista.intersect(outraLista2)

novaListagem: List[Int] = List(1, 3)

Função: isEmpty

Retorna “true” se a lista estiver vazia; e “false” caso contrário.

scala> val vazia = lista.isEmpty

vazia: Boolean = false

Função: last

Retorna o último elemento da lista.

scala> val ultimoElemento = lista.last

ultimoElemento: Int = 3

Função: lastIndexOf

Retorna a posição em que se encontra o elemento a partir da posição especificada, do


fim para o início. Caso não seja encontrado o elemento, é retornado “-1”.

PRÁTICA E LABORATÓRIO I 66
scala> val posição = lista.lastIndexOf(2,3)

posicao: Int = 1

scala> val posição = lista.lastIndexOf(2,0)

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> val novaLista = lista.map ( x => x * 2 )

novaLista: List[Int] = List(2, 4, 6, 6)

scala> val nomes = List("josé", "Luiz", "letícia")

nomes: List[String] = List(josé, Luiz, letícia)

scala> nomes.map(_.toUpperCase)

res44: List[String] = List(JOSÉ, LUIZ, LETÍCIA)

Observação:

Função: flatMap

Executa a função map; em seguida, executa a função flatten.

scala> nomes.flatMap(_.toUpperCase)

res45: List[Char] = List(J, O, S, É, L, U, I, Z, L, E, T, Í, C, I, A)

Função: max

Retorna o maior elemento da lista.

scala> val maiorElemento = lista.max

PRÁTICA E LABORATÓRIO I 67
maiorElemento: Int = 3

Função: min

Retorna o menor elemento da lista.

scala> val menorElemento = lista.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’).

scala> val str = lista.mkString(",")

str: String = 1,2,3,3

scala> val str = lista.mkString("\t")

str: String = 1 2 3 3

scala> val str = lista.mkString

str: String = 1233

Função: reverse

Retorna uma lista com os elementos em ordem invertida.

scala> val novaLista = lista.reverse

novaLista: List[Int] = List(3, 3, 2, 1)

Função: sorted

Retorna uma lista ordenada.

PRÁTICA E LABORATÓRIO I 68
scala> val listaNaoOrdenada = List(1,4,8,2,6,0,7,4,9)

listaNaoOrdenada: List[Int] = List(1, 4, 8, 2, 6, 0, 7, 4, 9)

scala> val listaOrdenada = listaNaoOrdenada.sorted

listaOrdenada: List[Int] = List(0, 1, 2, 4, 4, 6, 7, 8, 9)

Função: sum

Soma os elementos da lista.

scala> val soma = lista.sum

soma: Int = 9

Função: tail

Retorna todos os elementos da lista, com exceção do primeiro elemento.

scala> val novaLista = lista.tail

novaLista: List[Int] = List(2, 3, 3)

Função: take

Retorna os n primeiros elementos da lista.

scala> val novaLista = lista.take(2)

novaLista: List[Int] = List(1, 2)

Função: takeRight

Retorna os últimos n elementos.

scala> val novaLista = lista.takeRight(2)

novaLista: List[Int] = List(3, 3)

Função: toArray

Converte a lista em um array.


PRÁTICA E LABORATÓRIO I 69
scala> val novaLista = lista.toArray

novaLista: Array[Int] = Array(1, 2, 3, 3)

Função: toString

Converte a lista em uma string.

scala> val novaLista = lista.toString

novaLista: String = List(1, 2, 3, 3)

PRÁTICA E LABORATÓRIO I 70
Apache
Spark

Desenvolvido em 2009 pela Universidade da Califórnia, e hoje um projeto da Apache


Software Foundation, Spark é uma ferramenta big data construída para processar
grandes massas de dados de forma paralela e distribuída, apresentando um
desempenho bem maior do que o apresentado por Hadoop (cem vezes para operações
em memória; dez vezes para operações em disco), uma vez que o processamento
efetuado por Spark se dá quase que totalmente em memória, ao passo que Hadoop
dispende muito tempo em operações de entrada/saída de disco entre as fases Map e
Reduce (APACHE SPARK, 2016).

Figura 33: Tempos de processamento Hadoop/Spark

Com Spark, ao contrário de Hadoop, pode-se encadear uma sequência de estágios


(pipeline) utilizando um mecanismo de DAG (Directed Acyclic Graph, ou, Grafo Acíclico
Direcionado) avançado, o que permite a execução de fluxos de dados e computação
in-memory.

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.

Spark realiza operações MapReduce (Spark Core), consultas SQL (SparkSQL),


aprendizado de máquina (MLlib), streaming de dados (Spark Streaming) e
processamento sobre grafos (GraphX), podendo-se combinar tais bibliotecas na
mesma aplicação. Spark foi construído com a linguagem de programação Scala, mas
pode-se construir aplicações em Spark utilizando também as linguagens Scala, Java,
Phyton ou R, apesar de só possuir atualmente shell interativo para Scala e Phyton.

RDD

O Spark é dividido em dois grandes componentes:

- Driver: gerencia a execução do processo. Recebe a requisições de usuário. Dispara


os jobs para os executores.

- Executores: são as unidades de processamento (DataNodes). Os executores


possuem os dados a serem processados por Spark.

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.

Outra característica de Spark é que os dados de um RDD são armazenados em


diferentes partições, em um ou mais executores. Por exemplo, seja um cluster com
quatro executores (representados por quadrados):

PRÁTICA E LABORATÓRIO I 72
Executor 1 Executor 2

Executor 3 Executor 4

Figura 34: Representação dos nós executores de um cluster.

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:

Figura 35: RDDs e dados distribuídos por partições.

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

RDD suporta dois tipos de operações: transformação e ação.

- 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

No Scala, crie um RDD a partir de um array contendo números inteiros de 1 a 5 e,


depois, paralelize esses dados por meio do método paralelize do objeto sc
(SparkContext). Este objeto, sc, é o ponto de entrada para uma aplicação Spark, e é
criado automaticamente quando da execução de spark-shell na janela do console:

scala> val dados = Array(1, 2, 3, 4, 5)

dados: Array[Int] = Array(1, 2, 3, 4, 5)

scala> val dadosDist = sc.parallelize(dados)

dadosDist: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[1] at parallelize at


<console>:32

Observe que a execução de “val data = Array(1, 2, 3, 4, 5)” cria um array de Int,
contendo 5 elementos inteiros.

Já a execução de “val dadosDist = sc.parallelize(dados)”, cria uma coleção de


dados distribuída, ou RDD, como mostra o retorno de Scala:

dadosDist: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD

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:

scala> val enders = sc.textFile("hdfs://sandbox.hortonworks.


com:8020/user/raj_ops/enderecos.txt")

Pode-se executar, também:

scala> val enders = sc.textFile("enderecos.txt")

Agora, crie o RDD caractLinha, que conterá a quantidade de caracteres, linha a linha,
do RDD enders:

scala> val caractLinha = enders.map(x => x.length)

caractLinha: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[29] at map at


<console>:32

Ou seja, ao ser aplicada uma TRANSFORMAÇÃO ao RDD enders obtém-se o RDD


caractLinha. Observa-se que o RDD enders continua inalterado, e isto pode ser
constatado pela AÇÃO “collect”:

scala> enders.collect

res30: Array[String] = Array(001;Rua A;97;Bairro1, 002;Rua X;12;Bairro2, 003;Rua


T;147;Bairro1)

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

res31: Array[Int] = Array(20, 20, 21)

Para saber quantas linhas possui o RDD enders, aplica-se a ação count:

scala> enders.count

res34: Long = 3

E, finalmente, para saber quantos caracteres possui o arquivo, aplica-se a ação


reduce sobre o RDD caratLinha, obtendo-se, com isso, o RDD totalCaracteres:

scala> val totalCaracteres = caractLinha.reduce((x,y) => x+y)

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:

scala> val totalCaracteres = sc.textFile("enderecos.txt").


map(x => x.length).
reduce((x,y) => x+y)

totalCaracteres: Int = 61

Transformações

Funções de transformação permitem obter novos RDDs a partir de RDD anteriores.


Abaixo, são listadas algumas funções de transformação suportadas por Spark. A
função collect (ação) será executada após as transformações, para que o resultado
seja exibido.

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.

scala> sc.parallelize(Array(("RJ",2), ("SP",1), ("SP",2), ("MG",4), ("RJ",6),


("ES",8), ("RJ",8))).groupByKey().map(x => (x._1,x._2.sum)).collect

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).

scala> sc.parallelize(Array(("Leite",5.00), ("Margarina",3.99), ("Pão",0.30),


("Queijo",27.50))).filter(x => x._2 < 5.00).collect

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

scala> sc.parallelize(Array(("RJ",2), ("SP",1), ("SP",2), ("MG",4), ("RJ",6),


("ES",8), ("RJ",8))).flatMap(x => List(x._1)).collect

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:

scala> val est1 = sc.parallelize(Array(("BA",1), ("SE",2)))


scala> val est2 = sc.parallelize(Array(("GO",3), ("DF",4)))

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:

scala> val est1 = sc.parallelize(Array(("PR",1), ("SC",2)))


scala> val est2 = sc.parallelize(Array(("SC",2), ("RS",3)))

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

scala> sc.parallelize(Array(("PI",1), ("PI",1),("MT",2))).


distinct().collect

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

scala> sc.parallelize(Array(("RJ",2), ("SP",2), ("SP",2), ("MG",4), ("RJ",6),


("ES",8), ("RJ",8))).groupByKey().collect

resulta em:
res37: Array[(String, Iterable[Int])] = Array((RJ,CompactBuffer(2, 6, 8)),
(SP,CompactBuffer(2, 2)), (ES,CompactBuffer(8)), (MG,CompactBuffer(4)))

Você pode ler mais sobre iteradores em: http://docs.scala-lang.org/


overviews/collections/iterators.html

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:

scala> sc.parallelize(Array(("RJ",2), ("SP",1), ("SP",2),


("MG",4), ("RJ",6), ("ES",8), ("RJ",8))).reduceByKey(_+_).
collect
resulta em:
res54: Array[(String, Int)] = Array((RJ,16), (SP,3), (ES,8), (MG,4))

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:

scala> sc.parallelize(Array(("RS",2), ("PB",1), ("PE",2), ("RJ",4), ("PB",6),


("TO",8), ("AM",8))).sortByKey().collect

resulta em:
res70: Array[(String, Int)] = Array((AM,8), (PB,1), (PB,6), (PE,2), (RJ,4), (RS,2),
(TO,8))

Se for especificado false para descending, a ordenação será descrescente:

scala> sc.parallelize(Array(("RS",2), ("PB",1), ("PE",2), ("RJ",4), ("PB",6),


("TO",8), ("AM",8))).sortByKey(false).collect

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.

scala> sc.parallelize(Array(1, 2, 3, 4, 5)).reduce( (a, b) => a+b )

res0: Int = 15

Ou, pode-se utilizar o caractere underscore (_):

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.

scala> sc.parallelize(List(1, 2, 3, 4, 5)).collect

res2: Array[Int] = Array(1, 2, 3, 4, 5)

3) count()

Retorna o número de elementos de um RDD.

scala> sc.parallelize(List(1, 2, 3, 4, 5)).count

res3: Long = 5

4) first()

Retorna o primeiro elemento do RDD.

A função first é similar à função take, passando 1 como parâmetro.

scala> sc.parallelize(List("A", "B", "C", "D", "E")).first

res4: String = A

5) take(n)

Retorna um array com os n primeiros elementos do RDD.

scala> sc.parallelize(List("A", "B", "C", "D", "E")).take(2)


PRÁTICA E LABORATÓRIO I 82
res5: Array[String] = Array(A, B)

6) takeOrdered(n, [ordering])

Retorna os n primeiros elementos do RDD utilizando sua ordem natural ou um


comparador personalizado.

scala> sc.parallelize(List("E", "C", "A", "D", "B")).takeOrdered(3)

res6: Array[String] = Array(A, B, C)

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.

scala> sc.parallelize(List("A", "B", "C", "D", "E")).saveAsTextFile


("hdfs://sandbox.hortonworks.com:8020/user/raj_ops/outputDir")

scala> import sys.process._


import sys.process._

scala> "hdfs dfs -ls" !

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

scala> "hdfs dfs -ls outputDir" !

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

-rw-r--r-- 1 raj_ops hdfs 2 2016-11-07 01:21 outputDir/part-00000

-rw-r--r-- 1 raj_ops hdfs 2 2016-11-07 01:21 outputDir/part-00001

-rw-r--r-- 1 raj_ops hdfs 2 2016-11-07 01:21 outputDir/part-00002

-rw-r--r-- 1 raj_ops hdfs 4 2016-11-07 01:21 outputDir/part-00003

res14: Int = 0

scala> "hdfs dfs -cat outputDir/part-00000" !


A
res15: Int = 0
scala> "hdfs dfs -cat outputDir/part-00001" !
B
res16: Int = 0
scala> "hdfs dfs -cat outputDir/part-00002" !
C
res17: Int = 0
scala> "hdfs dfs -cat outputDir/part-00003" !
D
E
res18: Int = 0

8) saveAsSequenceFile(path)

Atua sobre um RDD contendo pares (K,V). Escreve os elementos do RDD em um


Hadoop SequenceFile, no HDFS ou em outro sistema de arquivos suportado por
Hadoop.

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)

Escreve os elementos do RDD em um formato simples, utilizando o mecanismo de


serialização de Java. O arquivo pode ser lido por meio de SparkContext.objectFile().
Esta função está disponível atualmente para Scala e Java.

scala> sc.parallelize(List("A","B","C","D","E")).saveAsObjectFile
("outputObjDir/*")

Leitura dos arquivos do diretório "outputObjDir" por meio do método


SparkContext.objectFile(), criando, com isso, o RDD “of” (object file):

Obs.: Em vez de “outputObjDir/*” poderia ter sido utilizado o caminho completo:

hdfs://sandbox.hortonworks.com:8020/user/raj_ops/outputObjDir/*

scala> val of = sc.objectFile("hdfs://sandbox.hortonworks.com:


8020/user/raj_ops/outputObjDir/*")

Quantidade de elementos do RDD "of":

scala> of.count
res42: Long = 5

11) foreach(func)

Executa uma função em cada elemento do RDD.

Para visualizar os elementos do RDD:

scala> val of = sc.objectFile("outputObjDir/*")

scala> of.foreach(println)

10) countByKey()

Somente é disponível em RDDs do tipo (K,V). Retorna um hashmap de pares (K,Int)


com a quantidade de cada chave.

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

res45: scala.collection.Map[String,Long] = Map(LIMPEZA -> 2, HIGIENE -> 2,


HORTIFRUTI -> 1)

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).

Para utilizar o Spark, deve-se conectá-lo ao aplicativo que contém o código de


manipulação (transformação/ação) dos dados. Conecta-se o Spark ao aplicativo por
meio de uma instância de SparkContext, que o ambiente interativo do Spark (spark-
shell) chama de sc, para criar/manipular os RDDs, que são abstrações que
representam as coleções de dados distribuídos.

Visando facilitar o processamento de dados estruturados, Spark oferece um módulo


denominado Spark SQL (SPARK SQL & DATAFRAMES_APACHE SPARK, 2016).
Há duas formas de interagir com Spark SQL: por meio de SQL e por meio da API
Dataset.

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.

Um Dataset pode ser construído com o método “toDS()” de RDD:

scala> val ds = sc.parallelize(List(("A",65),("B",66))).toDS

ds: org.apache.spark.sql.Dataset[(String, Int)] = [_1: string, _2: int]

E, uma vez construído, um Dataset pode ser manipulado por meio de funções de
transformação e ação:

scala> ds.collect

res59: Array[(String, Int)] = Array((A,65), (B,66))

scala> ds.map(_._2).collect

res60: Array[Int] = Array(65, 66)

DataFrame

Um Dataset organizado em termos de colunas nomeadas é chamado de DataFrame.


Conceitualmente, um DataFrame é equivalente à tabela no modelo de banco de dados
relacional, porém, bem mais otimizado.

Em Scala ou Java, um DataFrame é representado por um Dataset de linhas (rows)


descritas por um esquema. Em Scala, um DataFrame é um alias para o tipo
Dataset[Row]. Podem ser construídos a partir de RDDs, tabelas do Hive ou arquivos
com dados estruturados e semiestruturados, ou seja, um DataFrame é um Dataset.
DataFrames proveem uma Linguagem Específica de Domínio (DSL: Domain-Specific
Language) para manipulação de dados estruturados em Scala, Java, Python e R.

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:

scala> import sqlContext.implicits._

import sqlContext.implicits._

scala> val rows = sc.parallelize(List(("GARFO",11),("FACA",35)))

rows: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[15] at


parallelize at <console>:39

scala> val df = rows.toDF("produto","codigo")

df: org.apache.spark.sql.DataFrame = [produto: string, codigo: int]

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.

val sqlContext = new SQLContext(sc)

implicits deve ser importada explicitamente:

import sqlContext.implicits._

Exemplo: Cadastrar caracteres e seus códigos ASCII respectivos:

scala> import sqlContext.implicits._

import sqlContext.implicits._

scala> val rows = sc.parallelize(List(("A",65),("B",66)))

PRÁTICA E LABORATÓRIO I 89
rows: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[15] at
parallelize at <console>:39

scala> val df = rows.toDF("letra","cod")

df: org.apache.spark.sql.DataFrame = [letra: string, cod: int]


scala> df.show

+-----+---+
| letra|cod|
+-----+---+
| A | 65|
| B | 66|
+-----+---+

scala> df.printSchema

root
|-- letra: string (nullable = true)

|-- cod: integer (nullable = false)

scala> df.select(df("letra")).show

+-----+
| letra|
+-----+
| A |
| B |
+-----+

scala> df.select(df("letra"), df("cod")).filter(df("cod") === 65).show

+-----+---+
| letra|cod|
+-----+---+
| A | 65|
+-----+---+

Obs.1: A comparação de igualdade é realizada com três caracteres “igual” (===).

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

scala> df.filter( df.col("cod") === 65).show

scala> df.filter( $"cod" === 65).show


Inclusive, operações podem ser realizadas com estes campos. Por exemplo:

scala> df.filter(df.col("cod")+1 === 66).show

+-----+---+
| letra|cod|
+-----+---+
| A | 65|
+-----+---+

Se o desejado for somente a letra:

scala> df.filter(df.col("cod") === 66).select($"letra").show

+-----+
| 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

hashCode hex hour hypot hw

Para saber sobre a função, digite o nome da função e dê dois toques na tecla TAB:

scala> hex

def hex(column: Column): Column

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 |
+----------+

Exemplo: Gerar valor criptografado (MD5) de uma coluna.

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

def md5(e: Column): Column

scala> df.select(md5(bin(df("cod")))).show

+--------------------------+

PRÁTICA E LABORATÓRIO I 92
| md5(bin(cod)) |
+--------------------------+
|59e711d152de7bec7... |
|3e267ff3c8b6621e5... |
+--------------------------+

Um DataFrame é representado por um Dataset de linhas (rows) descritas por um


esquema. Uma vez associado a um esquema, pode-se criar uma tabela, uma abstração
que permite a manipulação dos “campos” de seus campos via SQL. Pode-se obter um
DataFrame associado a um esquema de dados a partir de um RDD de Rows:

scala> import org.apache.spark.sql.types._

scala> import org.apache.spark.sql.Row

scala> val rdd =


sc.parallelize(Seq(Seq("A",65),Seq("B",66)).map(Row.fromSeq(_)))

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]

Scala> val schema = StructType(Seq(StructField("letra", StringType),


StructField("cod", IntegerType)))

schema: org.apache.spark.sql.types.StructType =
StructType(StructField(letra,StringType,true), StructField(cod,IntegerType,true))

scala> val df = sqlContext.createDataFrame(rdd,schema)

df: org.apache.spark.sql.DataFrame = [letra: string, cod: int]

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")

result: org.apache.spark.sql.DataFrame = [letra: string, cod: int]

scala> result.show

+-----+---+
|letra |cod|
+-----+---+
|A | 65|
|B | 66|
+-----+---+

scala> val result1 = sql("SELECT * FROM ascii order by cod desc")

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

Apache Zeppelin é uma ferramenta para análise e visualização de dados em ambiente


big data por meio de notebooks.

Um notebook é um recurso que fornece um shell interativo, baseado na web, cujos


relatórios podem ser produzidos na forma textual ou gráfica. O suporte a Markdown e
Angular permite gerar relatórios no formato HTML.

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.

Figura 36: 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):

Figura 37: Acesso ao Apache Zeppelin.

Carregando o endereço informado em um navegador web, tem-se acesso ao Apache


Zeppelin:

Figura 38: Janela inicial do Apache Zeppelin.

PRÁTICA E LABORATÓRIO I 96
A partir da janela inicial, pode-se:

 Importar um notebook (arquivo no formato JSON);


 Criar um notebook;
 Carregar um notebook existente (como o notebook AON Demo, ou qualquer
outro disponível).

Importar Criar um Carregar um


notebook notebook notebook

Figura 39: Iniciando os trabalhos com Zeppelin.

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):

Figura 40: Importação de notebook.

Clicando em Choose a JSON here (à esquerda), abre-se a janela de seleção de


diretório onde se encontra o notebook a importar:

Figura 41: Importação de notebook: janela de seleção de diretório

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.

Figura 28: Importando o notebook Pbg

Figura 42: Fornecendo o nome do notebook a importar.

O processo de importação via URL é semelhante. Após inserir a URL na qual se


encontra o arquivo JSON com o notebook desejado, clica-se em Import note:

Figura 43: Importação de notebook via URL.


PRÁTICA E LABORATÓRIO I 99
O nome do notebook (no caso, Lab 101_DW Spark with Scala) é exibido no campo
Import AS,

Figura 44: Exibição do notebook importado via URL.

e, em seguida, na janela inicial do Zeppelin, pronto para ser aberto e manipulado.

Figura 45: Janela do Zeppelin e o notebook remoto.

PRÁTICA E LABORATÓRIO I 100


Utilizando um notebook

Para utilizar um notebook, basta dar um clique no seu nome. Por exemplo, carregue
o notebook Hello World Tutorial:

Figura 46: Acessando um notebook.

O notebook é carregado:

Figura 47: Carregando o notebook “Hello World Tutorial”.

PRÁTICA E LABORATÓRIO I 101


Observe que a primeira informação que aparece diz respeito aos interpretadores. Em
Zeppelin, interpretadores são plug-ins que habilitam o uso de
linguagem/processamento de dados específico. Por exemplo, o interpretador %spark
é necessário para executar código Scala em Zeppelin. Para executar comandos do shell
do Linux, é preciso especificar o interpretador %sh. Para criar/remover
interpretadores, deve-se clicar em interpreter para ir ao menu correspondente.

Figura 48: Interpretadores do notebook “Hello World Tutorial”.

PRÁTICA E LABORATÓRIO I 102


Criando um notebook

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.

Figura 49: Criando um notebook.

Obs.: o nome do notebook é definido pelo usuário do Zeppelin. No exemplo acima,


Pbg foi escolhido como acrônimo de “Pós Big Data“.

Surge a janela do notebook Pbg com seu único parágrafo.

Figura 50: Parágrafo do notebook Pbg.

Um notebook pode ser visto, então, como um contêiner de parágrafos.

Cada parágrafo consiste de duas seções:


- Seção de código;

- Seção de resultados.

PRÁTICA E LABORATÓRIO I 103


Na seção de código, serão inseridos códigos em Scala, comandos do shell Linux etc.
Por exemplo, digite o código para criar um RDD no parágrafo abaixo:

Figura 51: Utilizando a seção de código do parágrafo.

Em seguida, execute o código digitado pelo teclado (pressioando Shift-Enter) ou


clicando no ícone ao lado de READY:

Figura 52: Execução do código do parágrafo.

PRÁTICA E LABORATÓRIO I 104


O estado do parágrafo muda para PENDING (pendente):

Figura 53: Parágrafo no estado PENDING.

E, em seguida, para RUNNING (executando):

Figura 54: Parágrafo no estado RUNNING.

PRÁTICA E LABORATÓRIO I 105


Ao terminar a execução, o estado do parágrafo se altera para FINISHED.

Figura 55: Parágrafo no estado FINISHED.

Na primeira vez que um código é executado em um notebook, o tempo de execução


pode ser longo (no exemplo abaxo, a seção de resultados informa que o tempo total
de execução do código de criação do RDD foi de 4 minutos e 17 segundos).

Como comparação, o mesmo código, executado logo em seguida, levou cerca de 1


segundo:

Figura 56: Reexecutando o código em outro parágrafo.

PRÁTICA E LABORATÓRIO I 106


Desejando apagar o resultado produzido por algum código, deve-se clicar em

(opção disponível clicando-se no botão ).

Figura 57: Limpando a seção de resultados do parágrafo.

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.

Figura 58: Parágrafo com seção de resultados após a limpeza.

PRÁTICA E LABORATÓRIO I 107


Se for preciso remover um parágrafo, pode-se clicar no botão e, em seguida, em
.

Figura 59: Excluindo o parágrafo no notebook.

Figura 60: Confirmação de exclusão do parágrafo.

PRÁTICA E LABORATÓRIO I 108


Estudo de caso: Analisar as compras realizadas em um supermercado.

Solução: Os códigos de análise das compras serão exibidos nos parágrafos do


notebook, a ser criado com o nome “compras”.

Figura 61: Título do notebook “compras”.

Passos a serem executados para a solução da tarefa:

a) Fazer as importações:

Figura 62: Parágrafo com códigos de importação.

PRÁTICA E LABORATÓRIO I 109


b) Cadastrar a compra, inserindo os campos:

- secao: seção a que o produto pertence.


- produto: nome descritivo do produto.
- preco: preço em reais (R$) da unidade do produto.
- quant.: Quantidade de produto comprado. No caso da carne, será considerado o
preço da embalagem do produto, e não a quantidade em Kg.

Figura 63: Criação do RDD.

PRÁTICA E LABORATÓRIO I 110


c) Definição dos tipos de dados por meio da criação de um esquema:

Figura 64: Definição do esquema.

d) Criação do DataFrame baseado no esquema:

Figura 65: Criação do DataFrame.

PRÁTICA E LABORATÓRIO I 111


e) Registro do DataFrame como tabela:

Figura 66: Criação da tabela.

f) Obtendo um DataFrame a partir de cláusula SQL sobre a tabela criada em “e”:

Figura 67: Listando o conteúdo do DataFrame resul.

PRÁTICA E LABORATÓRIO I 112


O que foi apresentado até o momento não é muito diferente do que o já que foi obtido
em console. Porém, Zeppelin é uma ferramenta fantástica, que fornece análise e
visualização de dados como poucas ferramentas permitem.

No próximo parágrafo, no notebook “compras”, execute o seguinte código:

%sql

select * from compras order by produto

Zeppelin retorna uma listagem dos produtos cadastrados em forma de tabela:

Figura 68: Listagem dos dados em formato tabular.

PRÁTICA E LABORATÓRIO I 113


Esses dados podem ser visualizados em diferentes formatos, como gráfico de pizza,
por exemplo:

Figura 69: Alterando a visualização para gráfico de pizza.

O parágrafo altera a visualização dos dados para pizza:

Figura 70: Visualizando a saída no formato pizza.

PRÁTICA E LABORATÓRIO I 114


E de forma ampliada:

Figura 71: ampliação do relatório em forma de pizza.

Colocar o curso do mouse sobre uma determinada área mostra a quantidade daquela
área:

Figura 72: Quantitativo de produtos comprados por seção.

PRÁTICA E LABORATÓRIO I 115


Execute a query abaixo em outro parágrafo:

%sql

select secao, sum(preco*quant) from compras group by secao

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:

Figura 73: Quantitativo em reais (R$) comprado por seção.

Observa-se, também, que o gasto por seção está bem equilibrado.


Digamos que tivessem sido comprados 10 pacotes de alcatra, em vez de somente um
pacote:

Seq("açougue","alcatra",35.00,10)

PRÁTICA E LABORATÓRIO I 116


Neste caso, o gráfico de pizza seria modificado, conforme imagem abaixo:

Figura 74: Alteração do total de itens da seção açougue.

Obtendo o total (R$) gasto na seção “açougue“:

Figura 75: Novo quantitativo de compras da seção açougue.

PRÁTICA E LABORATÓRIO I 117


Referências

APACHE HADOOP. Diponível em: http://hadoop.apache.org. Acesso em: 13 jun.


2016.

APACHE HIVE. Diponível em: https://cwiki.apache.org/confluence/display/Hive/Home.


Acesso em: 31 out. 2016.

APACHE PIG. Diponível em: https://pig.apache.org/. Acesso em: 18 set. 2016.

APACHE HBASE REFERENCE GUIDE. Diponível em:


http://hbase.apache.org/book.html. Acesso em: 13 jun. 2016.

APACHE SPARK. Diponível em: http://spark.apache.org. Acesso em: 7 out. 2016.

ARNOLD, K.; GOSLING, J.; HOLMES, D. A linguagem de programação Java. 4. ed.


Porto Alegre: Bookman, 2007.

BASIC NTP CONFIGURATION. Diponível em:


http://www.tldp.org/LDP/sag/html/basic-ntp-config.html. Acesso em: 27 nov. 2016.

DEITEL, H. M.; Deitel, P. J. Java Como programar. 8. ed. São Paulo: Pearson
Prentice Hall, 2010.

LANGUAGE MANUAL TYPES – APACHE HIVE. Diponível em:


https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Types#Language
ManualTypes-NumericTypes. Acesso em: 31 out. 2016.

LANGUAGE MANUAL UDF – APACHE HIVE. Diponível em:


https://cwiki.apache.org/confluence/display/Hive/LanguageManual+UDF#Language
ManualUDF-ArithmeticOperators. Acesso em: 31 out. 2016.

PRÁTICA E LABORATÓRIO I 118


SCALA DOCUMENTATION. Diponível em: http://docs.scala-lang.org/
Acesso em: 01 out. 2016.

SPARK SQL & DATAFRAMES _ APACHE SPARK. Diponível em:


http://spark.apache.org/sql. Acesso em: 07 nov. 2016.

SPARK SQL FUNCTIONS. Diponível em:


https://spark.apache.org/docs/1.6.2/api/java/org/apache/spark/sql/functions.html.
Acesso em: 08 nov. 2016.

THE SCALA PROGRAMMING LANGUAGE. Disponível em http://www.scala-lang.org/.


Acesso em: 31 nov. 2016.

WHITE, T. Hadoop - The Definitive Guide. 4rd. ed. Sebastopol: O´Reilly, 2015.

PRÁTICA E LABORATÓRIO I 119

Você também pode gostar