2009-10-05 5 views
13

Ho un sito Web ad alto traffico e utilizzo la sospensione. Uso anche ehcache per memorizzare alcune entità e query necessarie per generare le pagine.Evitare ripopolazioni multiple della stessa area della cache (a causa della concorrenza)

Il problema è "cache cache misses" e la spiegazione lunga è che quando l'applicazione si avvia e le regioni della cache sono fredde ogni area della cache viene popolata molte volte (anziché solo una volta) da thread diversi perché il sito è in fase di elaborazione colpito da molti utenti allo stesso tempo. Inoltre, quando qualche regione della cache invalida, viene ripopolata molte volte a causa della stessa ragione. Come posso evitare questo?

Sono riuscito a convert 1 entity and 1 query cache to a BlockingCache fornendo la mia implementazione a hibernate.cache.provider_class ma la semantica di BlockingCache non sembra funzionare. Ancora peggiori a volte i deadlock di BlockingCache (blocchi) e l'applicazione si blocca completamente. Il dump del thread mostra che l'elaborazione è bloccata sul mutex di BlockingCache su un'operazione get.

Quindi, la domanda è: Hibernate supporta questo tipo di utilizzo?

E se no, come risolvere questo problema sulla produzione?

Edit: Il hibernate.cache.provider_class punti al mio fornitore di cache personalizzato, che è un copia incolla da SingletonEhCacheProvider e alla fine del metodo start() (dopo la linea 136) che faccio:

Ehcache cache = manager.getEhcache("foo"); 
if (!(cache instanceof BlockingCache)) { 
    manager.replaceCacheWithDecoratedCache(cache, new BlockingCache(cache)); 
} 

In questo modo, dopo l'inizializzazione, e prima che qualcun altro tocchi la cache denominata "foo", la decoro con BlockingCache. "foo" è una cache di query e "bar" (lo stesso codice ma omesso) è una cache di entità per un pojo.

Modifica 2: "Non sembra funzionare" significa che il problema iniziale esiste ancora. La cache "pippo" viene ancora reinserita molte volte con gli stessi dati, a causa della concorrenza. Convalido questo stressando il sito con JMeter con 10 thread. Mi aspetterei che i 9 thread blocchino fino a quando il primo che ha richiesto i dati da "foo" per finire il suo lavoro (eseguire query, memorizzare i dati nella cache), e quindi ottenere i dati direttamente dalla cache.

Modifica 3: Un'altra spiegazione di questo problema può essere vista allo https://forum.hibernate.org/viewtopic.php?f=1&t=964391&start=0 ma senza una risposta definita.

+0

Uno sviluppo interessante che può aiutare a mitigare questo problema è che ora ehcache (dal 2.1) supporta la strategia di concorrenza della cache transazionale per la sospensione: http://stackoverflow.com/questions/3472613/does-ehcache-2-1-support -the-transactional-cache-concurrency-strategy-in-hibernat/3474011 # 3474011 – cherouvim

risposta

1

Il più grande miglioramento su questo problema è che ora ehcache (dal 2.1) supports the transactional hibernate cache policy. Questo riduce notevolmente i problemi descritti in questo numero.

Per fare un ulteriore passo avanti (bloccare i thread mentre si accede alla stessa area della cache di query) è necessario implementare un QueryTranslatorFactory per restituire istanze personalizzate (estese) QueryTranslatorImpl che ispezionerebbero query e parametri e bloccheranno se necessario nell'elenco metodo. Questo riguarda ovviamente il caso d'uso specifico della cache di query usando hql che preleva molte entità.

5

io non sono molto sicuro, ma:

permette l'accesso simultaneo in lettura al elementi già nella cache. Se l'elemento è nullo, altre letture eseguiranno il blocco fino a quando un elemento con la stessa chiave viene inserito nella cache.

Non significa che Hibernate attenderà fino a quando un altro thread colloca l'oggetto nella cache? Questo è ciò che osservi, giusto?

Hib e la cache funziona così:

  1. Hib riceve una richiesta per un oggetto
  2. controlli Hib se l'oggetto è nella cache - cache.get()
  3. No? Hib carica l'oggetto dal DB e lo inserisce nella cache - cache.put()

Quindi, se l'oggetto non è nella cache (non inserito da qualche operazione di aggiornamento precedente), Hib aspetterebbe 1) per sempre.

Penso che sia necessaria una variante di cache in cui il thread attende solo un oggetto per un breve periodo di tempo. Per esempio. 100ms. Se l'oggetto non è arrivato, il thread dovrebbe essere nullo (e quindi Hibernate caricherà l'oggetto dal DB e lo inserirà nella cache).

In realtà, una logica migliore sarebbe:

  1. Verificare che un altro thread sta richiedendo lo stesso oggetto
  2. Se fosse vero, attendere per lunghi (500ms) per l'oggetto per arrivare
  3. Se non è vero , restituisce null immediatamente

(Non possiamo attendere per sempre, poiché il thread potrebbe non riuscire a mettere l'oggetto nella cache, a causa di un'eccezione).

Se BlockingCache non supporta questo comportamento, è necessario implementare una cache autonomamente. L'ho fatto in passato, non è difficile - i metodi principali sono get() e put() (anche se le API apparentemente sono cresciute da allora).

UPDATE

In realtà, ho appena letto le fonti di BlockingCache. Fa esattamente quello che ho detto - blocca e attendi il timeout. Quindi non devi fare nulla, basta usarlo ...

public Element get(final Object key) throws RuntimeException, LockTimeoutException { 
    Sync lock = getLockForKey(key); 
    Element element; 
     acquiredLockForKey(key, lock, LockType.WRITE); 
     element = cache.get(key); 
     if (element != null) { 
      lock.unlock(LockType.WRITE); 
     } 
    return element; 
} 

public void put(Element element) { 
    if (element == null) { 
     return; 
    } 
    Object key = element.getObjectKey(); 
    Object value = element.getObjectValue(); 

    getLockForKey(key).lock(LockType.WRITE); 
    try { 
     if (value != null) { 
      cache.put(element); 
     } else { 
      cache.remove(key); 
     } 
    } finally { 
     getLockForKey(key).unlock(LockType.WRITE); 
    } 
} 

Quindi è un po 'strano che non funzioni per voi. Dimmi qualcosa: nel tuo codice questo spot:

Ehcache cache = manager.getEhcache("foo"); 

è sincronizzato? Se più richieste arrivano allo stesso tempo, ci sarà solo una istanza di cache?

+0

Grazie per la risposta. Trovo ancora difficile credere che ho bisogno di scendere a un livello così basso con l'ibernazione e l'e-cache per risolvere questo problema. – cherouvim

+0

Non è sincronizzato ma viene chiamato solo una volta. Vive sotto vuoto finale pubblico di BlockingCacheProvider che viene chiamato solo una volta prima che qualcosa inizi. – cherouvim