Utilizzo di read-through e write-through nella cache distribuita

Autore: Iqbal Khan

Con l'esplosione di app Web con transazioni estremamente elevate, SOA, grid computing e altre applicazioni server, l'archiviazione dei dati non è in grado di tenere il passo. Il motivo è che l'archiviazione dei dati non può continuare ad aggiungere più server per la scalabilità orizzontale, a differenza delle architetture applicative estremamente scalabili.

In queste situazioni, la cache distribuita in memoria offre un'ottima soluzione ai colli di bottiglia dell'archiviazione dei dati. Si estende su più server (chiamati cluster) per raggruppare la loro memoria e mantenere tutta la cache sincronizzata tra i server. E può continuare a far crescere questo cluster di cache all'infinito, proprio come i server delle applicazioni. Ciò riduce la pressione sull'archiviazione dei dati in modo che non rappresenti più un collo di bottiglia per la scalabilità.

Esistono due modi principali in cui le persone utilizzano una cache distribuita:

  • Cache a parte: È qui che l'applicazione è responsabile della lettura e della scrittura dal database e la cache non interagisce affatto con il database. La cache viene "tenuta da parte" come archivio dati in memoria più veloce e scalabile. L'applicazione controlla la cache prima di leggere qualsiasi cosa dal database. Inoltre, l'applicazione aggiorna la cache dopo aver apportato aggiornamenti al database. In questo modo, l'applicazione garantisce che il la cache viene mantenuta sincronizzata con il database.
  • Lettura/scrittura (RT/WT): È qui che l'applicazione tratta la cache come archivio dati principale e legge i dati da esso e vi scrive i dati. La cache è responsabile della lettura e della scrittura di questi dati nel database, sollevando così l'applicazione di questa responsabilità.
Architettura di memorizzazione nella cache di lettura/scrittura
Figura 1: architettura di memorizzazione nella cache read-through/write-through

Vantaggi di Read-through e Write-through su Cache-aside

Cache-aside è una tecnica molto potente e consente di eseguire query di database complesse che coinvolgono join e query nidificate e di manipolare i dati nel modo desiderato. Nonostante che, Lettura / Scrivere attraverso presenta vari vantaggi rispetto alla cache a parte, come indicato di seguito:

  • Semplifica il codice dell'applicazione: Nell'approccio cache-aside, il codice dell'applicazione continua ad avere complessità e dipendenza diretta dal database e persino la duplicazione del codice se più applicazioni gestiscono gli stessi dati. Read-through/Write-through sposta parte del codice di accesso ai dati dalle tue applicazioni al livello di cache. Ciò semplifica notevolmente le applicazioni e astrae il database in modo ancora più chiaro.
  • Migliore scalabilità in lettura con Read-through: Ci sono molte situazioni in cui a l'elemento cache scade e più thread utente paralleli finiscono per colpire il database. Moltiplicando questo con milioni di elementi memorizzati nella cache e migliaia di richieste degli utenti paralleli, il carico sul database diventa notevolmente maggiore. Tuttavia, Read-through mantiene l'elemento della cache nella cache mentre ne preleva l'ultima copia dal database. Quindi aggiorna l'elemento della cache. Il risultato è che l'applicazione non passa mai al database per questi elementi della cache e il carico del database è ridotto al minimo.
  • Migliori prestazioni di scrittura con Write-behind: In cache-aside, l'applicazione aggiorna il database direttamente in modo sincrono. Considerando che, a Scrivi dietro consente alla tua applicazione di aggiornare rapidamente la cache e tornare. Quindi consente alla cache di aggiornare il database in background.
  • Migliore scalabilità del database con Write-behind: Con Write-behind, puoi specificare i limiti di limitazione in modo che le scritture del database non vengano eseguite alla stessa velocità degli aggiornamenti della cache e quindi la pressione sul database non è molta. Inoltre, puoi pianificare che le scritture del database avvengano durante le ore non di punta, sempre per ridurre al minimo la pressione.
  • Aggiorna automaticamente la cache alla scadenza: Lettura consente alla cache di ricaricare automaticamente un oggetto dal database alla scadenza. Ciò significa che la tua applicazione non deve raggiungere il database nelle ore di punta perché i dati più recenti sono sempre nella cache.
  • Aggiorna automaticamente la cache in caso di modifiche al database: Il read-through consente alla cache di ricaricare automaticamente un oggetto dal database quando i dati corrispondenti cambiano nel database. Ciò significa che la cache è sempre aggiornata e la tua applicazione non deve raggiungere il database nelle ore di punta perché i dati più recenti sono sempre nella cache.

Read-through/Write-through non deve essere utilizzato per tutti gli accessi ai dati nell'applicazione. È più adatto per situazioni in cui si leggono singole righe dal database o si leggono dati che possono essere mappati direttamente a un singolo elemento della cache. È ideale anche per i dati di riferimento che devono essere conservati nella cache per letture frequenti anche se questi dati cambiano periodicamente.

Sviluppo di un gestore read-through

Un gestore read-through viene registrato con il server della cache e consente alla cache di leggere direttamente i dati dal database. Il NCache server fornisce un'interfaccia del gestore Read-through che è necessario implementare. Ciò consente NCache per chiamare il tuo gestore Read-through.

