Uso de lectura simultánea y escritura simultánea en caché distribuida

Autor: Iqbal Kan

Con la explosión de las aplicaciones web de transacciones extremadamente altas, SOA, grid computing y otras aplicaciones de servidor, el almacenamiento de datos no puede mantenerse al día. La razón es que el almacenamiento de datos no puede seguir agregando más servidores para escalar, a diferencia de las arquitecturas de aplicaciones extremadamente escalables.

En estas situaciones, la caché distribuida en memoria ofrece una excelente solución para los cuellos de botella en el almacenamiento de datos. Abarca varios servidores (llamados un clúster) para agrupar su memoria y mantener todo el caché sincronizado entre servidores. Y puede seguir creciendo este clúster de caché sin cesar, al igual que los servidores de aplicaciones. Esto reduce la presión sobre el almacenamiento de datos para que ya no sea un cuello de botella de escalabilidad.

Hay dos formas principales en que las personas usan un caché distribuido:

  • Aparte de caché: Aquí es donde la aplicación es responsable de leer y escribir desde la base de datos y el caché no interactúa con la base de datos en absoluto. El caché se "mantiene a un lado" como un almacén de datos en memoria más rápido y escalable. La aplicación verifica el caché antes de leer algo de la base de datos. Y la aplicación actualiza el caché después de realizar cualquier actualización en la base de datos. De esta forma, la aplicación se asegura de que el el caché se mantiene sincronizado con la base de datos.
  • Lectura directa/Escritura directa (RT/WT): Aquí es donde la aplicación trata la memoria caché como el principal almacén de datos y lee datos de él y escribe datos en él. El caché es responsable de leer y escribir estos datos en la base de datos, liberando así a la aplicación de esta responsabilidad.
Arquitectura de almacenamiento en caché de lectura/escritura simultánea
Figura 1: Arquitectura de almacenamiento en caché de lectura/escritura simultánea

Beneficios de la lectura simultánea y la escritura simultánea frente a la caché aparte

Cache-aside es una técnica muy poderosa y le permite realizar consultas de base de datos complejas que involucran uniones y consultas anidadas y manipular datos de la forma que desee. A pesar de eso, Leer de parte a parte / Escriba por medio de tiene varias ventajas sobre el almacenamiento en caché, como se menciona a continuación:

  • Simplificar el código de la aplicación: En el enfoque de caché aparte, el código de su aplicación continúa teniendo complejidad y dependencia directa de la base de datos e incluso duplicación de código si varias aplicaciones manejan los mismos datos. Read-through/Write-through mueve parte del código de acceso a datos de sus aplicaciones al nivel de almacenamiento en caché. Esto simplifica drásticamente sus aplicaciones y abstrae la base de datos aún más claramente.
  • Mejor escalabilidad de lectura con Read-through: Hay muchas situaciones en las que un el elemento de caché caduca y múltiples subprocesos de usuarios paralelos terminan llegando a la base de datos. Multiplicando esto con millones de elementos almacenados en caché y miles de solicitudes de usuarios paralelas, la carga en la base de datos se vuelve notablemente mayor. Pero Read-through mantiene el elemento de caché en el caché mientras obtiene la última copia de la base de datos. Luego actualiza el elemento de caché. El resultado es que la aplicación nunca va a la base de datos en busca de estos elementos de caché y la carga de la base de datos se mantiene al mínimo.
  • Mejor rendimiento de escritura con Write-behind: Aparte del caché, la aplicación actualiza la base de datos directamente de forma sincrónica. Considerando que, un escritura detrás permite que su aplicación actualice rápidamente el caché y regrese. Luego, permite que el caché actualice la base de datos en segundo plano.
  • Mejor escalabilidad de la base de datos con Write-behind: Con Write-behind, puede especificar límites de aceleración para que las escrituras de la base de datos no se realicen tan rápido como las actualizaciones de caché y, por lo tanto, la presión sobre la base de datos no sea mucha. Además, puede programar las escrituras en la base de datos para que se produzcan durante las horas de menor actividad, de nuevo para minimizar la presión.
  • Actualización automática de caché al vencimiento: Leer de parte a parte permite que la memoria caché vuelva a cargar automáticamente un objeto de la base de datos cuando caduca. Esto significa que su aplicación no tiene que acceder a la base de datos en las horas pico porque los datos más recientes siempre están en el caché.
  • Actualización automática de caché en cambios de base de datos: La lectura permite que la memoria caché vuelva a cargar automáticamente un objeto de la base de datos cuando sus datos correspondientes cambien en la base de datos. Esto significa que la memoria caché siempre está actualizada y su aplicación no tiene que acceder a la base de datos en las horas pico porque los datos más recientes siempre están en la memoria caché.

La lectura/escritura simultánea no está diseñada para usarse para todos los accesos a datos en su aplicación. Es más adecuado para situaciones en las que está leyendo filas individuales de la base de datos o leyendo datos que pueden asignarse directamente a un elemento de caché individual. También es ideal para datos de referencia que deben guardarse en la memoria caché para lecturas frecuentes, aunque estos datos cambien periódicamente.

Desarrollo de un controlador de lectura directa

Un controlador de lectura directa se registra con el servidor de caché y permite que el caché lea directamente los datos de la base de datos. los NCache El servidor proporciona una interfaz de controlador de lectura directa que debe implementar. Esto permite NCache para llamar a su controlador de lectura completa.

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() realiza ciertas tareas de asignación de recursos, como establecer conexiones con la fuente de datos principal, mientras que Dispose() está destinado a restablecer todas esas asignaciones. LoadFromSource() es lo que llama el caché para leer los objetos.

Desarrollo de un controlador de escritura simultánea

Se invoca el controlador de escritura simultánea cuando la memoria caché necesita escribir en la base de datos a medida que se actualiza la memoria caché. Normalmente, la aplicación emite una actualización del caché a través de agregar, insertar o eliminar.

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() realiza tareas de asignación de recursos como establecer conexiones con la fuente de datos, mientras que Dispose() está destinado a restablecer todas esas asignaciones. Guardar es el método que llama la memoria caché para escribir objetos.

Llamada de lectura y escritura simultáneas desde la aplicación

El siguiente código de ejemplo muestra el uso de las capacidades de lectura/escritura simultáneas de la memoria caché desde una aplicación sencilla de 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);
        
        ...
    }
}

¿Qué hacer a continuación?

© Copyright Alachisoft 2002 - Todos los derechos reservados. NCache es una marca registrada de Diyatech Corp.