2009-06-29 7 views
9

Qualcuno ha qualche consiglio su quale metodo è meglio quando si memorizzano nella cache i dati in un'applicazione C# ASP.net?Best practice di memorizzazione nella cache - Singolo oggetto o più voci?

Attualmente sto usando una combinazione di due approcci, con alcuni dati (Elenco, dizionari, le solite informazioni specifiche del dominio) che vengono messi direttamente nella cache e inseriti quando necessario, e alcuni dati vengono mantenuti all'interno di una classe globaldata, e recuperato attraverso quella classe (cioè la classe GlobalData è memorizzata nella cache, e le sue proprietà sono i dati effettivi).

È un approccio preferibile?

Ho la sensazione che il caching di ogni elemento separatamente sia più sensato dal punto di vista della concorrenza, tuttavia crea molto più lavoro a lungo termine con più funzioni che riguardano puramente i dati da una posizione cache in una classe di utilità.

I suggerimenti sarebbero apprezzati.

risposta

2

Ed, presumo che tali elenchi e dizionari contengano dati quasi statici con scarse possibilità di scadenza. Poi ci sono dati che ricevono colpi frequenti, ma cambiano anche più frequentemente, quindi lo stai mettendo nella cache usando la cache HttpRuntime.

Ora, dovresti pensare a tutti quei dati e tutte le dipendenze tra tipi diversi. Se si trova logicamente che i dati memorizzati nella cache di HttpRuntime dipendono in qualche modo dagli elementi GlobalData, è necessario spostarli nella cache e impostare le dipendenze appropriate, in modo da trarre vantaggio dalla "scadenza a cascata".

Anche se si utilizza il meccanismo di memorizzazione nella cache personalizzato, sarà comunque necessario fornire tutta la sincronizzazione, in modo da non risparmiare su ciò evitando l'altro.

Se sono necessari (preordinati) elenchi di elementi con una variazione di frequenza molto bassa, è ancora possibile farlo utilizzando la cache HttpRuntime. Quindi puoi semplicemente mettere in cache un dizionario e usarlo per elencare i tuoi articoli o per indicizzare e accedere tramite la tua chiave personalizzata.

2

Come circa il migliore (peggiore?) Di entrambi i mondi?

Fare in modo che la classe globaldata gestisca internamente tutto l'accesso alla cache. Il resto del codice può quindi utilizzare solo globaldata, il che significa che non deve necessariamente essere abilitato alla cache.

È possibile modificare l'implementazione della cache come/quando si desidera semplicemente aggiornando globaldata e il resto del codice non saprà né si preoccuperà di ciò che sta accadendo all'interno.

+0

Sì, ho una classe di utilità che gestisce tutto il caching, quindi un normale bit di codice deve solo chiamare GetCachedWhatever() per ottenere qualsiasi cosa, è solo che alcuni Whatevers contengono anche elenchi e dizionari, e mi chiedevo quale sarebbe il il consenso generale era su quello –

+0

@Ed: Perché non avere solo un metodo GetWhatever(), e lasciare che si preoccupi della cache se e se e come? Il tuo codice normale ha solo bisogno di un "Qualunque", e non importa se proviene da cache, database, filesystem, webservice, ecc. – LukeH

+0

È importante che ci sia una distinzione, ci sono alcune cose a cui è necessario accedere dalla cache per la velocità e dal database per l'affidabilità a seconda della situazione, per esempio. –

3

In quali condizioni è necessario invalidare la cache? Gli oggetti devono essere archiviati in modo che, quando vengono invalidati, la ripopolazione della cache richieda solo il reinserimento nella cache degli elementi invalidati.

Ad esempio se nella cache è presente un oggetto Cliente che contiene i dettagli di consegna per un ordine insieme al carrello. Invalidare il carrello della spesa in quanto aggiunto o rimosso un articolo richiederebbe anche di ripopolare inutilmente i dettagli della consegna.

