«Top»

The Map-based reference implementation
of our example application does not implement any housekeeping, which
will eventually lead to OutOfMemory errors as the
Map
grows.

This part shows how to replace the ConcurrentMap with a Cache and utilize the
Cache’s eviction to limit the amount of memory used.

The following describes the Hazelcast example.

Initialization

In Hazelcast, the role of the CacheManager
is taken by a
Hazelcast
instance:

@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
    Config cfg = new Config();
    HazelcastInstance instance = Hazelcast.newHazelcastInstance(cfg);
    ConcurrentMap<String, UserEventList> map = instance.getMap("events");
    ServletContext context = servletContextEvent.getServletContext();
    context.setAttribute(CACHE, map);
}

Note that Hazelcast’s Cache directly implements ConcurrentMap, which means
that reading and writing data can be done with the the same
code as in the Map-based implementation.

By default, Hazelcast loads its config from a file named hazelcast.xml
in the Classpath. The following shows a simple configuration limiting
the Cache size to 1000 entries:

<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-2.1.xsd"
           xmlns="http://www.hazelcast.com/schema/config"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <map name="events">
        <eviction-policy>LRU</eviction-policy>
        <max-size policy="cluster_wide_map_size">1000</max-size>
        <eviction-percentage>25</eviction-percentage>
    </map>
</hazelcast>

As in the Map-based implementation, the
REST interface gets the ConcurrentMap from the
ServletContext:

// ...
@Context
private ServletContext context;
private ConcurrentMap<String, UserEventList> map;

@PostConstruct
@SuppressWarnings("unchecked")
public void init() {
    map = (ConcurrentMap) context.getAttribute(CACHE);
}

Shutdown

When the Hazelcast
Cache Manager is used for the first time, a Hazelcast member
(HazelcastInstance)
will start automatically. On shutdown, the Hazelcast member must be terminated:

@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
    Hazelcast.shutdownAll();
}

Write

As the Hazelcast cache implements ConcurrentMap, the write code is
one-to-one the same as in the
Map-based reference implementation:

@POST
@Path("{user}")
@Consumes(MediaType.APPLICATION_JSON)
public void appendEvent(@PathParam("user") String user, String msg) {
    boolean success;
    map.putIfAbsent(user, UserEventList.emptyList());
    do {
        UserEventList oldMsgList = map.get(user);
        UserEventList newMsgList = UserEventList.append(oldMsgList, msg);
        success = map.replace(user, oldMsgList, newMsgList);
    }
    while ( ! success );
}

Read

Like the write method, read is also a one-to-one copy of the
Map-based implementation:

@GET
@Path("{user}")
@Produces(MediaType.APPLICATION_JSON)
public List<String> searchByUser(@PathParam("user") String user) {
    UserEventList result = map.get(user);
    if ( result == null ) {
        return new ArrayList<>();
    }
    return result.getMessages();
}

Dependencies

<dependency>
    <groupId>com.hazelcast</groupId>
    <artifactId>hazelcast</artifactId>
    <version>2.5</version>
</dependency>

How To Run

Our example code is hosted on GitHub.
The example can be run with maven:

mvn tomcat7:run-war -pl part02.hazelcast -am verify

The Web interface is then accessible via
http://localhost:8080.

Note on Performance

As the local-only deployment involves no network interaction, the
performance is mainly influenced by the speed of serialization.

Unlike Ehcache, Hazelcast always performs serialization, which makes
Hazelcast not a good choice for single node caching.

Next

This page showed how the ConcurrentMap in our example can be replaced with
a local Hazelcast instance. The last page of part 02 will show how the same
can be done with Infinispan:

Monitoring-Workshop 2017 12./13.9. Düsseldorf