Table of Contents
Common Features of all Hazelcast Data Structures:
Data in the cluster is almost evenly distributed (partitioned) across all nodes.
So each node carries ~ (1/n
*
total-data) + backups , n being the
number of nodes in the cluster.
If a member goes down, its backup replica that also holds the same data, will dynamically redistribute the data including the ownership and locks on them to remaining live nodes. As a result, no data will get lost.
When a new node joins the cluster, new node takes ownership(responsibility) and
load of -some- of the entire data in the cluster. Eventually the new node will carry
almost (1/n
*
total-data) + backups and becomes the new partition
reducing the load on others.
There is no single cluster master or something that can cause single point of failure. Every node in the cluster has equal rights and responsibilities. No-one is superior. And no dependency on external 'server' or 'master' kind of concept.
Here is how you can retrieve existing data structure instances (map, queue, set, lock, topic, etc.) and how you can listen for instance events to get notified when an instance is created or destroyed.
import java.util.Collection; import com.hazelcast.config.Config; import com.hazelcast.core.*; public class Sample implements DistributedObjectListener { public static void main(String[] args) { Sample sample = new Sample(); Config cfg = new Config(); HazelcastInstance hz = Hazelcast.newHazelcastInstance(cfg); hz.addDistributedObjectListener(sample); Collection<DistributedObject> distributedObjects = hz.getDistributedObjects(); for (DistributedObject distributedObject : distributedObjects) { System.out.println(distributedObject.getName() + "," + distributedObject.getId()); } } @Override public void distributedObjectCreated(DistributedObjectEvent event) { DistributedObject instance = event.getDistributedObject(); System.out.println("Created " + instance.getName() + "," + instance.getId()); } @Override public void distributedObjectDestroyed(DistributedObjectEvent event) { DistributedObject instance = event.getDistributedObject(); System.out.println("Destroyed " + instance.getName() + "," + instance.getId()); } }
Hazelcast will partition your map entries; and almost evenly
distribute onto all Hazelcast members. Distributed maps have 1 backup by
default so that if a member goes down, we don't lose data. Backup operations are synchronous
so when a
map.put(key, value)
returns, it is guaranteed that the entry is
replicated to one other node. For the reads, it is also guaranteed that
map.get(key)
returns the latest value of the entry. Consistency is
strictly enforced.
import com.hazelcast.core.Hazelcast; import java.util.Map; import java.util.Collection; import com.hazelcast.config.Config; Config cfg = new Config(); HazelcastInstance hz = Hazelcast.newHazelcastInstance(cfg); Map<String, Customer> mapCustomers = hz.getMap("customers"); mapCustomers.put("1", new Customer("Joe", "Smith")); mapCustomers.put("2", new Customer("Ali", "Selam")); mapCustomers.put("3", new Customer("Avi", "Noyan")); Collection<Customer> colCustomers = mapCustomers.values(); for (Customer customer : colCustomers) { // process customer }
Hazelcast.getMap()
actually returns
com.hazelcast.core.IMap
which extends
java.util.concurrent.ConcurrentMap
interface. So methods like
ConcurrentMap.putIfAbsent(key,value)
and
ConcurrentMap.replace(key,value)
can be used on distributed map as
shown in the example below.
import com.hazelcast.core.Hazelcast; import java.util.concurrent.ConcurrentMap; Customer getCustomer (String id) { ConcurrentMap<String, Customer> map = Hazelcast.getMap("customers"); Customer customer = map.get(id); if (customer == null) { customer = new Customer (id); customer = map.putIfAbsent(id, customer); } return customer; } public boolean updateCustomer (Customer customer) { ConcurrentMap<String, Customer> map = Hazelcast.getMap("customers"); return (map.replace(customer.getId(), customer) != null); } public boolean removeCustomer (Customer customer) { ConcurrentMap<String, Customer> map = Hazelcast.getMap("customers"); return map.remove(customer.getId(), customer) ); }
All
ConcurrentMap
operations such as
put
and
remove
might wait if the key is locked by another thread in the local
or remote JVM, but they will eventually return with success.
ConcurrentMap
operations never
throwjava.util.ConcurrentModificationException
.
Also see:
Distributed Map internals.
Hazelcast will distribute map entries onto multiple JVMs (cluster members). Each JVM
holds some portion of the data but we don't want to lose data when a member JVM crashes.
To provide data-safety, Hazelcast allows you to specify the number of backup copies you
want to have. That way data on a JVM will be copied onto other JVM(s). Hazelcast supports both
sync
and async
backups.
Sync
backups block operations until backups are successfully copied to
backups nodes (or deleted from backup nodes in case of remove)
and acknowledgements are received. In contrast, async
backups do not block
operations, they are fire & forget and do not require acknowledgements.
By default, Hazelcast will have one sync backup copy.
If backup count >= 1, then each member will carry both owned entries and backup copies of other
member(s). So for the map.get(key)
call, it is possible that calling member has backup
copy of that key but by default, map.get(key)
will always read the
value from the actual owner of the key for consistency. It is possible to enable backup
reads by changing the configuration. Enabling backup reads will give you greater performance.
<hazelcast> ... <map name="default"> <!-- Number of sync-backups. If 1 is set as the backup-count for example, then all entries of the map will be copied to another JVM for fail-safety. Valid numbers are 0 (no backup), 1, 2, 3. --> <backup-count>1</backup-count> <!-- Number of async-backups. If 1 is set as the backup-count for example, then all entries of the map will be copied to another JVM for fail-safety. Valid numbers are 0 (no backup), 1, 2, 3. --> <async-backup-count>1</async-backup-count> <!-- Can we read the local backup entries? Default value is false for strong consistency. Being able to read backup data will give you greater performance. --> <read-backup-data>false</read-backup-data> ... </map> </hazelcast>
Hazelcast also supports policy based eviction for distributed map. Currently supported
eviction policies are LRU (Least Recently Used) and LFU (Least Frequently Used). This
feature enables Hazelcast to be used as a distributed cache. If
time-to-live-seconds
is not 0 then entries older than
time-to-live-seconds
value will get evicted, regardless of the
eviction policy set. Here is a sample configuration for eviction:
<hazelcast> ... <map name="default"> <!-- Number of backups. If 1 is set as the backup-count for example, then all entries of the map will be copied to another JVM for fail-safety. Valid numbers are 0 (no backup), 1, 2, 3. --> <backup-count>1</backup-count> <!-- Maximum number of seconds for each entry to stay in the map. Entries that are older than <time-to-live-seconds> and not updated for <time-to-live-seconds> will get automatically evicted from the map. Any integer between 0 and Integer.MAX_VALUE. 0 means infinite. Default is 0. --> <time-to-live-seconds>0</time-to-live-seconds> <!-- Maximum number of seconds for each entry to stay idle in the map. Entries that are idle(not touched) for more than <max-idle-seconds> will get automatically evicted from the map. Entry is touched if get, put or containsKey is called. Any integer between 0 and Integer.MAX_VALUE. 0 means infinite. Default is 0. --> <max-idle-seconds>0</max-idle-seconds> <!-- Valid values are: NONE (no extra eviction, <time-to-live-seconds> may still apply), LRU (Least Recently Used), LFU (Least Frequently Used). NONE is the default. Regardless of the eviction policy used, <time-to-live-seconds> will still apply. --> <eviction-policy>LRU</eviction-policy> <!-- Maximum size of the map. When max size is reached, map is evicted based on the policy defined. Any integer between 0 and Integer.MAX_VALUE. 0 means Integer.MAX_VALUE. Default is 0. --> <max-size policy="PER_NODE">5000</max-size> <!-- When max. size is reached, specified percentage of the map will be evicted. Any integer between 0 and 100. If 25 is set for example, 25% of the entries will get evicted. --> <eviction-percentage>25</eviction-percentage> </map> </hazelcast>
Max-Size Policies
There are 4 defined policies can be used in max-size configuration.
PER_NODE: Max map size per instance.
<max-size policy="PER_NODE">5000</max-size>
PER_PARTITION: Max map size per each partition.
<max-size policy="PER_PARTITION">27100</max-size>
USED_HEAP_SIZE: Max used heap size in MB (mega-bytes) per JVM.
<max-size policy="USED_HEAP_SIZE">4096</max-size>
USED_HEAP_PERCENTAGE: Max used heap size percentage per JVM.
<max-size policy="USED_HEAP_PERCENTAGE">75</max-size>
Hazelcast allows you to load and store the distributed map entries from/to a
persistent datastore such as relational database. If a loader implementation is
provided, when
get(key)
is called, if the map entry doesn't exist
in-memory then Hazelcast will call your loader implementation to load the entry from a
datastore. If a store implementation is provided, when
put(key,value)
is called, Hazelcast will call your store implementation to store the entry into a
datastore. Hazelcast can call your implementation to store the entries synchronously
(write-through) with no-delay or asynchronously (write-behind) with delay and it is
defined by the
write-delay-seconds
value in the configuration.
If it is write-through, when the
map.put(key,value)
call returns,
you can be sure that
MapStore.store(key,value)
is successfully called so the
entry is persisted.
In-Memory entry is updated
In-Memory backup copies are successfully created on other JVMs (if backup-count is greater than 0)
If it is write-behind, when the
map.put(key,value)
call returns, you can be sure that
In-Memory entry is updated
In-Memory backup copies are successfully created on other JVMs (if backup-count is greater than 0)
The entry is marked as
dirty
so that after
write-delay-seconds
, it can be persisted.
Same behavior goes for the
remove(key
and
MapStore.delete(key)
. If
MapStore
throws an
exception then the exception will be propagated back to the original
put
or
remove
call in the form of
RuntimeException
. When write-through is used, Hazelcast will call
MapStore.store(key,value)
and
MapStore.delete(key)
for each entry update. When write-behind is
used, Hazelcast will callMapStore.store(map)
, and
MapStore.delete(collection)
to do all writes in a single call.
Also note that your MapStore or MapLoader implementation should not use Hazelcast
Map/Queue/MultiMap/List/Set operations. Your implementation should only work with your
data store. Otherwise you may get into deadlock situations.
Here is a sample configuration:
<hazelcast> ... <map name="default"> ... <map-store enabled="true"> <!-- Name of the class implementing MapLoader and/or MapStore. The class should implement at least of these interfaces and contain no-argument constructor. Note that the inner classes are not supported. --> <class-name>com.hazelcast.examples.DummyStore</class-name> <!-- Number of seconds to delay to call the MapStore.store(key, value). If the value is zero then it is write-through so MapStore.store(key, value) will be called as soon as the entry is updated. Otherwise it is write-behind so updates will be stored after write-delay-seconds value by calling Hazelcast.storeAll(map). Default value is 0. --> <write-delay-seconds>0</write-delay-seconds> </map-store> </map> </hazelcast>
Initialization on startup:
MapLoader.loadAllKeys
API is used for pre-populating the
in-memory map when the map is first touched/used. If
MapLoader.loadAllKeys
returns NULL then nothing will be loaded.
Your
MapLoader.loadAllKeys
implementation can return all or some of the keys. You may
select and return only the
hot
keys, for instance. Also note that
this is the fastest way of pre-populating the map as Hazelcast will optimize the loading
process by having each node loading owned portion of the entries.
Here is MapLoader initialization flow;
When
getMap()
first called from any node, initialization starts
Hazelcast will call
MapLoader.loadAllKeys()
to get all your keys on each
node
Each node will figure out the list of keys it owns
Each node will load all its owned keys by calling
MapLoader.loadAll(keys)
Each node puts its owned entries into the map by
calling
IMap.putTransient(key,value)
Hazelcast partitions your data and spreads across cluster of servers. You can surely iterate over the map entries and look for certain entries you are interested in but this is not very efficient as you will have to bring entire entry set and iterate locally. Instead, Hazelcast allows you to run distributed queries on your distributed map.
Let's say you have a "employee" map containing values of
Employee
objects:
import java.io.Serializable; public class Employee implements Serializable { private String name; private int age; private boolean active; private double salary; public Employee(String name, int age, boolean live, double price) { this.name = name; this.age = age; this.active = live; this.salary = price; } public Employee() { } public String getName() { return name; } public int getAge() { return age; } public double getSalary() { return salary; } public boolean isActive() { return active; } }
Now you are looking for the employees who are active and with age less than 30. Hazelcast allows you to find these entries in two different ways:
Distributed SQL Query
SqlPredicate
takes regular SQL where clause. Here is an example:
import com.hazelcast.core.IMap; import com.hazelcast.query.SqlPredicate; Config cfg = new Config(); HazelcastInstance hz = Hazelcast.newHazelcastInstance(cfg); IMap map = hz.getMap("employee"); Set<Employee> employees = (Set<Employee>) map.values(new SqlPredicate("active AND age < 30"));
Supported SQL syntax:
AND/OR
<expression> AND <expression> AND
<expression>...
active AND age>30
active=false OR age = 45 OR name =
'Joe'
active AND (age >20 OR salary <
60000)
=, !=, <, <=, >, >=
<expression> = value
age <= 30
name ="Joe"
salary != 50000
BETWEEN
<attribute> [NOT] BETWEEN <value1> AND
<value2>
age BETWEEN 20 AND 33 (same as age >=20
AND age<=33)
age NOT BETWEEN 30 AND 40 (same as age
<30 OR age>40)
LIKE
<attribute> [NOT] LIKE 'expression'
%
(percentage sign) is placeholder for many
characters,
_
(underscore) is placeholder for
only one character.
name LIKE 'Jo%'
(true for 'Joe',
'Josh', 'Joseph' etc.)
name LIKE 'Jo_'
(true for 'Joe';
false for 'Josh')
name NOT LIKE 'Jo_'
(true for
'Josh'; false for 'Joe')
name LIKE 'J_s%'
(true for
'Josh', 'Joseph'; false 'John', 'Joe')
IN
<attribute> [NOT] IN (val1, val2,
...)
age IN (20, 30, 40)
age NOT IN (60, 70)
Examples:
active AND (salary >= 50000 OR (age NOT BETWEEN 20 AND
30))
age IN (20, 30, 40) AND salary BETWEEN (50000, 80000)
Criteria API
If SQL is not enough or programmable queries are preferred then JPA criteria like API can be used. Here is an example:
import com.hazelcast.core.IMap;
import com.hazelcast.query.Predicate;
import com.hazelcast.query.PredicateBuilder;
import com.hazelcast.query.EntryObject;
import com.hazelcast.config.Config;
Config cfg = new Config();
HazelcastInstance hz = Hazelcast.newHazelcastInstance(cfg);
IMap map = hz.getMap("employee");
EntryObject e = new PredicateBuilder().getEntryObject();
Predicate predicate = e.is("active").and(e.get("age").lessThan(30));
Set<Employee> employees = (Set<Employee>) map.values(predicate);
Hazelcast distributed queries will run on each member in parallel and only results
will return the conn. When a query runs on a member, Hazelcast will iterate through
the entire owned entries and find the matching ones. Can we make this even faster? Yes
by indexing the mostly queried fields. Just like you would do for your database. Of
course, indexing will add overhead for each
write
operation but
queries will be a lot faster. If you are querying your map a lot then make sure to add
indexes for most frequently queried fields. So if your
active and age <
30
query, for example, is used a lot then make sure you add index for
active
and
age
fields. Here is how:
IMap imap = Hazelcast.getMap("employees"); imap.addIndex("age", true); // ordered, since we have ranged queries for this field imap.addIndex("active", false); // not ordered, because boolean field cannot have range
API
IMap.addIndex(fieldName, ordered)
is used for adding
index. For a each indexed field, if you have -ranged- queries such
asage>30
,
age BETWEEN 40 AND 60
then
ordered
parameter should betrue
, otherwise set
it tofalse
.
Also you can define
IMap
indexes in configuration.
Hazelcast XML configuration
<map name="default"> ... <indexes> <index ordered="false">name</index> <index ordered="true">age</index> </indexes> </map>
Config API
mapConfig.addMapIndexConfig(new MapIndexConfig("name", false)); mapConfig.addMapIndexConfig(new MapIndexConfig("age", true));
Spring XML configuration
<hz:map name="default"> <hz:indexes> <hz:index attribute="name"/> <hz:index attribute="age" ordered="true"/> </hz:indexes> </hz:map>
One of the new features of version 3.0 is the continuous query. You can listen map entry events providing a predicate and so event will be fired for each entry validated by your query. IMap has a single method for listening map providing query.
/** * Adds an continuous entry listener for this map. Listener will get notified * for map add/remove/update/evict events filtered by given predicate. * * @param listener entry listener * @param predicate predicate for filtering entries */ void addEntryListener(EntryListener<K, V> listener, Predicate<K, V> predicate, K key, boolean includeValue);
Starting with version 3.0, Hazelcast supports entry processing. The interface EntryProcessor gives you the ability to execute your code on an entry in an atomic way. You do not need any explicit lock on entry. Practically, hazelcast locks the entry runs the EntryProcessor, then unlocks the entry. If entry processing is the major operation for a map and the map consists of complex objects then using Object type as in-memory-format is recommended to minimize serialization cost.
There are two methods in IMap interface for entry processing:
/** * Applies the user defined EntryProcessor to the entry mapped by the key. * Returns the the object which is result of the process() method of EntryProcessor. * <p/> * * @return result of entry process. */ Object executeOnKey(K key, EntryProcessor entryProcessor); /** * Applies the user defined EntryProcessor to the all entries in the map. * Returns the results mapped by each key in the map. * <p/> * */ Map<K,Object> executeOnAllKeys(EntryProcessor entryProcessor);
Using executeOnAllKeys method, if the number of entries is high and you do need the results then returing null in process(..) method is a good practice.
Here EntryProcessor interface:
public interface EntryProcessor<K, V> extends Serializable { Object process(Map.Entry<K, V> entry); EntryBackupProcessor<K, V> getBackupProcessor(); }
If your code is modifying the data then you should also provide a processor for backup entries:
public interface EntryBackupProcessor<K, V> extends Serializable { void processBackup(Map.Entry<K, V> entry); }
Example Usage:
public class EntryProcessorTest { @Test public void testMapEntryProcessor() throws InterruptedException { Config cfg = new Config(); cfg.getMapConfig("default").setInMemoryFormat(MapConfig.InMemoryFormat.OBJECT); HazelcastInstance instance1 = Hazelcast.newHazelcastInstance(cfg); HazelcastInstance instance2 = Hazelcast.newHazelcastInstance(cfg); IMap<Integer, Integer> map = instance1.getMap("testMapEntryProcessor"); map.put(1, 1); EntryProcessor entryProcessor = new IncrementorEntryProcessor(); map.executeOnKey(1, entryProcessor); assertEquals(map.get(1), (Object) 2); instance1.getLifecycleService().shutdown(); instance2.getLifecycleService().shutdown(); } @Test public void testMapEntryProcessorAllKeys() throws InterruptedException { StaticNodeFactory nodeFactory = new StaticNodeFactory(2); Config cfg = new Config(); cfg.getMapConfig("default").setInMemoryFormat(MapConfig.InMemoryFormat.OBJECT); HazelcastInstance instance1 = nodeFactory.newHazelcastInstance(cfg); HazelcastInstance instance2 = nodeFactory.newHazelcastInstance(cfg); IMap<Integer, Integer> map = instance1.getMap("testMapEntryProcessorAllKeys"); int size = 100; for (int i = 0; i < size; i++) { map.put(i, i); } EntryProcessor entryProcessor = new IncrementorEntryProcessor(); Map<Integer, Object> res = map.executeOnAllKeys(entryProcessor); for (int i = 0; i < size; i++) { assertEquals(map.get(i), (Object) (i+1)); } for (int i = 0; i < size; i++) { assertEquals(map.get(i)+1, res.get(i)); } instance1.getLifecycleService().shutdown(); instance2.getLifecycleService().shutdown(); } static class IncrementorEntryProcessor implements EntryProcessor, EntryBackupProcessor, Serializable { public Object process(Map.Entry entry) { Integer value = (Integer) entry.getValue(); entry.setValue(value + 1); return value + 1; } public EntryBackupProcessor getBackupProcessor() { return IncrementorEntryProcessor.this; } public void processBackup(Map.Entry entry) { entry.setValue((Integer) entry.getValue() + 1); } } }
Another new feature with version 3.0 is the interceptors. You can add intercept operations and execute your own business logic synchronously blocking the operation. You can change the returned value from a get operation, change the value to be put or cancel operations by throwing exception.
Interceptors are different from listeners as with listeners you just take an action after the operation has been completed. Interceptor actions are synchronous and you can alter the behaviour of operation, change the values or totally cancel it.
IMap API has two method for adding and removing interceptor to the map.
/** * Adds an interceptor for this map. Added interceptor will intercept operations * and execute user defined methods and will cancel operations if user defined method throw exception. * <p/> * * @param interceptor map interceptor * @return id of registered interceptor */ String addInterceptor(MapInterceptor interceptor); /** * Removes the given interceptor for this map. So it will not intercept operations anymore. * <p/> * * @param id registration id of map interceptor */ void removeInterceptor(String id);
Here MapInterceptor interface:
public interface MapInterceptor extends Serializable { /** * Intercept get operation before returning value. * Return another object to change the return value of get(..) * Returning null will cause the get(..) operation return original value, namely return null if you do not want to change anything. * <p/> * * @param value the original value to be returned as the result of get(..) operation * @return the new value that will be returned by get(..) operation */ Object interceptGet(Object value); /** * Called after get(..) operation is completed. * <p/> * * @param value the value returned as the result of get(..) operation */ void afterGet(Object value); /** * Intercept put operation before modifying map data. * Return the object to be put into the map. * Returning null will cause the put(..) operation to operate as expected, namely no interception. * Throwing an exception will cancel the put operation. * <p/> * * @param oldValue the value currently in map * @param newValue the new value to be put * @return new value after intercept operation */ Object interceptPut(Object oldValue, Object newValue); /** * Called after put(..) operation is completed. * <p/> * * @param value the value returned as the result of put(..) operation */ void afterPut(Object value); /** * Intercept remove operation before removing the data. * Return the object to be returned as the result of remove operation. * Throwing an exception will cancel the remove operation. * <p/> * * @param removedValue the existing value to be removed * @return the value to be returned as the result of remove operation */ Object interceptRemove(Object removedValue); /** * Called after remove(..) operation is completed. * <p/> * * @param value the value returned as the result of remove(..) operation */ void afterRemove(Object value); }
Example Usage:
public class InterceptorTest { final String mapName = "map"; @Test public void testMapInterceptor() throws InterruptedException { Config cfg = new Config(); HazelcastInstance instance1 = Hazelcast.newHazelcastInstance(cfg); HazelcastInstance instance2 = Hazelcast.newHazelcastInstance(cfg); final IMap<Object, Object> map = instance1.getMap("testMapInterceptor"); SimpleInterceptor interceptor = new SimpleInterceptor(); map.addInterceptor(interceptor); map.put(1, "New York"); map.put(2, "Istanbul"); map.put(3, "Tokyo"); map.put(4, "London"); map.put(5, "Paris"); map.put(6, "Cairo"); map.put(7, "Hong Kong"); try { map.remove(1); } catch (Exception ignore) { } try { map.remove(2); } catch (Exception ignore) { } assertEquals(map.size(), 6); assertEquals(map.get(1), null); assertEquals(map.get(2), "ISTANBUL:"); assertEquals(map.get(3), "TOKYO:"); assertEquals(map.get(4), "LONDON:"); assertEquals(map.get(5), "PARIS:"); assertEquals(map.get(6), "CAIRO:"); assertEquals(map.get(7), "HONG KONG:"); map.removeInterceptor(interceptor); map.put(8, "Moscow"); assertEquals(map.get(8), "Moscow"); assertEquals(map.get(1), null); assertEquals(map.get(2), "ISTANBUL"); assertEquals(map.get(3), "TOKYO"); assertEquals(map.get(4), "LONDON"); assertEquals(map.get(5), "PARIS"); assertEquals(map.get(6), "CAIRO"); assertEquals(map.get(7), "HONG KONG"); } static class SimpleInterceptor implements MapInterceptor, Serializable { @Override public Object interceptGet(Object value) { if(value == null) return null; return value + ":"; } @Override public void afterGet(Object value) { } @Override public Object interceptPut(Object oldValue, Object newValue) { return newValue.toString().toUpperCase(); } @Override public void afterPut(Object value) { } @Override public Object interceptRemove(Object removedValue) { if(removedValue.equals("ISTANBUL")) throw new RuntimeException("you can not remove this"); return removedValue; } @Override public void afterRemove(Object value) { // do something } } }
Map entries in Hazelcast are partitioned across the cluster. Imagine that you are
reading key
k
so many times and
k
is owned by another member in your cluster. Each
map.get(k)
will
be a remote operation; lots of network trips.
If you have a map that is read-mostly then you should consider creating a
Near Cache
for the map so that reads can be much faster and consume less network traffic.
All these benefits don't come free. When using near cache, you should consider the following issues:
JVM will have to hold extra cached data so it will increase the memory consumption.
If invalidation is turned on and entries are updated frequently, then invalidations will be costly.
Near cache breaks the strong consistency guarantees; you might be reading stale data.
Near cache is highly recommended for the maps that are read-mostly. Here is a near-cache configuration for a map :
<hazelcast> ... <map name="my-read-mostly-map"> ... <near-cache> <!-- Maximum size of the near cache. When max size is reached, cache is evicted based on the policy defined. Any integer between 0 and Integer.MAX_VALUE. 0 means Integer.MAX_VALUE. Default is 0. --> <max-size>5000</max-size> <!-- Maximum number of seconds for each entry to stay in the near cache. Entries that are older than <time-to-live-seconds> will get automatically evicted from the near cache. Any integer between 0 and Integer.MAX_VALUE. 0 means infinite. Default is 0. --> <time-to-live-seconds>0</time-to-live-seconds> <!-- Maximum number of seconds each entry can stay in the near cache as untouched (not-read). Entries that are not read (touched) more than <max-idle-seconds> value will get removed from the near cache. Any integer between 0 and Integer.MAX_VALUE. 0 means Integer.MAX_VALUE. Default is 0. --> <max-idle-seconds>60</max-idle-seconds> <!-- Valid values are: NONE (no extra eviction, <time-to-live-seconds> may still apply), LRU (Least Recently Used), LFU (Least Frequently Used). NONE is the default. Regardless of the eviction policy used, <time-to-live-seconds> will still apply. --> <eviction-policy>LRU</eviction-policy> <!-- Should the cached entries get evicted if the entries are changed (updated or removed). true of false. Default is true. --> <invalidate-on-change>true</invalidate-on-change> </near-cache> </map> </hazelcast>
Hazelcast keeps extra information about each map entry such as creationTime, lastUpdateTime, lastAccessTime,
number of hits, version, and this information is exposed to the developer via
IMap.getMapEntry(key)
call. Here is
an example:
import com.hazelcast.core.Hazelcast; import com.hazelcast.core.EntryView; Config cfg = new Config(); HazelcastInstance hz = Hazelcast.newHazelcastInstance(cfg); EntryView entry = hz.getMap("quotes").getEntryView("1"); System.out.println ("size in memory : " + entry.getCost(); System.out.println ("creationTime : " + entry.getCreationTime(); System.out.println ("expirationTime : " + entry.getExpirationTime(); System.out.println ("number of hits : " + entry.getHits(); System.out.println ("lastAccessedTime: " + entry.getLastAccessTime(); System.out.println ("lastUpdateTime : " + entry.getLastUpdateTime(); System.out.println ("version : " + entry.getVersion(); System.out.println ("key : " + entry.getKey(); System.out.println ("value : " + entry.getValue();
With version 3.0, in-memory-format configuration option has been added to distributed map. By default Hazelcast stores data into memory in binary (serialized) format. But sometimes it can be efficient to store the entries in their objects form especially in cases of local processing like entry processor and queries. Setting in-memory-format in map's configuration, you can decide how the data will be store in memory. There are three options.
BINARY (default):This is the default option. The data will be stored in serialized binary format.
OBJECT:The data will be stored in de-serialized form. This configuration is good for maps where entry processing and queries form the majority of all operations and the objects are complex ones so serialization cost is respectively high. By storing objects, entry processing will not contain the de-serialization cost.
CACHED:This option is useful if your map's main use case is the queries. Internally data is in both binary and de-serialized form so queries do not handle serialization.
To learn about wildcard configuration feature, see Wildcard Configuration page.