(NOTA: questo è un esempio eccezionale e non sto sostenendo questo solo cercando di dimostrare il principio e la mia immaginazione è un po 'fuori oggi).

1

C'è molto di più da considerare quando si progetta la strategia di caching. Pensa al tuo cache store come se fosse il tuo db in memoria. Quindi gestisci attentamente le dipendenze e i criteri di scadenza per ogni tipo archiviato. In realtà non importa ciò che si usa per la memorizzazione nella cache (system.web, altra soluzione commerciale, rolling your own ...).

Vorrei provare a centralizzarlo e utilizzare anche una sorta di architettura plug-in. Fai in modo che i tuoi dati gli utenti accedano tramite un'API comune (una cache astratta che lo espone) e collega il livello di caching in fase di runtime (diciamo la cache di asp.net).

Si consiglia di adottare un approccio dall'alto verso il basso durante la memorizzazione nella cache dei dati per evitare qualsiasi tipo di problemi di integrità dei dati (dipendenze appropriate come ho detto) e quindi provvedere alla sincronizzazione.

3

È possibile visualizzare Microsoft Best Practices nel blocco di applicazioni Caching della libreria Enterprise.

È possibile leggere l'introduzione here

+1

Sebbene questo collegamento possa rispondere alla domanda, è meglio includere qui le parti essenziali della risposta e fornire il link per riferimento. Le risposte di solo collegamento possono diventare non valide se la pagina collegata cambia. - [Dalla recensione] (/ recensione/post di bassa qualità/17993555) – mvermef

7

In generale le prestazioni della cache è molto meglio che la fonte sottostante (ad esempioun DB) che le prestazioni della cache non sono un problema. L'obiettivo principale è piuttosto quello di ottenere il più alto rapporto cache-hit possibile (a meno che non si stia sviluppando su larga scala perché poi si paga per ottimizzare anche la cache).

Per raggiungere questo obiettivo, in genere cerco di rendere il più semplice possibile allo sviluppatore l'utilizzo della cache (in modo da non perdere nessuna possibilità di hit della cache solo perché lo sviluppatore è troppo pigro per usare la cache) . In alcuni progetti abbiamo utilizzato una versione modificata di CacheHandler disponibile nella libreria aziendale di Microsoft.

Con CacheHandler (che utilizza Policy Injection) è possibile rendere facilmente un metodo "memorizzabile nella cache" aggiungendo semplicemente un attributo. Ad esempio:

[CacheHandler(0, 30, 0)] 
public Object GetData(Object input) 
{ 
} 

renderebbe tutte le chiamate a quel metodo memorizzate nella cache per 30 minuti. Tutte le chiamate ottengono una chiave di cache univoca basata sui dati di input e sul nome del metodo, quindi se si chiama il metodo due volte con input diversi non viene memorizzato nella cache, ma se lo si chiama> 1 volte nell'intervallo di timeout con lo stesso input allora il metodo viene eseguito solo una volta.

La nostra versione modificata è simile al seguente:

using System; 
using System.Diagnostics; 
using System.IO; 
using System.Reflection; 
using System.Runtime.Remoting.Contexts; 
using System.Text; 
using System.Web; 
using System.Web.Caching; 
using System.Web.UI; 
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration; 
using Microsoft.Practices.Unity.InterceptionExtension; 


namespace Middleware.Cache 
{ 
    /// <summary> 
    /// An <see cref="ICallHandler"/> that implements caching of the return values of 
    /// methods. This handler stores the return value in the ASP.NET cache or the Items object of the current request. 
    /// </summary> 
    [ConfigurationElementType(typeof (CacheHandler)), Synchronization] 
    public class CacheHandler : ICallHandler 
    { 
     /// <summary> 
     /// The default expiration time for the cached entries: 5 minutes 
     /// </summary> 
     public static readonly TimeSpan DefaultExpirationTime = new TimeSpan(0, 5, 0); 

     private readonly object cachedData; 

     private readonly DefaultCacheKeyGenerator keyGenerator; 
     private readonly bool storeOnlyForThisRequest = true; 
     private TimeSpan expirationTime; 
     private GetNextHandlerDelegate getNext; 
     private IMethodInvocation input; 


     public CacheHandler(TimeSpan expirationTime, bool storeOnlyForThisRequest) 
     { 
      keyGenerator = new DefaultCacheKeyGenerator(); 
      this.expirationTime = expirationTime; 
      this.storeOnlyForThisRequest = storeOnlyForThisRequest; 
     } 

     /// <summary> 
     /// This constructor is used when we wrap cached data in a CacheHandler so that 
     /// we can reload the object after it has been removed from the cache. 
     /// </summary> 
     /// <param name="expirationTime"></param> 
     /// <param name="storeOnlyForThisRequest"></param> 
     /// <param name="input"></param> 
     /// <param name="getNext"></param> 
     /// <param name="cachedData"></param> 
     public CacheHandler(TimeSpan expirationTime, bool storeOnlyForThisRequest, 
          IMethodInvocation input, GetNextHandlerDelegate getNext, 
          object cachedData) 
      : this(expirationTime, storeOnlyForThisRequest) 
     { 
      this.input = input; 
      this.getNext = getNext; 
      this.cachedData = cachedData; 
     } 


     /// <summary> 
     /// Gets or sets the expiration time for cache data. 
     /// </summary> 
     /// <value>The expiration time.</value> 
     public TimeSpan ExpirationTime 
     { 
      get { return expirationTime; } 
      set { expirationTime = value; } 
     } 

     #region ICallHandler Members 

     /// <summary> 
     /// Implements the caching behavior of this handler. 
     /// </summary> 
     /// <param name="input"><see cref="IMethodInvocation"/> object describing the current call.</param> 
     /// <param name="getNext">delegate used to get the next handler in the current pipeline.</param> 
     /// <returns>Return value from target method, or cached result if previous inputs have been seen.</returns> 
     public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) 
     { 
      lock (input.MethodBase) 
      { 
       this.input = input; 
       this.getNext = getNext; 

       return loadUsingCache(); 
      } 
     } 

     public int Order 
     { 
      get { return 0; } 
      set { } 
     } 

     #endregion 

     private IMethodReturn loadUsingCache() 
     { 
      //We need to synchronize calls to the CacheHandler on method level 
      //to prevent duplicate calls to methods that could be cached. 
      lock (input.MethodBase) 
      { 
       if (TargetMethodReturnsVoid(input) || HttpContext.Current == null) 
       { 
        return getNext()(input, getNext); 
       } 

       var inputs = new object[input.Inputs.Count]; 
       for (int i = 0; i < inputs.Length; ++i) 
       { 
        inputs[i] = input.Inputs[i]; 
       } 

       string cacheKey = keyGenerator.CreateCacheKey(input.MethodBase, inputs); 
       object cachedResult = getCachedResult(cacheKey); 

       if (cachedResult == null) 
       { 
        var stopWatch = Stopwatch.StartNew(); 
        var realReturn = getNext()(input, getNext); 
        stopWatch.Stop(); 
        if (realReturn.Exception == null && realReturn.ReturnValue != null) 
        { 
         AddToCache(cacheKey, realReturn.ReturnValue); 
        } 
        return realReturn; 
       } 

       var cachedReturn = input.CreateMethodReturn(cachedResult, input.Arguments); 

       return cachedReturn; 
      } 
     } 

     private object getCachedResult(string cacheKey) 
     { 
      //When the method uses input that is not serializable 
      //we cannot create a cache key and can therefore not 
      //cache the data. 
      if (cacheKey == null) 
      { 
       return null; 
      } 

      object cachedValue = !storeOnlyForThisRequest ? HttpRuntime.Cache.Get(cacheKey) : HttpContext.Current.Items[cacheKey]; 
      var cachedValueCast = cachedValue as CacheHandler; 
      if (cachedValueCast != null) 
      { 
       //This is an object that is reloaded when it is being removed. 
       //It is therefore wrapped in a CacheHandler-object and we must 
       //unwrap it before returning it. 
       return cachedValueCast.cachedData; 
      } 
      return cachedValue; 
     } 

     private static bool TargetMethodReturnsVoid(IMethodInvocation input) 
     { 
      var targetMethod = input.MethodBase as MethodInfo; 
      return targetMethod != null && targetMethod.ReturnType == typeof (void); 
     } 

     private void AddToCache(string key, object valueToCache) 
     { 
      if (key == null) 
      { 
       //When the method uses input that is not serializable 
       //we cannot create a cache key and can therefore not 
       //cache the data. 
       return; 
      } 

      if (!storeOnlyForThisRequest) 
      { 
       HttpRuntime.Cache.Insert(
        key, 
        valueToCache, 
        null, 
        System.Web.Caching.Cache.NoAbsoluteExpiration, 
        expirationTime, 
        CacheItemPriority.Normal, null); 
      } 
      else 
      { 
       HttpContext.Current.Items[key] = valueToCache; 
      } 
     } 
    } 

    /// <summary> 
    /// This interface describes classes that can be used to generate cache key strings 
    /// for the <see cref="CacheHandler"/>. 
    /// </summary> 
    public interface ICacheKeyGenerator 
    { 
     /// <summary> 
     /// Creates a cache key for the given method and set of input arguments. 
     /// </summary> 
     /// <param name="method">Method being called.</param> 
     /// <param name="inputs">Input arguments.</param> 
     /// <returns>A (hopefully) unique string to be used as a cache key.</returns> 
     string CreateCacheKey(MethodBase method, object[] inputs); 
    } 

    /// <summary> 
    /// The default <see cref="ICacheKeyGenerator"/> used by the <see cref="CacheHandler"/>. 
    /// </summary> 
    public class DefaultCacheKeyGenerator : ICacheKeyGenerator 
    { 
     private readonly LosFormatter serializer = new LosFormatter(false, ""); 

     #region ICacheKeyGenerator Members 

     /// <summary> 
     /// Create a cache key for the given method and set of input arguments. 
     /// </summary> 
     /// <param name="method">Method being called.</param> 
     /// <param name="inputs">Input arguments.</param> 
     /// <returns>A (hopefully) unique string to be used as a cache key.</returns> 
     public string CreateCacheKey(MethodBase method, params object[] inputs) 
     { 
      try 
      { 
       var sb = new StringBuilder(); 

       if (method.DeclaringType != null) 
       { 
        sb.Append(method.DeclaringType.FullName); 
       } 
       sb.Append(':'); 
       sb.Append(method.Name); 

       TextWriter writer = new StringWriter(sb); 

       if (inputs != null) 
       { 
        foreach (var input in inputs) 
        { 
         sb.Append(':'); 
         if (input != null) 
         { 
          //Diffrerent instances of DateTime which represents the same value 
          //sometimes serialize differently due to some internal variables which are different. 
          //We therefore serialize it using Ticks instead. instead. 
          var inputDateTime = input as DateTime?; 
          if (inputDateTime.HasValue) 
          { 
           sb.Append(inputDateTime.Value.Ticks); 
          } 
          else 
          { 
           //Serialize the input and write it to the key StringBuilder. 
           serializer.Serialize(writer, input); 
          } 
         } 
        } 
       } 

       return sb.ToString(); 
      } 
      catch 
      { 
       //Something went wrong when generating the key (probably an input-value was not serializble. 
       //Return a null key. 
       return null; 
      } 
     } 

     #endregion 
    } 
} 

Microsoft merita più credito per questo codice. Abbiamo aggiunto elementi come il caching a livello di richiesta anziché attraverso le richieste (più utili di quanto si possa pensare) e abbiamo corretto alcuni bug (ad esempio, oggetti DateTime uguali che serializzano su valori diversi).

Problemi correlati