打算高效缓存多个值时,Guava缓存应该如何实现?
How should I implement Guava cache when I plan to cache multiple values efficiently?
我有一个 Java class 有一个 Guava LoadingCache<String, Integer>
并且在那个缓存中,我打算存储两件事:活跃员工的平均工作时间天和他们的效率。我正在缓存这些值,因为每次收到请求时都需要进行计算。此外,缓存的内容每分钟都会刷新 (refreshAfterWrite
)。
我曾考虑在这种情况下使用 CacheLoader
,但是,它的加载方法每个键只加载一个值。在我的 CacheLoader
中,我打算做类似的事情:
private Service service = new Service();
public Integer load(String key) throws Exception {
if (key.equals("employeeAvg"))
return calculateEmployeeAvg(service.getAllEmployees());
if (key.equals("employeeEff"))
return calculateEmployeeEff(service.getAllEmployees());
return -1;
}
对我来说,我发现这非常低效,因为为了加载两个值,我必须调用 service.getAllEmployees()
两次,因为如果我错了请纠正我,CacheLoader
应该是无国籍。
这让我想到使用 LoadingCache.put(key, value)
方法,这样我就可以创建一个实用方法来调用 service.getAllEmployees()
一次并即时计算值。但是,如果我确实使用 LoadingCache.put()
,我将没有 refreshAfterWrite
功能,因为它依赖于缓存加载程序。
如何提高效率?
However, if I do use LoadingCache.put()
, I won't have the refreshAfterWrite
feature since it's dependent on a cache loader.
我不确定,但您可以从 load
方法中调用它。我的意思是,像您一样计算请求的值,而在另一个中计算 put
。然而,这感觉很糟糕。
如果service.getAllEmployees
很昂贵,那么你可以缓存它。如果 calculateEmployeeAvg
和 calculateEmployeeEff
都很便宜,则在需要时重新计算它们。否则,看起来您可以使用两个缓存。
我想,一次计算这两个值的方法可能是一个合理的解决方案。创建一个小型的 Pair-like class 聚合它们并将其用作缓存值。只有一把钥匙。
关于您自己的解决方案,它可以像
一样微不足道
class EmployeeStatsCache {
private long validUntil;
private List<Employee> employeeList;
private Integer employeeAvg;
private Integer employeeEff;
private boolean isValid() {
return System.currentTimeMillis() <= validUntil;
}
private synchronized List<Employee> getEmployeeList() {
if (!isValid || employeeList==null) {
employeeList = service.getAllEmployees();
validUntil = System.currentTimeMillis() + VALIDITY_MILLIS;
}
return employeeList;
}
public synchronized int getEmployeeAvg() {
if (!isValid || employeeAvg==null) {
employeeAvg = calculateEmployeeAvg(getEmployeeList());
}
return employeeAvg;
}
public synchronized int getEmployeeEff() {
if (!isValid || employeeAvg==null) {
employeeAvg = calculateEmployeeEff(getEmployeeList());
}
return employeeAvg;
}
}
您可能希望在私有最终字段上同步,而不是 synchronized
方法。还有其他可能性(例如 Atomic*
),但基本设计可能比改编 Guava 的 Cache
.
更简单
现在,我看到 Guava 中有 Suppliers#memoizeWithExpiration
。这可能更简单。
您的问题似乎源于使用字符串表示值类型(有效 Java 项目 50)。相反,请考虑定义一个适当的值类型来存储此数据,并使用 memoizing Supplier
来避免重新计算它们。
public static class EmployeeStatistics {
private final int average;
private final int efficiency;
// constructor, getters and setters
}
Supplier<EmployeeStatistics> statistics = Suppliers.memoize(
new Supplier<EmployeeStatistics>() {
@Override
public EmployeeStatistics get() {
List<Employee> employees = new Service().getAllEmployees();
return new EmployeeStatistics(
calculateEmployeeAvg(employees),
calculateEmployeeEff(employees));
}});
您甚至可以将这些计算方法移到 EmployeeStatistics
中,只需将所有员工传递给构造函数并让它计算适当的数据。
如果您需要配置您的缓存行为超过 Suppliers.memoize()
或 Suppliers.memoizeWithExpiration()
可以提供的,请考虑这种类似的模式,它隐藏了您在内部使用 Cache
的事实一个 Supplier
:
Supplier<EmployeeStatistics> statistics = new Supplier<EmployeeStatistics>() {
private final Object key = new Object();
private final LoadingCache<Object, EmployeeStatistics> cache =
CacheBuilder.newBuilder()
// configure your builder
.build(
new CacheLoader<Object, EmployeeStatistics>() {
public EmployeeStatistics load(Object key) {
// same behavior as the Supplier above
}});
@Override
public EmployeeStatistics get() {
return cache.get(key);
}};
我有一个 Java class 有一个 Guava LoadingCache<String, Integer>
并且在那个缓存中,我打算存储两件事:活跃员工的平均工作时间天和他们的效率。我正在缓存这些值,因为每次收到请求时都需要进行计算。此外,缓存的内容每分钟都会刷新 (refreshAfterWrite
)。
我曾考虑在这种情况下使用 CacheLoader
,但是,它的加载方法每个键只加载一个值。在我的 CacheLoader
中,我打算做类似的事情:
private Service service = new Service();
public Integer load(String key) throws Exception {
if (key.equals("employeeAvg"))
return calculateEmployeeAvg(service.getAllEmployees());
if (key.equals("employeeEff"))
return calculateEmployeeEff(service.getAllEmployees());
return -1;
}
对我来说,我发现这非常低效,因为为了加载两个值,我必须调用 service.getAllEmployees()
两次,因为如果我错了请纠正我,CacheLoader
应该是无国籍。
这让我想到使用 LoadingCache.put(key, value)
方法,这样我就可以创建一个实用方法来调用 service.getAllEmployees()
一次并即时计算值。但是,如果我确实使用 LoadingCache.put()
,我将没有 refreshAfterWrite
功能,因为它依赖于缓存加载程序。
如何提高效率?
However, if I do use
LoadingCache.put()
, I won't have therefreshAfterWrite
feature since it's dependent on a cache loader.
我不确定,但您可以从 load
方法中调用它。我的意思是,像您一样计算请求的值,而在另一个中计算 put
。然而,这感觉很糟糕。
如果service.getAllEmployees
很昂贵,那么你可以缓存它。如果 calculateEmployeeAvg
和 calculateEmployeeEff
都很便宜,则在需要时重新计算它们。否则,看起来您可以使用两个缓存。
我想,一次计算这两个值的方法可能是一个合理的解决方案。创建一个小型的 Pair-like class 聚合它们并将其用作缓存值。只有一把钥匙。
关于您自己的解决方案,它可以像
一样微不足道class EmployeeStatsCache {
private long validUntil;
private List<Employee> employeeList;
private Integer employeeAvg;
private Integer employeeEff;
private boolean isValid() {
return System.currentTimeMillis() <= validUntil;
}
private synchronized List<Employee> getEmployeeList() {
if (!isValid || employeeList==null) {
employeeList = service.getAllEmployees();
validUntil = System.currentTimeMillis() + VALIDITY_MILLIS;
}
return employeeList;
}
public synchronized int getEmployeeAvg() {
if (!isValid || employeeAvg==null) {
employeeAvg = calculateEmployeeAvg(getEmployeeList());
}
return employeeAvg;
}
public synchronized int getEmployeeEff() {
if (!isValid || employeeAvg==null) {
employeeAvg = calculateEmployeeEff(getEmployeeList());
}
return employeeAvg;
}
}
您可能希望在私有最终字段上同步,而不是 synchronized
方法。还有其他可能性(例如 Atomic*
),但基本设计可能比改编 Guava 的 Cache
.
现在,我看到 Guava 中有 Suppliers#memoizeWithExpiration
。这可能更简单。
您的问题似乎源于使用字符串表示值类型(有效 Java 项目 50)。相反,请考虑定义一个适当的值类型来存储此数据,并使用 memoizing Supplier
来避免重新计算它们。
public static class EmployeeStatistics {
private final int average;
private final int efficiency;
// constructor, getters and setters
}
Supplier<EmployeeStatistics> statistics = Suppliers.memoize(
new Supplier<EmployeeStatistics>() {
@Override
public EmployeeStatistics get() {
List<Employee> employees = new Service().getAllEmployees();
return new EmployeeStatistics(
calculateEmployeeAvg(employees),
calculateEmployeeEff(employees));
}});
您甚至可以将这些计算方法移到 EmployeeStatistics
中,只需将所有员工传递给构造函数并让它计算适当的数据。
如果您需要配置您的缓存行为超过 Suppliers.memoize()
或 Suppliers.memoizeWithExpiration()
可以提供的,请考虑这种类似的模式,它隐藏了您在内部使用 Cache
的事实一个 Supplier
:
Supplier<EmployeeStatistics> statistics = new Supplier<EmployeeStatistics>() {
private final Object key = new Object();
private final LoadingCache<Object, EmployeeStatistics> cache =
CacheBuilder.newBuilder()
// configure your builder
.build(
new CacheLoader<Object, EmployeeStatistics>() {
public EmployeeStatistics load(Object key) {
// same behavior as the Supplier above
}});
@Override
public EmployeeStatistics get() {
return cache.get(key);
}};