Skip to main content

Hazelcast Clustering

Hazelcast provides distributed data structures for clustering multiple Spring Boot Admin Server instances. This enables high availability and shared state across servers.

Overview

With Hazelcast clustering:

  • Multiple Admin Server instances share event store
  • No single point of failure
  • Automatic synchronization across nodes
  • Distributed notifications

Architecture

Why Hazelcast?

  • High Availability: No single point of failure
  • Scalability: Add more Admin Server instances
  • Shared State: All servers see the same application state
  • Distributed Events: Events propagated across cluster
  • Simple Setup: Minimal configuration required

Setting Up Hazelcast

Add Dependencies

pom.xml
<dependencies>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
</dependency>
</dependencies>

Configure Hazelcast

HazelcastConfig.java
import com.hazelcast.config.Config;
import com.hazelcast.config.MapConfig;
import com.hazelcast.config.MergePolicyConfig;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.map.IMap;
import com.hazelcast.spi.merge.PutIfAbsentMergePolicy;
import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
import de.codecentric.boot.admin.server.domain.values.InstanceId;
import de.codecentric.boot.admin.server.eventstore.HazelcastEventStore;
import de.codecentric.boot.admin.server.eventstore.InstanceEventStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@Configuration
public class HazelcastConfig {

@Bean
public Config hazelcastConfig() {
MapConfig mapConfig = new MapConfig("spring-boot-admin-event-store")
.setBackupCount(1)
.setMergePolicyConfig(new MergePolicyConfig(
PutIfAbsentMergePolicy.class.getName(), 100));

Config config = new Config();
config.addMapConfig(mapConfig);
config.setProperty("hazelcast.jmx", "true");

// Network configuration
config.getNetworkConfig()
.setPort(5701)
.setPortAutoIncrement(true)
.getJoin()
.getMulticastConfig()
.setEnabled(true);

return config;
}

@Bean
public HazelcastInstance hazelcastInstance(Config hazelcastConfig) {
return Hazelcast.newHazelcastInstance(hazelcastConfig);
}

@Bean
public InstanceEventStore eventStore(HazelcastInstance hazelcastInstance) {
IMap<InstanceId, List<InstanceEvent>> map =
hazelcastInstance.getMap("spring-boot-admin-event-store");
return new HazelcastEventStore(100, map);
}
}

Basic Configuration

application.yml
spring:
application:
name: spring-boot-admin-server

hazelcast:
network:
port: 5701
port-auto-increment: true
join:
multicast:
enabled: true
tcp-ip:
enabled: false

Network Configuration

Multicast (Development)

Automatic discovery using multicast:

config.getNetworkConfig()
.getJoin()
.getMulticastConfig()
.setEnabled(true)
.setMulticastGroup("224.2.2.3")
.setMulticastPort(54327);
hazelcast:
network:
join:
multicast:
enabled: true
multicast-group: 224.2.2.3
multicast-port: 54327

TCP/IP (Production)

Explicit member list for production:

config.getNetworkConfig()
.getJoin()
.getMulticastConfig()
.setEnabled(false);

config.getNetworkConfig()
.getJoin()
.getTcpIpConfig()
.setEnabled(true)
.addMember("192.168.1.100")
.addMember("192.168.1.101")
.addMember("192.168.1.102");
hazelcast:
network:
join:
multicast:
enabled: false
tcp-ip:
enabled: true
members:
- 192.168.1.100
- 192.168.1.101
- 192.168.1.102

Kubernetes

For Kubernetes deployments:

<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast-kubernetes</artifactId>
</dependency>
config.getNetworkConfig()
.getJoin()
.getMulticastConfig()
.setEnabled(false);

config.getNetworkConfig()
.getJoin()
.getKubernetesConfig()
.setEnabled(true)
.setProperty("namespace", "default")
.setProperty("service-name", "spring-boot-admin");

Event Store Configuration

Map Configuration

