简体   繁体   English

当我计划有效地缓存多个值时,我应该如何实现Guava缓存?

[英]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 the refreshAfterWrite 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. 如果calculateEmployeeAvgcalculateEmployeeEff都很便宜,那么在需要时重新计算它们。 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM