Chapter 3. Going Concurrent
Along with the simple distributed collections offered, Hazelcast also provides us with additional complementary capabilities, allowing us to further parallelize our applications. Some of these features come as standard within more traditional data stores, while others are inspired by similar technologies. In this chapter we will look at:
Atomic and consistent nature of simple collections
Distributed locking to provide a cluster wide mutex
Transactional support to cater for more complex operations
Cluster-wide atomic ID generator
JMS-like topics for broadcast messaging (publish, subscribe)
When interacting with Hazelcast's distributed collections, we set and retrieve data in a consistent and atomic way. In that when we modify an entry, it is immediately available on other nodes irrespective of their processing state. This does mean that we have to be careful when developing our applications, as data may change underneath us while performing an operation. However, it is this default lockless nature that significantly increases application scalability, especially under load. Two of the collections we have previously looked at additionally implement specific atomic capabilities provided by the java.util.concurrent
interfaces.
As we've previously seen, the distributed map collection provided by Hazelcast is defined by its own IMap
class. This actually extends ConcurrentMap
, which will provide us with additional atomic operations such as putIfAbsent(key, value)
and replace(key, oldValue, newValue)
. These capabilities may go some way to prevent any concurrent modification...
In building a broad scalable application, one aspect we tend to lose is our ability to restrict and prevent concurrent activity. Within a single JVM we would use a synchronized
lock to gatekeeper, a section of functionality from concurrent execution. Once we move away from a single JVM, this problem becomes a much bigger issue. Traditional approaches would leverage a transactional database to provide a system for locking, in the form of a table rowlock or transactional state. However, this approach presents us with a single point of failure and concurrency issues when scaling up our application.
Hazelcast offers a distributed locking facility, allowing us to attempt to acquire a cluster-wide named lock and to gatekeeper the functionality behind it. If we can create an example class LockingExample
, we can demonstrate this ability.
Transactionally rolling on
Now that we have looked at the simple atomic approach we can take when dealing with the concurrency of consumption and changes to the persisted data, what happens if this is just too simple for our use case? Well, now that we have the ability to lock both globally across the cluster and on individual data items, we can prevent unexpected changes to our supporting data in the middle of an operation. But if we needed to stop and undo changes we had made part way through an operation, how might we achieve that?
Luckily, drawing on inspiration from traditional roots, Hazelcast provides us with transactional capabilities. Offering a REPEATABLE_READ
transaction isolation (the only transactional mode currently supported), once you enter a transaction, Hazelcast will automatically acquire the appropriate key locks for each entry that is interacted with; any changes we write will be buffered locally until the transaction is complete. If the transaction was successful and...
The final collection capability offered by Hazelcast is a broadcast messaging system. This is very much inspired by JMS topics and offers a comparable set of features, in that, we can publish events on to messaging bus to deliver to a large number of subscribed receivers.
As we can see in the following diagram, an application can publish a message onto a topic that will then be distributed across over to all instances of our application who have subscribed to the topic. This will include the instance that originally sent the message in the first place, assuming it too has a listener subscribed to the topic.
First things first, we'll need a MessageListener
class to handle messages, implementing an onMessage(Message<T>)
method as required.
Let's create a class to broadcast...
We have now expanded our awareness of all the data storage and distribution collections offered by Hazelcast. Additionally we have learned about the default atomic nature of data concurrency but also the mechanisms to combat, and gatekeeper concurrency, should our application demand great degrees of control over the data flow. Finally, we have discovered comparable versions of features found in traditional alternatives, as well as offered by other Java technologies. By now we are finding out how extensive and flexible Hazelcast can be. While we've now touched on most of the basics, there is plenty more detail left to be discovered.
Now we have discovered the various types of collections that we have available for us to use in our applications, in the next chapter we shall look at how Hazelcast splits and shares the data around to unlock its incredible scalability.