2012-01-08 3 views
2

Какая структура данных в Java лучше всего реализовать в кэше объектов в памяти, где объекты имеют индивидуальное время истечения?Структура данных кеша объектов с «истечением срока действия объекта»

В основном для кеша я мог бы использовать Map (где ключ может быть String), который предлагает методы put и get, и использовать упорядоченный список пар «timestamp» + «object» для управления временем истечения. Таким образом, поток очистки может проверять запись первого списка и удалять объект, когда прошло его время истечения. (Удаление первого элемента должно быть в O (1) раз)

ответ

9

Что вы описываете здание в основном ExpiringMap. Существуют и другие аналогичные реализации, такие как Guava (см. CacheBuilder) - хотя я не верю, что он поддерживает истечение срока действия, как это делает ExpiringMap.

+1

+1 для Guache's CacheBuilder. Я считаю это наиболее подходящим предложением, поскольку он прост в использовании и облегчен. –

+0

Активировано, потому что OP не запрашивает ферму серверов кеша, но для структуры кэша в памяти. Guava 'Cache' лучше всего подходит. –

+0

Как установить истечение срока действия одного объекта с помощью guava? – sodik

1

Я думаю, что ваше решение правильное. Я бы использовал HashMap, чтобы быть точным.

+1

Бедный выбор, 'LinkedHashMap' будет намного лучше здесь, так как он имеет возможность уменьшить потребление памяти, удалив устаревшие записи. –

4

Я хотел бы использовать существующую библиотеку, такую ​​как ehcache.

Однако, если вы хотите написать свой собственный, я бы не использовал фоновый поток, если он вам не нужен, так как он добавляет сложности. Вместо этого у меня была бы проблема переднего плана удалить истекшие записи.

Я бы использовал LinkedHashMap, если вам нужен только кеш LRU. Тем не менее, если вы хотите приурочено истечение, я хотел бы использовать HashMap с PriorityQueue (так что вы можете проверить, является ли следующим истекают запись истекла)

+0

Действительно, 'LinkedHashMap' - отличный выбор. Но одна вещь, чтобы прояснить: для успешного удаления истекших записей вы бы объединили «LinkedHashMap» с каким-то потоком, который выполняет эту работу, правильно? Если да, разве вы не думаете, что это немного снизит производительность такого кеша? А во-вторых: почему foreground thread вместо фона? –

+2

@GrzesiekD. Вы можете удалить устаревшие элементы при доступе к карте, а не использовать фоновый поток. –

6

рамки кэширования довольно зрелая Сейчас:

Однако, если вы настаиваете на изобретать велосипед, не забудьте учесть использование памяти. Слишком часто я вижу, что плохо реализованный кеш (HashMap) эффективно превращается в утечку памяти.

ответ знакомства Коуэна здесь: Java's WeakHashMap and caching: Why is it referencing the keys, not the values?

3

гуавы Cachebuilder:

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder() 
     .maximumSize(10000) 
     .expireAfterWrite(10, TimeUnit.MINUTES) 
     .removalListener(MY_LISTENER) 
     .build(
      new CacheLoader<Key, Graph>() { 
      public Graph load(Key key) throws AnyException { 
       return createExpensiveGraph(key); 
      } 
      }); 

Поскольку WeekHashmap не подходит кэширование, но вы всегда можете использовать Map<K,WeakReference<V>>, значение которого получают право на GC на неделю ссылки.

Прежде всего, мы всегда Ehcache, Memcached и когерентность, как популярный выбор.

1

Как уже упоминалось в предыдущих ответах, то лучше использовать один из популярных кэшей в памяти, как Ehcache, Memcached и т.д.

Но так же, как вы хотели реализовать его на собственном кэше, который имеет объект экспирации функцию и меньше времени, я попытался реализовать его вот так - (любые отзывы об испытаниях/предложения очень ценятся).

public class ObjectCache<K, V> { 

    private volatile boolean shutdown; 
    private final long maxObjects; 
    private final long timeToLive; 
    private final long removalThreadRunDelay; 
    private final long objectsToRemovePerRemovalThreadRun; 
    private final AtomicLong objectsCount; 
    private final Map<K, CacheEntryWrapper> cachedDataStore; 
    private final BlockingQueue<CacheEntryReference> queue; 
    private final Object lock = new Object(); 
    private ScheduledExecutorService executorService; 

    public ObjectCache(long maxObjects, long timeToLive, long removalThreadRunDelay, long objectsToRemovePerRemovalThreadRun) { 
     this.maxObjects = maxObjects; 
     this.timeToLive = timeToLive; 
     this.removalThreadRunDelay = removalThreadRunDelay; 
     this.objectsToRemovePerRemovalThreadRun = objectsToRemovePerRemovalThreadRun; 
     this.objectsCount = new AtomicLong(0); 
     this.cachedDataStore = new HashMap<K, CacheEntryWrapper>(); 
     this.queue = new LinkedBlockingQueue<CacheEntryReference>(); 
    } 

    public void put(K key, V value) { 
     if (key == null || value == null) { 
      throw new IllegalArgumentException("Key and Value both should be not null"); 
     } 
     if (objectsCount.get() + 1 > maxObjects) { 
      throw new RuntimeException("Max objects limit reached. Can not store more objects in cache."); 
     } 
     // create a value wrapper and add it to data store map 
     CacheEntryWrapper entryWrapper = new CacheEntryWrapper(key, value); 
     synchronized (lock) { 
      cachedDataStore.put(key, entryWrapper); 
     } 
     // add the cache entry reference to queue which will be used by removal thread 
     queue.add(entryWrapper.getCacheEntryReference()); 
     objectsCount.incrementAndGet(); 
     // start the removal thread if not started already 
     if (executorService == null) { 
      synchronized (lock) { 
       if (executorService == null) { 
        executorService = Executors.newSingleThreadScheduledExecutor(); 
        executorService.scheduleWithFixedDelay(new CacheEntryRemover(), 0, removalThreadRunDelay, TimeUnit.MILLISECONDS); 
       } 
      } 
     } 
    } 