public class SqlReadThruProvider : IReadThruProvider
    {
        private SqlConnection _connection;
        
        // Called upon startup to initialize connection
        public void Init(IDictionary parameters, string cacheId)
        {
            _connection = new SqlConnection(parameters["connstring"]);
            _connection.Open();
        }
        
        // Called at the end to close connection
        public void Dispose()
        {
            _connection.Close();
        }
        
        // Responsible for loading object from external data source
        public ProviderCacheItem LoadFromSource(string key)
        {
            string sql = "SELECT * FROM Customers WHERE ";
            sql += "CustomerID = @ID";
            
            SqlCommand cmd = new SqlCommand(sql, _connection);
            cmd.Parameters.Add("@ID", System.Data.SqlDbType.VarChar);
            
            // Let's extract actual customerID from "key"
            int keyFormatLen = "Customers:CustomerID:".Length;
            string custId = key.Substring(keyFormatLen,
            key.Length - keyFormatLen);
            
            cmd.Parameters["@ID"].Value = custId;
            
            // fetch the row in the table
            SqlDataReader reader = cmd.ExecuteReader();
            
            Customers customer = new Customers();
            // copy data from "reader" to "cust" object
            FillCustomers(reader, customer);
            
            ProviderCacheItem cacheItem = new ProviderCacheItem(customer);
            
            // specify a SqlCacheDependency for this object
            CacheDependency dep = new SqlCacheDependency(_connection.ToString(), cmd.ToString());
            cacheItem.Dependency = dep;
            
            return cacheItem;
        }

Init() esegue alcune attività di allocazione delle risorse come stabilire connessioni all'origine dati principale, mentre Dispose() ha lo scopo di ripristinare tutte queste allocazioni. LoadFromSource() è ciò che la cache chiama per leggere gli oggetti.

Sviluppo di un gestore di scrittura

Viene richiamato il gestore di scrittura quando la cache deve scrivere nel database mentre la cache viene aggiornata. Normalmente, l'applicazione emette un aggiornamento alla cache tramite aggiunta, inserimento o rimozione.

public class SqlWriteThruProvider : IWriteThruProvider
    {
        private SqlConnection _connection;
        
        // Called upon startup to initialize connection
        public void Init(IDictionary parameters, string cacheId)
        {
            _connection = new SqlConnection((string)parameters["connstring"]);
            _connection.Open();
        }
        
        // Called at the end to close connection
        public void Dispose()
        {
            _connection.Close();
        }
        
    public OperationResult WriteToDataSource(WriteOperation operation)
    {
        int rowsChanged = 0;
        OperationResult result = new OperationResult(operation, OperationResult.Status.Failure);
        ProviderCacheItem cacheItem = operation.ProviderItem;
        Customers cust = cacheItem.GetValue<Customers>();
        string[] customer = {cust.Id,cust.ContactName,cust.CompanyName,
        cust.Address,cust.City, cust.Country,cust.PostalCode,
		cust.Phone,cust.Fax};
            
        SqlCommand cmd = _connection.CreateCommand();
        cmd.CommandText = String.Format(CultureInfo.InvariantCulture,
            "Update dbo.Customers " + "Set CustomerID='{0}'," +
            "ContactName='{1}',CompanyName='{2}'," +
            "Address='{3}',City='{4}'," +
            "Country='{5}',PostalCode='{6}'," +
            "Phone='{7}',Fax='{8}'" +
            "Where CustomerID = '{0}'", customer);
                
        rowsChanged = cmd.ExecuteNonQuery();
            if (rowsChanged > 0)
            {
                result.OperationStatus = OperationResult.Status.Success;
                return result;
            }
            return result;
        }
}

Init() esegue attività di allocazione delle risorse come stabilire connessioni all'origine dati, mentre Dispose() ha lo scopo di ripristinare tutte queste allocazioni. Save è il metodo che la cache chiama per gli oggetti write-through.

Chiamata read-through e write-through dall'applicazione

Il codice di esempio seguente mostra l'uso delle funzionalità di lettura/scrittura della cache da una semplice applicazione Windows

using Alachisoft.NCache.Client;
...

internal class MainForm : System.Windows.Forms.Form
{
    /// Fetches record from the cache, which internally accesses the
    /// datasource using read-thru provider
    private void OnClickFind(object sender, System.EventArgs e)
    {
        Customer customer;
        Cache cache = NCache.Caches[CacheName];
        string key = cboCustomerID.Text.Trim();
        string providerName = cboReadThruProvider.Text;
        
        customer = (Customer) cache.Get(key,
                    providerName,
                    DSReadOption.ReadThru);
                    
                    ...
    }
    
    /// Updates the record using the cache, which internally accesses
    /// the datasource using write-thru provider
    private void OnClickUpdate(object sender, System.EventArgs e)
    {
        Cache cache = NCache.Caches[CacheName];
        Customer customer = new Customer();
        
        ...
        
        string key = customer.CustomerID;
        string providerName = cboWriteThruProvider.Text;
        
        cache.Insert(key, new CacheItem(customer), DSWriteOption.WriteThru, providerName, null);
        
        ...
    }
}

Cosa fare dopo?

© Copyright Alachisoft 2002 - . Tutti i diritti riservati. NCache è un marchio registrato di Diyatech Corp.