1

我有带有键-> 值映射的咖啡因缓存。Key接口的实现有多种不同的equals方法。为了从基于 的缓存中删除值someOtherVal,我不得不使用cache.asMap().keySet().removeIf(comp::isSame)超级慢的代码。

对于这种缓存中单值映射的许多键,还有其他解决方案吗?我想到的一件事是有 2 个 Cache 实例,一个带有Cache<Key, String>,另一个带有Cache<someOtherVal, Key>,每当我想删除一个值时,我都会使用另一个缓存来定位 Key 。

那么唯一的问题是如何使这两个缓存保持同步?是否已经有解决方案?

import java.time.Duration;
import java.util.Objects;
import java.util.UUID;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.base.Stopwatch;

public class Removal {
    private static final int MAX = 1_000_000;

    interface Key{
        String getSomeOtherVal();
        default boolean isSame(Key k){
            return Objects.equals(k.getSomeOtherVal(),getSomeOtherVal());
        }
    }

    static class KeyImpl implements Key{
        int id;
        String someOtherVal;

        public KeyImpl(int id, String someOtherVal) {
            this.id = id;
            this.someOtherVal = someOtherVal;
        }

        public int getId() {
            return id;
        }

        @Override
        public String getSomeOtherVal() {
            return someOtherVal;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;
            KeyImpl key = (KeyImpl)o;
            return id == key.id;
        }

        @Override
        public int hashCode() {
            return Objects.hash(id);
        }
    }

    Cache<Key, String> cache = Caffeine.newBuilder().build();

    public static void main(String[] args) {
        Removal s = new Removal();
        s.fill();
        Duration sRem = s.slowRemovalFirst100();
        Duration fRem = s.fastRemoval100To200();
        System.out.println("Slow removal in " + sRem);
        System.out.println("Fast removal in " + fRem);
    }

    private Duration slowRemovalFirst100(){
        Stopwatch sw = Stopwatch.createStarted();
        for(int i=0; i<100; i++){
            Key comp = new KeyImpl(i, String.valueOf(i));
            cache.asMap().keySet().removeIf(comp::isSame);  //Finds a key by some other property and then removes it (SLOW)
            //System.out.println("Removed " + i);
        }
        return sw.stop().elapsed();
    }

    private Duration fastRemoval100To200(){
        Stopwatch sw = Stopwatch.createStarted();
        for(int i=100; i<200; i++){
            Key comp = new KeyImpl(i, String.valueOf(i));
            cache.invalidate(comp); //Uses direct access to map by key (FAST)
            //System.out.println("Removed " + i);
        }
        return sw.stop().elapsed();
    }

    private void fill(){
        for(int i=0; i<MAX; i++){
            cache.put(new KeyImpl(i, String.valueOf(i)), UUID.randomUUID().toString());
        }
    }
}

在我的机器上运行此代码的结果:

Slow removal in PT2.807105177S
Fast removal in PT0.000126183S

哪里能看出这么大的区别……

4

1 回答 1

3

好的,我设法解决了这个问题:

public class IndexedCache<K,V> implements Cache<K,V> {

    @Delegate
    private Cache<K, V> cache;
    private Map<Class<?>, Map<Object, Set<K>>> indexes;

    private IndexedCache(Builder<K, V> bldr){
        this.indexes = bldr.indexes;
        cache = bldr.caf.build();
    }

    public <R> void invalidateAllWithIndex(Class<R> clazz, R value) {
        cache.invalidateAll(indexes.get(clazz).getOrDefault(value, new HashSet<>()));
    }

    public static class Builder<K, V>{
        Map<Class<?>, Function<K, ?>> functions = new HashMap<>();
        Map<Class<?>, Map<Object, Set<K>>> indexes = new ConcurrentHashMap<>();
        Caffeine<K,V> caf;

        public <R> Builder<K,V> withIndex(Class<R> clazz, Function<K, R> function){
            functions.put(clazz, function);
            indexes.put(clazz, new ConcurrentHashMap<>());
            return this;
        }

        public IndexedCache<K, V> buildFromCaffeine(Caffeine<Object, Object> caffeine) {
            caf = caffeine.writer(new CacheWriter<K, V>() {

                @Override
                public void write( K k, V v) {
                    for(Map.Entry<Class<?>, Map<Object, Set<K>>> indexesEntry : indexes.entrySet()){
                        indexesEntry.getValue().computeIfAbsent(functions.get(indexesEntry.getKey()).apply(k), (ky)-> new HashSet<>())
                        .add(k);
                    }
                }

                @Override
                public void delete( K k,  V v,  RemovalCause removalCause) {
                    for(Map.Entry<Class<?>, Map<Object, Set<K>>> indexesEntry : indexes.entrySet()){
                        indexesEntry.getValue().remove(functions.get(indexesEntry.getKey()).apply(k));
                    }
                }
            });
            return new IndexedCache<>(this);
        }
    }

}

这是用例:

@AllArgsConstructor
    @Data
    @EqualsAndHashCode(onlyExplicitlyIncluded = true)
    static class CompositeKey{
        @EqualsAndHashCode.Include
        Integer k1;
        String k2;
        Long k3;
    }


    public static void main(String[] args) {


        Caffeine<Object, Object> cfein = Caffeine.newBuilder().softValues().maximumSize(200_000);

        IndexedCache<CompositeKey, String> cache = new IndexedCache.Builder<CompositeKey, String>()
                .withIndex(Long.class, ck -> ck.getK3())
                .withIndex(String.class, ck -> ck.getK2())
                .buildFromCaffeine(cfein);


        for(int i=0; i<100; i++){
            cache.put(new CompositeKey(i, String.valueOf(i), Long.valueOf(i)), "sdfsdf");
        }


        for(int i=0; i<10; i++){
            //use equals method of CompositeKey to do equals comp.
            cache.invalidate(new CompositeKey(i, String.valueOf(i), Long.valueOf(i)));
        }

        for(int i=10; i<20; i++){
            //use Long index
            cache.invalidateAllWithIndex(Long.class, Long.valueOf(i));
        }

        for(int i=20; i<30; i++){
            //use String index
            cache.invalidateAllWithIndex(String.class, String.valueOf(i));
        }


        int y = 4;

    }

这是我讨论的链接:https ://github.com/ben-manes/caffeine/issues/279

于 2018-11-05T08:42:50.157 回答