mirror of
https://github.com/tiennm99/zfoo.git
synced 2026-08-10 06:22:35 +00:00
ref[cache]: move LazyCache from orm to scheduler
This commit is contained in:
+1
-1
@@ -22,12 +22,12 @@ import com.zfoo.orm.cache.persister.PNode;
|
||||
import com.zfoo.orm.model.EntityDef;
|
||||
import com.zfoo.orm.model.IEntity;
|
||||
import com.zfoo.orm.query.Page;
|
||||
import com.zfoo.orm.util.LazyCache;
|
||||
import com.zfoo.protocol.collection.CollectionUtils;
|
||||
import com.zfoo.protocol.exception.RunException;
|
||||
import com.zfoo.protocol.model.Pair;
|
||||
import com.zfoo.protocol.util.AssertionUtils;
|
||||
import com.zfoo.protocol.util.ThreadUtils;
|
||||
import com.zfoo.scheduler.util.LazyCache;
|
||||
import com.zfoo.scheduler.util.TimeUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
package com.zfoo.orm.util;
|
||||
|
||||
import com.zfoo.protocol.model.Pair;
|
||||
import com.zfoo.scheduler.util.TimeUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* lazy detect the expiration time
|
||||
*
|
||||
* @author godotg
|
||||
*/
|
||||
public class LazyCache<K, V> {
|
||||
|
||||
private static class CacheValue<V> {
|
||||
public volatile V value;
|
||||
public volatile long expireTime;
|
||||
}
|
||||
|
||||
public static enum RemovalCause {
|
||||
/**
|
||||
* The entry was manually removed by the user. This can result from the user invoking any of the
|
||||
* following methods on the cache or map view.
|
||||
* remove()
|
||||
*/
|
||||
EXPLICIT,
|
||||
|
||||
/**
|
||||
* The entry itself was not actually removed, but its value was replaced by the user. This can
|
||||
* result from the user invoking any of the following methods on the cache or map view.
|
||||
* put()
|
||||
*/
|
||||
REPLACED,
|
||||
|
||||
|
||||
/**
|
||||
* The entry's expiration timestamp has passed.
|
||||
*/
|
||||
EXPIRED,
|
||||
|
||||
/**
|
||||
* The entry was evicted due to size constraints.
|
||||
*/
|
||||
SIZE;
|
||||
}
|
||||
|
||||
|
||||
private int maximumSize;
|
||||
private long expireAfterAccessMillis;
|
||||
private long expireCheckIntervalMillis;
|
||||
private volatile long minExpireTime;
|
||||
private AtomicLong expireCheckTimeAtomic;
|
||||
private ConcurrentMap<K, CacheValue<V>> cacheMap;
|
||||
private BiConsumer<Pair<K, V>, RemovalCause> removeListener = (pair, removalCause) -> {
|
||||
};
|
||||
|
||||
public LazyCache(int maximumSize, long expireAfterAccessMillis, long expireCheckIntervalMillis, BiConsumer<Pair<K, V>, RemovalCause> removeListener) {
|
||||
this.maximumSize = maximumSize;
|
||||
this.expireAfterAccessMillis = expireAfterAccessMillis;
|
||||
this.expireCheckIntervalMillis = expireCheckIntervalMillis;
|
||||
this.minExpireTime = TimeUtils.now();
|
||||
this.expireCheckTimeAtomic = new AtomicLong(TimeUtils.now() + expireCheckIntervalMillis);
|
||||
this.cacheMap = new ConcurrentHashMap<>(Math.max(maximumSize / 16, 512));
|
||||
if (removeListener != null) {
|
||||
this.removeListener = removeListener;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the cache previously contained a value associated with the key, the old value is replaced by the new value.
|
||||
*/
|
||||
public void put(K key, V value) {
|
||||
var cacheValue = new CacheValue<V>();
|
||||
cacheValue.value = value;
|
||||
cacheValue.expireTime = TimeUtils.now();
|
||||
var oldCacheValue = cacheMap.put(key, cacheValue);
|
||||
if (oldCacheValue != null) {
|
||||
removeListener.accept(new Pair<>(key, oldCacheValue.value), RemovalCause.REPLACED);
|
||||
}
|
||||
checkMaximumSize();
|
||||
}
|
||||
|
||||
public V get(K key) {
|
||||
checkExpire();
|
||||
|
||||
var cacheValue = cacheMap.get(key);
|
||||
if (cacheValue == null) {
|
||||
return null;
|
||||
}
|
||||
if (cacheValue.expireTime < TimeUtils.now()) {
|
||||
removeForCause(key, RemovalCause.EXPIRED);
|
||||
return null;
|
||||
}
|
||||
cacheValue.expireTime = TimeUtils.now() + expireAfterAccessMillis;
|
||||
return cacheValue.value;
|
||||
}
|
||||
|
||||
|
||||
public void remove(K key) {
|
||||
removeForCause(key, RemovalCause.EXPLICIT);
|
||||
}
|
||||
|
||||
private void removeForCause(K key, RemovalCause removalCause) {
|
||||
if (key == null) {
|
||||
return;
|
||||
}
|
||||
var cacheValue = cacheMap.remove(key);
|
||||
if (cacheValue != null) {
|
||||
removeListener.accept(new Pair<>(key, cacheValue.value), removalCause);
|
||||
}
|
||||
}
|
||||
|
||||
public void forEach(BiConsumer<K, V> biConsumer) {
|
||||
for (var entry : cacheMap.entrySet()) {
|
||||
biConsumer.accept(entry.getKey(), entry.getValue().value);
|
||||
}
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return cacheMap.size();
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
private void checkMaximumSize() {
|
||||
if (cacheMap.size() <= maximumSize) {
|
||||
return;
|
||||
}
|
||||
K minKey = null;
|
||||
var minTimestamp = Long.MAX_VALUE;
|
||||
for (var entry : cacheMap.entrySet()) {
|
||||
if (entry.getValue().expireTime < minTimestamp) {
|
||||
minKey = entry.getKey();
|
||||
minTimestamp = entry.getValue().expireTime;
|
||||
}
|
||||
}
|
||||
removeForCause(minKey, RemovalCause.SIZE);
|
||||
}
|
||||
|
||||
private void checkExpire() {
|
||||
var now = TimeUtils.now();
|
||||
var expireCheckTime = expireCheckTimeAtomic.get();
|
||||
if (now > expireCheckTime) {
|
||||
if (expireCheckTimeAtomic.compareAndSet(expireCheckTime, now + expireCheckIntervalMillis)) {
|
||||
if (now > this.minExpireTime) {
|
||||
var minTimestamp = Long.MAX_VALUE;
|
||||
var removeList = new ArrayList<K>();
|
||||
for (var entry : cacheMap.entrySet()) {
|
||||
var expireTime = entry.getValue().expireTime;
|
||||
if (expireTime < now) {
|
||||
removeList.add(entry.getKey());
|
||||
continue;
|
||||
}
|
||||
if (expireTime < minTimestamp) {
|
||||
minTimestamp = expireTime;
|
||||
}
|
||||
}
|
||||
removeList.forEach(it -> removeForCause(it, RemovalCause.EXPIRED));
|
||||
if (this.minExpireTime < Long.MAX_VALUE) {
|
||||
this.minExpireTime = minTimestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package com.zfoo.orm.cache;
|
||||
|
||||
import com.zfoo.orm.util.LazyCache;
|
||||
import com.zfoo.protocol.model.Pair;
|
||||
import com.zfoo.protocol.util.StringUtils;
|
||||
import com.zfoo.protocol.util.ThreadUtils;
|
||||
import com.zfoo.scheduler.util.TimeUtils;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* @author godotg
|
||||
*/
|
||||
@Ignore
|
||||
public class LazyCacheTest {
|
||||
|
||||
private static final BiConsumer<Pair<Integer, String>, LazyCache.RemovalCause> myRemoveCallback = new BiConsumer<Pair<Integer, String>, LazyCache.RemovalCause>() {
|
||||
@Override
|
||||
public void accept(Pair<Integer, String> pair, LazyCache.RemovalCause removalCause) {
|
||||
System.out.println(StringUtils.format("remove key:[{}] value:[{}] removalCause:[{}]", pair.getKey(), pair.getValue(), removalCause));
|
||||
}
|
||||
};
|
||||
|
||||
@Test
|
||||
public void putTest() {
|
||||
var lazyCache = new LazyCache<Integer, String>(3, 10 * TimeUtils.MILLIS_PER_SECOND, 5 * TimeUtils.MILLIS_PER_SECOND, myRemoveCallback);
|
||||
|
||||
lazyCache.put(1, "a");
|
||||
lazyCache.put(2, "b");
|
||||
ThreadUtils.sleep(1000);
|
||||
lazyCache.put(3, "c");
|
||||
ThreadUtils.sleep(1000);
|
||||
lazyCache.put(4, "d");
|
||||
ThreadUtils.sleep(1000);
|
||||
lazyCache.put(5, "e");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expireTest() {
|
||||
var lazyCache = new LazyCache<Integer, String>(10, 10 * TimeUtils.MILLIS_PER_SECOND, 5 * TimeUtils.MILLIS_PER_SECOND, myRemoveCallback);
|
||||
|
||||
lazyCache.put(1, "a");
|
||||
lazyCache.put(2, "b");
|
||||
lazyCache.put(3, "c");
|
||||
lazyCache.put(4, "d");
|
||||
lazyCache.put(5, "e");
|
||||
ThreadUtils.sleep(11 * TimeUtils.MILLIS_PER_SECOND);
|
||||
System.out.println(lazyCache.get(1));
|
||||
System.out.println(lazyCache.get(2));
|
||||
System.out.println(lazyCache.get(3));
|
||||
System.out.println(lazyCache.get(4));
|
||||
System.out.println(lazyCache.get(5));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expire1Test() {
|
||||
var lazyCache = new LazyCache<Integer, String>(10, 10 * TimeUtils.MILLIS_PER_SECOND, 5 * TimeUtils.MILLIS_PER_SECOND, myRemoveCallback);
|
||||
|
||||
lazyCache.put(1, "a");
|
||||
lazyCache.put(2, "b");
|
||||
lazyCache.put(3, "c");
|
||||
lazyCache.put(4, "d");
|
||||
lazyCache.put(5, "e");
|
||||
for (int i = 0; i < 11; i++) {
|
||||
lazyCache.get(1);
|
||||
lazyCache.get(2);
|
||||
ThreadUtils.sleep(1 * TimeUtils.MILLIS_PER_SECOND);
|
||||
}
|
||||
System.out.println(lazyCache.get(1));
|
||||
System.out.println(lazyCache.get(2));
|
||||
System.out.println(lazyCache.get(3));
|
||||
System.out.println(lazyCache.get(4));
|
||||
System.out.println(lazyCache.get(5));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void batchTest() {
|
||||
var lazyCache = new LazyCache<Integer, String>(1_0000, 10 * TimeUtils.MILLIS_PER_SECOND, 5 * TimeUtils.MILLIS_PER_SECOND, myRemoveCallback);
|
||||
|
||||
for (int i = 0; i < 1000_0000; i++) {
|
||||
lazyCache.put(i, String.valueOf(i));
|
||||
if (i % 1_0000 == 0) {
|
||||
ThreadUtils.sleep(5 * TimeUtils.MILLIS_PER_SECOND);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user