Hazelcast
Data Grid e processamento distribuído
Domine IMap, entry processors, particionamento, backups e o modelo de processamento onde os dados vivem no Hazelcast.
Nesta aula você vai
- Configurar mapas distribuídos com políticas de eviction, TTL e backup
- Implementar entry processors para lógica atômica nos dados
- Explicar como o particionamento afeta performance e tolerância a falhas
Data Grid e processamento distribuído
Objetivos
Nesta aula você vai:
- Configurar um data grid Hazelcast com parâmetros de produção conscientes
- Executar lógica nos dados com entry processors em vez de round-trips desnecessários
- Entender particionamento, backups e o impacto em latência e disponibilidade
Introdução
Um data grid é como uma narrativa contada por vários narradores simultâneos, cada um responsável por um capítulo — mas o leitor deve perceber uma história única. O Hazelcast IMap é esse contrato: chaves distribuídas em partições, backups em nós distintos, operações que parecem locais mas atravessam a rede quando necessário.
A diferença entre usar Hazelcast como "Redis em Java" e usar como data grid está no processamento: mover código até os dados, não dados até o código. Esta aula ensina essa inversão — a mesma que separa leitura passiva de crítica literária ativa.
Conteúdo
IMap — o coração do data grid
IMap<K,V> é um ConcurrentHashMap distribuído. Operações básicas:
IMap<String, Integer> estoque = hz.getMap("estoque-produtos");
estoque.put("SKU-42", 100);
estoque.putIfAbsent("SKU-99", 50);
Integer qtd = estoque.get("SKU-42");
boolean removido = estoque.remove("SKU-42", 100);
Configuração declarativa (hazelcast.xml ou programática):
<map name="estoque-produtos">
<backup-count>1</backup-count>
<async-backup-count>0</async-backup-count>
<time-to-live-seconds>3600</time-to-live-seconds>
<max-idle-seconds>1800</max-idle-seconds>
<eviction eviction-policy="LRU" max-size-policy="PER_NODE" size="10000"/>
</map>
| Parâmetro | Função |
|---|---|
backup-count |
Cópias síncronas da partição em outros nós |
time-to-live-seconds |
Expiração absoluta da entrada |
max-idle-seconds |
Expiração por inatividade |
eviction |
Remove entradas quando mapa excede limite por nó |
Particionamento e data locality
Chaves são hasheadas para uma de N partições (padrão: 271). Cada partição tem um owner — o membro responsável por operações de escrita.
chave "SKU-42" → hash → Partição 17 → Owner: Nó B
- Leitura no owner — latência mínima
- Leitura no backup — possível com configuração, reduz carga no owner
- Leitura em nó sem cópia — proxy remoto, latência de rede
Para minimizar tráfego, co-localize processamento e dados com entry processors.
Entry Processors — lógica no lugar certo
Anti-padrão:
// Duas viagens de rede: GET + PUT
Integer atual = estoque.get("SKU-42");
estoque.put("SKU-42", atual - 1);
Padrão correto:
import com.hazelcast.map.EntryProcessor;
public class DecrementarEstoque implements EntryProcessor<String, Integer, Integer> {
@Override
public Integer process(Map.Entry<String, Integer> entry) {
Integer valor = entry.getValue();
if (valor == null || valor <= 0) return -1;
entry.setValue(valor - 1);
return valor - 1;
}
}
// Uma viagem — executa no owner da partição
Integer restante = estoque.executeOnKey("SKU-42", new DecrementarEstoque());
Entry processors executam atomicamente na partição — equivalente conceitual a script Lua no Redis, mas com código Java tipado e testável.
Processamento em lote e predicados
Para operações em múltiplas chaves:
import com.hazelcast.query.Predicates;
// Atualiza todas as entradas com estoque < 10
estoque.executeOnEntries(
entry -> {
if (entry.getValue() < 10) entry.setValue(0);
return null;
},
Predicates.lessThan("this", 10)
);
Cuidado: executeOnEntries sem predicado varre todo o mapa — operação cara em datasets grandes. Sempre filtre.
Near Cache — leitura local
Clientes Java podem habilitar Near Cache — cópia local das entradas mais acessadas:
ClientConfig config = new ClientConfig();
NearCacheConfig nearCache = new NearCacheConfig("estoque-produtos")
.setInvalidateOnChange(true);
config.addNearCacheConfig(nearCache);
| Benefício | Custo |
|---|---|
| Leituras locais em microssegundos | Memória adicional no cliente |
| Reduz tráfego de rede | Risco de dados stale se invalidação falhar |
Ideal para catálogos lidos intensamente, raramente alterados.
Jet — streaming e batch (visão geral)
Hazelcast Jet processa fluxos de dados distribuídos — Kafka, JDBC, IMap — com pipeline declarativo:
// Conceitual — pipeline que conta eventos por tipo
Pipeline p = Pipeline.create();
p.readFrom(Sources.mapJournal("eventos", START_FROM_OLDEST))
.groupingKey(e -> e.getTipo())
.aggregate(AggregateOperations.counting())
.writeTo(Sinks.logger());
Jet transforma Hazelcast de data grid em motor de stream processing leve — alternativa a Flink/Spark para cargas moderadas integradas ao mesmo cluster.
Tolerância a falhas
Com backup-count=1, perda de um nó não perde dados — partições promovidas de backup a owner. Tempo de recuperação depende do tamanho das partições e número de backups.
Nó B (owner P17) falha → Nó C (backup P17) promovido → Cluster reequilibrado
Monitore eventos de MEMBER_REMOVED e métricas de rebalancing. Durante rebalancing, latência pode aumentar temporariamente.
Resumo
- IMap é mapa distribuído com TTL, eviction e backups configuráveis por mapa
- Particionamento determina qual nó é owner de cada chave — afeta latência de leitura/escrita
- Entry processors executam lógica atomicamente no owner, evitando round-trips GET/PUT
- Near Cache acelera leituras no cliente à custa de memória e complexidade de invalidação
- Jet estende Hazelcast para processamento de streams integrado ao data grid
- Backups síncronos garantem durabilidade em memória clusterizada — não substituem persistência em disco para dados críticos