🎯 OBJECTIF
Comprendre comment :
🧠 MODÈLE MENTAL
Dans Kubernetes, chaque pod a sa propre JVM et sa propre RAM — il n'y a pas de mémoire partagée physique. Quand on dit que Hazelcast "partage" les données, ça veut dire que les pods communiquent via le réseau pour accéder à des données dont chacun est propriétaire d'une partition.
Le modèle mental : 271 partitions fixes réparties entre tous les members actifs. Quand Pod A écrit map.put("user:42", user), Hazelcast hash la clé, identifie la partition (ex: 105), voit que Pod B est propriétaire de cette partition, et envoie la donnée à Pod B via TCP interne. C'est transparent pour le code — mais chaque opération peut traverser le réseau même quand les données semblent "locales".
Le piège classique : mal configurer le discovery. Si chaque pod ne trouve pas les autres, il démarre son propre cluster de 1 — aucun partage réel, aucun log d'erreur, l'application semble fonctionner jusqu'à ce qu'on remarque que le cache n'est pas cohérent entre les pods.
Sans Hazelcast (cache local par pod) :
Pod A — met user:42 en cache → hit ratio 100% pour Pod A
Pod B — ne sait rien de user:42 → va en DB
Pod C — ne sait rien de user:42 → va en DB
Avec Hazelcast (cache distribué) :
Pod A — écrit user:42 dans la partition 105 (Pod B)
Pod B — stocke user:42 dans sa RAM
Pod C — lit user:42 depuis Pod B via TCP → cache hit
flowchart LR
subgraph Cluster["Cluster Hazelcast (3 members)"]
A["Pod A\nPartitions 0–90"]
B["Pod B\nPartitions 91–180"]
C["Pod C\nPartitions 181–270"]
A -- backup --> B
B -- backup --> C
C -- backup --> A
end
App["Code applicatif\nmap.put('user:42', ...)"] --> A
A -->|"hash → partition 105"| BmermaidSéquence d'un map.put("user:42", user) depuis Pod A :
user:42 → partition 105backupCount=1)🚨 Discovery mal configuré = N clusters isolés
Si le discovery n'est pas configuré correctement en Kubernetes (ex: multicast désactivé mais Kubernetes API non configuré), chaque pod démarre son propre cluster de 1 membre. Aucune donnée n'est partagée, aucune erreur n'est loguée. Le symptôme : cache miss constant sur tous les pods sauf celui qui a écrit.
@Configuration
@EnableCaching
public class HazelcastConfig {
@Bean
public HazelcastInstance hazelcastInstance() {
Config config = new Config();
config.setClusterName("my-cluster");
// Discovery Kubernetes (recommandé en K8s)
config.getNetworkConfig()
.getJoin()
.getMulticastConfig().setEnabled(false);
// Hazelcast se connecte à l'API K8s pour trouver les pods via un label
// Requiert hazelcast-kubernetes plugin
// Configuration d'une IMap
MapConfig mapConfig = new MapConfig("users");
mapConfig.setBackupCount(1); // 1 copie en cas de crash
mapConfig.setTimeToLiveSeconds(300); // expiration 5 min
EvictionConfig eviction = new EvictionConfig()
.setSize(10000)
.setMaxSizePolicy(MaxSizePolicy.ENTRY_COUNT)
.setEvictionPolicy(EvictionPolicy.LRU);
mapConfig.setEvictionConfig(eviction);
config.addMapConfig(mapConfig);
return Hazelcast.newHazelcastInstance(config);
}
}java@Service
public class UserService {
@Cacheable(value = "users", key = "#id") // ✓ Spring Cache → Hazelcast IMap
public User getUser(String id) {
return findInDatabase(id);
}
@CacheEvict(value = "users", key = "#id") // ✓ invalidation synchronisée sur le cluster
public void updateUser(String id, User user) {
updateDatabase(id, user);
}
}javaLe problème des sessions en multi-pods : HttpSession est stockée en mémoire locale de l'instance. Si le load balancer envoie la requête suivante vers un autre pod, la session est introuvable → utilisateur déconnecté.
sequenceDiagram
participant C as Client
participant LB as Load Balancer
participant A as Pod A
participant B as Pod B
participant HZ as Hazelcast Cluster
C->>LB: POST /login
LB->>A: route vers Pod A
A->>HZ: Spring Session stocke la session
A-->>C: 200 OK + cookie JSESSIONID
C->>LB: GET /dashboard
LB->>B: route vers Pod B (Pod A surchargé)
B->>HZ: Spring Session lit la session depuis Hazelcast
HZ-->>B: session retrouvée
B-->>C: 200 OK — session transparentemermaidConfiguration :
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-hazelcast</artifactId>
</dependency>xml@Bean
public HazelcastInstance hazelcastForSession() {
Config config = new Config();
MapConfig sessionMap = new MapConfig("spring:session:sessions");
sessionMap.setBackupCount(1);
sessionMap.setTimeToLiveSeconds(1800); // 30 min d'inactivité
config.addMapConfig(sessionMap);
return Hazelcast.newHazelcastInstance(config);
}javaSpring Session intercepte toutes les lectures/écritures de HttpSession et les redirige vers Hazelcast. Transparent côté code.
| AP (défaut — IMap, IQueue...) | CP Subsystem (Locks, Atomics...) | |
|---|---|---|
| En cas de partition réseau | Les deux côtés continuent d'accepter des écritures | Une seule partie peut continuer |
| Cohérence | Éventuelle — divergence temporaire possible | Forte — last-write-wins |
| Usage | Cache, sessions, données reconstructibles | Locks distribués, compteurs critiques, coordination |
| Résolution split-brain | Last-write-wins (défaut) | N/A — bloque sans quorum |
Règle de choix
Cache et sessions → AP (données reconstructibles, cohérence éventuelle acceptable). Coordination distribuée (lock d'exclusion mutuelle, compteur d'idempotence) → CP Subsystem.
flowchart LR
subgraph Embedded["Mode embedded (simple)"]
E1["Pod Spring Boot\n+ Member Hazelcast"]
E2["Pod Spring Boot\n+ Member Hazelcast"]
E3["Pod Spring Boot\n+ Member Hazelcast"]
end
subgraph CS["Mode client/server (prod avancé)"]
A1["Pod Spring Boot\n(client)"]
A2["Pod Spring Boot\n(client)"]
HZ1["Pod Hazelcast\n(server)"]
HZ2["Pod Hazelcast\n(server)"]
A1 --> HZ1
A2 --> HZ2
endmermaid| Embedded | Client/Server | |
|---|---|---|
| Simplicité | ✅ Très simple | ❌ Plus complexe |
| Scaling | ⚠️ Lié à l'app | ✅ Indépendant |
| Mémoire | Partagée avec l'app | ✅ Isolée |
| Rebalancing au scaling app | Oui (migration de partitions) | Non (cluster stable) |
⚡ TL;DR — chaque concept en une ligne
Partitionnement ✓ 271 partitions fixes réparties entre les members — chaque clé est routée vers le member propriétaire de sa partition. ⚠ Chaque opération peut traverser le réseau si le pod local n'est pas propriétaire — pas une mémoire partagée, du routage réseau.
Discovery ✓ Discovery = comment les pods se trouvent pour former un cluster unique. ⚠ Discovery mal configuré = N clusters de 1 pod — aucune donnée partagée, aucune erreur visible.
Session clustering
✓ Spring Session + Hazelcast remplace HttpSession locale par une IMap distribuée — sessions transparentes entre tous les pods.
⚠ Taille des objets en session = pression mémoire Hazelcast — stocker des DTOs légers, pas des entités JPA entières.
AP vs CP ✓ IMap en mode AP (défaut) : haute disponibilité, divergence temporaire acceptable pour un cache. ⚠ Pour les locks distribués ou la coordination critique, utiliser le CP Subsystem explicitement.
🎓 À retenir
backupCount, un crash de pod = perte des partitions — avec backupCount=0 (défaut si non configuré), si le pod propriétaire d'une partition crashe, toutes les données de cette partition sont perdues. Toujours configurer backupCount=1 en production.