Hazelcast IMap TTL Expiry - hazelcast

How to invoke a method to sync the data to some DB or Kafka once the TTL set is expired during the put method of IMap class.
eg:IMap.put(key,value,TTL,TimeUnit.SECONDS);
if the above TTL is set to like 10 seconds i must call some store or some mechanism where i could sync that key and value to DB or Kafka in real time. As of now when i tried the store method it is immediately calling the method instead of 10 seconds wait time.

You may set an EntryExpiredListener to your map config.
It feeds on two sources of expiration based eviction, they are max-idle-seconds and time-to-live-seconds.
Example Listener class:
#Slf4j
public class MyExpiredEntryListener implements EntryExpiredListener<String, String>, MapListener {
#Override
public void entryExpired(EntryEvent<String, String> event) {
log.info("entry Expired {}", event);
}
}
You can add this config via programmatically or you may set mapconfig via xml config file.
Example usage:
public static void main(String[] args) {
Config config = new Config();
MapConfig mapConfig = config.getMapConfig("myMap");
mapConfig.setTimeToLiveSeconds(10);
mapConfig.setEvictionPolicy(EvictionPolicy.RANDOM);
HazelcastInstance hz = Hazelcast.newHazelcastInstance(config);
IMap<String, String> map = hz.getMap("myMap");
map.addEntryListener(new MyExpiredEntryListener(), true);
for (int i = 0; i < 100; i++) {
String uuid = UUID.randomUUID().toString();
map.put(uuid, uuid);
}
}
You will see the logs like below when running this implementation.
entry Expired EntryEvent{entryEventType=EXPIRED, member=Member [192.168.1.1]:5701 - ca76c6d8-abe0-4efe-a6a6-24330657675b this, name='myMap', key=70ee594c-ffea-4584-aefe-1148b9fcdf9f, oldValue=70ee594c-ffea-4584-aefe-1148b9fcdf9f, value=null, mergingValue=null}
Also, you can use other entry listeners according to your requirements.

Related

Is there a way to modify a JCache's expiry policy that has been declared declaratively?

