2015-09-05 31 views
9

Sono bloccato in uno scenario. Il mio codice è come qui di seguito:Oggetto cache con ObjectCache in .Net con scadenza

Update: la sua non su come utilizzare cache di dati, io sto già usando e il suo funzionamento, la sua di espandere in modo che il metodo non fanno chiamata tra il tempo di scadenza e ottenere nuovi dati da una fonte esterna

object = (string)this.GetDataFromCache(cache, cacheKey); 

if(String.IsNullOrEmpty(object)) 
{ 
    // get the data. It takes 100ms 
    SetDataIntoCache(cache, cacheKey, object, DateTime.Now.AddMilliseconds(500)); 
} 

questo modo l'utente ha colpito la cache e ottenere i dati da esso se la voce scadere chiama e ottenere i dati dal servizio e salvarlo in caso, t egli problema è, quando mai c'è una richiesta in sospeso (richiesta in corso) il servizio invia un'altra richiesta perché l'oggetto è scaduto. in finale ci dovrebbero essere max 2-3 chiamate/secondi e ci sono 10-20 chiamate al secondo al servizio esterno.

Esiste un modo ottimale per farlo in modo che non vi siano conflitti tra le richieste di tempo oltre alla creazione di una propria classe personalizzata con array e timestamp, ecc.?

btw il codice risparmio per la cache è-

private void SetDataIntoCache(ObjectCache cacheStore, string cacheKey, object target, DateTime slidingExpirationDuration) 
{ 
    CacheItemPolicy cacheItemPolicy = new CacheItemPolicy(); 

    cacheItemPolicy.AbsoluteExpiration = slidingExpirationDuration; 
    cacheStore.Add(cacheKey, target, cacheItemPolicy); 
} 
+0

Verificare questo. Penso che risolverà il tuo problema. http://stackoverflow.com/questions/26581065/memory-cache-in-web-api –

+0

ciao, almeno quello che ho capito, sto già usando la classe e funziona bene, non è su come usarlo, anche aggiornato la mia domanda – kawafan

+0

@kawafan, Risposta suggerita da Kari è corretta, Lazy supporta l'esecuzione singleton e gestisce il blocco. Non si tratta di utilizzare la classe o enumerabile, si tratta di utilizzare Lazy . –

risposta

1

ho adattato la soluzione da Micro Caching in .NET per l'utilizzo con la System.Runtime.Caching.ObjectCache per MvcSiteMapProvider. L'implementazione completa ha un'interfaccia ICacheProvider che consente lo scambio tra System.Runtime.Caching e System.Web.Caching, ma questa è una versione ridotta che dovrebbe soddisfare le tue esigenze.

La caratteristica più interessante di questo modello è che utilizza una versione leggera di un lazy lock per garantire che i dati vengano caricati dall'origine dati solo una volta dopo la scadenza della cache, indipendentemente dal numero di thread simultanei che stanno tentando di caricare i dati.

using System; 
using System.Runtime.Caching; 
using System.Threading; 

public interface IMicroCache<T> 
{ 
    bool Contains(string key); 
    T GetOrAdd(string key, Func<T> loadFunction, Func<CacheItemPolicy> getCacheItemPolicyFunction); 
    void Remove(string key); 
} 

