2013-01-23 8 views
7

Come evitare di caricare il valore che non è presente nella cache molte volte simultaneamente, nel modo efficiente?Come impedire il caricamento simultaneo di valori non memorizzati più volte?

Un tipico utilizzo della cache è pseudocodice seguente:

Object get(Object key) { 
Object value = cache.get(key); 
if (value == null) { 
    value = loadFromService(key); 
    cache.set(key,value); 
} 
return value; 
} 

Il problema: prima che il valore viene caricato dal servizio (Database, WebService, RemoteEJB o altro) una seconda chiamata può essere effettuata nello stesso tempo , che renderà il valore caricato ancora una volta. Ad esempio, quando memorizzo nella cache tutti gli elementi per l'utente X, e questo utente viene spesso visualizzato e contiene molti elementi, c'è un'alta probabilità di chiamare simultaneamente il carico di tutti gli elementi, con conseguente carico gravoso sul server .

Potrei effettuare la funzione getsincronizzata, ma questo forzerebbe altre ricerche ad attendere, non ha molto senso. Potrei creare nuovo blocco per ogni chiave, ma non so se è una buona idea gestire un numero così grande di blocchi in Java (questa parte è specifica della lingua, la ragione per cui l'ho etichettata come java) .

Oppure c'è un altro approccio che potrei usare? Se sì, quale sarebbe il più efficiente?

+1

Stai pensando troppo seriamente a questo. A meno che il tempo di caricare i dati dal servizio sia tremendamente lungo, questo non sarà mai un problema. – pablochan

+0

Ho un codice EJB alieno che può richiedere fino a 20 secondi nell'ambiente di test, quindi temo cosa succederebbe con 10 o 20 richieste simultanee –

risposta

3

Non reinventare la ruota, l'utilizzo LoadingCache o memoizing supplier guava.

Se si utilizza Ehcache, leggi circa read-through, questo è lo schema che si richiede. È necessario implementare l'interfaccia CacheEntryFactory per istruire la cache su come leggere gli oggetti in una cache mancante e si deve avvolgere l'istanza Ehcache con un'istanza di SelfPopulatingCache.

+0

Come ho capito, CacheLoader sta facendo ciò che mi aspetto, con la gestione interna della sincronizzazione che sarebbe necessaria ? –

+0

Sì, e offre molto di più - sfratto, rimozione degli ascoltatori, ecc. – mindas

+0

Vedo, interessante, stavo usando ehcache ma posso considerare l'utilizzo di guava, tuttavia ehcache supporta l'overflow del disco, e ancora, il modo in cui implementarlo è esso stesso interessante. –

7

Qualcosa che puoi fare in modo generico è utilizzare il codice hash dell'oggetto.

È possibile disporre di una serie di blocchi utilizzati in base al codice hash per ridurre la possibilità di collisioni. O come hack puoi usare il fatto che i byte auto-box restituiscono sempre gli stessi oggetti.

Object get(Object key) { 
    Object value = cache.get(key); 
    if (value == null) { 
     // every possible Byte is cached by the JLS. 
     Byte b = Byte.valueOf((byte) key.hashCode()); 
     synchronized (b) { 
      value = cache.get(key); 
      if (value == null) { 
       value = loadFromService(key); 
       cache.set(key, value); 
      } 
     } 
    } 
    return value; 
} 
+0

Ottima idea con il lock pooling basato su hashCode! Ma dopo aver ottenuto il blocco puoi trovare il tuo valore memorizzato nella cache da un altro processo, quindi dovresti anche controllare se è caricato :) –

+2

Wow, non vorrei mai usare i byte in questo modo! –

+0

Inoltre, questa è la prima volta che vedo l'utilizzo pratico dei valori Byte che raggruppano per valoreOf :) –

1

Per il tempo di caricamento, inserire un oggetto intermedio nella mappa anziché il risultato per indicare che il caricamento è iniziato ma non è terminato. Sotto java.util.concurrent.FutureTask viene utilizzato per l'oggetto intermedio:

Object get(final Object key) throws Exception { 
    boolean doRun = false; 
    Object value; 
    synchronized (cache) { 
     value = cache.get(key); 
     if (value == null) { 
      value = new FutureTask(new Callable() { 
       @Override 
       public Object call() throws Exception { 
        Object loadedValue = loadFromService(key); 
        synchronized (cache) {cache.put(key, loadedValue);}; 
        return loadedValue; 
       } 

      }); 
      cache.put(key, value); 
      doRun=true; 
     } 
    } 
    if (value instanceof FutureTask) { 
     FutureTask task = (FutureTask) value; 
     if (doRun) { 
      task.run(); 
     } 
     return task.get(); 
    } 
    return value; 
}` 
+0

Hmm la soluzione richiede di eseguire sempre la sincronizzazione sull'intera cache, ma la parte sincronizzata è abbastanza veloce. Cosa pensereste di fare inizialmente ottenere, ed eseguire la parte sincronizzata solo quando il valore è nullo? –

+0

Dipende da quante richieste alla cache ci si aspetta al secondo. La parte sincronizzata ha una durata inferiore a 1 microsecondo, quindi se la frequenza è inferiore a 100000 richieste al secondo, la possibilità di collisione è comunque trascurabile, quindi eventuali complicazioni non darebbero alcun risultato. Se il tasso è maggiore, allora c'è un'altra storia, e devi tener conto di molte cose diverse, tra cui il caching del processore, il cambio di thread e il garbage collector, tra cui l'accesso alla cache potrebbe non essere in primo luogo dal punto di performance di vista. –

Problemi correlati