2012-12-20 10 views
21

Sto usando il LoadingCache di Guava nel mio progetto per gestire il caricamento della cache thread- {safe, friendly} e funziona meravigliosamente bene. Tuttavia, c'è una limitazione.Progettazione di una cache di caricamento Guava con scadenza variabile

Il codice corrente che definisce la cache assomiglia a questo:

cache = CacheBuilder.newBuilder().maximumSize(100L).build(new CacheLoader<K, V>() 
{ 
    // load() method implemented here 
} 

io non specificano un tempo di scadenza.

Il problema è che in base ai valori della chiave, alcuni valori associati possono scadere e altri no. E CacheLoader non tiene conto di questo, se si specifica un tempo di scadenza, è per ogni voce.

Come affronteresti questo problema?

+0

Perché non utilizzare 2 cache: una con scadenza, un altro per gli enti eterni? – hoaz

+0

Poiché la scadenza della voce è completamente casuale (ovvero, considera HTTP con diverse intestazioni 'Cache-Control'). – fge

+0

Quindi suggerisco di includere il tempo di scadenza direttamente nella classe di voce e di rimuoverlo manualmente dalla cache se è scaduto immediatamente dopo averlo prelevato dalla cache. – hoaz

risposta

21

Un'altra alternativa è ExpiringMap, che supporta l'ingresso variabile di scadenza:

Map<String, String> map = ExpiringMap.builder().variableExpiration().build(); 
map.put("foo", "bar", ExpirationPolicy.ACCESSED, 5, TimeUnit.MINUTES); 
map.put("baz", "pez", ExpirationPolicy.CREATED, 10, TimeUnit.MINUTES); 
+0

Grande scoperta! Grazie! – fge

+0

Grazie per il link! Da quando ne hai parlato, ho chiesto all'autore di implementare il caricamento per le voci che ha fatto e testato ... Davvero una grande scoperta! – fge

0

Immagino che sia possibile utilizzare l'invalidazione esplicita per definire esattamente quali voci devono essere eliminate, ma probabilmente non è ciò che si desidera.

Tuttavia è possibile assegnare pesi diversi per le voci. Non è perfetto, ma puoi guidare la cache per rimuovere le voci che sono meno importanti. Vedere Weighter, le voci con peso 0 non saranno sfrattate dallo sfratto basato sulle dimensioni.

10

suggerisco di includere il tempo di scadenza direttamente alla tua classe di ingresso e manualmente sfrattare dalla cache se è scaduto subito dopo aver recuperato dalla cache:

MyItem item = cache.getIfPresent(key); 
if (item != null && item.isExpired()) { 
    cache.invalidate(key); 
    item = cache.get(key); 
    // or use cache.put if you load it externally 
} 

In alternativa, vi posso suggerire a controllare la libreria EhCache che supporta criteri per scadenza elementi.

+0

Questo aiuta a evitare l'uso di voci scadute, ma costano ancora della memoria: rimangono nella cache solo per essere invalidate e ricaricate. – maaartinus

+0

ecco perché ho suggerito di utilizzare un'implementazione più matura della cache :) – hoaz

+0

Questo è molto, molto vicino a quello che voglio fare, e guarderò davvero in ehcache. L'unico problema che ho con esso è la dimensione della dipendenza pura (più di tutta Guava!) E che avrei bisogno di fare in modo che chiavi e valori implementino 'Serializable'. Ugh. Immagino che non possa essere aiutato ... – fge

0

Nel caso in cui le voci sono grandi ed è necessario conservare la memoria, non credo ci sia una bella soluzione, ma questi hack vengono in mente:

  • Utilizzare un PriorityQueue ordinato dal scadenza tempo per rimuovere manualmente le voci. Se si vuole essere sicuri che non venga utilizzata nessuna voce scaduta, è necessario combinarla con la soluzione di hoaz; la coda impedisce solo alle voci inutili di occupare memoria.

  • Hai scritto "alcuni valori associati possono scadere e altri no", il che suggerisce che il ritardo di scadenza è lo stesso per tutte le voci in scadenza. Ciò consentirebbe di utilizzare Queue più semplice e più veloce (ad esempio ArrayDeque anziché PriorityQueue).

  • Nel caso in cui il ritardo di scadenza sia piuttosto ampio, è possibile lasciare scadere tutte le voci e reinserire quelle che dovrebbero vivere per sempre in un RemovalListener. Questo può fallire in due modi: 1. Nel frattempo potresti perdere. 2. Le rimozioni e i reinserimenti possono costare molto tempo della CPU.

5

LoadingCache offre alcuni criteri di scadenza comunemente usate, ma quando questi sono inferiori per quello che ti serve, è necessario roll your own.

Basta aggiungere un DelayQueue. Ogni volta che aggiungi qualcosa alla cache, aggiungi un Delayed a quella coda, con la scadenza appropriata. L'oggetto Delayed dovrebbe avere un riferimento (debole?) Alla chiave.

L'ingrediente finale è che è necessario eseguire periodicamente il polling di questa coda, per vedere se qualcosa è scaduto e deve essere rimosso. Non aggiungere necessariamente un thread per fare ciò, potresti semplicemente portarlo su qualunque thread stia accedendo allo LoadingCache. Appena prima di accedere alla cache, ad esempio:

private void drainCache() { 
    MyDelayed expired; 
    while ((expired = delayedQueue.poll()) != null) { 
    K key = expired.getReference(); 
    if (key != null) { // this only in case if you hold the key in a weak reference 
     loadingCache.invalidate(key); 
    } 
    } 
} 

.. 
V lookup(K key) { 
    drainCache(); 
    return loadingCache.getUnchecked(key); 
} 
+0

Hmmm, interessante ... Lo studierò un po 'di più. Grazie! – fge

+0

Mi piace questa risposta – Jasonw

Problemi correlati