public class MicroCache<T> : IMicroCache<T> 
{ 
    public MicroCache(ObjectCache objectCache) 
    { 
     if (objectCache == null) 
      throw new ArgumentNullException("objectCache"); 

     this.cache = objectCache; 
    } 
    private readonly ObjectCache cache; 
    private ReaderWriterLockSlim synclock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); 

    public bool Contains(string key) 
    { 
     synclock.EnterReadLock(); 
     try 
     { 
      return this.cache.Contains(key); 
     } 
     finally 
     { 
      synclock.ExitReadLock(); 
     } 
    } 

    public T GetOrAdd(string key, Func<T> loadFunction, Func<CacheItemPolicy> getCacheItemPolicyFunction) 
    { 
     LazyLock<T> lazy; 
     bool success; 

     synclock.EnterReadLock(); 
     try 
     { 
      success = this.TryGetValue(key, out lazy); 
     } 
     finally 
     { 
      synclock.ExitReadLock(); 
     } 

     if (!success) 
     { 
      synclock.EnterWriteLock(); 
      try 
      { 
       if (!this.TryGetValue(key, out lazy)) 
       { 
        lazy = new LazyLock<T>(); 
        var policy = getCacheItemPolicyFunction(); 
        this.cache.Add(key, lazy, policy); 
       } 
      } 
      finally 
      { 
       synclock.ExitWriteLock(); 
      } 
     } 

     return lazy.Get(loadFunction); 
    } 

    public void Remove(string key) 
    { 
     synclock.EnterWriteLock(); 
     try 
     { 
      this.cache.Remove(key); 
     } 
     finally 
     { 
      synclock.ExitWriteLock(); 
     } 
    } 


    private bool TryGetValue(string key, out LazyLock<T> value) 
    { 
     value = (LazyLock<T>)this.cache.Get(key); 
     if (value != null) 
     { 
      return true; 
     } 
     return false; 
    } 

    private sealed class LazyLock<T> 
    { 
     private volatile bool got; 
     private T value; 

     public T Get(Func<T> activator) 
     { 
      if (!got) 
      { 
       if (activator == null) 
       { 
        return default(T); 
       } 

       lock (this) 
       { 
        if (!got) 
        { 
         value = activator(); 

         got = true; 
        } 
       } 
      } 

      return value; 
     } 
    } 
} 

Uso

// Load the cache as a static singleton so all of the threads 
// use the same instance. 
private static IMicroCache<string> stringCache = 
    new MicroCache<string>(System.Runtime.Caching.MemoryCache.Default); 

public string GetData(string key) 
{ 
    return stringCache.GetOrAdd(
     key, 
     () => LoadData(key), 
     () => LoadCacheItemPolicy(key)); 
} 

private string LoadData(string key) 
{ 
    // Load data from persistent source here 

    return "some loaded string"; 
} 

private CacheItemPolicy LoadCacheItemPolicy(string key) 
{ 
    var policy = new CacheItemPolicy(); 

    // This ensures the cache will survive application 
    // pool restarts in ASP.NET/MVC 
    policy.Priority = CacheItemPriority.NotRemovable; 

    policy.AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(1); 

    // Load Dependencies 
    // policy.ChangeMonitors.Add(new HostFileChangeMonitor(new string[] { fileName })); 

    return policy; 
} 

NOTA: Come è stato accennato in precedenza, si sono probabilmente non guadagnando nulla per la memorizzazione nella cache un valore che prende 100 ms per recuperare solo 500 ms. Molto probabilmente dovresti scegliere un periodo di tempo più lungo per conservare gli elementi nella cache. Gli articoli sono davvero così volatili nell'origine dati che potrebbero cambiare così velocemente? Se è così, forse dovresti considerare l'uso di ChangeMonitor per invalidare i dati non aggiornati in modo da non spendere così tanto tempo di CPU per caricare la cache. Quindi puoi cambiare il tempo di cache in minuti invece di millisecondi.

+0

ciao, sì, in realtà questi sono i prezzi di scambio, e le sue variazioni come a 200 ms, e ho circa 500 richieste in arrivo al secondo per ottenerlo. l'utilizzo della CPU non è un problema, ho un server 64 core in esecuzione su questo progetto e tocco il massimo del 7-8% di carico nelle ore di punta – kawafan

0

Si dovrà utilizzare il blocco per assicurarsi richiesta non è inviare quando la cache è scaduto e un altro thread è ottenerlo dal servizio remoto/lento, esso simile a questa (non ci sono implementazioni migliori là fuori che sono più facili da usare, ma richiedono classi separate):

private static readonly object _Lock = new object(); 

... 

object = (string)this.GetDataFromCache(cache, cacheKey); 

