Add an option to use soft references for eviction with LocalCachedMap.

pull/777/head
Jarrod Ribble 8 years ago
parent 7421ff96b8
commit 796eab814d

@ -224,4 +224,4 @@ before_script:
- export REDIS_VERSION="$(redis-cli INFO SERVER | sed -n 2p)"
- echo $REDIS_VERSION
- redis-cli SHUTDOWN NOSAVE
script: mvn -Dtest=$REDISSON_TEST -Dsurefire.rerunFailingTestsCount=5 -DargLine="-Xmx2g -DredisBinary=$REDIS_BIN/redis-server -DtravisEnv=true" -Punit-test clean test -e -X
script: mvn -Dtest=$REDISSON_TEST -Dsurefire.rerunFailingTestsCount=5 -DargLine="-Xmx2g -DredisBinary=$REDIS_BIN/redis-server -DtravisEnv=true -XX:SoftRefLRUPolicyMSPerMB=0" -Punit-test clean test -e -X

@ -48,12 +48,7 @@ import org.redisson.client.protocol.convertor.NumberConvertor;
import org.redisson.client.protocol.decoder.ObjectMapEntryReplayDecoder;
import org.redisson.client.protocol.decoder.ObjectSetReplayDecoder;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.misc.Cache;
import org.redisson.misc.Hash;
import org.redisson.misc.LFUCacheMap;
import org.redisson.misc.LRUCacheMap;
import org.redisson.misc.NoneCacheMap;
import org.redisson.misc.RPromise;
import org.redisson.misc.*;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
@ -215,6 +210,9 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
if (options.getEvictionPolicy() == EvictionPolicy.LFU) {
cache = new LFUCacheMap<CacheKey, CacheValue>(options.getCacheSize(), options.getTimeToLiveInMillis(), options.getMaxIdleInMillis());
}
if (options.getEvictionPolicy() == EvictionPolicy.SOFT) {
cache = new SoftCacheMap<CacheKey, CacheValue>(options.getTimeToLiveInMillis(), options.getMaxIdleInMillis());
}
invalidationTopic = new RedissonTopic<Object>(commandExecutor, suffixName(name, "topic"));
if (options.isInvalidateEntryOnChange()) {

@ -25,7 +25,7 @@ import java.util.concurrent.TimeUnit;
*/
public class LocalCachedMapOptions {
public enum EvictionPolicy {NONE, LRU, LFU};
public enum EvictionPolicy {NONE, LRU, LFU, SOFT};
private boolean invalidateEntryOnChange;
private EvictionPolicy evictionPolicy;
@ -115,6 +115,7 @@ public class LocalCachedMapOptions {
* @param evictionPolicy
* <p><code>LRU</code> - uses cache with LRU (least recently used) eviction policy.
* <p><code>LFU</code> - uses cache with LFU (least frequently used) eviction policy.
* <p><code>SOFT</code> - uses cache with soft references. The garbage collector will evict items from the cache when the JVM is running out of memory.
* <p><code>NONE</code> - doesn't use eviction policy, but timeToLive and maxIdleTime params are still working.
* @return LocalCachedMapOptions instance
*/

@ -37,54 +37,6 @@ import io.netty.util.internal.PlatformDependent;
*/
public abstract class AbstractCacheMap<K, V> implements Cache<K, V> {
public static class CachedValue {
private final Object key;
private final Object value;
long ttl;
long maxIdleTime;
long creationTime;
long lastAccess;
public CachedValue(Object key, Object value, long ttl, long maxIdleTime) {
this.value = value;
this.ttl = ttl;
this.key = key;
this.maxIdleTime = maxIdleTime;
creationTime = System.currentTimeMillis();
lastAccess = creationTime;
}
public boolean isExpired() {
boolean result = false;
long currentTime = System.currentTimeMillis();
if (ttl != 0 && creationTime + ttl < currentTime) {
result = true;
}
if (maxIdleTime != 0 && lastAccess + maxIdleTime < currentTime) {
result = true;
}
return result;
}
public Object getKey() {
return key;
}
public Object getValue() {
lastAccess = System.currentTimeMillis();
return value;
}
@Override
public String toString() {
return "CachedValue [key=" + key + ", value=" + value + "]";
}
}
final int size;
final ConcurrentMap<K, CachedValue> map = PlatformDependent.newConcurrentHashMap();
private final long timeToLiveInMillis;
@ -237,7 +189,7 @@ public abstract class AbstractCacheMap<K, V> implements Cache<K, V> {
}
protected CachedValue create(K key, V value, long ttl, long maxIdleTime) {
return new CachedValue(key, value, ttl, maxIdleTime);
return new StdCachedValue(key, value, ttl, maxIdleTime);
}
protected void onValueCreate(CachedValue entry) {

@ -0,0 +1,12 @@
package org.redisson.misc;
/**
* Created by jribble on 2/20/17.
*/
public interface CachedValue<K, V> {
boolean isExpired();
K getKey();
V getValue();
}

@ -57,7 +57,7 @@ public class LFUCacheMap<K, V> extends AbstractCacheMap<K, V> {
}
public static class LFUCachedValue extends CachedValue {
public static class LFUCachedValue extends StdCachedValue {
Long id;
long accessCount;

@ -0,0 +1,39 @@
/**
* Copyright 2016 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.misc;
/**
*
* @author Nikita Koksharov
*
* @param <K> key
* @param <V> value
*/
public class SoftCacheMap<K, V> extends AbstractCacheMap<K, V> {
public SoftCacheMap(long timeToLiveInMillis, long maxIdleInMillis) {
super(0, timeToLiveInMillis, maxIdleInMillis);
}
protected CachedValue create(K key, V value, long ttl, long maxIdleTime) {
return new SoftCachedValue(key, value, ttl, maxIdleTime);
}
@Override
protected void onMapFull() {
}
}

@ -0,0 +1,35 @@
package org.redisson.misc;
import java.lang.ref.SoftReference;
/**
* Created by jribble on 2/20/17.
*/
public class SoftCachedValue<K, V> implements CachedValue<K, V> {
StdCachedValue<K, SoftReference<V>> value;
public SoftCachedValue(K key, V value, long ttl, long maxIdleTime) {
this.value = new StdCachedValue<>(key, new SoftReference<>(value), ttl, maxIdleTime);
}
@Override
public boolean isExpired() {
return value.isExpired();
}
@Override
public K getKey() {
return value.getKey();
}
@Override
public V getValue() {
return value.getValue().get();
}
@Override
public String toString() {
return value.toString();
}
}

@ -0,0 +1,56 @@
package org.redisson.misc;
/**
* Created by jribble on 2/20/17.
*/
public class StdCachedValue<K, V> implements CachedValue<K, V> {
protected final K key;
protected final V value;
long ttl;
long maxIdleTime;
long creationTime;
long lastAccess;
public StdCachedValue(K key, V value, long ttl, long maxIdleTime) {
this.value = value;
this.ttl = ttl;
this.key = key;
this.maxIdleTime = maxIdleTime;
creationTime = System.currentTimeMillis();
lastAccess = creationTime;
}
@Override
public boolean isExpired() {
boolean result = false;
long currentTime = System.currentTimeMillis();
if (ttl != 0 && creationTime + ttl < currentTime) {
result = true;
}
if (maxIdleTime != 0 && lastAccess + maxIdleTime < currentTime) {
result = true;
}
return result;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
lastAccess = System.currentTimeMillis();
return value;
}
@Override
public String toString() {
return "CachedValue [key=" + key + ", value=" + value + "]";
}
}

@ -1,7 +1,10 @@
package org.redisson.misc;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import org.junit.Test;

@ -0,0 +1,65 @@
package org.redisson.misc;
import org.junit.Test;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
public class SoftCacheMapTest {
@Test
public void testMaxIdleTimeEviction() throws InterruptedException {
Cache<Integer, Integer> map = new SoftCacheMap<Integer, Integer>(0, 0);
map.put(1, 0, 0, TimeUnit.MILLISECONDS, 400, TimeUnit.MILLISECONDS);
assertThat(map.get(1)).isEqualTo(0);
Thread.sleep(200);
assertThat(map.get(1)).isEqualTo(0);
Thread.sleep(200);
assertThat(map.get(1)).isEqualTo(0);
Thread.sleep(200);
assertThat(map.get(1)).isEqualTo(0);
Thread.sleep(410);
assertThat(map.keySet()).isEmpty();
}
@Test
public void testTTLEviction() throws InterruptedException {
Cache<Integer, Integer> map = new SoftCacheMap<Integer, Integer>(0, 0);
map.put(1, 0, 500, TimeUnit.MILLISECONDS, 0, TimeUnit.MILLISECONDS);
assertThat(map.get(1)).isEqualTo(0);
Thread.sleep(100);
assertThat(map.get(1)).isEqualTo(0);
assertThat(map.keySet()).containsOnly(1);
Thread.sleep(500);
assertThat(map.keySet()).isEmpty();
}
@Test
public void testSizeEviction() {
Cache<Integer, Integer> map = new SoftCacheMap<Integer, Integer>(0, 0);
map.put(1, 0);
map.put(2, 0);
assertThat(map.keySet()).containsOnly(1, 2);
map.put(3, 0);
map.put(4, 0);
assertThat(map.keySet()).containsOnly(1, 2, 3, 4);
}
// This test requires using -XX:SoftRefLRUPolicyMSPerMB=0 to pass
@Test
public void testSoftReferences() {
Cache<Integer, Integer> map = new SoftCacheMap<Integer, Integer>(0, 0);
for(int i=0;i<100000;i++) {
map.put(i, new Integer(i));
}
assertThat(map.values().stream().filter(Objects::nonNull).count()).isEqualTo(100000);
System.gc();
assertThat(map.values().stream().filter(Objects::nonNull).count()).isZero();
}
}
Loading…
Cancel
Save