[英]How should I implement Guava cache when I plan to cache multiple values efficiently?
I have a Java class that has a Guava LoadingCache<String, Integer>
and in that cache, I'm planning to store two things: the average time active employees have worked for the day and their efficiency. 我有一个Java类,它有一个Guava
LoadingCache<String, Integer>
,在那个缓存中,我打算存储两件事:活跃员工当天工作的平均时间和效率。 I am caching these values because it would be expensive to compute every time a request comes in. Also, the contents of the cache will be refreshed ( refreshAfterWrite
) every minute. 我正在缓存这些值,因为每次请求进入时计算都会很昂贵。而且,每分钟都会刷新缓存的内容(
refreshAfterWrite
)。
I was thinking of using a CacheLoader
for this situation, however, its load method only loads one value per key. 我想在这种情况下使用
CacheLoader
,但是,它的加载方法只为每个键加载一个值。 In my CacheLoader
, I was planning to do something like: 在我的
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;
}
For me, I find this very inefficient since in order to load both values, I have to invoke service.getAllEmployees()
twice because, correct me if I'm wrong, CacheLoader
's should be stateless. 对我来说,我发现这非常低效,因为为了加载这两个值,我必须两次调用
service.getAllEmployees()
因为,如果我错了,请纠正我, CacheLoader
应该是无状态的。
Which made me think to use the LoadingCache.put(key, value)
method so I can just create a utility method that invokes service.getAllEmployees()
once and calculate the values on the fly. 这使我想到使用
LoadingCache.put(key, value)
方法,所以我可以创建一个实用程序方法,调用service.getAllEmployees()
一次并动态计算值。 However, if I do use LoadingCache.put()
, I won't have the refreshAfterWrite
feature since it's dependent on a cache loader. 但是,如果我使用
LoadingCache.put()
,我将没有refreshAfterWrite
功能,因为它依赖于缓存加载器。
How do I make this more efficient? 如何提高效率?
It seems like your problem stems from using strings to represent value types (Effective Java Item 50). 看起来您的问题源于使用字符串来表示值类型(Effective Java Item 50)。 Instead, consider defining a proper value type that stores this data, and use a memoizing
Supplier
to avoid recomputing them. 相反,请考虑定义存储此数据的正确值类型,并使用记忆
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));
}});
You could even move these calculation methods inside EmployeeStatistics
and simply pass in all employees to the constructor and let it compute the appropriate data. 您甚至可以在
EmployeeStatistics
移动这些计算方法,只需将所有员工传递给构造函数,然后让它计算相应的数据。
If you need to configure your caching behavior more than Suppliers.memoize()
or Suppliers.memoizeWithExpiration()
can provide, consider this similar pattern, which hides the fact that you're using a Cache
inside a Supplier
: 如果您需要配置您的缓存行为超过
Suppliers.memoize()
或Suppliers.memoizeWithExpiration()
可以提供,请考虑这种类似的模式,这隐藏了您在Supplier
使用Cache
的事实:
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);
}};
However, if I do use
LoadingCache.put()
, I won't have therefreshAfterWrite
feature since it's dependent on a cache loader.但是,如果我使用
LoadingCache.put()
,我将没有refreshAfterWrite
功能,因为它依赖于缓存加载器。
I'm not sure, but you might be able to call it from inside the load
method. 我不确定,但您可以从
load
方法中调用它。 I mean, compute the requested value as you do and put
in the other. 我的意思是,像你一样计算所要求的值并
put
另一个。 However, this feels hacky. 然而,这感觉很糟糕。
If service.getAllEmployees
is expensive, then you could cache it. 如果
service.getAllEmployees
很昂贵,那么你可以缓存它。 If both calculateEmployeeAvg
and calculateEmployeeEff
are cheap, then recompute them when needed. 如果
calculateEmployeeAvg
和calculateEmployeeEff
都很便宜,那么在需要时重新计算它们。 Otherwise, it looks like you could use two caches. 否则,看起来你可以使用两个缓存。
I guess, a method computing both values at once could be a reasonable solution. 我想,一次计算两个值的方法可能是一个合理的解决方案。 Create a tiny Pair-like class aggregating them and use it as the cache value.
创建一个类似于Pair的类,聚合它们并将其用作缓存值。 There'll be a single key only.
只有一把钥匙。
Concerning your own solution, it could be as trivial as 关于你自己的解决方案,它可能是微不足道的
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;
}
}
Instead of synchronized
methods you may want to synchronize on a private final field. 您可能希望在私有最终字段上同步,而不是
synchronized
方法。 There are other possibilities (eg Atomic*
), but the basic design is probably simpler than adapting Guava's Cache
. 还有其他可能性(例如
Atomic*
),但基本设计可能比调整Guava的Cache
更简单。
Now, I see that there's Suppliers#memoizeWithExpiration
in Guava. 现在,我看到Guava中有
Suppliers#memoizeWithExpiration
。 That's probably even simpler. 这可能更简单。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.