mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#314 - ServerCache API - change from int to long for ServerCacheStatistics (hitCount, missCount) and ServerCache remove putIfAbsent() method
This commit is contained in:
@@ -44,4 +44,12 @@ public @interface CacheTuning {
|
||||
* </p>
|
||||
*/
|
||||
int maxSecsToLive() default 0;
|
||||
|
||||
/**
|
||||
* The frequency (in seconds) that cache trimming should occur.
|
||||
* <p>
|
||||
* This is a hint for cache implementations that use background cache trimming.
|
||||
* </p>
|
||||
*/
|
||||
int trimFrequency() default 0;
|
||||
}
|
||||
|
||||
@@ -48,12 +48,6 @@ public interface ServerCache {
|
||||
*/
|
||||
Object put(Object id, Object value);
|
||||
|
||||
/**
|
||||
* Put the value in the cache but only if a matching value is not already in
|
||||
* the cache.
|
||||
*/
|
||||
Object putIfAbsent(Object id, Object value);
|
||||
|
||||
/**
|
||||
* Remove a entry from the cache given its id.
|
||||
*/
|
||||
|
||||
+24
-5
@@ -10,6 +10,7 @@ public class ServerCacheOptions {
|
||||
private int maxSize;
|
||||
private int maxIdleSecs;
|
||||
private int maxSecsToLive;
|
||||
private int trimFrequency;
|
||||
|
||||
/**
|
||||
* Construct with no set options.
|
||||
@@ -25,15 +26,17 @@ public class ServerCacheOptions {
|
||||
this.maxSize = cacheTuning.maxSize();
|
||||
this.maxIdleSecs = cacheTuning.maxIdleSecs();
|
||||
this.maxSecsToLive = cacheTuning.maxSecsToLive();
|
||||
this.trimFrequency = cacheTuning.trimFrequency();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create merging default options with the deployment specified ones.
|
||||
*/
|
||||
public ServerCacheOptions(ServerCacheOptions d) {
|
||||
this.maxSize = d.getMaxSize();
|
||||
this.maxIdleSecs = d.getMaxIdleSecs();
|
||||
this.maxSecsToLive = d.getMaxIdleSecs();
|
||||
public ServerCacheOptions(ServerCacheOptions defaults) {
|
||||
this.maxSize = defaults.getMaxSize();
|
||||
this.maxIdleSecs = defaults.getMaxIdleSecs();
|
||||
this.maxSecsToLive = defaults.getMaxIdleSecs();
|
||||
this.trimFrequency = defaults.getTrimFrequency();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,6 +53,9 @@ public class ServerCacheOptions {
|
||||
if (maxSecsToLive == 0) {
|
||||
maxSecsToLive = defaults.getMaxSecsToLive();
|
||||
}
|
||||
if (trimFrequency == 0) {
|
||||
trimFrequency = defaults.getTrimFrequency();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,7 +67,7 @@ public class ServerCacheOptions {
|
||||
copy.maxSize = maxSize;
|
||||
copy.maxIdleSecs = maxIdleSecs;
|
||||
copy.maxSecsToLive = maxSecsToLive;
|
||||
|
||||
copy.trimFrequency = trimFrequency;
|
||||
return copy;
|
||||
}
|
||||
|
||||
@@ -107,4 +113,17 @@ public class ServerCacheOptions {
|
||||
this.maxSecsToLive = maxSecsToLive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the trim frequency in seconds.
|
||||
*/
|
||||
public int getTrimFrequency() {
|
||||
return trimFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the trim frequency in seconds.
|
||||
*/
|
||||
public void setTrimFrequency(int trimFrequency) {
|
||||
this.trimFrequency = trimFrequency;
|
||||
}
|
||||
}
|
||||
|
||||
+175
-23
@@ -5,9 +5,9 @@ package com.avaje.ebean.cache;
|
||||
* <p>
|
||||
* These can be monitored to review the effectiveness of a particular cache.
|
||||
* </p>
|
||||
*
|
||||
* @author rbygrave
|
||||
*
|
||||
* <p>
|
||||
* Depending on the cache implementation not all the statistics may be collected.
|
||||
* </p>
|
||||
*/
|
||||
public class ServerCacheStatistics {
|
||||
|
||||
@@ -17,21 +17,63 @@ public class ServerCacheStatistics {
|
||||
|
||||
protected int size;
|
||||
|
||||
protected int hitCount;
|
||||
protected long hitCount;
|
||||
|
||||
protected int missCount;
|
||||
protected long missCount;
|
||||
|
||||
protected long insertCount;
|
||||
|
||||
protected long updateCount;
|
||||
|
||||
protected long removeCount;
|
||||
|
||||
protected long clearCount;
|
||||
|
||||
protected long evictionRunCount;
|
||||
|
||||
protected long evictionRunMicros;
|
||||
|
||||
protected long evictByIdle;
|
||||
|
||||
protected long evictByTTL;
|
||||
|
||||
protected long evictByLRU;
|
||||
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(cacheName);
|
||||
sb.append(" maxSize:").append(maxSize);
|
||||
sb.append(" size:").append(size);
|
||||
sb.append(" hitRatio:").append(getHitRatio());
|
||||
sb.append(" hitCount:").append(hitCount);
|
||||
sb.append(" missCount:").append(missCount);
|
||||
sb.append(" maxSize:").append(maxSize);
|
||||
sb.append(" hit:").append(hitCount);
|
||||
sb.append(" miss:").append(missCount);
|
||||
sb.append(" insert:").append(insertCount);
|
||||
sb.append(" update:").append(updateCount);
|
||||
sb.append(" remove:").append(removeCount);
|
||||
sb.append(" clear:").append(clearCount);
|
||||
sb.append(" evictByIdle:").append(evictByIdle);
|
||||
sb.append(" evictByTTL:").append(evictByTTL);
|
||||
sb.append(" evictByLRU:").append(evictByLRU);
|
||||
sb.append(" evictionRunCount:").append(evictionRunCount);
|
||||
sb.append(" evictionRunMicros:").append(evictionRunMicros);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an int from 0 to 100 (percentage) for the hit ratio.
|
||||
* <p>
|
||||
* A hit ratio of 100 means every get request against the cache hits an entry.
|
||||
* </p>
|
||||
*/
|
||||
public int getHitRatio() {
|
||||
long totalCount = hitCount + missCount;
|
||||
if (totalCount == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return (int)(hitCount * 100 / totalCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the cache.
|
||||
*/
|
||||
@@ -49,28 +91,28 @@ public class ServerCacheStatistics {
|
||||
/**
|
||||
* Return the hit count. The number of successful gets.
|
||||
*/
|
||||
public int getHitCount() {
|
||||
public long getHitCount() {
|
||||
return hitCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the hit count.
|
||||
*/
|
||||
public void setHitCount(int hitCount) {
|
||||
public void setHitCount(long hitCount) {
|
||||
this.hitCount = hitCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the miss count. The number of gets that returned null.
|
||||
*/
|
||||
public int getMissCount() {
|
||||
public long getMissCount() {
|
||||
return missCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the miss count.
|
||||
*/
|
||||
public void setMissCount(int missCount) {
|
||||
public void setMissCount(long missCount) {
|
||||
this.missCount = missCount;
|
||||
}
|
||||
|
||||
@@ -107,18 +149,128 @@ public class ServerCacheStatistics {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an int from 0 to 100 (percentage) for the hit ratio.
|
||||
* <p>
|
||||
* A hit ratio of 100 means every get request against the cache hits an entry.
|
||||
* </p>
|
||||
* Set the put insert count.
|
||||
*/
|
||||
public int getHitRatio() {
|
||||
int totalCount = hitCount + missCount;
|
||||
if (totalCount == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return hitCount * 100 / totalCount;
|
||||
}
|
||||
public void setInsertCount(long insertCount) {
|
||||
this.insertCount = insertCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the put insert count.
|
||||
*/
|
||||
public long getInsertCount() {
|
||||
return insertCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the put update count.
|
||||
*/
|
||||
public void setUpdateCount(long updateCount) {
|
||||
this.updateCount = updateCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the put update count.
|
||||
*/
|
||||
public long getUpdateCount() {
|
||||
return updateCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the remove count.
|
||||
*/
|
||||
public void setRemoveCount(long removeCount) {
|
||||
this.removeCount = removeCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the remove count.
|
||||
*/
|
||||
public long getRemoveCount() {
|
||||
return removeCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the clear count.
|
||||
*/
|
||||
public void setClearCount(long clearCount) {
|
||||
this.clearCount = clearCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the clear count.
|
||||
*/
|
||||
public long getClearCount() {
|
||||
return clearCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the eviction run count.
|
||||
*/
|
||||
public void setEvictionRunCount(long evictCount) {
|
||||
this.evictionRunCount = evictCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the eviction run count.
|
||||
*/
|
||||
public long getEvictionRunCount() {
|
||||
return evictionRunCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the eviction run time in micros.
|
||||
*/
|
||||
public void setEvictionRunMicros(long evictionRunMicros) {
|
||||
this.evictionRunMicros = evictionRunMicros;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the eviction run time in micros.
|
||||
*/
|
||||
public long getEvictionRunMicros() {
|
||||
return evictionRunMicros;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the count of entries evicted due to idle time.
|
||||
*/
|
||||
public void setEvictByIdle(long evictByIdle) {
|
||||
this.evictByIdle = evictByIdle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the count of entries evicted due to idle time.
|
||||
*/
|
||||
public long getEvictByIdle() {
|
||||
return evictByIdle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the count of entries evicted due to time to live.
|
||||
*/
|
||||
public void setEvictByTTL(long evictByTTL) {
|
||||
this.evictByTTL = evictByTTL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the count of entries evicted due to time to live.
|
||||
*/
|
||||
public long getEvictByTTL() {
|
||||
return evictByTTL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the count of entries evicted due to time least recently used.
|
||||
*/
|
||||
public void setEvictByLRU(long evictByLRU) {
|
||||
this.evictByLRU = evictByLRU;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the count of entries evicted due to time least recently used.
|
||||
*/
|
||||
public long getEvictByLRU() {
|
||||
return evictByLRU;
|
||||
}
|
||||
}
|
||||
|
||||
+354
-341
@@ -1,22 +1,18 @@
|
||||
package com.avaje.ebeaninternal.server.cache;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
import com.avaje.ebean.EbeanServer;
|
||||
import com.avaje.ebean.cache.ServerCache;
|
||||
import com.avaje.ebean.cache.ServerCacheOptions;
|
||||
import com.avaje.ebean.cache.ServerCacheStatistics;
|
||||
import com.avaje.ebeaninternal.server.util.LongAdder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* The default cache implementation.
|
||||
@@ -27,375 +23,392 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
public class DefaultServerCache implements ServerCache {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DefaultServerCache.class);
|
||||
protected static final Logger logger = LoggerFactory.getLogger(DefaultServerCache.class);
|
||||
|
||||
private static final CacheEntryComparator comparator = new CacheEntryComparator();
|
||||
|
||||
private final ConcurrentHashMap<Object, CacheEntry> map = new ConcurrentHashMap<Object, CacheEntry>();
|
||||
|
||||
private final AtomicInteger missCount = new AtomicInteger();
|
||||
|
||||
private final AtomicInteger removedHitCount = new AtomicInteger();
|
||||
|
||||
private final Object monitor = new Object();
|
||||
|
||||
private final String name;
|
||||
|
||||
private int maxSize;
|
||||
|
||||
private long trimFrequency;
|
||||
|
||||
private int maxIdleSecs;
|
||||
|
||||
private int maxSecsToLive;
|
||||
|
||||
public DefaultServerCache(String name, ServerCacheOptions options) {
|
||||
this(name, options.getMaxSize(), options.getMaxIdleSecs(), options.getMaxSecsToLive());
|
||||
}
|
||||
|
||||
public DefaultServerCache(String name, int maxSize, int maxIdleSecs, int maxSecsToLive) {
|
||||
this.name = name;
|
||||
this.maxSize = maxSize;
|
||||
this.maxIdleSecs = maxIdleSecs;
|
||||
this.maxSecsToLive = maxSecsToLive;
|
||||
this.trimFrequency = 60;
|
||||
|
||||
}
|
||||
|
||||
public void init(EbeanServer server) {
|
||||
|
||||
TrimTask trim = new TrimTask();
|
||||
|
||||
BackgroundExecutor executor = server.getBackgroundExecutor();
|
||||
executor.executePeriodically(trim, trimFrequency, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public ServerCacheStatistics getStatistics(boolean reset) {
|
||||
|
||||
ServerCacheStatistics s = new ServerCacheStatistics();
|
||||
s.setCacheName(name);
|
||||
s.setMaxSize(maxSize);
|
||||
|
||||
// these counters won't necessarily be consistent with
|
||||
// respect to each other as activity can occur while
|
||||
// they are being calculated
|
||||
int mc = reset ? missCount.getAndSet(0) : missCount.get();
|
||||
int hc = getHitCount(reset);
|
||||
int size = size();
|
||||
|
||||
s.setSize(size);
|
||||
s.setHitCount(hc);
|
||||
s.setMissCount(mc);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
public int getHitRatio() {
|
||||
|
||||
int mc = missCount.get();
|
||||
int hc = getHitCount(false);
|
||||
|
||||
int totalCount = hc + mc;
|
||||
if (totalCount == 0){
|
||||
return 0;
|
||||
} else {
|
||||
return hc * 100 / totalCount;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private int getHitCount(boolean reset) {
|
||||
|
||||
int hc = reset ? removedHitCount.getAndSet(0) : removedHitCount.get();
|
||||
|
||||
for (CacheEntry cacheEntry : map.values()) {
|
||||
hc += cacheEntry.getHitCount(reset);
|
||||
}
|
||||
|
||||
return hc;
|
||||
}
|
||||
/**
|
||||
* Compare by last access time (for LRU eviction).
|
||||
*/
|
||||
public static final CompareByLastAccess BY_LAST_ACCESS = new CompareByLastAccess();
|
||||
|
||||
|
||||
public ServerCacheOptions getOptions() {
|
||||
synchronized (monitor) {
|
||||
ServerCacheOptions o = new ServerCacheOptions();
|
||||
o.setMaxIdleSecs(maxIdleSecs);
|
||||
o.setMaxSize(maxSize);
|
||||
o.setMaxSecsToLive(maxSecsToLive);
|
||||
return o;
|
||||
}
|
||||
}
|
||||
|
||||
public void setOptions(ServerCacheOptions o) {
|
||||
synchronized (monitor) {
|
||||
maxIdleSecs = o.getMaxIdleSecs();
|
||||
maxSize = o.getMaxSize();
|
||||
maxSecsToLive = o.getMaxSecsToLive();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the max cache size.
|
||||
*/
|
||||
public int getMaxSize() {
|
||||
return maxSize;
|
||||
}
|
||||
/**
|
||||
* The underlying map (ConcurrentHashMap or similar)
|
||||
*/
|
||||
protected final Map<Object, CacheEntry> map;
|
||||
|
||||
/**
|
||||
* Set the max cache size.
|
||||
*/
|
||||
public void setMaxSize(int maxSize) {
|
||||
synchronized (monitor) {
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
}
|
||||
// LongAdder is a highly concurrent low latency counter (back ported from Java8)
|
||||
protected final LongAdder missCount = new LongAdder();
|
||||
protected final LongAdder hitCount = new LongAdder();
|
||||
protected final LongAdder insertCount = new LongAdder();
|
||||
protected final LongAdder updateCount = new LongAdder();
|
||||
protected final LongAdder removeCount = new LongAdder();
|
||||
protected final LongAdder clearCount = new LongAdder();
|
||||
|
||||
/**
|
||||
* Return the max idle time.
|
||||
*/
|
||||
public long getMaxIdleSecs() {
|
||||
return maxIdleSecs;
|
||||
}
|
||||
protected final LongAdder evictByIdle = new LongAdder();
|
||||
protected final LongAdder evictByTTL = new LongAdder();
|
||||
protected final LongAdder evictByLRU = new LongAdder();
|
||||
protected final LongAdder evictCount = new LongAdder();
|
||||
protected final LongAdder evictMicros = new LongAdder();
|
||||
|
||||
/**
|
||||
* Set the max idle time.
|
||||
*/
|
||||
public void setMaxIdleSecs(int maxIdleSecs) {
|
||||
synchronized (monitor) {
|
||||
this.maxIdleSecs = maxIdleSecs;
|
||||
}
|
||||
}
|
||||
protected final Object monitor = new Object();
|
||||
|
||||
/**
|
||||
* Return the maximum time to live.
|
||||
*/
|
||||
public long getMaxSecsToLive() {
|
||||
return maxSecsToLive;
|
||||
}
|
||||
protected final String name;
|
||||
|
||||
/**
|
||||
* Set the maximum time to live.
|
||||
*/
|
||||
public void setMaxSecsToLive(int maxSecsToLive) {
|
||||
synchronized (monitor) {
|
||||
this.maxSecsToLive = maxSecsToLive;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the cache.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
protected int maxSize;
|
||||
|
||||
/**
|
||||
* Clear the cache.
|
||||
*/
|
||||
public void clear() {
|
||||
map.clear();
|
||||
}
|
||||
protected int trimFrequency;
|
||||
|
||||
/**
|
||||
* Return a value from the cache.
|
||||
*/
|
||||
public Object get(Object key) {
|
||||
|
||||
CacheEntry entry = map.get(key);
|
||||
|
||||
if (entry == null){
|
||||
missCount.incrementAndGet();
|
||||
return null;
|
||||
|
||||
} else {
|
||||
// get value incrementing last
|
||||
// access time and hitCount
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
protected int maxIdleSecs;
|
||||
|
||||
/**
|
||||
* Put a value into the cache.
|
||||
*/
|
||||
public Object put(Object key, Object value) {
|
||||
// put new entry with create time
|
||||
CacheEntry entry = map.put(key, new CacheEntry(key, value));
|
||||
if (entry == null){
|
||||
return null;
|
||||
} else {
|
||||
int removedHits = entry.getHitCount(true);
|
||||
removedHitCount.addAndGet(removedHits);
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
protected int maxSecsToLive;
|
||||
|
||||
/**
|
||||
* Put a value into the cache but only if absent.
|
||||
*/
|
||||
public Object putIfAbsent(Object key, Object value) {
|
||||
CacheEntry entry = map.putIfAbsent(key, new CacheEntry(key, value));
|
||||
if (entry == null){
|
||||
return null;
|
||||
} else {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Construct using a ConcurrentHashMap and cache options.
|
||||
*/
|
||||
public DefaultServerCache(String name, ServerCacheOptions options) {
|
||||
this(name, new ConcurrentHashMap<Object, CacheEntry>(), options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an entry from the cache.
|
||||
*/
|
||||
public Object remove(Object key) {
|
||||
CacheEntry entry = map.remove(key);
|
||||
if (entry == null){
|
||||
return null;
|
||||
} else {
|
||||
int removedHits = entry.getHitCount(true);
|
||||
removedHitCount.addAndGet(removedHits);
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Construct passing in name, map and base eviction controls as ServerCacheOptions.
|
||||
*/
|
||||
public DefaultServerCache(String name, Map<Object, CacheEntry> map, ServerCacheOptions options) {
|
||||
this(name, map, options.getMaxSize(), options.getMaxIdleSecs(), options.getMaxSecsToLive(), options.getTrimFrequency());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of elements in the cache.
|
||||
*/
|
||||
public int size() {
|
||||
return map.size();
|
||||
}
|
||||
/**
|
||||
* Construct passing in name, map and base eviction controls.
|
||||
*/
|
||||
public DefaultServerCache(String name, Map<Object, CacheEntry> map, int maxSize, int maxIdleSecs, int maxSecsToLive, int trimFrequency) {
|
||||
this.name = name;
|
||||
this.map = map;
|
||||
this.maxSize = maxSize;
|
||||
this.maxIdleSecs = maxIdleSecs;
|
||||
this.maxSecsToLive = maxSecsToLive;
|
||||
this.trimFrequency = trimFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* The task used to periodically trim the cache.
|
||||
*/
|
||||
private class TrimTask implements Runnable {
|
||||
@Override
|
||||
public void init(EbeanServer server) {
|
||||
|
||||
public void run() {
|
||||
EvictionRunnable trim = new EvictionRunnable();
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
if (logger.isTraceEnabled()){
|
||||
logger.trace("trimming cache " + name);
|
||||
}
|
||||
|
||||
int trimmedByIdle = 0;
|
||||
int trimmedByTTL = 0;
|
||||
int trimmedByLRU = 0;
|
||||
// default to trimming the cache every 60 seconds
|
||||
long trimFreqSecs = (trimFrequency == 0) ? 60 : trimFrequency;
|
||||
|
||||
boolean trimMaxSize = maxSize > 0 && maxSize < size();
|
||||
BackgroundExecutor executor = server.getBackgroundExecutor();
|
||||
executor.executePeriodically(trim, trimFreqSecs, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
ArrayList<CacheEntry> activeList = new ArrayList<CacheEntry>();
|
||||
@Override
|
||||
public ServerCacheStatistics getStatistics(boolean reset) {
|
||||
|
||||
long idleExpire = System.currentTimeMillis() - (maxIdleSecs*1000);
|
||||
long ttlExpire = System.currentTimeMillis() - (maxSecsToLive*1000);
|
||||
ServerCacheStatistics cacheStats = new ServerCacheStatistics();
|
||||
cacheStats.setCacheName(name);
|
||||
cacheStats.setMaxSize(maxSize);
|
||||
|
||||
Iterator<CacheEntry> it = map.values().iterator();
|
||||
while (it.hasNext()) {
|
||||
CacheEntry cacheEntry = it.next();
|
||||
if (maxIdleSecs > 0 && idleExpire > cacheEntry.getLastAccessTime()) {
|
||||
it.remove();
|
||||
trimmedByIdle++;
|
||||
// these counters won't necessarily be consistent with
|
||||
// respect to each other as activity can occur while
|
||||
// they are being calculated here but they should be good enough
|
||||
// and we don't want to reduce concurrent use to make them consistent
|
||||
long clear = reset ? clearCount.sumThenReset() : clearCount.sum();
|
||||
long remove = reset ? removeCount.sumThenReset() : removeCount.sum();
|
||||
long update = reset ? updateCount.sumThenReset() : updateCount.sum();
|
||||
long insert = reset ? insertCount.sumThenReset() : insertCount.sum();
|
||||
long miss = reset ? missCount.sumThenReset() : missCount.sum();
|
||||
long hit = reset ? hitCount.sumThenReset() : hitCount.sum();
|
||||
|
||||
} else if (maxSecsToLive > 0 && ttlExpire > cacheEntry.getCreateTime()) {
|
||||
it.remove();
|
||||
trimmedByTTL++;
|
||||
long evict = reset ? evictCount.sumThenReset() : evictCount.sum();
|
||||
long evictTime = reset ? evictMicros.sumThenReset() : evictMicros.sum();
|
||||
long evictIdle = reset ? evictByIdle.sumThenReset() : evictByIdle.sum();
|
||||
long evictTTL = reset ? evictByTTL.sumThenReset() : evictByTTL.sum();
|
||||
long evictLRU = reset ? evictByLRU.sumThenReset() : evictByLRU.sum();
|
||||
|
||||
} else if (trimMaxSize) {
|
||||
activeList.add(cacheEntry);
|
||||
}
|
||||
}
|
||||
int size = size();
|
||||
|
||||
if (trimMaxSize) {
|
||||
trimmedByLRU = activeList.size() - maxSize;
|
||||
cacheStats.setSize(size);
|
||||
cacheStats.setHitCount(hit);
|
||||
cacheStats.setMissCount(miss);
|
||||
cacheStats.setInsertCount(insert);
|
||||
cacheStats.setUpdateCount(update);
|
||||
cacheStats.setRemoveCount(remove);
|
||||
cacheStats.setClearCount(clear);
|
||||
|
||||
if (trimmedByLRU > 0) {
|
||||
// sort into last access time ascending
|
||||
Collections.sort(activeList, comparator);
|
||||
for (int i = maxSize; i < activeList.size(); i++) {
|
||||
// remove if still in the cache
|
||||
map.remove(activeList.get(i).getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long exeTime = System.currentTimeMillis() - startTime;
|
||||
|
||||
if (logger.isDebugEnabled()){
|
||||
logger.debug("Executed trim of cache " + name + " in ["+exeTime
|
||||
+"]millis idle[" + trimmedByIdle + "] timeToLive["
|
||||
+ trimmedByTTL + "] accessTime["
|
||||
+ trimmedByLRU + "]");
|
||||
}
|
||||
cacheStats.setEvictionRunCount(evict);
|
||||
cacheStats.setEvictionRunMicros(evictTime);
|
||||
cacheStats.setEvictByIdle(evictIdle);
|
||||
cacheStats.setEvictByTTL(evictTTL);
|
||||
cacheStats.setEvictByLRU(evictLRU);
|
||||
|
||||
}
|
||||
return cacheStats;
|
||||
}
|
||||
|
||||
}
|
||||
@Override
|
||||
public int getHitRatio() {
|
||||
|
||||
/**
|
||||
* Comparator for sorting by last access time.
|
||||
*/
|
||||
private static class CacheEntryComparator implements Comparator<CacheEntry>, Serializable {
|
||||
long mc = missCount.sum();
|
||||
long hc = hitCount.sum();
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
long totalCount = hc + mc;
|
||||
if (totalCount == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return (int) (hc * 100 / totalCount);
|
||||
}
|
||||
}
|
||||
|
||||
public int compare(CacheEntry o1, CacheEntry o2) {
|
||||
|
||||
return o1.getLastAccessLong().compareTo(o2.getLastAccessLong());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the values to additionally hold createTime and lastAccessTime.
|
||||
*/
|
||||
public static class CacheEntry {
|
||||
/**
|
||||
* Return the options controlling the cache.
|
||||
*/
|
||||
@Override
|
||||
public ServerCacheOptions getOptions() {
|
||||
synchronized (monitor) {
|
||||
ServerCacheOptions options = new ServerCacheOptions();
|
||||
options.setMaxIdleSecs(maxIdleSecs);
|
||||
options.setMaxSize(maxSize);
|
||||
options.setMaxSecsToLive(maxSecsToLive);
|
||||
options.setTrimFrequency(trimFrequency);
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
private final Object key;
|
||||
private final Object value;
|
||||
private final long createTime;
|
||||
private final AtomicInteger hitCount = new AtomicInteger();
|
||||
private Long lastAccessTime;
|
||||
/**
|
||||
* Set the options controlling the cache
|
||||
*/
|
||||
@Override
|
||||
public void setOptions(ServerCacheOptions options) {
|
||||
synchronized (monitor) {
|
||||
maxIdleSecs = options.getMaxIdleSecs();
|
||||
maxSize = options.getMaxSize();
|
||||
maxSecsToLive = options.getMaxSecsToLive();
|
||||
}
|
||||
}
|
||||
|
||||
public CacheEntry(Object key, Object value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
this.createTime = System.currentTimeMillis();
|
||||
this.lastAccessTime = Long.valueOf(createTime);
|
||||
}
|
||||
/**
|
||||
* Return the name of the cache.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Object getKey() {
|
||||
return key;
|
||||
}
|
||||
/**
|
||||
* Clear the cache.
|
||||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
clearCount.increment();
|
||||
map.clear();
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
// object assignment is atomic
|
||||
hitCount.incrementAndGet();
|
||||
this.lastAccessTime = Long.valueOf(System.currentTimeMillis());
|
||||
return value;
|
||||
}
|
||||
/**
|
||||
* Return a value from the cache.
|
||||
*/
|
||||
@Override
|
||||
public Object get(Object key) {
|
||||
|
||||
public long getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
CacheEntry entry = map.get(key);
|
||||
if (entry == null) {
|
||||
missCount.increment();
|
||||
return null;
|
||||
|
||||
public long getLastAccessTime() {
|
||||
return lastAccessTime.longValue();
|
||||
}
|
||||
} else {
|
||||
// Important that hitCount.increment() MUST be low latency under concurrent
|
||||
// use hence must use LongAdder or better here
|
||||
hitCount.increment();
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
public Long getLastAccessLong() {
|
||||
return lastAccessTime;
|
||||
}
|
||||
/**
|
||||
* Put a value into the cache.
|
||||
*/
|
||||
@Override
|
||||
public Object put(Object key, Object value) {
|
||||
CacheEntry entry = map.put(key, new CacheEntry(key, value));
|
||||
if (entry == null) {
|
||||
insertCount.increment();
|
||||
return null;
|
||||
} else {
|
||||
updateCount.increment();
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
public int getHitCount(boolean reset) {
|
||||
if (reset){
|
||||
return hitCount.getAndSet(0);
|
||||
/**
|
||||
* Remove an entry from the cache.
|
||||
*/
|
||||
@Override
|
||||
public Object remove(Object key) {
|
||||
CacheEntry entry = map.remove(key);
|
||||
if (entry == null) {
|
||||
return null;
|
||||
} else {
|
||||
removeCount.increment();
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of elements in the cache.
|
||||
*/
|
||||
@Override
|
||||
public int size() {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the size to trim to based on the max size.
|
||||
* <p>
|
||||
* This returns 90% of the max size.
|
||||
* </p>
|
||||
*/
|
||||
protected int getTrimSize() {
|
||||
return (maxSize * 90 / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the eviction based on Idle time, Time to live and LRU last access.
|
||||
*/
|
||||
public void runEviction() {
|
||||
|
||||
long trimForMaxSize;
|
||||
if (maxSize == 0) {
|
||||
trimForMaxSize = 0;
|
||||
} else {
|
||||
trimForMaxSize = size() - maxSize;
|
||||
}
|
||||
|
||||
if (maxIdleSecs == 0 && maxSecsToLive == 0 && trimForMaxSize < 0) {
|
||||
// nothing to trim on this cache
|
||||
return;
|
||||
}
|
||||
|
||||
long startNanos = System.nanoTime();
|
||||
|
||||
long trimmedByIdle = 0;
|
||||
long trimmedByTTL = 0;
|
||||
long trimmedByLRU = 0;
|
||||
|
||||
ArrayList<CacheEntry> activeList = new ArrayList<CacheEntry>();
|
||||
|
||||
long idleExpire = System.currentTimeMillis() - (maxIdleSecs * 1000);
|
||||
long ttlExpire = System.currentTimeMillis() - (maxSecsToLive * 1000);
|
||||
|
||||
Iterator<CacheEntry> it = map.values().iterator();
|
||||
while (it.hasNext()) {
|
||||
CacheEntry cacheEntry = it.next();
|
||||
if (maxIdleSecs > 0 && idleExpire > cacheEntry.getLastAccessTime()) {
|
||||
it.remove();
|
||||
trimmedByIdle++;
|
||||
|
||||
} else if (maxSecsToLive > 0 && ttlExpire > cacheEntry.getCreateTime()) {
|
||||
it.remove();
|
||||
trimmedByTTL++;
|
||||
|
||||
} else if (trimForMaxSize > 0) {
|
||||
activeList.add(cacheEntry);
|
||||
}
|
||||
}
|
||||
|
||||
if (trimForMaxSize > 0) {
|
||||
trimmedByLRU = activeList.size() - maxSize;
|
||||
if (trimmedByLRU > 0) {
|
||||
// sort into last access time ascending
|
||||
Collections.sort(activeList, BY_LAST_ACCESS);
|
||||
int trimSize = getTrimSize();
|
||||
for (int i = trimSize; i < activeList.size(); i++) {
|
||||
// remove if still in the cache
|
||||
map.remove(activeList.get(i).getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long exeNanos = System.nanoTime() - startNanos;
|
||||
long exeMicros = TimeUnit.MICROSECONDS.convert(exeNanos, TimeUnit.NANOSECONDS);
|
||||
|
||||
// increment the eviction statistics
|
||||
evictMicros.add(exeMicros);
|
||||
evictCount.increment();
|
||||
evictByIdle.add(trimmedByIdle);
|
||||
evictByTTL.add(trimmedByTTL);
|
||||
evictByLRU.add(trimmedByLRU);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Executed trim of cache {} in [{}]millis idle[{}] timeToLive[{}] accessTime[{}]"
|
||||
, name, exeMicros, trimmedByIdle, trimmedByTTL, trimmedByLRU);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runnable that calls the eviction routine.
|
||||
*/
|
||||
public class EvictionRunnable implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
runEviction();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator for sorting by last access time.
|
||||
*/
|
||||
public static class CompareByLastAccess implements Comparator<CacheEntry>, Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public int compare(CacheEntry entry1, CacheEntry entry2) {
|
||||
return Long.compare(entry1.getLastAccessTime(), entry2.getLastAccessTime());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the value to additionally hold createTime and lastAccessTime and hit counter.
|
||||
*/
|
||||
public static class CacheEntry {
|
||||
|
||||
private final Object key;
|
||||
private final Object value;
|
||||
private final long createTime;
|
||||
private long lastAccessTime;
|
||||
|
||||
public CacheEntry(Object key, Object value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
this.createTime = System.currentTimeMillis();
|
||||
this.lastAccessTime = createTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the entry key.
|
||||
*/
|
||||
public Object getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the entry value.
|
||||
*/
|
||||
public Object getValue() {
|
||||
// long assignment should be atomic these days (Ref Cliff Click)
|
||||
lastAccessTime = System.currentTimeMillis();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the time the entry was created.
|
||||
*/
|
||||
public long getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the time the entry was last accessed.
|
||||
*/
|
||||
public long getLastAccessTime() {
|
||||
return lastAccessTime;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
return hitCount.get();
|
||||
}
|
||||
}
|
||||
public int getHitCount() {
|
||||
return hitCount.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +219,16 @@ public class DefaultContainer implements SpiContainer {
|
||||
|
||||
ServerCacheFactory cacheFactory = serverConfig.getServerCacheFactory();
|
||||
if (cacheFactory == null) {
|
||||
cacheFactory = new DefaultServerCacheFactory();
|
||||
ServiceLoader<ServerCacheFactory> cacheFactories = ServiceLoader.load(ServerCacheFactory.class);
|
||||
Iterator<ServerCacheFactory> iterator = cacheFactories.iterator();
|
||||
if (iterator.hasNext()) {
|
||||
// use the cacheFactory (via classpath service loader)
|
||||
cacheFactory = iterator.next();
|
||||
logger.debug("using ServerCacheFactory {}", cacheFactory.getClass());
|
||||
} else {
|
||||
// use the built in default
|
||||
cacheFactory = new DefaultServerCacheFactory();
|
||||
}
|
||||
}
|
||||
|
||||
return new DefaultServerCacheManager(cacheFactory, beanOptions, queryOptions);
|
||||
|
||||
Reference in New Issue
Block a user