MapConfig mapConfig = new MapConfig("spring-boot-admin-event-store")
.setBackupCount(1) // Number of backup copies
.setAsyncBackupCount(0) // Async backups
.setTimeToLiveSeconds(0) // No expiration
.setMaxIdleSeconds(0) // No idle timeout
.setMergePolicyConfig(new MergePolicyConfig(
PutIfAbsentMergePolicy.class.getName(), 100));

Event Store Size

Limit events per instance:

@Bean
public InstanceEventStore eventStore(HazelcastInstance hazelcastInstance) {
IMap<InstanceId, List<InstanceEvent>> map =
hazelcastInstance.getMap("spring-boot-admin-event-store");
return new HazelcastEventStore(500, map); // Max 500 events per instance
}

High Availability Setup

Load Balancer Configuration

upstream spring_boot_admin {
least_conn;
server admin1:8080;
server admin2:8080;
server admin3:8080;
}

server {
listen 80;
server_name admin.example.com;

location / {
proxy_pass http://spring_boot_admin;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

Session Persistence

Use sticky sessions for the UI:

upstream spring_boot_admin {
ip_hash; # Sticky sessions
server admin1:8080;
server admin2:8080;
}

Or use Spring Session:

<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-hazelcast</artifactId>
</dependency>
@EnableHazelcastHttpSession
@Configuration
public class SessionConfig {
// Hazelcast will be used for session storage
}

Docker Compose Example

docker-compose.yml
version: '3'

services:
admin1:
build: ./admin-server
ports:
- "8080:8080"
environment:
- HAZELCAST_MEMBERS=admin1,admin2,admin3
- SERVER_PORT=8080

admin2:
build: ./admin-server
ports:
- "8081:8080"
environment:
- HAZELCAST_MEMBERS=admin1,admin2,admin3
- SERVER_PORT=8080

admin3:
build: ./admin-server
ports:
- "8082:8080"
environment:
- HAZELCAST_MEMBERS=admin1,admin2,admin3
- SERVER_PORT=8080

nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- admin1
- admin2
- admin3

Monitoring Hazelcast

Management Center

Use Hazelcast Management Center:

hazelcast:
management-center:
enabled: true
url: http://localhost:8083/mancenter

JMX Monitoring

Enable JMX:

config.setProperty("hazelcast.jmx", "true");

Health Checks

Check cluster health:

@Component
public class HazelcastHealthCheck {

private final HazelcastInstance hazelcastInstance;

public HazelcastHealthCheck(HazelcastInstance hazelcastInstance) {
this.hazelcastInstance = hazelcastInstance;
}

public boolean isHealthy() {
return hazelcastInstance.getCluster().getMembers().size() > 0;
}

public int getClusterSize() {
return hazelcastInstance.getCluster().getMembers().size();
}
}

Troubleshooting

Split Brain

Configure merge policy:

mapConfig.setMergePolicyConfig(new MergePolicyConfig(
PutIfAbsentMergePolicy.class.getName(), 100));

Members Not Joining

  1. Check network connectivity:

    telnet admin1 5701
  2. Verify multicast:

    # Check if multicast is enabled
    ip maddr show
  3. Check logs:

    Hazelcast logs will show connection attempts

Performance Issues

  1. Increase backup count:

    mapConfig.setBackupCount(2);
  2. Use async backups:

    mapConfig.setAsyncBackupCount(1);
  3. Monitor map size:

    IMap map = hazelcastInstance.getMap("spring-boot-admin-event-store");
    log.info("Map size: {}", map.size());

Best Practices

  1. Use TCP/IP in Production: Multicast may not work in cloud environments

  2. Configure Appropriate Backups:

    mapConfig.setBackupCount(1);  // At least 1 backup
  3. Set Event Store Limits:

    new HazelcastEventStore(500, map);  // Reasonable limit
  4. Monitor Cluster Health: Use Management Center or JMX

  5. Use Load Balancer: Distribute traffic across servers

  6. Enable Session Persistence: For seamless failover

  7. Configure Network Properly: Especially in Kubernetes/Docker

Complete Example

See the spring-boot-admin-sample-hazelcast project for a complete working example.

See Also