如何在Guava缓存中存储Map

时间:2015-02-26 23:32:51

标签: java caching dictionary guava

我有Map<Range<Double>, String>检查特定Double值(得分)映射到String(级别)的位置。最终用户希望能够动态地更改此映射,从长远来看,我们希望有一个基于Web的GUI来控制它,但是从短期来看,他们很高兴文件是在S3中,并在需要更改时进行编辑。我不想为每个请求点击S3并希望缓存它,因为它不会太频繁地更改(每周一次左右)。我不想让代码更改并退回我的服务。

以下是我的想法 -

public class Mapper() {
    private LoadingCache<Score, String> scoreToLevelCache;

public Mapper() {
    scoreToLevelCache = CacheBuilder.newBuilder()
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .build(new CacheLoader<Score, String>() {
                public String load(Score score) {
                    Map<Range<Double>, String> scoreToLevelMap = readMappingFromS3(); //readMappingFromS3 omitted for brevity
                    for(Range<Double> key : scoreToLevelMap.keySet()) {
                        if(key.contains(score.getCount())) { return scoreToLevelMap.get(key); }
                    }
                    throw new IllegalArgumentException("The score couldn't be mapped to a level. Either the score passed in was incorrect or the mapping is incorrect");
                }
            }); 
}

public String getContentLevelForScore(Score Score) {
    try {
        return scoreToLevelCache.get(Score);
    } catch (ExecutionException e) { throw new InternalServerException(e); }
  } 
}

当我这样做时,这种方法的明显问题在于load方法 Map<Range<Double>, String> scoreToLevelMap = readMappingFromS3(); 对于每个键,我一遍又一遍地加载整个地图。这不是性能问题,但是当尺寸增加时它可能会变成一个,无论如何这不是一种有效的方法。

我认为将整个地图保留在缓存中会更好,但我不知道该怎么做。任何人都可以帮助解决这个问题,或者提出一种更优雅的方法来实现这一目标。

4 个答案:

答案 0 :(得分:6)

Guava对“只包含一个值的缓存”有不同的机制;它被称为Suppliers.memoizeWithExpiration

private Supplier<Map<Range<Double>, String> cachedMap = 
    Suppliers.memoizeWithExpiration(
        new Supplier<Map<Range<Double>, String>() {
            public Map<Range<Double>, String> get() {
                return readMappingFromS3();
            }
        }, 10, TimeUnit.MINUTES);

public String getContentLevelForScore(Score score) {
    Map<Range<Double>, String> scoreMap = cachedMap.get();
    // etc.
}

答案 1 :(得分:2)

不要混用缓存和业务逻辑。 除非您的分数映射很大并且您可以加载单个部分,例如使用readMappingFromS3(Double d) - 只需缓存整个地图。

    public static final String MAGIC_WORD = "oh please please give me my data!!!";
    private final LoadingCache<String, Map<Range<Double>, String>> scoreToLevelCache;


    public Mapper() {
        scoreToLevelCache = CacheBuilder.newBuilder()
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .build(new CacheLoader<String, Map<Range<Double>, String>>() {
                    public Map<Range<Double>, String> load(String score) {
                        return readMappingFromS3(); //readMappingFromS3 omitted for brevity
                    }
                });
    }

    public Map<Range<Double>, String> getScoreMap() {
        try {
            return scoreToLevelCache.get(MAGIC_WORD);
        } catch (ExecutionException e) {
            throw new InternalServerException(e);
        }
    }

像这样获取级别名称

    public String findLevel(final Double score) {
        final Map<Range<Double>, String> scoreMap = getScoreMap();
        for (final Range<Double> key : scoreMap.keySet()) {
            if (key.contains(score)) {
                return scoreMap.get(key);
            }
        }
        ...
    }

答案 2 :(得分:0)

这是一个解决方案,可以一次读取整个地图,并通过refreshing it asynchronouzly as needed保留asyncReloading

它会在refresh期间返回旧值而不会阻止多个读取器线程,例如Suppliers.memoizeWithExpiration does

private static final Object DUMMY_KEY = new Object();
private static final LoadingCache<Object, Map<Range<Double>, String>> scoreToLevelCache = 
        CacheBuilder.newBuilder()
        .maximumSize(1)
        .refreshAfterWrite(10, TimeUnit.MINUTES)
        .build(CacheLoader.asyncReloading(
                new CacheLoader<Object, Map<Range<Double>, String>>() {
                    public Map<Range<Double>, String> load(Object key) {
                        return readMappingFromS3();
                    }
                },
                Executors.newSingleThreadExecutor()));

public String getContentLevelForScore(Score score) {
    try {
        Map<Range<Double>, String> scoreMap = scoreToLevelCache.get(DUMMY_KEY);
        // traverse scoreMap ranges
        return level;
    } catch (ExecutionException e) {
        Throwables.throwIfUnchecked(e);
        throw new IllegalStateException(e);
    }
}

另请考虑将Map<Range<Double>, String>替换为RangeMap <Double, String>,以执行有效的远程查找。

答案 3 :(得分:0)

到目前为止,我找不到一种方法来动态存储由番石榴缓存的地图值,看起来所有值都需要在缓存地图初始化期间加载一次。尽管您需要随时间推移加载地图,但是一种解决方案是使用org.apache.commons.collections4.map库中的“ PassiveExpiringMap”

private static Map<String, String> cachedMap = new PassiveExpiringMap<>(30, TimeUnit.SECONDS);

将给定时间的高速缓存键添加到地图中。