I'm using Hazelcast 3.12 jCache implementation. My cache has been declared declaratively (via hazelcast-config.xml), however I want to be able to change the ExpiryPolicy's duration dynamically, as the timeout value is only available programatically.
I've looked at the docs for ICache but do not see any methods that would allow me to retreive and/or modify the expiry policy for the cache as a whole.
My cache is declared as:
<cache name="toto">
<async-backup-count>1</async-backup-count>
<backup-count>1</backup-count>
<cache-entry-listeners>
<cache-entry-listener old-value-required="true">
<cache-entry-listener-factory class-name="cache.UserRolesEntryListenerStaticFactory"/>
</cache-entry-listener>
</cache-entry-listeners>
<key-type class-name="java.lang.String"/>
<management-enabled>true</management-enabled>
<statistics-enabled>true</statistics-enabled>
<quorum-ref>1</quorum-ref>
<partition-lost-listeners></partition-lost-listeners>
</cache>
and I would like to set/update the expiry policy when it is retrieved as:
#Produces
#Singleton
#UserRolesCache
public Cache<String, String[]> createUserRoleCache(#HazelcastDistributed CacheManager cacheManager) {
Cache cache = cacheManager.getCache("toto");
// get expiry timeout from a 3rd service
int timeout = configService.getCacheExpiry();
// how to set the expiry policy here???
// cache.setExpiryPolicy(.....) ?????
}
Is this feasible using the jCache or Hazelcast API?
There is no explicit way to change the default expiry policy of a Cache (either in JCache or
Hazelcast-specific APIs).
You can achieve the same effect by configuring your Cache with a custom Factory<ExpiryPolicy>.
In your ExpiryPolicy implementation you can consult your external service to query the current
expiry duration and return that so the JCache implementation applies it. Notice that since the expiry policy
is queried on each entry creation/access/update, it is best that ExpiryPolicy methods implementation do not involve
any remote service queries or database access, otherwise you will experience high latency. For example,
it is best to register your expiry policy as a listener to your external service (if supported)
or have a separate executor to schedule queries to the external service for updates.
Example implementation using JCache API:
class Scratch {
public static void main(String[] args)
throws InterruptedException {
MutableConfiguration<String, String[]> roleCacheConfig = new MutableConfiguration<>();
// other config here...
roleCacheConfig.setExpiryPolicyFactory(new CustomExpiryPolicyFactory());
CachingProvider provider = Caching.getCachingProvider();
CacheManager manager = provider.getCacheManager();
Cache<String, String[]> cache = manager.createCache("test", roleCacheConfig);
cache.put("a", new String[]{}); // consults getExpiryForCreation
Thread.sleep(1000);
cache.get("a"); // consults getExpiryForAccess
Thread.sleep(1000);
cache.put("a", new String[]{}); // consults getExpiryForUpdate
}
public static class CustomExpiryPolicyFactory implements Factory<ExpiryPolicy>, Serializable {
#Override
public ExpiryPolicy create() {
// initialize custom expiry policy: at this point
// the custom expiry policy should be registered as listener to
// external service publishing expiry info or otherwise configured
// to poll the external service for new expiry info
CustomExpiryPolicy expiryPolicy = new CustomExpiryPolicy(120, 30, 120);
return expiryPolicy;
}
}
public static class CustomExpiryPolicy implements ExpiryPolicy {
private volatile Duration expiryForCreation;
private volatile Duration expiryForAccess;
private volatile Duration expiryForUpdate;
public CustomExpiryPolicy(long secondsForCreation,
long secondsForAccess,
long secondsForUpdate) {
this.expiryForCreation = new Duration(TimeUnit.SECONDS, secondsForCreation);
this.expiryForAccess = new Duration(TimeUnit.SECONDS, secondsForAccess);
this.expiryForUpdate = new Duration(TimeUnit.SECONDS, secondsForUpdate);
}
// assuming this is invoked from external service whenever there is a change
public void onExpiryChanged(long secondsForCreation,
long secondsForAccess,
long secondsForUpdate) {
this.expiryForCreation = new Duration(TimeUnit.SECONDS, secondsForCreation);
this.expiryForAccess = new Duration(TimeUnit.SECONDS, secondsForAccess);
this.expiryForUpdate = new Duration(TimeUnit.SECONDS, secondsForUpdate);
}
#Override
public Duration getExpiryForCreation() {
return expiryForCreation;
}
#Override
public Duration getExpiryForAccess() {
return expiryForAccess;
}
#Override
public Duration getExpiryForUpdate() {
return expiryForUpdate;
}
}
}
You can supply your custom expiry policy factory class name in declarative Hazelcast XML config like this:
<cache name="toto">
<async-backup-count>1</async-backup-count>
<backup-count>1</backup-count>
...
<expiry-policy-factory class-name="com.example.cache.CustomExpirePolicyFactory" />
...
</cache>
As a side-note, there are methods in ICache, the Hazelcast-specific extended Cache interface, that allow
you to perform operations on a key or set of keys with a custom expiry policy specified per-key (but not change the cache-wide applicable expiry policy).

EntryProcessor without locking entries

