在分布式缓存中使用直读和直写

作者:伊克巴尔汗

随着极高事务 Web 应用程序、SOA、网格计算和其他服务器应用程序的爆炸式增长,数据存储无法跟上。 原因是数据存储不能不断添加更多服务器来横向扩展,这与高度可扩展的应用程序架构不同。

在这些情况下,内存中分布式缓存为数据存储瓶颈提供了出色的解决方案。 它跨越多个服务器(称为集群)以将它们的内存集中在一起并保持所有缓存在服务器之间同步。 而且,它可以像应用服务器一样不断地增长这个缓存集群。 这减少了数据存储的压力,使其不再是可扩展性瓶颈。

人们使用分布式缓存有两种主要方式:

  • 缓存侧: 这是应用程序负责从数据库读取和写入的地方,缓存根本不与数据库交互。 缓存被“保留”为更快、更具可扩展性的内存数据存储。 应用程序在从数据库读取任何内容之前检查缓存。 并且,应用程序在对数据库进行任何更新后更新缓存。 这样,应用程序可确保 缓存与数据库保持同步.
  • 通读/通写 (RT/WT): 这是应用程序将缓存视为主要数据存储和 从中读取数据并向其写入数据. 缓存负责将这些数据读写到数据库中,从而减轻应用程序的这个责任。
直读/直写缓存架构
图 1:Read-Through/Write-Through 缓存架构

Read-through & Write-through 优于 Cache-aside 的优势

Cache-aside 是一种非常强大的技术,它允许您发出涉及连接和嵌套查询的复杂数据库查询,并以您想要的任何方式操作数据。 尽管那样, 通读 / 直写 与 cache-aside 相比具有多种优势,如下所述:

  • 简化应用程序代码: 在缓存侧方法中,您的应用程序代码继续具有复杂性并直接依赖于数据库,如果多个应用程序处理相同的数据,甚至代码重复。 Read-through/Write-through 将一些数据访问代码从您的应用程序移动到缓存层。 这极大地简化了您的应用程序并更加清晰地抽象出数据库。
  • 通过 Read-through 获得更好的读取可扩展性: 在很多情况下, 缓存项过期 并且多个并行用户线程最终会访问数据库。 再加上数以百万计的缓存项和数以千计的并行用户请求,数据库上的负载变得明显更高。 但是,Read-through 在从数据库中获取它的最新副本时将缓存项保留在缓存中。 然后它更新缓存项。 结果是应用程序永远不会为这些缓存项进入数据库,并且数据库负载保持在最低水平。
  • 使用 Write-behind 获得更好的写入性能: 在缓存端,应用程序直接同步更新数据库。鉴于,一个 后写 让您的应用程序快速更新缓存并返回。 然后它让缓存在后台更新数据库。
  • 通过 Write-behind 获得更好的数据库可扩展性: 使用 Write-behind,您可以指定节流限制,因此数据库写入的执行速度不如缓存更新快,因此对数据库的压力不大。 此外,您可以将数据库写入安排在非高峰时段进行,以最大程度地减少压力。
  • 过期时自动刷新缓存: 通读 允许缓存在过期时自动从数据库重新加载对象。 这意味着您的应用程序不必在高峰时段访问数据库,因为最新的数据始终在缓存中。
  • 数据库更改时自动刷新缓存: 通读允许缓存在数据库中相应数据发生更改时自动从数据库中重新加载对象。 这意味着缓存始终是最新的,您的应用程序不必在高峰时段访问数据库,因为最新的数据始终在缓存中。

Read-through/Write-through 并不打算用于应用程序中的所有数据访问。 它最适合您从数据库中读取单个行或读取可以直接映射到单个缓存项的数据的情况。 它也非常适合用于保存在缓存中以供频繁读取的参考数据,即使这些数据会定期更改。

开发通读处理程序

通读处理程序向缓存服务器注册,并允许缓存直接从数据库中读取数据。 这 NCache server 提供了一个需要实现的 Read-through 处理程序接口。 这使 NCache 调用您的通读处理程序。

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() 执行某些资源分配任务,例如建立与主数据源的连接,而 Dispose() 旨在重置所有此类分配。 LoadFromSource() 是缓存调用以读取对象的内容。

开发直写处理程序

当缓存更新时缓存需要写入数据库时​​,将调用直写处理程序。 通常,应用程序通过添加、插入或删除向缓存发出更新。

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() 执行资源分配任务,例如建立与数据源的连接,而 Dispose() 旨在重置所有此类分配。 Save 是缓存调用直写对象的方法。

从应用程序调用 Read-Through & Write-Through

下面的示例代码展示了如何在一个简单的 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);
        
        ...
    }
}

接下来做什么?

联系我们

联系电话
©版权所有 Alachisoft 2002 - 版权所有。 NCache 是 Diyatech Corp. 的注册商标。