org.infinispan.hotrod.near.NearCacheService Maven / Gradle / Ivy
The newest version!
package org.infinispan.hotrod.near;
import java.net.SocketAddress;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import org.infinispan.api.common.CacheEntry;
import org.infinispan.commons.util.BloomFilter;
import org.infinispan.commons.util.IntSet;
import org.infinispan.commons.util.MurmurHash3BloomFilter;
import org.infinispan.commons.util.Util;
import org.infinispan.hotrod.configuration.NearCache;
import org.infinispan.hotrod.configuration.NearCacheConfiguration;
import org.infinispan.hotrod.event.ClientCacheEntryExpiredEvent;
import org.infinispan.hotrod.event.ClientCacheEntryModifiedEvent;
import org.infinispan.hotrod.event.ClientCacheEntryRemovedEvent;
import org.infinispan.hotrod.event.ClientCacheFailoverEvent;
import org.infinispan.hotrod.event.impl.ClientListenerNotifier;
import org.infinispan.hotrod.impl.cache.RemoteCache;
import org.infinispan.hotrod.impl.logging.Log;
import org.infinispan.hotrod.impl.logging.LogFactory;
/**
* Near cache service, manages the lifecycle of the near cache.
*
* @since 14.0
*/
public class NearCacheService implements NearCache {
private static final Log log = LogFactory.getLog(NearCacheService.class);
private final NearCacheConfiguration config;
private final ClientListenerNotifier listenerNotifier;
private final AtomicInteger nearCacheRemovals = new AtomicInteger();
private Object listener;
private byte[] listenerId;
private NearCache cache;
private Runnable invalidationCallback;
private int bloomFilterBits = -1;
private int bloomFilterUpdateThreshold;
private RemoteCache remote;
private SocketAddress listenerAddress;
protected NearCacheService(NearCacheConfiguration config, ClientListenerNotifier listenerNotifier) {
this.config = config;
this.listenerNotifier = listenerNotifier;
}
public SocketAddress start(RemoteCache remote) {
if (cache == null) {
// Create near cache
cache = createNearCache(config, this::entryRemovedFromNearCache);
// Add a listener that updates the near cache
listener = new InvalidatedNearCacheListener<>(this);
int maxEntries = config.maxEntries();
if (maxEntries > 0 && config.bloomFilter()) {
bloomFilterBits = determineBloomFilterBits(maxEntries);
// We want to scale the update frequency of the bloom filter to be based on the number of max entries
// This number along with default values of 3 hash algorithms and 4x bit size we end up with
// between 14.689 and 16.573 percent hits per entry.
bloomFilterUpdateThreshold = maxEntries / 16 + 3;
listenerAddress = remote.addNearCacheListener(listener, bloomFilterBits);
} else {
//FIXME: remote.addClientListener(listener);
}
// Get the listener ID for faster listener connected lookups
listenerId = listenerNotifier.findListenerId(listener);
}
this.remote = remote;
return listenerAddress;
}
private static int determineBloomFilterBits(int maxEntries) {
int bloomFilterBitScaler = Integer.parseInt(System.getProperty("infinispan.bloom-filter.bit-multiplier", "4"));
return maxEntries * bloomFilterBitScaler;
}
void entryRemovedFromNearCache(CacheEntry entry) {
while (true) {
int removals = nearCacheRemovals.get();
if (removals >= bloomFilterUpdateThreshold) {
if (nearCacheRemovals.compareAndSet(removals, 0)) {
remote.updateBloomFilter();
break;
}
} else if (nearCacheRemovals.compareAndSet(removals, removals + 1)) {
break;
}
}
}
public void stop(RemoteCache remote) {
if (log.isTraceEnabled())
log.tracef("Stop near cache, remove underlying listener id %s", Util.printArray(listenerId));
// Remove listener
//FIXME: remote.removeClientListener(listener);
// Empty cache
cache.clear();
}
protected NearCache createNearCache(NearCacheConfiguration config, Consumer> removedConsumer) {
return config.nearCacheFactory().createNearCache(config, removedConsumer);
}
public static NearCacheService create(
NearCacheConfiguration config, ClientListenerNotifier listenerNotifier) {
return new NearCacheService<>(config, listenerNotifier);
}
@Override
public boolean putIfAbsent(K key, CacheEntry entry) {
boolean inserted = cache.putIfAbsent(key, entry);
if (log.isTraceEnabled())
log.tracef("Conditionally put %s if absent in near cache (listenerId=%s): %s", entry,
Util.printArray(listenerId), inserted);
return inserted;
}
@Override
public boolean replace(K key, CacheEntry prevValue, CacheEntry newValue) {
boolean replaced = cache.replace(key, prevValue, newValue);
if (log.isTraceEnabled()) {
log.tracef("Replaced key=%s and value=%s with new value=%s in near cache (listenerId=%s): %s",
key, prevValue, newValue, Util.printArray(listenerId), replaced);
}
return replaced;
}
@Override
public boolean remove(K key) {
boolean removed = cache.remove(key);
if (removed) {
if (invalidationCallback != null) {
invalidationCallback.run();
}
if (log.isTraceEnabled())
log.tracef("Removed key=%s from near cache (listenedId=%s)", key, Util.printArray(listenerId));
} else {
log.tracef("Received false positive remove for key=%s from near cache (listenedId=%s)", key, Util.printArray(listenerId));
// There was a false positive, add that to the removal
entryRemovedFromNearCache(null);
}
return removed;
}
@Override
public boolean remove(K key, CacheEntry entry) {
boolean removed = cache.remove(key, entry);
if (log.isTraceEnabled())
log.tracef("Removed value=%s for key=%s from near cache (listenedId=%s): %s", entry, key, Util.printArray(listenerId), removed);
return removed;
}
@Override
public CacheEntry get(K key) {
boolean listenerConnected = isConnected();
if (listenerConnected) {
CacheEntry entry = cache.get(key);
if (log.isTraceEnabled())
log.tracef("Get key=%s returns entry=%s (listenerId=%s)", key, entry, Util.printArray(listenerId));
return entry;
}
if (log.isTraceEnabled())
log.tracef("Near cache disconnected from server, returning null for key=%s (listenedId=%s)",
key, Util.printArray(listenerId));
return null;
}
@Override
public void clear() {
cache.clear();
if (log.isTraceEnabled()) log.tracef("Cleared near cache (listenerId=%s)", Util.printArray(listenerId));
}
@Override
public int size() {
return cache.size();
}
@Override
public Iterator> iterator() {
return cache.iterator();
}
boolean isConnected() {
return listenerNotifier.isListenerConnected(listenerId);
}
public void setInvalidationCallback(Runnable r) {
this.invalidationCallback = r;
}
public int getBloomFilterBits() {
return bloomFilterBits;
}
public byte[] getListenerId() {
return listenerId;
}
public byte[] calculateBloomBits() {
if (bloomFilterBits <= 0) {
return null;
}
BloomFilter bloomFilter = MurmurHash3BloomFilter.createFilter(bloomFilterBits);
for (CacheEntry entry : cache) {
bloomFilter.addToFilter(remote.keyToBytes(entry.key()));
}
IntSet intSet = bloomFilter.getIntSet();
return intSet.toBitSet();
}
private static class InvalidatedNearCacheListener {
private static final Log log = LogFactory.getLog(InvalidatedNearCacheListener.class);
private final NearCache cache;
private InvalidatedNearCacheListener(NearCache cache) {
this.cache = cache;
}
@SuppressWarnings("unused")
public void handleModifiedEvent(ClientCacheEntryModifiedEvent event) {
invalidate(event.getKey());
}
@SuppressWarnings("unused")
public void handleRemovedEvent(ClientCacheEntryRemovedEvent event) {
invalidate(event.getKey());
}
@SuppressWarnings("unused")
public void handleExpiredEvent(ClientCacheEntryExpiredEvent event) {
invalidate(event.getKey());
}
@SuppressWarnings("unused")
public void handleFailover(ClientCacheFailoverEvent e) {
if (log.isTraceEnabled()) log.trace("Clear near cache after fail-over of server");
cache.clear();
}
private void invalidate(K key) {
cache.remove(key);
}
}
}