【问题标题】:How do I store a Map in a Guava Cache如何将地图存储在番石榴缓存中
【发布时间】:2015-04-29 13:31:10
【问题描述】:

我有一个Map<Range<Double>, String>,它检查特定Double 值(分数)映射到String(级别)的位置。最终用户希望能够动态更改此映射,从长远来看,我们希望有一个基于 Web 的 GUI,他们可以在其中控制它,但从短期来看,他们很高兴有一个文件在 @ 987654325@ 并在需要更改时进行编辑。我不想为每个请求点击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&lt;Range&lt;Double&gt;, String&gt; scoreToLevelMap = readMappingFromS3(); 对于每个键,我一遍又一遍地加载整个地图。这不是性能问题,但是当大小增加时它可能会成为一个问题,无论如何这不是一种有效的方法。

我认为将整个地图保存在缓存中会更好,但我不确定如何在此处执行此操作。任何人都可以提供帮助或建议一种更优雅的方式来实现这一点。

【问题讨论】:

    标签: java caching dictionary guava


    【解决方案1】:

    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.
    }
    

    【讨论】:

    • 这正是我要找的,我在这里没有看到供应商 - code.google.com/p/guava-libraries/wiki/GuavaExplained。有没有什么秘密的地方可以让我找到更多番石榴隐藏的宝石。
    • @nikhil,别忘了标记已回答的问题
    • 请注意,此解决方案将在到期时block 多个读取器线程。在我的回答中查看如何通过使用刷新番石榴缓存来避免这种情况。
    【解决方案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);
                }
            }
            ...
        }
    

    【讨论】:

    • 加 1,谢谢,如果 David 没有告诉我有关供应商的信息,我会这样做。
    【解决方案3】:

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

    它将在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&lt;Range&lt;Double&gt;, String&gt; 替换为RangeMap&lt;Double, String&gt; 以执行有效的范围查找。

    【讨论】:

      【解决方案4】:

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

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

      在给定时间内哪些缓存键被添加到地图中。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-04-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多