    public V get(K key) { 
     if (key == null) { 
      throw new IllegalArgumentException("Key can not be null"); 
     } 
     CacheEntryWrapper entryWrapper; 
     synchronized (lock) { 
      entryWrapper = cachedDataStore.get(key); 
      if (entryWrapper != null) { 
       // reset the last access time 
       entryWrapper.resetLastAccessedTime(); 
       // reset the reference (so the weak reference is cleared) 
       entryWrapper.resetCacheEntryReference(); 
       // add the new reference to queue 
       queue.add(entryWrapper.getCacheEntryReference()); 
      } 
     } 
     return entryWrapper == null ? null : entryWrapper.getValue(); 
    } 

    public void remove(K key) { 
     if (key == null) { 
      throw new IllegalArgumentException("Key can not be null"); 
     } 
     CacheEntryWrapper entryWrapper; 
     synchronized (lock) { 
      entryWrapper = cachedDataStore.remove(key); 
      if (entryWrapper != null) { 
       // reset the reference (so the weak reference is cleared) 
       entryWrapper.resetCacheEntryReference(); 
      } 
     } 
     objectsCount.decrementAndGet(); 
    } 

    public void shutdown() { 
     shutdown = true; 
     executorService.shutdown(); 
     queue.clear(); 
     cachedDataStore.clear(); 
    } 

    public static void main(String[] args) throws Exception { 
     ObjectCache<Long, Long> cache = new ObjectCache<>(1000000, 60000, 1000, 1000); 
     long i = 0; 
     while (i++ < 10000) { 
      cache.put(i, i); 
     } 
     i = 0; 
     while(i++ < 100) { 
      Thread.sleep(1000); 
      System.out.println("Data store size: " + cache.cachedDataStore.size() + ", queue size: " + cache.queue.size()); 
     } 
     cache.shutdown(); 
    } 

    private class CacheEntryRemover implements Runnable { 
     public void run() { 
      if (!shutdown) { 
       try { 
        int count = 0; 
        CacheEntryReference entryReference; 
        while ((entryReference = queue.peek()) != null && count++ < objectsToRemovePerRemovalThreadRun) { 
         long currentTime = System.currentTimeMillis(); 
         CacheEntryWrapper cacheEntryWrapper = entryReference.getWeakReference().get(); 
         if (cacheEntryWrapper == null || !cachedDataStore.containsKey(cacheEntryWrapper.getKey())) { 
          queue.poll(100, TimeUnit.MILLISECONDS); // remove the reference object from queue as value is removed from cache 
         } else if (currentTime - cacheEntryWrapper.getLastAccessedTime().get() > timeToLive) { 
          synchronized (lock) { 
           // get the cacheEntryWrapper again just to find if put() has overridden the same key or remove() has removed it already 
           CacheEntryWrapper newCacheEntryWrapper = cachedDataStore.get(cacheEntryWrapper.getKey()); 
           // poll the queue if - 
           // case 1 - value is removed from cache 
           // case 2 - value is overridden by new value 
           // case 3 - value is still in cache but it is old now 
           if (newCacheEntryWrapper == null || newCacheEntryWrapper != cacheEntryWrapper || currentTime - cacheEntryWrapper.getLastAccessedTime().get() > timeToLive) { 
            queue.poll(100, TimeUnit.MILLISECONDS); 
            newCacheEntryWrapper = newCacheEntryWrapper == null ? cacheEntryWrapper : newCacheEntryWrapper; 
            if (currentTime - newCacheEntryWrapper.getLastAccessedTime().get() > timeToLive) { 
             remove(newCacheEntryWrapper.getKey()); 
            } 
           } else { 
            break; // try next time 
           } 
          } 
         } 
        } 
       } catch (Exception e) { 
        e.printStackTrace(); 
       } 
      } 
     } 
    } 

    private class CacheEntryWrapper { 
     private K key; 
     private V value; 
     private AtomicLong lastAccessedTime; 
     private CacheEntryReference cacheEntryReference; 

     public CacheEntryWrapper(K key, V value) { 
      this.key = key; 
      this.value = value; 
      this.lastAccessedTime = new AtomicLong(System.currentTimeMillis()); 
      this.cacheEntryReference = new CacheEntryReference(this); 
     } 

     public K getKey() { 
      return key; 
     } 

     public V getValue() { 
      return value; 
     } 

     public AtomicLong getLastAccessedTime() { 
      return lastAccessedTime; 
     } 

     public CacheEntryReference getCacheEntryReference() { 
      return cacheEntryReference; 
     } 

     public void resetLastAccessedTime() { 
      lastAccessedTime.set(System.currentTimeMillis()); 
     } 

     public void resetCacheEntryReference() { 
      cacheEntryReference.clear(); 
      cacheEntryReference = new CacheEntryReference(this); 
     } 
    } 

    private class CacheEntryReference { 
     private WeakReference<CacheEntryWrapper> weakReference; 

     public CacheEntryReference(CacheEntryWrapper entryWrapper) { 
      this.weakReference = new WeakReference<CacheEntryWrapper>(entryWrapper); 
     } 

     public WeakReference<CacheEntryWrapper> getWeakReference() { 
      return weakReference; 
     } 

     public void clear() { 
      weakReference.clear(); 
     } 
    } 
}