我的理想缓存使用番石榴

2022-09-02 10:57:07

在过去的几周里,我一直在断断续续地尝试使用番石榴的MapMaker找到理想的缓存实现。在这里这里看到我之前的两个问题,以遵循我的思考过程。

根据我所学到的知识,我的下一次尝试是放弃软值,转而使用maxSize和perupAfterAccess:

ConcurrentMap<String, MyObject> cache = new MapMaker()
        .maximumSize(MAXIMUM_SIZE)
        .expireAfterAccess(MINUTES_TO_EXPIRY, TimeUnit.MINUTES)
        .makeComputingMap(loadFunction);

哪里

Function<String, MyObject> loadFunction = new Function<String, MyObject>() {
   @Override
   public MyObject apply(String uidKey) {
      return getFromDataBase(uidKey);
   }
};

但是,我仍然在努力解决的一个剩余问题是,一旦时间到了,这个实现也会逐出对象,即使它们很强地可访问。这可能会导致具有相同UID的多个对象在环境中浮动,这是我不希望的(我相信我试图实现的被称为规范化)。

因此,据我所知,唯一的答案是有一个额外的映射,它充当一个内部人员,我可以检查它以查看数据对象是否仍在内存中:

ConcurrentMap<String, MyObject> interner = new MapMaker()
        .weakValues()
        .makeMap();

并且将修改加载函数:

Function<String, MyObject> loadFunction = new Function<String, MyObject>() {
   @Override
   public MyObject apply(String uidKey) {
      MyObject dataObject = interner.get(uidKey);
      if (dataObject == null) {
         dataObject = getFromDataBase(uidKey);
         interner.put(uidKey, dataObject);
      }
      return dataObject;
   }
};

但是,对缓存使用两个映射而不是一个映射似乎效率低下。有没有更复杂的方法来解决这个问题?一般来说,我是以正确的方式这样做,还是应该重新考虑我的缓存策略?


答案 1

两个映射是否有效完全取决于 getFromDatabase() 的昂贵程度,以及对象的大小。做这样的事情似乎并不超出所有合理的界限。

至于实现,看起来你可以以稍微不同的方式对地图进行分层,以获得所需的行为,并且仍然具有良好的并发属性。

  1. 创建第一个具有弱值的映射,并将计算函数 getFromDatabase() 放在此映射上。
  2. 第二张地图是即将到期的地图,也是计算的,但这个函数只是从第一张地图中获取的。

通过第二张地图完成所有访问。

换句话说,即将过期的映射的作用是将最近使用的对象子集固定在内存中,而弱引用映射是真正的缓存。

-dg


答案 2

我不明白这里的全貌,但有两件事。

  1. 给定以下语句:“一旦对象的时间到了,此实现也会逐出对象,即使它们非常可访问。这可能会导致具有相同UID的多个对象在环境中浮动,我不想要“ - 听起来你只需要使用weakKeys()而不是使用定时或基于大小的驱逐。

  2. 或者,如果您确实想将“实习生”带入其中,我会使用真正的.Interners.newWeakInterner