In my application, I'm trying to process data in IMap, the scenario is as follows:
application recieves request (REST for example) with set of keys to be processed
application processes entries with given key and returns result - map where key is original key of the entry and result is calculated
for this scenario IMap.executeOnKeys is almost perfect, with one problem - the entry is locked while being processed - and really it hurts thruput. The IMap is populated on startup and never modified.
Is it possible to process entries without locking them? If possible without sending entries to another node and without causing network overhead (sending 1000 tasks to single node in for-loop)
Here is reference implementation to demonstrate what I'm trying to achieve:
public class Main {
public static void main(String[] args) throws Exception {
HazelcastInstance instance = Hazelcast.newHazelcastInstance();
IMap<String, String> map = instance.getMap("the-map");
// populated once on startup, never modified
for (int i = 1; i <= 10; i++) {
map.put("key-" + i, "value-" + i);
}
Set<String> keys = new HashSet<>();
keys.add("key-1"); // every requst may have different key set, they may overlap
System.out.println(" ---- processing ----");
ForkJoinPool pool = new ForkJoinPool();
// to simulate parallel requests on the same entry
pool.execute(() -> map.executeOnKeys(keys, new MyEntryProcessor("first")));
pool.execute(() -> map.executeOnKeys(keys, new MyEntryProcessor("second")));
System.out.println(" ---- pool is waiting ----");
pool.shutdown();
pool.awaitTermination(5, TimeUnit.MINUTES);
System.out.println(" ------ DONE -------");
}
static class MyEntryProcessor implements EntryProcessor<String, String> {
private String name;
MyEntryProcessor(String name) {
this.name = name;
}
#Override
public Object process(Map.Entry<String, String> entry) {
System.out.println(name + " is processing " + entry);
return calculate(entry); // may take some time, doesn't modify entry
}
#Override
public EntryBackupProcessor<String, String> getBackupProcessor() {
return null;
}
}
}
Thanks in advance
In executeOnKeys the entries are not locked. Maybe you mean that the processing happens on partitionThreads, so that there may be no other processing for the particular key? Anyhow, here's the solution:
Your EntryProcessor should implement:
Offloadable interface -> this means that the partition-thread will be used only for reading the value. The calculation will be done in the offloading thread-pool.
ReadOnly interface -> in this case the EP won't hop on the partition-thread again to save the modification you might have done in the entry. Since your EP does not modify entries, this will increase the performance.

How to get a file daily via SFTP using Spring Integration with Java config?

