Hazelcast Tutorial
Hazelcast Tutorial
1
Useful Methods ..................................................................................................................................................................................... 33
ISET .................................................................................................................................................................................................................34
Adding elements and reading elements ...................................................................................................................................... 34
Useful Methods ..................................................................................................................................................................................... 35
ILIST ................................................................................................................................................................................................................36
Adding elements and reading elements ...................................................................................................................................... 36
Useful Methods ..................................................................................................................................................................................... 38
IQUEUE ...........................................................................................................................................................................................................38
Adding elements and reading elements ...................................................................................................................................... 38
Useful Methods ..................................................................................................................................................................................... 40
IMAP................................................................................................................................................................................................................41
Creation & Read/Write ..................................................................................................................................................................... 41
Useful Methods ..................................................................................................................................................................................... 42
Eviction .................................................................................................................................................................................................... 43
Partitioned data and High Availability ....................................................................................................................................... 44
Hashcode and Equals ......................................................................................................................................................................... 46
EntryProcessor ..................................................................................................................................................................................... 49
HAZELCAST – CLIENT ............................................................................................................................................................ 52
LOAD BALANCING..........................................................................................................................................................................................54
FAILOVER ........................................................................................................................................................................................................54
HAZELCAST – SERIALIZATION ........................................................................................................................................... 56
JAVA SERIALIZATION.....................................................................................................................................................................................56
JAVA EXTERNALIZABLE ................................................................................................................................................................................59
HAZELCAST – SPRING INTEGRATION .............................................................................................................................. 64
HAZELCAST – MONITORING................................................................................................................................................ 69
MONITORING HAZELCAST VIA REST API ................................................................................................................................................69
JMX MONITORING .........................................................................................................................................................................................71
HAZELCAST – MAP REDUCE & AGGREGATIONS ........................................................................................................... 73
HAZELCAST – COLLECTION LISTENER ............................................................................................................................ 77
HAZELCAST – COMMON PITFALLS & PERFORMANCE TIPS ..................................................................................... 80
HAZELCAST QUEUE ON SINGLE MACHINE ..................................................................................................................................................80
USING MAP'S SET METHOD INSTEAD OF PUT ............................................................................................................................................80
HAZELCAST USES SERIALIZED DATA FOR OBJECT COMPARISON .............................................................................................................81
USE MONITORING ..........................................................................................................................................................................................81
HOMOGENEOUS CLUSTER .............................................................................................................................................................................81
2
Overview
Hazelcast is a distributed IMDG, i.e. in-memory data grid, which is used widely across industries
by companies like Nissan, JPMorgan, Tmobile, to name a few.
It offers various rich features including distributed cache to store key-value pairs, constructs to
create and use distributed data structure, and a way to distribute your computation and queries
among nodes in a cluster.
Hazelcast is a very useful tool in developing applications that require high scalability,
performance, and availability.
Audience
This tutorial deep dives into various features that make Hazelcast a very useful tool. It is directed
towards software professionals who want to develop highly scalable and performant applications.
Post this tutorial, you would have intermediate knowledge of Hazelcast and its usage.
Prerequisite
To make the most of this tutorial, you should have working knowledge of Data Structures, while
having some exposure to Java is preferable.
3
Hazelcast – Introduction
It supports other data structures like locks, semaphores, sets, list, and queues.
It provides a way to query the stored data by rich querying languages, for example, SQL.
It provides a distributed execution engine which helps to operate on the data in parallel.
Benefits of Hazelcast
Support multiple data structures: Hazelcast supports the usage of multiple data
structures along with Map. Some of the examples are Lock, Semaphore, Queue, List, etc.
Fast R/W access: Given that all the data is in-memory, Hazelcast offers very high-speed
data read/write access.
High availability: Hazelcast supports the distribution of data across machines along with
additional support for backup. This means that the data is not stored on a single machine.
So, even if a machine goes down, which occurs frequently in a distributed environment,
the data is not lost.
High Performance: Hazelcast provides constructs which can be used to distribute the
workload/computation/query among multiple worker machines. This means a
computation/query uses resources from multiple machines which reduces the execution
time drastically.
4
Hazelcast vs Other Caches & Key-Value stores
Comparing Hazelcast with other caches like Ehcache, Guava, and Caffeine may not be very
useful. It is because, unlike other caches, Hazelcast is a distributed cache, that is, it spreads the
data across machines/JVM. Although Hazelcast can work very well on single JVM as well,
however, it is more useful is a distributed environment.
Similarly comparing it with Databases like MongoDB is also of not much use. This is because,
Hazelcast mostly stores data in memory (although it also supports writing to disk). So, it offers
high R/W speed with the limitation that data needs to be stored in memory.
Hazelcast also supports caching/storing complex data types and provides an interface to query
them, unlike other data stores.
A comparison, however, can be made with Redis which also offers similar features.
Hazelcast vs Redis
In terms of features, both Redis and Hazelcast are very similar. However, following are the points
where Hazelcast scores over Redis:
Built for Distributed Environment from ground-up: Unlike Redis, which started as
single machine cache, Hazelcast, from the very beginning, has been built for distributed
environment.
Simple cluster scale in/out: Maintaining a cluster where nodes are added or removed is
very simple in case of Hazelcast, for example, adding a node is a matter of launching the
node with the required configuration. Removing a node requires simple shutting down of
the node. Hazelcast automatically handles partitioning of data, etc. Having the same setup
for Redis and performing the above operation requires more precaution and manual
efforts.
Less resources needs to support failover: Redis follows master-slave approach. For
failover, Redis requires additional resources to setup Redis Sentinel. These Sentinel
nodes are responsible to elevate a slave to master if the original master node goes down.
In Hazelcast, all nodes are treated equal, failure of a node is detected by other nodes. So,
the case of a node going down is handled pretty transparently and that too without any
additional set of monitoring servers.
5
Hazelcast – Setup
Hazelcast requires Java 1.6 or above. Hazelcast can also be used with .NET, C++, or other JVM
based languages like Scala and Clojure. However, for this tutorial, we are going to use Java 8.
Before we move on, following is the project setup that we will use for this tutorial.
hazelcast/
├── [Link]/
│ ├── [Link]
│ ├── [Link]
│ ├── [Link]
│ └── ....
├── [Link]
├── target/
├── [Link]
├── [Link]
├── ...
For now, we can just create the package, i.e., [Link] inside the hazelcast directory.
Then, just cd to that directory. We will look at other files in the upcoming sections.
Installing Hazelcast
Installing Hazelcast simply involves adding a JAR file to your build file. POM file or [Link]
based on whether you are using Maven or Gradle respectively.
If you are using Gradle, adding the following to [Link] file would be enough:
dependencies {
compile "[Link]:hazelcast:3.12.12”
}
6
POM for the tutorial
We will use the following POM for our tutorial:
<properties>
<[Link]>1.8</[Link]>
<[Link]>1.8</[Link]>
</properties>
<dependencies>
<dependency>
<groupId>[Link]</groupId>
<artifactId>hazelcast</artifactId>
<version>3.12.12</version>
</dependency>
</dependencies>
<!-- Below build plugin is not needed for Hazelcast, it is being used only
to created a shaded JAR so that -->
<!-- using the output i.e. the JAR becomes simple for testing snippets in
the tutorial-->
<build>
7
<plugins>
<plugin>
<!-- Create a shaded JAR and specify the entry point class-->
<groupId>[Link]</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
8
Hazelcast – First Application
Hazelcast can be run in isolation (single node) or multiple nodes can be run to form a cluster. Let
us first try starting a single instance.
Single Instance
Now, let us try creating and using a single instance of Hazelcast cluster. For that, we will create
[Link] file.
package [Link];
import [Link];
import [Link];
import [Link];
[Link](“Hello world”);
9
Now let’s compile the code and execute it:
Hello World
However, more importantly, you will also notice log lines from Hazelcast which signifies that
Hazelcast has started. Since we are running this code only once, i.e., a single JVM, we would
only have one member in our cluster.
You will also notice log lines from Hazelcast at the end which signifies
Hazelcast was shutdown:
INFO: [localhost]:5701 [dev] [3.12.12] Hazelcast Shutdown is completed in 784 ms.
10
Jan 30, 2021 10:26:57 AM [Link]
INFO: [localhost]:5701 [dev] [3.12.12] [localhost]:5701 is SHUTDOWN
package [Link];
import [Link];
import [Link];
//print the socket address of this member and also the size of the cluster
[Link]([Link]("[%s]: No. of hazelcast members: %s",
[Link]().getLocalMember().getSocketAddress(),
[Link]().getMembers().size()));
11
Let’s execute the following command on two different shells:
You would notice on the 1st shell that a Hazelcast instance has been started and a member has
been assigned. Note the last line of output which says that there is a single member using port
5701.
You would notice on the 2nd shell that a Hazelcast instance has joined the 1st instance. Note
the last line of the output which says that there are now two members using port 5702.
12
Hazelcast – Configuration
XML Configuration
The [Link] is where these configurations need to be placed. The file is searched for in
the following location (in same order) and is chosen from the first available location:
Passing the location of the XML to the JVM via the system property -
[Link]=/path/to/[Link]
Once the XML is found, Hazelcast would load the required configuration from the XML file.
Let's try that out with an example. Create an XML in your current directory with the name
[Link].
<hazelcast
xsi:schemaLocation="[Link]
[Link]
xmlns="[Link]
xmlns:xsi="[Link]
</hazelcast>
13
The XML as of now only contains the schema location of the Hazelcast XML which is used for
validation. But more importantly, it contains the instance name.
package [Link];
import [Link];
import [Link];
14
The output for above command would be:
As you see, Hazelcast loaded the configuration and printed the name which was specified in the
configuration (last line).
There are a whole lot of configuration options which can be specified in the XML. The complete
list can be found at:
[Link]
[Link]
Programmatic Configuration
As stated earlier, XML configuration is ultimately done via programmatic configuration. So, let’s
try programmatic configuration for the same example which we saw in XML configuration. For
that, let’s create the [Link] file with the following content.
package [Link];
import [Link];
import [Link];
15
import [Link];
Let’s execute the code without passing any [Link] file by:
Logging
To avoid dependencies, Hazelcast by default uses JDK based logging. But it also supports
logging via slf4j, log4j. For example, if we want to setup logging via for sl4j with logback, we can
update the POM to contain the following dependencies:
16
<!-- contains both sl4j bindings and the logback core -->
<dependency>
<groupId>[Link]</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
Define a configuration [Link] file and add it to your classpath, for example,
src/main/resources.
<configuration>
<appender name="STDOUT" class="[Link]">
<encoder>
<pattern>%d{HH:mm:[Link]} [%thread] %-5level %logger{36} -
%msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
</root>
Now, when we execute the following command, we notice that all the meta information about the
Hazelcast member creation etc. is not printed. And this is because we have set the logging level
for Hazelcast to error and asked Hazelcast to use sl4j logger.
Output
John
17
Variables
Value written to XML configuration files can vary based on the environment. For example, in
production, you may use a different username/password for connecting to the Hazelcast cluster
compared to the dev environment. Instead of maintaining separate XML files, one can also write
variables in the XML files and then pass those variables via command line or programmatically to
Hazelcast. Here is an example for choosing the name of the instance from the command line.
<hazelcast
xsi:schemaLocation="[Link]
[Link]
xmlns="[Link]
xmlns:xsi="[Link]
<instance-name>${instance_name}</instance-name>
</hazelcast>
And here is the sample Java code we would use to print the variable value:
package [Link];
import [Link];
import [Link];
import [Link];
18
And, following is the command:
And the output shows that the variable was replaced by Hazelcast correctly.
19
Hazelcast – Setting up multi-node instances
Given that Hazelcast is a distributed IMDG and typically is set up on multiple machines, it requires
access to the internal/external network. The most important use-case being discovery of
Hazelcast nodes within a cluster.
Multicast
Multicast joining mechanism is enabled by default. [Link] is a way
of communication form in which message is transmitted to all the nodes in a group. And this is
what Hazelcast uses to discover other members of the cluster. All the examples that we have
looked at earlier use multicast to discover members.
<hazelcast
xsi:schemaLocation="[Link]
[Link]
xmlns="[Link]
xmlns:xsi="[Link]
<network>
<join>
20
<multicast enabled="true" />
</join>
</network>
</hazelcast>
In the output, we notice the following lines from Hazelcast which effectively means that multicast
joiner is used to discover the members
.
Jan 30, 2021 5:26:15 PM [Link]
INFO: [localhost]:5701 [dev] [3.12.12] Creating MulticastJoiner
.
Multicast, by default, accepts communication from all the machines in the multicast group. This
may be a security concern and that is why typically, on-premise, multicast communication is
firewalled. So, while multicast is good for development work, in production, it is best to use TCP/IP
based discovery.
TCP/IP
Due to the drawbacks stated for Multicast, TCP/IP is the preferred way for communication. In
case of TCP/IP, a member can connect to only known/listed members.
Let’s use TCP/IP for discovery mechanisms. Save the following in [Link]
<hazelcast
xsi:schemaLocation="[Link]
[Link]
xmlns="[Link]
xmlns:xsi="[Link]
<network>
<join>
<multicast enabled="false" />
<tcp-ip enabled="true">
<members>localhost</members>
</tcp-ip>
</join>
</network>
</hazelcast>
21
And then, let’s execute the following command:
.
The above output shows that TCP/IP joiner was use to join two members
Note that we can specify more IPs or the machine names (which would be resolved by DNS) in
the XML configuration file.
<hazelcast
xsi:schemaLocation="[Link]
[Link]
xmlns="[Link]
xmlns:xsi="[Link]
<network>
<join>
<multicast enabled="false" />
<tcp-ip enabled="true">
22
<members>machine1, machine2....</members>
</tcp-ip>
</join>
</network>
</hazelcast>
23
Hazelcast – Data Structures
Better Performance: If more than one machine has access to the data, all of them can
work in parallel and complete the work in a lesser timespan.
IAtomicLong
The Atomic Long data structure in Java provides a thread safe way for using Long.
AtomicLong has one synchronous backup which means if we have a setup where we have, say,
5 JVMs running, only two JVMs will hold this variable.
24
HazelcastInstance hazelcast = [Link]();
IAtomicLong counter = [Link]("counter");
[Link]([Link]());
[Link](2);
[Link]([Link]());
[Link](0);
}
}
When the above code is executed, it will produce the following output:
0
2
25
The 2nd line of the output of the above code would always be:
2000
If incrementAndGet() would not have been thread safe, the above code may have not given
2000 as the output all the time. It would probably be less than that, as the writes one thread may
have gotten overwritten by another.
Useful Methods
addAndGet(long value) Atomically add the value and return the updated value
ILock
Similarly, ILock extends the interface to provide a distributed version of Java Lock. It provides
similar functions: lock, unlock, tryLock
26
But a major difference between ILock and Java Lock is that while Java Lock provides protection
of critical section from threads in a single JVM, ILock provides synchronization for threads in a
single JVM as well as multiple JVMs.
ILock has one synchronous backup meaning that if we have a setup where we have, say, 5 JVMs
running, only two JVMs will hold this variable.
// create a lock
ILock hzLock = [Link]("lock_1");
IAtomicLong counter = [Link]("counter");
// acquire lock
[Link]();
[Link]("Acquiring Lock");
try{
[Link](5000);
[Link]("Incrementing Counter");
[Link]();
[Link]("Counter: " + [Link]());
}
finally {
// release lock
[Link]("Lock Released");
[Link]();
}
[Link](0);
}
27
The output of the above function shows that the second JVM was able to acquire lock only after
first JVM released the lock.
Acquired Lock
Incrementing Counter
Counter: 1
Lock Released
Acquired Lock
Incrementing Counter
Counter: 2
Lock Released
// create a lock
ILock hzLock = [Link]("lock_1");
// acquire lock
if([Link](2000, [Link])) {
[Link]("Acquired Lock");
[Link](5000);
[Link]("Lock Released");
[Link]();
}
else
[Link]("Couldn't acquire lock");
28
[Link](0);
}
Acquired Lock
Couldn't acquire lock
Lock Released
● While acquiring locks can be very useful, it is recommended to keep the critical section as
short as possible. This ensures that the performance does not degrade and it also reduces
the chances of a deadlock.
● If a member (which has acquired) goes down, the lock is automatically released and is up
for grabs for other members.
● The lock is rentrant; it ensures that the same thread can acquire a lock multiple times
without causing a deadlock.
Useful Methods
Function Name Description
29
ISemaphore
Sounds a lot like a lock, right? But there are two major differences between lock and semaphore:
● Semaphore does not have ownership. It can be acquired by a thread and released by
another thread. Locks are tied to a thread. It needs to be released and acquired by the
same thread.
● Semaphore supports entry of 1 of one or more threads into the critical section based on
req based on available permits.
But a major difference between ISemaphore and Java Semaphore is that while the Java
Semaphore provides protection of critical section from threads in a single JVM, ISemaphore
provides synchronization for threads in a single JVM as well as multiple JVMs.
ISemaphore has one synchronous backup which means, if we have a setup where we have, say,
5 JVMs running, only two JVMs will hold this semaphore.
// create a lock
ISemaphore hzSemaphore = [Link]("semaphore_1");
IAtomicLong activeThreads = [Link]("threads");
[Link](2);
30
if([Link](2000, [Link]));
{
[Link]("Thread count: " +
[Link]());
[Link](2000);
[Link]();
[Link]();
}
}
[Link](0);
}
The output for the code shows that we have 1 or 2 threads active which is what we expect given
the permit being set to 2.
Good Practices
● If a member which has permit goes down, the permit is released automatically, making it
available for other threads to acquire.
● Avoid using acquire() of semaphore, as it is a blocking call which may lead to deadlock.
It’s better to use tryAcquire() with a timeout to avoid blocking.
Useful Methods
31
ICountDownLatch
The [Link] provides a way for threads to wait, while other
threads complete a set of operations in a multithreaded environment in a JVM.
A major difference between ICountDownLatch and Java CountDownLatch is that while Java
CountDownLatch provides protection of critical section from threads in a single JVM,
ICountDownLatch provides synchronization for threads in a single JVM as well as multiple JVMs.
ICountDownLatch has one synchronous backup which means if we have a setup where we have,
say, 5 JVMs running, only two JVMs will hold this latch.
The first piece is the master code which creates a latch and counts it down.
// create a lock
ICountDownLatch countDownLatch =
[Link]("count_down_1");
[Link]("Setting counter");
[Link](2);
[Link](2000);
[Link]("Counting down");
[Link]();
[Link](2000);
[Link]("Counting down");
[Link]();
[Link](0);
}
32
The second piece is of worker code which creates a latch and counts it down.
// create a lock
ICountDownLatch countDownLatch =
[Link]("count_down_1");
[Link](5000, [Link]);
[Link]("Worker successful");
[Link](0);
}
The output for the code shows that the worker prints only after the countdown was completed to 0.
Setting counter
Counting down
Counting down
Worker successful
Useful Methods
Function Name Description
await() Wait for the latch’s count to reach to zero before proceeding
33
ISet
The [Link] provides an interface for holding collections of objects which are unique.
The ordering of elements does not matter.
Similarly, ISet implements a distributed version of Java Set. It provides similar functions: add,
forEach, etc.
One important point to note about ISet is that, unlike other collection data, it is not partitioned.
All the data is stored/present on a single JVM. Data is still accessible to all JVMs, but the set
cannot be scaled beyond a single machine/JVM.
The set supports synchronous backup as well as asynchronous backup. Synchronous backup
ensures that even if the JVM holding the set goes down, all elements would be preserved and
available from the backup.
The first piece is the producer code which creates a set and adds item to it.
// create a set
ISet<String> hzFruits = [Link]("fruits");
[Link]("Mango");
[Link]("Apple");
[Link]("Banana");
[Link](0);
}
34
The second piece is of consumer code which reads set elements.
// create a set
ISet<String> hzFruits = [Link]("fruits");
[Link](2000);
[Link]([Link]::println);
[Link](0);
}
The output for the code for the producer shows that it is not able to add an existing element.
false
3
The output for the code for the consumer prints set size and the fruits which are can be in a
different order.
3
Banana
Mango
Apple
Useful Methods
35
getPartitionKey() Return the partition key which hold the set
IList
The [Link] provides an interface for holding collections of objects that do not
necessarily need to be unique. The ordering of elements does not matter.
Similarly, IList implements a distributed version of Java List. It provides similar functions: add,
forEach, etc.
All the data which is present in IList is stored/present on a single JVM. Data is still accessible
to all the JVMs, but the list cannot be scaled beyond a single machine/JVM.
The list supports synchronous backup as well as asynchronous backup. Synchronous backup
ensures that even if the JVM holding the list goes down, all the elements would be preserved and
available from the backup.
Let’s execute the following code on 2 JVMs. The producer code on one and consumer code on
other.
The first piece is the producer code which creates a list and adds item to it.
// create a list
IList<String> hzFruits = [Link]("fruits");
[Link]("Mango");
[Link]("Apple");
[Link]("Banana");
36
// adding an existing fruit
[Link]([Link]("Apple"));
[Link](0);
}
The second piece is of consumer code which reads the list elements.
// create a list
IList<String> hzFruits = [Link]("fruits");
[Link](2000);
[Link]([Link]::println);
[Link](0);
}
The output for the code for the producer shows that it is not able to add an existing element.
true
4
The output for the code for the consumer prints the list size and the fruits are in expected order.
4
Mango
Apple
Banana
Apple
37
Useful Methods
IQueue
The [Link] provides an interface which supports threads in a
JVM to produce and consume messages at different rates. The producer blocks based on
available capacity and the consumer blocks for the element to be available in the queue.
Similarly, IQueue extends the BlockingQueue and makes it a distributed version of it. It provides
similar functions: put, take, etc.
One important point to note about IQueue is that, unlike other collections, data is not partitioned.
All the data is stored/present on a single JVM. Data is still accessible to all the JVMs, but the
queue cannot be scaled beyond a single machine/JVM. If the number of elements increases
beyond available memory, an OutOfMemoryException is thrown.
The queue supports synchronous backup as well as asynchronous backup. Synchronous backup
ensures that even if the JVM holding the queue goes down, all the elements would be preserved
and available from the backup.
Let’s execute the following code on 3 JVMs. The producer code on one and 2 consumers code
on others.
38
The first piece is the producer code which creates a queue and adds item to it.
// create a queue
IQueue<String> hzFruits = [Link]("fruits");
[Link](0);
}
while(![Link]()) {
[Link]("Consuming: " + [Link]());
[Link](2000);
}
[Link](0);
}
39
The output for the code for the producer shows that it is not able to add an existing element.
Producing Mango
Producing Apple
Producing Banana
Producing Watermelon
The output for the code for the first consumer shows that it consumes some part of the data.
Consuming Mango
Consuming Banana
The output for the code for the second consumer shows that it consumes the other part of the
data:
Consuming Apple
Consuming Watermelon
Useful Methods
40
IMap
The [Link] provides an interface which supports storing key value pair in
a single JVM. While [Link] extends this to support thread
safety in a single JVM with multiple threads.
Similarly, IMap extends the ConcurrentHashMap and provides an interface which makes the map
thread safe across JVMs. It provides similar functions: put, get etc.
The IMap supports synchronous backup as well as asynchronous backup. Synchronous backup
ensures that even if the JVM holding the queue goes down, all elements would be preserved and
available from the backup.
The first piece is the producer code which creates a map and adds item to it.
// create a map
IMap<String, String> hzStock = [Link]("stock");
[Link]("Mango", "4");
[Link]("Apple", "1");
[Link]("Banana", "7");
[Link]("Watermelon", "10");
[Link](5000);
[Link](0);
}
41
The second piece is of consumer code which reads the elements.
// create a map
IMap<String, String> hzStock = [Link]("stock");
[Link](5000);
[Link](0);
}
Mango:4
Apple:1
Banana:7
Watermelon:10
Useful Methods
42
size() Return the count of elements in the map
Eviction
By default, keys in Hazelcast stay indefinitely in the IMap. If we have a very large set of keys, then
we need to ensure that the keys which are heavily used are stored in the IMap as compared to
the ones which are used less often, in order to have better performance and efficient memory
usage.
For this purpose, one can manually delete keys via remove()/evict() functions for the keys which
are not used that often. However, Hazelcast also provides automatic eviction of keys based on
various eviction algorithms.
This policy can be set by XML or programmatically. Let’s look at an example for the same:
<map name="stock">
<max-size policy="FREE_HEAP_PERCENTAGE">30</max-size>
<eviction-policy>LFU</eviction-policy>
</map>
● Max-size: Policy which is used to communicate to Hazelcast the limit at which we claim
that max size of the map “stock” has reached.
● Eviction-policy: Once the above max-size policy is hit, what algorithm to use to
remove/evict the key.
43
Here are some of the useful max_size policy.
Max number of entries per JVM for the map which is the
PER_NODE
default policy.
USED_HEAP Maximum allowed heap memory used in the JVM (in MBytes)
USED_HEAP_PERCENTAGE Maximum allowed heap memory used in the JVM (in percent)
Another useful parameter for eviction is also time-to-live-seconds, i.e., TTL. With this, we can
ask Hazelcast to remove any key which is older than X seconds. This ensures that we are
proactive in removing older keys before max-size policy is hit.
The IMap instances are divided into multiple partitions. By default, the map is divided into 271
partitions. And these partitions are distributed across Hazelcast members available. Each entry
in which is added to the map is stored in a single partition.
44
Let’s execute this code on 2 JVMs.
// create a map
IMap<String, String> hzStock = [Link]("stock");
[Link]("Mango", "4");
[Link]("Apple", "1");
[Link]("Banana", "7");
[Link]("Watermelon", "10");
[Link](5000);
[Link](0);
}
As seen in the following output, the consumer 1 prints its own partition which contains 2 keys:
Mango
Watermelon
Banana
Apple
By default, IMap has one synchronous backup, which means that even if one node/member goes
down, the data would not get lost. There are two types of back up.
● Synchronous: The [Link](key, value) would not succeed till the key is also backed
up on another node/member. Sync backups are blocking and thus impact the performance
of the put call.
● Async: The backup of the stored key is performed eventually. Async backups are non-blocking
and fast but they do not guarantee existence of the data if a member were to go down.
45
The value can be configured using XML configuration. For example, let's do it for out stock map:
<map name="stock">
<backup-count>1</backup-count>
<async-backup-count>1<async-backup-count>
</map>
@Override
public int hashCode() {
46
final int prime = 31;
int result = 1;
result = prime * result + serialId;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != [Link]())
return false;
Vehicle other = (Vehicle) obj;
if (serialId != [Link])
return false;
return true;
}
}
When we try using the above class as the key for HashMap and IMap, we see the difference in
comparison.
47
// create a hazelcast map
HazelcastInstance hazelcast = [Link]();
IMap<Vehicle, String> hzVehicleOwner = [Link]("owner");
[Link](v1, "John");
[Link]([Link](v2));
[Link](0);
}
Hazelcast serializes the key and stores it as a byte array in binary format. As these keys are
serialized, the comparison cannot be made based on equals() and hashcode().
Serializing and Deserializing are required in case of Hazelcast because the function get(),
containsKey(), etc. may be invoked on the node which does not own the key, so remote call is
required.
Serializing and Deserializng are expensive operations and so, instead of using equals() method,
Hazelcast compares byte arrays.
What this means is that all the attributes of the Vehicle class should match not just id. So, let’s
execute the following code:
[Link](0);
48
}
true
This output means all the attributes of Vehicle should match for equality.
EntryProcessor
EntryProcessor is a construct which supports sending of code to the data instead of bringing data to
the code. It supports serializing, transferring, and the execution of function on the node which owns
the IMap keys instead of bringing in the data to the node which initiates the execution of the function.
Let’s understand this with an example. Let’s say we create an IMap of Vehicle -> Owner. And
now, we want to store lowercase for the owner. So, how do we do that?
[Link](0);
}
john
jane
49
betty
While this code seems simple, it has a major drawback in terms of scale if there are high number
of keys:
Processing would happen on the single/caller node instead of being distributed across nodes.
More time as well as memory would be needed to get the key information on the caller node.
That is where the EntryProcessor helps. We send the function of converting to lowercase to each
node which holds the key. This makes the processing parallel and keeps the memory
requirements in check.
[Link](new OwnerToLowerCaseEntryProcessor());
[Link](0);
}
john
jane
betty
50
51
Hazelcast – Client
Hazelcast clients are the lightweight clients to Hazelcast members. Hazelcast members are
responsible to store data and the partitions. They act like the server in the traditional client-server
model.
Hazelcast clients are created only for accessing data stored with Hazelcast members of the
cluster. They are not responsible to store data and do not take any ownership to store data.
The clients have their own life cycle and do not affect the Hazelcast member instances.
import [Link];
import [Link];
import [Link];
52
Now, run the above class.
<dependency>
<groupId>[Link]</groupId>
<artifactId>hazelcast-client</artifactId>
<version>3.12.12</version>
</dependency>
Let's now create [Link]. Note that similar to Hazelcast members, clients can also be
configured programmatically or via XML configuration (i.e., via -[Link] or
[Link]).
Let’s use the default configuration which means our client would be able to connect to local
instances.
import [Link];
import [Link];
import [Link];
// perform shutdown
[Link]().shutdown();
}
}
53
Now, run the above class.
Honda-9235
Member of cluster: [Member [localhost]:5701 - a47ec375-3105-42cd-96c7-fc5eb382e1b0]
Load Balancing
Hazelcast Client supports load balancing using various algorithms. Load balancing ensures that
the load is shared across members and no single member of the cluster is overloaded. The default
load balancing mechanism is set to round-robin. The same can be changed by using the
loadBalancer tag in the config.
We can specify the type of load balancer using the load-balancer tag in the configuration. Here is
a sample for choosing a strategy that randomly picks up a node.
<hazelcast-client xmlns="[Link]
xmlns:xsi="[Link]
xsi:schemaLocation="[Link]
[Link]
[Link]">
<load-balancer type="random"/>
</hazelcast-client>
Failover
In a distributed environment, members can fail arbitrarily. For supporting failover, it is
recommended that address to multiple members is provided. If the client gets access to any one
54
member, that is sufficient for it to get addressed to other members. The parameters addressList
can be specified in the client configuration.
<hazelcast-client xmlns="[Link]
xmlns:xsi="[Link]
xsi:schemaLocation="[Link]
[Link]
[Link]">
<address-list>machine1, machine2</address-list>
</hazelcast-client>
Even if, say, machine1 goes down, clients can use machine2 to get access to other members of
the cluster.
55
Hazelcast – Serialization
Hazelcast is ideally used in an environment where data/query are distributed across machines.
This requires data to be serialized from our Java objects to a byte array which can be transferred
over the network.
Hazelcast supports various types of Serialization. However, let’s look at some commonly used
ones, i.e., Java Serialization and Java Externalizable.
Java Serialization
First let's look at Java Serialization. Let's say, we define an Employee class with Serializable
interface implemented.
56
@Override
public String toString() {
return "Employee [name=" + name + ", department=" + department + "]";
}
}
Let’s now write code to add Employee object to the Hazelcast map.
A very important aspect here is that simply by implementing a Serializable interface, we can make
Hazelcast use Java Serialization. Also note that Hazelcast stores serialized data for key and value
57
instead of storing it in-memory like HashMap. So, Hazelcast does the heavy-lifting of Serialization
and Deserialization.
However, there is a pitfall here. In the above case, what if the department of the employee
changes? The person is still the same.
58
Output of the above code is:
It is because Hazelcast does not deserialize the key, i.e., Employee while comparison. It directly
compares the bytecode of the serialized key. So, an object with the same value to all the attributes
would be treated the same. But if the value to those attributes changes, for example, department
in the above scenario, those two keys are treated as unique.
Java Externalizable
What if, in the above example, we don't care about the value of the department while performing
serialization/deserialization of keys. Hazelcast also supports Java Externalizable which gives us
control over what tags are used for serialization and deserialization.
@Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
[Link]("Deserializaing....");
[Link] = [Link]();
}
@Override
59
public void writeExternal(ObjectOutput out) throws IOException {
[Link]("Serializing....");
[Link](name);
}
@Override
public String toString() {
return "Employee [name=" + name + ", department=" + department + "]";
}
}
So, as you can see from the code, we have added readExternal/writeExternal methods
which are responsible for serialization/deserialization. Given that we are not interested in the
department while serialization/deserialization, we exclude those in
readExternal/writeExternal methods.
60
Map<EmplyoeeExternalizable, String> employeeOwners =
[Link]("employeeVehicleMap");
Serializing....
Checking if employee with John Smith is present
Serializing....
true
Checking if employee with John Smith is present
Serializing....
true
As the output shows, using Externalizable interface, we can provide Hazelcast with serialized
data for only the name of the employee.
61
Once while storing the key,
And, second for searching the given key in the map. As stated earlier, this is because
Hazelcast uses serialized byte arrays for key comparison.
Overall, using Externalizable has more benefits as compared to Serializable if we want to have
more control over what attributes are to be serialized and how we want to handle them.
62
Hazelcast – Advanced
63
Hazelcast – Spring Integration
Hazelcast supports an easy way to integrate with Spring Boot application. Let's try to understand
that via an example.
We will create a simple API application which provides an API to get employee information for a
company. For this purpose, we will use Spring Boot driven RESTController along with Hazelcast
for caching data.
Note that to integrate Hazelcast in Spring Boot, we will need two things:
Add Hazelcast as a dependency to our project.
Define a configuration (static or programmatic) and make it available to Hazelcast
Let’s first define the POM. Note that we have to specify Hazelcast JAR to use it in the Spring Boot
project.
<properties>
<[Link]>1.8</[Link]>
<[Link]>1.8</[Link]>
</properties>
<parent>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
64
</parent>
<dependencies>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>hazelcast-all</artifactId>
<version>4.0.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>[Link]</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
<hazelcast
xsi:schemaLocation="[Link]
[Link]
xmlns="[Link]
xmlns:xsi="[Link]
<instance-name>XML_Hazelcast_Instance</instance-name>
</hazelcast>
65
Define an entry point file for Spring Boot to use. Ensure that we have @EnableCaching specified:
package [Link];
import [Link];
import [Link];
import [Link];
@EnableCaching
@SpringBootApplication
public class CompanyApplication {
public static void main(String[] args) {
[Link]([Link], args);
}
}
package [Link];
import [Link];
66
public String getName() {
return name;
}
@Override
public String toString() {
return "Employee [empId=" + empId + ", name=" + name + ", department=" +
department + "]";
}
}
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
@RestController
@RequestMapping("/v1/")
class CompanyApplicationController{
@Cacheable(value = "employee")
@GetMapping("employee/{id}")
public Employee getSubscriber(@PathVariable("id") int id) throws
InterruptedException {
[Link]("Finding employee information with id " + id + "
...");
[Link](5000);
67
return new Employee(id, "John Smith", "CS");
}
}
You will notice that the output of the command would contain Hazelcast member information
which mean Hazelcast Instance is automatically configured for us using [Link]
configuration.
..
Members {size:1, ver:1} [
Member [localhost]:5701 - 91b3df1d-a226-428a-bb74-6eec0a6abb14 this
]
.
Now let us execute via curl or use browser to access API:
{
"empId": 5,
"name": "John Smith",
"department": "CS"
}
In the server logs (i.e. where Spring Boot application running), we see the following line:
However, note that it takes almost 5 secs (because of sleep we added) to access the information.
But If we call the API again, the output of the API is immediate. This is because we have specified
@Cacheable notation. The data of our first API call has been cached using Hazelcast as a
backend.
68
Hazelcast – Monitoring
Hazelcast provides multiple ways to monitor the cluster. We will look into how to monitor via REST
API and via JMX. Let's first look into REST API.
<hazelcast
xsi:schemaLocation="[Link]
[Link]
xmlns="[Link]
xmlns:xsi="[Link]
<instance-name>XML_Hazelcast_Instance</instance-name>
<network>
<rest-api enabled="true">
<endpoint-group name="CLUSTER_READ" enabled="true" />
<endpoint-group name="HEALTH_CHECK" enabled="true" />
</rest-api>
</network>
</hazelcast>
69
// do not shutdown, let the server run
//[Link]();
}
}
Once started, the health of the cluster can be found out by calling the API like:
[Link]
Hazelcast::NodeState=ACTIVE
Hazelcast::ClusterState=ACTIVE
Hazelcast::ClusterSafe=TRUE
Hazelcast::MigrationQueueSize=0
Hazelcast::ClusterSize=1
More detailed information about the nodes, for example, IP, port, name can be found using:
[Link]
ConnectionCount: 1
AllConnectionCount: 2
70
JMX monitoring
Hazelcast also supports JMX monitoring of the data structures embedded inside it, for example,
IMap, Iqueue, and so on.
To enable JMX monitoring, we first need to enable JVM based JMX agents. This can be done
by passing "-[Link]" to the JVM. For using different ports or use
authentication, we can use -[Link], -
[Link], respectively.
Apart from this, we have to enable JMX for Hazelcast MBeans. Let us enable JMX based
monitoring via XML configuration in [Link]:
<hazelcast
xsi:schemaLocation="[Link]
[Link]
xmlns="[Link]
xmlns:xsi="[Link]
<instance-name>XML_Hazelcast_Instance</instance-name>
<properties>
<property name="[Link]">true</property>
</properties>
</hazelcast>
Let us create a Hazelcast instance which runs indefinitely in [Link] file and add a map:
71
Now we can execute the following command to enable JMX:
The JMX ports can now be connected by JMX clients like jConsole, VisualVM, etc.
Here is a snapshot of what we will get if we connect using jConsole and see the attributes for
VehicleMap. As we can see, the name of the map as vehicleOwnerMap and the size of map
being 1.
72
Hazelcast – Map Reduce & Aggregations
MapReduce is a computation model which is useful for data processing when you have lots of
data and you need multiple machines, i.e., a distributed environment to calculate data. It involves
'map'ing of data into key-value pairs and then 'reducing', i.e., grouping these keys and performing
operation on the value.
Given the fact that Hazelcast is designed keeping a distributed environment in mind, implementing
Map-Reduce Frameworks comes naturally to it.
For example, let's suppose we have data about a car (brand & car number) and the owner of that car.
Honda-9235, John
Hyundai-235, Alice
Honda-935, Bob
Mercedes-235, Janice
Honda-925, Catnis
Hyundai-1925, Jane
And now, we have to figure out the number of cars for each brand, i.e., Hyundai, Honda, etc.
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
73
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
[Link]("Honda-9235", "John");
[Link]"Hyundai-235", "Alice");
[Link]("Honda-935", "Bob");
[Link]("Mercedes-235", "Janice");
[Link]("Honda-925", "Catnis");
[Link]("Hyundai-1925", "Jane");
74
private static class BrandMapper implements Mapper<String, String, String,
Integer> {
@Override
public void map(String key, String value, Context<String, Integer>
context) {
[Link]([Link]("-", 0)[0], 1);
}
}
@Override
public void reduce(Integer value) {
[Link](value);
}
@Override
public Integer finalizeReduce() {
return [Link]();
}
}
}
1. We create Hazelcast members. In the example, we have a single member, but there can
well be multiple members.
2. We create a map using dummy data and create a Key-Value store out of it.
3. We create a Map-Reduce job and ask it to use the Key-Value store as the data.
75
5. The mapper creates a key, i.e., extracts brand information from the original key and sets
the value to 1 and then emits that information as K-V to the reducer.
6. The reducer simply sums the value, grouping the data, based on key, i.e., brand name.
76
Hazelcast – Collection Listener
Hazelcast supports addition of listeners when a given collection, for example, queue, set, list, etc.
is updated. Typical events include entry added and entry removed.
Let's see how to implement a set listener via an example. So, let's say we want to implement a
listener which tracks the number of elements in a set.
[Link](5000);
// create a set
ISet<String> hzFruits = [Link]("fruits");
[Link]("Mango");
[Link](2000);
[Link]("Apple");
[Link](2000);
[Link]("Banana");
[Link](0);
}
}
77
Now let's implement the listener:
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
// create a set
ISet<String> hzFruits = [Link]("fruits");
[Link](0);
}
@Override
public void itemAdded(ItemEvent<String> item) {
[Link]("item added" + item);
count ++;
[Link]("Total elements" + count);
}
@Override
public void itemRemoved(ItemEvent<String> item) {
count --;
}
}
}
78
We will first run the producer:
79
Hazelcast – Common Pitfalls & Performance Tips
Let’s see how to store and inject references to Hazelcast structures. The following code creates
a map of the name "stock" and adds Mango at one place and Apple at another.
// create a map
IMap<String, String> hzStockTemp = [Link]("stock");
[Link]("Mango", "4");
However, the problem here is that we are using getMap("stock") twice. Although this call seems
harmless in a single node environment, it creates slowness in a clustered environment. The
function call getMap() involves network round trips to other members of the cluster.
So, it is recommended that we store the reference to the map locally and use the referencing
while operating on the map. For example:
80
// create a map
IMap<String, String> hzStock = [Link]("stock");
[Link]("Mango", "4");
[Link]("Apple", "3");
Use monitoring
In a large-scale distributed system, monitoring plays a very important role. Using REST API and
JMX for monitoring is very important for taking proactive measures instead of being reactive.
Homogeneous cluster
Hazelcast assumes all the machines are equal, i.e., all the machines have same resources. But
if our cluster contains a less powerful machine, for example, less memory, lesser CPU power,
etc., then it can create slowness if the computation happens on that machine. Worst, the weaker
machine can run out of resources causing cascading failures. So, it is necessary that Hazelcast
members have equal resource power.
81