ref[cache]: move LazyCache from orm to scheduler

This commit is contained in:
godotg
2024-03-26 11:21:12 +08:00
parent 854ba4faf2
commit f56bb656ae
3 changed files with 3 additions and 6 deletions
+1 -1
View File
@@ -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;
}
}
}
}
}
}
-89
View File
@@ -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);
}
}
}
}