HazelCast Server based on Spring boot
Repo: https://github.com/R0NGSH3N/hazelcast-server
APP Description
- Standalone HazelCast Server
- Manage the Data in cache.
- Implement Authentication and Authorization
- Implement Listener to notify users.
- Impelemnt Gauge to pre-active monitoring/measuring the sever performance
Enviroments
- Spring boots 2.3.5
- HazelCast 4.1
Description
1. Listener Implementation
Server Side Listener types
HazelCast doesn’t categorize the listener from server side or client side, but I want server/member/partition
related events handled by server side listener, and mapEntry
related events handle by each client, becuase Server should not care about the object get add/remove/update in the cache, client should be responsible for it.
- Memebership Listener
- Distributed Object Listener
- Migration and Partition Lost Listener
- Lifecycle Listener
- Client Listener
- Spring ApplicationEvent Listner
For our purpose, I wrote a Listener to delegate all above listeners. Here are the list of events we will listening on:
public enum EVENT_TYPE {
MEMBERS_ADDED_EVENT,
MEMBERS_REMOVED_EVENT,
DISTRIBUTED_OBJECT_CREATED_EVENT,
DISTRIBUTED_OBJECT_DESTROYED_EVENT,
PARTITION_LOST_EVENT,
LIFECYCLE_CHANGE_EVENT
}
Here is the declaration for Listener:
public class HazelCastEventListener implements
MembershipListener,
DistributedObjectListener,
PartitionLostListener,
LifecycleListener, ApplicationListener<HazelCastEvent> {
...
}
The Listener injected into HazelCast Config bean, in that case, we will be able to catch most of the events:
hazelcastConfig.addListenerConfig(new ListenerConfig(hazelCastEventListener));
Client Side Listener types:
MapEntryListener
is added by client, the purpose of this listner is to monitoring the Map
object that client created and insert into the cache. It is client’s responsibility to react on those events, not servers.
2. Notifier
Notifier is used to notife the event through different Channel
, it could be through restful
, MQ
or email
. The Notifier should be decouple with listener, it is configed separately in Spring boot Config class
Notifier connect the event
and NotifierChannels
, it keep map that connect 2 of them, when HazelCast Instance throw event, it will be first captured by Listener
, then Listener
will call Notifier
, Notifier
will select NotificationChannel
based on the map, then Notifier pass the event to NotificationChannel
for the channel object to send message.
Following is the Notifier
:
public class HazelCastEventNotifier {
private Map<HazelCastEvent.EVENT_TYPE, List<HazelCastEventNotifierChannel>> notifierchannelmap;
public void setNotifier(Map<HazelCastEvent.EVENT_TYPE, List<HazelCastEventNotifierChannel>> notifier) {
this.notifierchannelmap = notifier;
}
/**
* Map that mapping between Listener and Notification Channels
* */
public Map<HazelCastEvent.EVENT_TYPE, List<HazelCastEventNotifierChannel>> getNotifier() {
if (notifierchannelmap == null) {
notifierchannelmap = new HashMap<HazelCastEvent.EVENT_TYPE, List<HazelCastEventNotifierChannel>>();
}
return notifierchannelmap;
}
/**
* go through all Channels to send event
*/
public void inform(final HazelCastEvent event) {
this.notifierchannelmap.get(event.getEventType()).forEach(e -> e.sendEvent(event));
}
}
following is the RestfulNotifictionChannel
:
public class RestfulRequestChannel implements HazelCastEventNotifierChannel{
private String baseUrl;
private String uri;
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public void setUri(String uri) {
this.uri = uri;
}
public String getBaseUrl() {
return baseUrl;
}
public String getUri() {
return uri;
}
@Override
public void sendEvent(HazelCastEvent event) {
WebClient webClient = WebClient.builder()
.baseUrl(baseUrl)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
webClient.post().uri(uri)
.body(Mono.just(event), HazelCastEvent.class)
.retrieve()
.bodyToMono(HazelCastEvent.class);
}
}
3. Serialization Implementation.
The Object store in hazelcast need to be serialized, the default you can you just Serializable
interface, but the performance will be slow. To improve the performance, HazelCast provide IdentifiedDataSerializable
interface, following to show how to implement.
3.1 Create your DataSerializableFactory
object
public class HazelCastEventDataSerializableFactory implements DataSerializableFactory {
public static final int FACTORY_ID = 1;
public static final int HAZELCAST_EVENT_ID = 1;
@Override
public IdentifiedDataSerializable create(int typeId) {
if ( typeId == HAZELCAST_EVENT_ID ) {
return new HazelCastEvent();
} else {
return null;
}
}
}
FACTORY_ID
is the id used by object in entity object (see below) and HAZELCAST_EVENT_ID
used by factory to create new entity object instance.
3.2 Let your model/pojo object implements IdentifiedDataSerializable
In your object, you need implements IdentifiedDataSerializable
and its 2 methods:
-
int getClassId()
:IdentifiedDataSerializable
uses getClassId() instead of class name. -
int getFactoryId()
:IdentifiedDataSerializable
uses getFactoryId() to load the class when given the id.
@Override
public int getFactoryId() {
return HazelCastEventDataSerializableFactory.FACTORY_ID;
}
@Override
public int getClassId() {
return HazelCastEventDataSerializableFactory.HAZELCAST_EVENT_ID;
}
Both Id are source from factory object.
you also need to implement writeData()
and readData()
method, this is same as other Serializable interface (eg: ExternalDataSerializable interface from java).