I need to get a file daily via SFTP. I would like to use Spring Integration with Java config. The file is generally available at a specific time each day. The application should try to get the file near that time each day. If the file is not available, it should continue to retry for x attempts. After x attempts, it should send an email to let the admin know that the file is still not available on the SFTP site.
One option is to use SftpInboundFileSynchronizingMessageSource. In the MessageHandler, I can kick off a job to process the file. However, I really don't need synchronization with the remote file system. After all, it is a scheduled delivery of the file. Plus, I need to delay at most 15 minutes for the next retry and to poll every 15 minutes seems a bit overkill for a daily file. I guess that I could use this but would need some mechanism to send email after a certain time elapsed and no file was received.
The other option seems to be using get of the SFTP Outbound Gateway. But the only examples I can find seem to be XML config.
Update
Adding code after using help provided by Artem Bilan's answer below:
Configuration class:
#Bean
#InboundChannelAdapter(autoStartup="true", channel = "sftpChannel", poller = #Poller("pollerMetadata"))
public SftpInboundFileSynchronizingMessageSource sftpMessageSource(ApplicationProperties applicationProperties, PropertiesPersistingMetadataStore store) {
SftpInboundFileSynchronizingMessageSource source =
new SftpInboundFileSynchronizingMessageSource(sftpInboundFileSynchronizer(applicationProperties));
source.setLocalDirectory(new File("ftp-inbound"));
source.setAutoCreateLocalDirectory(true);
FileSystemPersistentAcceptOnceFileListFilter local = new FileSystemPersistentAcceptOnceFileListFilter(store,"test");
source.setLocalFilter(local);
source.setCountsEnabled(true);
return source;
}
#Bean
public PollerMetadata pollerMetadata() {
PollerMetadata pollerMetadata = new PollerMetadata();
List<Advice> adviceChain = new ArrayList<Advice>();
adviceChain.add(retryCompoundTriggerAdvice());
pollerMetadata.setAdviceChain(adviceChain);
pollerMetadata.setTrigger(compoundTrigger());
return pollerMetadata;
}
#Bean
public RetryCompoundTriggerAdvice retryCompoundTriggerAdvice() {
return new RetryCompoundTriggerAdvice(compoundTrigger(), secondaryTrigger());
}
#Bean
public CompoundTrigger compoundTrigger() {
CompoundTrigger compoundTrigger = new CompoundTrigger(primaryTrigger());
return compoundTrigger;
}
#Bean
public Trigger primaryTrigger() {
return new CronTrigger("*/60 * * * * *");
}
#Bean
public Trigger secondaryTrigger() {
return new PeriodicTrigger(10000);
}
#Bean
#ServiceActivator(inputChannel = "sftpChannel")
public MessageHandler handler(PropertiesPersistingMetadataStore store) {
return new MessageHandler() {
#Override
public void handleMessage(Message<?> message) throws MessagingException {
System.out.println(message.getPayload());
store.flush();
}
};
}
RetryCompoundTriggerAdvice class:
public class RetryCompoundTriggerAdvice extends AbstractMessageSourceAdvice {
private final CompoundTrigger compoundTrigger;
private final Trigger override;
private int count = 0;
public RetryCompoundTriggerAdvice(CompoundTrigger compoundTrigger, Trigger overrideTrigger) {
Assert.notNull(compoundTrigger, "'compoundTrigger' cannot be null");
this.compoundTrigger = compoundTrigger;
this.override = overrideTrigger;
}
#Override
public boolean beforeReceive(MessageSource<?> source) {
return true;
}
#Override
public Message<?> afterReceive(Message<?> result, MessageSource<?> source) {
if (result == null && count <= 5) {
count++;
this.compoundTrigger.setOverride(this.override);
}
else {
this.compoundTrigger.setOverride(null);
if (count > 5) {
//send email
}
count = 0;
}
return result;
}
}
Since Spring Integration 4.3 there is CompoundTrigger:
* A {#link Trigger} that delegates the {#link #nextExecutionTime(TriggerContext)}
* to one of two Triggers. If the {#link #setOverride(Trigger) override} trigger is
* {#code null}, the primary trigger is invoked; otherwise the override trigger is
* invoked.
With the combination of CompoundTriggerAdvice:
* An {#link AbstractMessageSourceAdvice} that uses a {#link CompoundTrigger} to adjust
* the poller - when a message is present, the compound trigger's primary trigger is
* used to determine the next poll. When no message is present, the override trigger is
* used.
it can be used to reach your task:
The primaryTrigger can be a CronTrigger to run the task only once a day.
The override could be a PeriodicTrigger with desired short period to retry.
The retry logic you can utilize with one more Advice for poller or just extend that CompoundTriggerAdvice to add count logic to send an email eventually.
Since there is no file, therefore no message to kick the flow. And we don't have choice unless dance around the poller infrastructure.

How to write client proxy for SPI and what the difference between client and server proxies?

I have developed own idGenerator based on Hazelcast IdGenerator class (with storing each last_used_id into db). Now I want to run hazelcast cluster as a single java application and my web-application as other app (web-application restart shouldn't move id values to next block). I move MyIdGeneratorProxy and MyIdGeneratorService to new application, run it, run web-application as a hazelcast-client and get
IllegalArgumentException: No factory registered for service: ecs:impl:idGeneratorService
It was okay when client and server were the same application.
It seems it's unable to process without some clientProxy. I have compared IdGeneratorProxy and ClientIdGeneratorProxy and it looks the same. What is the idea? How to write client proxy for services? I have found no documentation yet. Is direction of investigations correct? I thought it is possible to divide hazelcast inner services (like a id generator service) and my business-processes. Should I store custom ClientProxy (for custom spi) in my web-application?
This is a demo how to create a client proxy, the missing part CustomClientProxy function call, is quit complicated(more like a server proxy,here is called ReadRequest, the server is called Operation), you can find a how AtomicLong implement.For every client proxy method you have to make a request.
#Test
public void client() throws InterruptedException, IOException
{
ClientConfig cfg = new XmlClientConfigBuilder("hazelcast-client.xml").build();
ServiceConfig serviceConfig = new ServiceConfig();
serviceConfig.setName(ConnectorService.NAME)
.setClassName(ConnectorService.class.getCanonicalName())
.setEnabled(true);
ProxyFactoryConfig proxyFactoryConfig = new ProxyFactoryConfig();
proxyFactoryConfig.setService(ConnectorService.NAME);
proxyFactoryConfig.setClassName(CustomProxyFactory.class.getName());
cfg.addProxyFactoryConfig(proxyFactoryConfig);
HazelcastInstance hz = HazelcastClient.newHazelcastClient(cfg);
Thread.sleep(1000);
for (int i = 0; i < 10; i++)
{
Connector c = hz.getDistributedObject(ConnectorService.NAME, "Connector:" + ThreadLocalRandom.current()
.nextInt(10000));
System.out.println(c.snapshot());
}
}
private static class CustomProxyFactory implements ClientProxyFactory
{
#Override
public ClientProxy create(String id)
{
return new CustomClientProxy(ConnectorService.NAME, id);
}
}
private static class CustomClientProxy extends ClientProxy implements Connector
{
protected CustomClientProxy(String serviceName, String objectName)
{
super(serviceName, objectName);
}
#Override
public ConnectorState snapshot()
{
return null;
}
#Override
public void loadState(ConnectorState state)
{
}
#Override
public boolean reconnect(HostNode node)
{
return false;
}
#Override
public boolean connect()
{
return false;
}
}
EDIT
In hazelcast the IdGenerate is implemented as a wrapper for AtomicLong, you should implement you IdGenerate by you own, instead of extend IdGenerate.
So you have to implement these(more like a todo list XD):
API
interface MyIdGenerate
Server
MyIdGenerateService
MyIdGenerateProxy
MyIdGenerateXXXOperation
Client
ClientMyIdGenerateFactory
ClientMyIdGenerateProxy
MyIdGenerateXXXRequest
I also made a sequence(same as IdGenerate) here, this is backed by zookeeper or redis,also it's easy to add a db backend,too.I will integrate to hazelcast if I got time.

Hazelcast Near Cache

I have a setup of two node hazelcast 3.2 with each node containing 500 MB of data.I have configured client side Near cache.
I have following questions
How to verify that data is fetched from near cache hashmap?
Some of my maps have only one entry i.e., size=1, which sometimes on
heavy load takes 20 to 30 ms to fetch the data?
The time taken on heavy load is between 4ms to 20 ms.If it is
fetching from near cache,what will be the optimal fetch time in
millisecond.
What is the best GC policy to be set on the server?
My server side config
mapCfg = new MapConfig();
mapCfg.setName("TL_MAP_2");
mapCfg.setBackupCount(1);
mapCfg.setReadBackupData(true);//enabled as it is sync backup
mapCfg.setInMemoryFormat(InMemoryFormat.BINARY);
cfg.addMapConfig(mapCfg);
My client side code is a singleton
public enum CacheService {
INSTANCE;
private static ClientConfig clientConfig = null;
private static HazelcastInstance cacheClient = null;
private static Set<String> nearcaches = new HashSet<String>(Arrays.asList("TL_MAP_1","TL_MAP_2","TL_MAP_3","TL_MAP_4","TL_MAP_5","TL_MAP_6","TL_MAP_7","TL_MAP_8","TL_MAP_9","TL_MAP_10"));
static{
if(clientConfig ==null || cacheClient ==null){
clientConfig = new ClientConfig();
clientConfig.getGroupConfig().setName("dev").setPassword("dev-pass");
clientConfig.getNetworkConfig().addAddress("XXX.XX.XXX.XXX:5701","XXX.XX.XXX.XXX:5702").setSmartRouting(true);;
clientConfig.setExecutorPoolSize(120);
Iterator<String> iterator = nearcaches.iterator();
String near = null;
while(iterator.hasNext()){
near = iterator.next();
clientConfig.addNearCacheConfig(near, new NearCacheConfig().setInMemoryFormat(InMemoryFormat.OBJECT));
}
cacheClient = HazelcastClient.newHazelcastClient(clientConfig);
}
}
public HazelcastInstance getCacheClient(){
return cacheClient;
}
public Map get(String cacheName) {
Map cacheMap = cacheClient.getMap(cacheName);
if (nearcaches.contains(cacheName)) {
NearCacheStats stats = ((IMap) cacheMap).getLocalMapStats()
.getNearCacheStats();
logger.info("Near Cache HITS for map " + cacheName + " ::"
+ stats.getHits());
}
return cacheMap;
}
}

Resources