如何将地图存储在 Guava 缓存中
How do I store a Map in a Guava Cache
我有一个 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();
对于每个键,我都会一遍又一遍地加载整个地图。这不是一个性能问题,但当大小增加时它可能会成为一个问题,无论如何这都不是一种有效的方法。
我认为将整个地图保存在缓存中会更好,但我不确定如何在此处执行此操作。任何人都可以帮助解决这个问题或提出更优雅的实现方法。
不要混合缓存和业务逻辑。
除非你的分数映射很大并且你可以加载单个片段,例如使用 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);
}
}
...
}
Guava 对 "a cache that only ever contains one value" 有不同的机制;它叫做 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.
}
这是一次读取整个地图并保留 via asyncReloading 的解决方案。
return refresh without blocking multiple reader threads like 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>
以执行有效的远程查找。
到目前为止,我找不到可以动态存储由 guava 缓存的地图值的方法,看起来所有值都需要在缓存地图初始化期间加载一次。虽然您需要随时间加载地图,但解决方案是使用 org.apache.commons.collections4.map 库
中的 "PassiveExpiringMap"
private static Map<String, String> cachedMap = new PassiveExpiringMap<>(30, TimeUnit.SECONDS);
哪些缓存键在给定时间内添加到地图中。
我有一个 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();
对于每个键,我都会一遍又一遍地加载整个地图。这不是一个性能问题,但当大小增加时它可能会成为一个问题,无论如何这都不是一种有效的方法。
我认为将整个地图保存在缓存中会更好,但我不确定如何在此处执行此操作。任何人都可以帮助解决这个问题或提出更优雅的实现方法。
不要混合缓存和业务逻辑。 除非你的分数映射很大并且你可以加载单个片段,例如使用 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);
}
}
...
}
Guava 对 "a cache that only ever contains one value" 有不同的机制;它叫做 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.
}
这是一次读取整个地图并保留
return refresh without blocking multiple reader threads like 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>
以执行有效的远程查找。
到目前为止,我找不到可以动态存储由 guava 缓存的地图值的方法,看起来所有值都需要在缓存地图初始化期间加载一次。虽然您需要随时间加载地图,但解决方案是使用 org.apache.commons.collections4.map 库
中的 "PassiveExpiringMap"private static Map<String, String> cachedMap = new PassiveExpiringMap<>(30, TimeUnit.SECONDS);
哪些缓存键在给定时间内添加到地图中。