if(object == null) 
{ 
    lock(_Lock) 
    { 
     object = (string)this.GetDataFromCache(cache, cacheKey); 
     if(String.IsNullOrEmpty(object)) 
     { 
      get the data // take 100ms 
      SetDataIntoCache(cache, cacheKey, object, DateTime.Now.AddMilliseconds(500)); 
     } 
    } 
} 

return object; 

Inoltre, si vuole assicurarsi che il servizio non restituisce nulla in quanto si assume che nessuna cache esiste e proverà a ottenere i dati su ev richiesta Questo è il motivo per cui le implementazioni più avanzate generalmente usano qualcosa come CacheObject, che supporta l'archiviazione di valori nulli.

5

Uso Double-checked locking modello:

var cachedItem = (string)this.GetDataFromCache(cache, cacheKey); 
if (String.IsNullOrEmpty(object)) { // if no cache yet, or is expired 
    lock (_lock) { // we lock only in this case 
     // you have to make one more check, another thread might have put item in cache already 
     cachedItem = (string)this.GetDataFromCache(cache, cacheKey); 
     if (String.IsNullOrEmpty(object)) { 
      //get the data. take 100ms 
      SetDataIntoCache(cache, cacheKey, cachedItem, DateTime.Now.AddMilliseconds(500)); 
     } 
    } 
} 

In questo modo, mentre v'è un elemento nella cache (quindi, non è ancora scaduto), tutte le richieste sarà completato senza bloccare. Ma se non c'è ancora una voce nella cache, o è scaduta - solo un thread riceverà i dati e li metterà nella cache. Assicurati di aver capito questo modello, perché ci sono alcuni avvertimenti durante l'implementazione in .NET.

Come indicato nei commenti, non è necessario utilizzare un oggetto di blocco "globale" per proteggere ogni singolo accesso alla cache. Supponiamo di avere due metodi nel codice e ognuno di questi metodi memorizza nella cache l'oggetto utilizzando la propria chiave di cache (ma utilizzando ancora la stessa cache). Quindi è necessario utilizzare due oggetti di blocco separati, poiché se si utilizzerà un oggetto di blocco "globale", le chiamate a un metodo non attenderanno le chiamate all'altro metodo, mentre non funzioneranno mai con le stesse chiavi di cache.

+1

È importante sottolineare qui che '_lock' definisce la granularità per la concorrenza; spesso, lo stesso oggetto cache contiene un intervallo di dati non correlati e non in competizione, quindi potrebbe essere necessario assicurarsi che la granularità del blocco rifletta i dati. –

0

A proposito, 500 millisecondi sono troppo piccoli per la cache, si finirà un sacco di ciclo della CPU solo per aggiungere/rimuovere la cache che alla fine rimuoverà la cache troppo presto prima che qualsiasi altra richiesta possa beneficiare della cache. Dovresti profilare il tuo codice per vedere se ne avvantaggia effettivamente.

Ricordate, la cache ha molto codice in termini di bloccaggio, hashing e molti altri muoversi dati, che costa buona quantità di cicli di CPU e ricordate, tutti però cicli di CPU sono piccole, ma in multi threaded, server di connessione a più, La CPU ha molte altre cose da fare.

risposta originale https://stackoverflow.com/a/16446943/85597

private string GetDataFromCache(
      ObjectCache cache, 
      string key, 
      Func<string> valueFactory) 
{ 
    var newValue = new Lazy<string>(valueFactory);    

    //The line below returns existing item or adds 
    // the new value if it doesn't exist 
    var value = cache.AddOrGetExisting(key, newValue, DateTimeOffset.Now.AddMilliseconds(500)) as Lazy<string>; 
    // Lazy<T> handles the locking itself 
    return (value ?? newValue).Value; 
} 


// usage... 


object = this.GetDataFromCache(cache, cacheKey,() => { 

     // get the data... 

     // this method will be called only once.. 

     // Lazy will automatically do necessary locking 
     return data; 
});