Um guia para o Apache Ignite
*1. Introdução *
O Apache Ignite é uma plataforma distribuída centrada na memória de código aberto. Podemos usá-lo como um banco de dados, um sistema de cache ou para o processamento de dados na memória.
A plataforma usa a memória como uma camada de armazenamento, portanto, possui uma taxa de desempenho impressionante. Simplificando,* esta é uma das plataformas de processamento de dados atômicos mais rápidas atualmente em uso de produção. *
===* 2. Instalação e configuração *
Para começar, consulte a getting página iniciada para obter as instruções iniciais de configuração e instalação.
As dependências do Maven para o aplicativo que vamos criar:
<dependency>
<groupId>org.apache.ignite</groupId>
<artifactId>ignite-core</artifactId>
<version>${ignite.version}</version>
</dependency>
<dependency>
<groupId>org.apache.ignite</groupId>
<artifactId>ignite-indexing</artifactId>
<version>${ignite.version}</version>
</dependency>
*https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.apache.ignite%22%20AND%20a%3A%22ignite-core%22 [_ignite-core_] é o única dependência obrigatória para o projeto* . Como também queremos interagir com o SQL, _ignite-indexing_ também está aqui. https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.apache.ignite%22%20AND%20a%3A%22ignite-core%22[_$\{ignite.version } _] é a versão mais recente do Apache Ignite.
Como a última etapa, iniciamos o nó Ignite:
Ignite node started OK (id=53c77dea)
Topology snapshot [ver=1, servers=1, clients=0, CPUs=4, offheap=1.2GB, heap=1.0GB]
Data Regions Configured:
^-- default [initSize=256.0 MiB, maxSize=1.2 GiB, persistenceEnabled=false]
A saída do console acima mostra que estamos prontos para começar.
3. Arquitetura de memória
*A plataforma é baseada na arquitetura de memória durável* . Isso permite armazenar e processar os dados no disco e na memória. Aumenta o desempenho usando os recursos de RAM do cluster efetivamente.
Os dados na memória e no disco têm a mesma representação binária. Isso significa que não há conversão adicional dos dados ao passar de uma camada para outra.
A arquitetura de memória durável se divide em blocos de tamanho fixo chamados páginas. As páginas são armazenadas fora do heap Java e organizadas em uma RAM. Ele possui um identificador exclusivo: FullPageId.
As páginas interagem com a memória usando a abstração PageMemory.
Ajuda a ler, escrever uma página, também para alocar um ID de página. Na memória, o Ignite associa as páginas a Memory Buffers .
*4. Páginas de memória *
Uma página pode ter os seguintes estados:
-
Descarregado - nenhum buffer de página carregado na memória
-
Limpar - o buffer da página é carregado e sincronizado com os dados no disco
-
Durty - o buffer da página contém dados diferentes dos do disco
-
Sujo no ponto de verificação - há outra modificação iniciada antes da primeira persistir no disco. Aqui um ponto de verificação é iniciado e PageMemory mantém dois buffers de memória para cada página.
*A memória durável aloca um segmento de memória local chamado _Data Region _.* Por padrão, possui capacidade de 20% da memória do cluster. A configuração de várias regiões permite manter os dados utilizáveis na memória.
A capacidade máxima da região é um segmento de memória. É uma memória física ou uma matriz de bytes contínua.
*Para evitar fragmentações de memória, uma única página contém várias entradas de valores-chave* . Cada nova entrada será adicionada à página mais ideal. Se o tamanho do par de valores-chave exceder a capacidade máxima da página, o Ignite armazenará os dados em mais de uma página. A mesma lógica se aplica à atualização dos dados.
Os índices SQL e cache são armazenados em estruturas conhecidas como Árvores B +. As chaves de cache são ordenadas por seus valores de chave.
5. Ciclo da vida
*Cada nó Ignite é executado em uma única instância da JVM* . No entanto, é possível configurar para ter vários nós do Ignite em execução em um único processo da JVM.
Vamos analisar os tipos de evento do ciclo de vida:
-
BEFORE_NODE_START - antes da inicialização do nó Ignite
-
AFTER_NODE_START - é acionado logo após o início do nó Ignite
-
BEFORE_NODE_STOP - antes de iniciar a parada do nó
-
AFTER_NODE_STOP - depois que o nó Ignite parar
Para iniciar um nó Ignite padrão:
Ignite ignite = Ignition.start();
Ou a partir de um arquivo de configuração:
Ignite ignite = Ignition.start("config/example-cache.xml");
Caso necessitemos de mais controle sobre o processo de inicialização, existe outra maneira com a ajuda da interface LifecycleBean:
public class CustomLifecycleBean implements LifecycleBean {
@Override
public void onLifecycleEvent(LifecycleEventType lifecycleEventType)
throws IgniteException {
if(lifecycleEventType == LifecycleEventType.AFTER_NODE_START) {
//...
}
}
}
Aqui, podemos usar os tipos de eventos do ciclo de vida para executar ações antes ou depois do nó iniciar/parar.
Para esse propósito, passamos a instância de configuração com o CustomLifecycleBean para o método start:
IgniteConfiguration configuration = new IgniteConfiguration();
configuration.setLifecycleBeans(new CustomLifecycleBean());
Ignite ignite = Ignition.start(configuration);
6. Grade de dados na memória
*Ignite data grid é um armazenamento de valor-chave distribuído* , muito familiar ao _HashMap_ particionado. É dimensionado horizontalmente. Isso significa que mais nós de cluster adicionamos, mais dados são armazenados em cache ou armazenados na memória.
Ele pode fornecer uma melhoria significativa no desempenho de software de terceiros, como os bancos de dados NoSql e RDMS, como uma camada adicional para armazenamento em cache.
6.1 Suporte de cache
*A API de acesso a dados é baseada na especificação JCache JSR 107.*
Como exemplo, vamos criar um cache usando uma configuração de modelo:
IgniteCache<Employee, Integer> cache = ignite.getOrCreateCache(
"baeldingCache");
Vamos ver o que está acontecendo aqui para mais detalhes. Primeiro, o Ignite localiza a região da memória em que o cache foi armazenado.
Em seguida, a página de índice da árvore B + será localizada com base no código de hash da chave. Se o índice existir, uma Página de dados da chave correspondente será localizada.
*Quando o índice é NULL, a plataforma cria a nova entrada de dados usando a chave fornecida.*
Em seguida, vamos adicionar alguns objetos Employee:
cache.put(1, new Employee(1, "John", true));
cache.put(2, new Employee(2, "Anna", false));
cache.put(3, new Employee(3, "George", true));
Novamente, a memória durável procurará a região de memória à qual o cache pertence. Com base na chave de cache, a página de índice estará localizada em uma estrutura de árvore B +.
*Quando a página de índice não existe, uma nova é solicitada e adicionada à árvore.*
Em seguida, uma página de dados é atribuída à página de índice.
Para ler o funcionário do cache, usamos apenas o valor da chave:
Employee employee = cache.get(1);
6.2 Suporte para streaming
Na memória, o fluxo de dados fornece uma abordagem alternativa para os aplicativos de processamento de dados baseados em disco e sistema de arquivos. A API de streaming divide o fluxo de dados de alta carga em vários estágios e os encaminha para processamento .
Podemos modificar nosso exemplo e transmitir os dados do arquivo. Primeiro, definimos um streamer de dados:
IgniteDataStreamer<Integer, Employee> streamer = ignite
.dataStreamer(cache.getName());
Em seguida, podemos registrar um transformador de fluxo para marcar os funcionários recebidos como empregados:
streamer.receiver(StreamTransformer.from((e, arg) -> {
Employee employee = e.getValue();
employee.setEmployed(true);
e.setValue(employee);
return employee;
}));
Como etapa final, iteramos nas linhas do arquivo employees.txt e as convertemos em objetos Java:
Path path = Paths.get(IgniteStream.class.getResource("employees.txt")
.toURI());
Gson gson = new Gson();
Files.lines(path)
.forEach(l -> streamer.addData(
employee.getId(),
gson.fromJson(l, Employee.class)));
*Com o uso de _streamer.addData () _, coloque os objetos do funcionário no fluxo.
===* 7. Suporte SQL *
*A plataforma fornece banco de dados SQL tolerante a falhas, centralizado em memória.
Podemos nos conectar com a API SQL pura ou com o JDBC. A sintaxe SQL aqui é ANSI-99, portanto, todas as funções de agregação padrão nas operações de linguagem consultas, DML e DDL são suportadas.
====* 7.1 JDBC *
Para ficar mais prático, vamos criar uma tabela de funcionários e adicionar alguns dados a ela.
Para esse fim,* registramos um driver JDBC e abrimos uma conexão *como uma próxima etapa:
Class.forName("org.apache.ignite.IgniteJdbcThinDriver");
Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1/");
Com a ajuda do comando DDL padrão, preenchemos a tabela Employee:
sql.executeUpdate("CREATE TABLE Employee (" +
" id LONG PRIMARY KEY, name VARCHAR, isEmployed tinyint(1)) " +
" WITH \"template=replicated\"");
Após a palavra-chave WITH, podemos definir o modelo de configuração do cache. Aqui usamos o REPLICATED.* Por padrão, o modo de modelo é PARTITIONED . *Para especificar o número de cópias dos dados, também podemos especificar o parâmetro BACKUPS aqui, que é 0 por padrão.
Então, vamos adicionar alguns dados usando a instrução INSERT DML:
PreparedStatement sql = conn.prepareStatement(
"INSERT INTO Employee (id, name, isEmployed) VALUES (?, ?, ?)");
sql.setLong(1, 1);
sql.setString(2, "James");
sql.setBoolean(3, true);
sql.executeUpdate();
//add the rest
Depois, selecionamos os registros:
ResultSet rs
= sql.executeQuery("SELECT e.name, e.isEmployed "
+ " FROM Employee e "
+ " WHERE e.isEmployed = TRUE ")
7.2 Consultar os objetos
Também é possível executar uma consulta sobre objetos Java armazenados no cache. Ignite trata o objeto Java como um registro SQL separado:
IgniteCache<Integer, Employee> cache = ignite.cache("Cache");
SqlFieldsQuery sql = new SqlFieldsQuery(
"select name from Employee where isEmployed = 'true'");
QueryCursor<List<?>> cursor = cache.query(sql);
for (List<?> row : cursor) {
//do something with the row
}
*8. Resumo *
Neste tutorial, vimos rapidamente o projeto Apache Ignite. Este guia destaca as vantagens da plataforma sobre outros produtos simiais, como ganhos de desempenho, durabilidade e APIs leves.
Como resultado,* aprendemos como usar a linguagem SQL e a API Java para armazenar, recuperar, transmitir dados dentro da grade de persistência ou na memória. *
Como de costume, o código completo deste artigo está disponível over no GitHub.