Autor: Iqbal Khan
Com a explosão de aplicativos da Web de transações extremamente altas, SOA, computação em grade e outros aplicativos de servidor, o armazenamento de dados não consegue acompanhar. O motivo é que o armazenamento de dados não pode continuar adicionando mais servidores para expandir, ao contrário das arquiteturas de aplicativos extremamente escaláveis.
Nessas situações, o cache distribuído na memória oferece uma excelente solução para gargalos de armazenamento de dados. Ele abrange vários servidores (chamados de cluster) para agrupar sua memória e manter todo o cache sincronizado entre os servidores. E ele pode continuar aumentando esse cluster de cache indefinidamente, assim como os servidores de aplicativos. Isso reduz a pressão no armazenamento de dados para que não seja mais um gargalo de escalabilidade.
Existem duas maneiras principais pelas quais as pessoas usam um cache distribuído:
Cache-aside é uma técnica muito poderosa e permite que você emita consultas de banco de dados complexas envolvendo junções e consultas aninhadas e manipule dados da maneira que desejar. Apesar disso, Leia / Gravação tem várias vantagens sobre o cache-aside, conforme mencionado abaixo:
A leitura/gravação não se destina a ser usada para todos os acessos a dados em seu aplicativo. Ele é mais adequado para situações em que você está lendo linhas individuais do banco de dados ou lendo dados que podem mapear diretamente para um item de cache individual. Também é ideal para dados de referência que devem ser mantidos no cache para leituras frequentes, mesmo que esses dados sejam alterados periodicamente.
Um manipulador de leitura é registrado com o servidor de cache e permite que o cache leia dados diretamente do banco de dados. O NCache server fornece uma interface de manipulador de leitura que você precisa implementar. Isso permite NCache para chamar seu manipulador de leitura.
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()
executa certas tarefas de alocação de recursos, como estabelecer conexões com a fonte de dados principal, enquanto Dispose()
destina-se a redefinir todas essas alocações. LoadFromSource()
é o que o cache chama para ler os objetos.
O manipulador de gravação é chamado quando o cache precisa gravar no banco de dados à medida que o cache é atualizado. Normalmente, o aplicativo emite uma atualização no cache por meio de adicionar, inserir ou remover.
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()
executa tarefas de alocação de recursos, como estabelecer conexões com a fonte de dados, enquanto Dispose()
destina-se a redefinir todas essas alocações. Save é o método que o cache chama para objetos write-through.
O código de exemplo a seguir mostra o uso dos recursos de leitura/gravação do cache de um aplicativo Windows simples
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);
...
}
}