Azure设计模式之缓存模式

缓存模式


将数据按需从数据库加载到缓存中。可以提高性能,并且还有助于保持缓存数据与数据库中数据的一致性。


问题背景
应用程序使用缓存来改进对数据库信息的重复访问。然而,若使缓存中数据始终与数据库中的完全一致是不切实际的。应用程序应实现有助于确保缓存中数据尽可能保持最新的策略,也可检测并处理缓存中数据变得过时出现的情况。


解决方案
许多商业缓存系统提供直读,直写,后台写操作。在这些系统中,应用程序通过引用缓存来获取数据。如果数据不在缓存中,则从数据库中获取,并将其添加到缓存中。缓存中所保存数据的任何修改也会自动写回到数据库。


对于没有这样的缓存机制,使用缓存的应用程序自己负责维护数据。


应用程序可以通过实现缓存策略来模拟直读缓存的功能。此策略将数据按需加载到缓存中。该图说明了如何使用缓存模式将数据存储在缓存中。



如果应用程序更新信息,则可以对数据库直接进行写入,并使缓存中的相应项目失效。


当该数据再次被读取时,缓存策略将从数据中获取并将更新的数据更新到缓存。


问题和思考


缓存数据的生命周期。许多缓存实现了使数据无效的到期策略,如果在指定的时间段内未被访问,则会将其从缓存中删除。为了使缓存有效,需要确保到期策略与应用程序的数据访问模式相匹配。不要使缓存生命周期太短,因为这可能导致应用程序不断的从数据中查询数据并将其更新到缓存中。同样,不要使缓存生命周期过长,导致缓存的数据过时。请记住,缓存对于相对静态的数据和被经常读取的数据是最有效的。


数据排除。与作为数据源的数据库相比,大多数缓存的大小有限,如果需要,要对数据进行排除。大多数缓存采用最近最少使用的策略来选择要排除的数据,但这个逻辑是可自定义的。可配置缓存的全局过期时间和其他属性以及每个缓存项的到期时间,以确保缓存具有高性价比。将全局排除策略应用于缓存中的每个项目并不总是适用的。例如,如果缓存的某数据从数据库中获取是非常昂贵的,那么将该数据保存在高速缓存中是有益的。


启动缓存。许多解决方案将缓存与应用程序所需要的数据的加载做为启动处理的一部分。如果某些数据到期或被排除,缓存模式仍有用。


一致性。实现缓存模式不能保证数据库和缓存之间的一致性。数据库中的数据可以随时通过外部进程被更改,并且在下次加载数据时,更新可能无法反映到缓存中。在跨数据库同步数据的系统中,如果频繁发生,这个问题可能会更加明显。


本地(内存中)缓存。缓存可能是在应用程序实例本地,存储在内存中。如果应用程序重复访问相同的数据,这种缓存很有用。然而,本地缓存是私有的,因此不同的应用程序实例可以各自具有相同的缓存数据的副本。这些数据可能会与全局缓存之间变得不一致,因此可能需要使私有缓存中的数据过期,并更频繁地刷新数据。在这些情况下,请考虑共享或使用分布式缓存。




这种模式适用于:
不提供本地的直读和直写操作的缓存。
资源访问需求是不可预测的。该模式使应用程序能够按需加载数据。而不提供应用程序应提前加载哪些数据的假设。


这种模式不适用于:
当缓存的数据是静态的。如果缓存空间允许,考虑在启动时将数据加载到缓存,并防止数据过期。
在WebFarm环境中的Web应用程序。在这种环境中,应该避免引入基于client-server亲和性的依赖关系。


例子
在MicrosoftAzure中,可以使用AzureRedisCache创建被应用程序的多实例共享的分布式缓存。


要连接到AzureRedisCache实例,只需调用静态Connect方法并传入连接字符串。该方法返回一个ConnectionMultiplexer代表连接上下文。在应用程序中共享ConnectionMultiplexer实例的一种方法是使用静态属性,与以下示例类似。这种方法提供了线程安全的方法来初始化一个连接的实例。





以下代码示例中的GetMyEntityAsync方法是基于AzureRedisCache的缓存模式的实现。此方法使
···
private static ConnectionMultiplexer Connection;


// Redis Connection string info
private static Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
{
    string cacheConnection = ConfigurationManager.AppSettings["CacheConnection"].ToString();
    return ConnectionMultiplexer.Connect(cacheConnection);
});


public static ConnectionMultiplexer Connection => lazyConnection.Value;
···

用直读方法从缓存中查询对象。


整数ID作为关键字以标识对象。GetMyEntityAsync方法尝试从缓存中查询具有该密钥的数据。如果找到匹配项,则返回。如果缓存中没有匹配,GetMyEntityAsync方法将从数据库中检索对象,将其添加到缓存中,然后返回。实际上从数据库读取数据的代码在这里没有显示,因为它取决于数据库。缓存数据被标识为过期,以防止其被更新。


···
// Set five minute expiration as a default
private const double DefaultExpirationTimeInMinutes = 5.0;


public async Task<MyEntity> GetMyEntityAsync(int id)
{
  // Define a unique key for this method and its parameters.
  var key = $"MyEntity:{id}";
  var cache = Connection.GetDatabase();


  // Try to get the entity from the cache.
  var json = await cache.StringGetAsync(key).ConfigureAwait(false);
  var value = string.IsNullOrWhiteSpace(json) 
                ? default(MyEntity) 
                : JsonConvert.DeserializeObject<MyEntity>(json);


  if (value == null) // Cache miss
  {
    // If there's a cache miss, get the entity from the original store and cache it.
    // Code has been omitted because it's data store dependent.  
    value = ...;


    // Avoid caching a null value.
    if (value != null)
    {
      // Put the item in the cache with a custom expiration time that 
      // depends on how critical it is to have stale data.
      await cache.StringSetAsync(key, JsonConvert.SerializeObject(value)).ConfigureAwait(false);
      await cache.KeyExpireAsync(key, TimeSpan.FromMinutes(DefaultExpirationTimeInMinutes)).ConfigureAwait(false);
    }
  }


  return value;
}


···




这些示例使用AzureRedisCacheAPI来访问数据存储并从缓存中查询信息。有关详细信息,请参阅使用Microsoft Azure Redis Cache(https://docs.microsoft.com/en-us/azure/redis-cache/cache-dotnet-how-to-use-azure-redis-cache)以及如何使用Redis Cache创建Web应用程序(https://docs.microsoft.com/en-us/azure/redis-cache/cache-web-app-howto)
下面的UpdateEntityAsync方法演示了当应用程序修改数据时,如何使缓存中的对象无效。这是一个直写方法的例子。代码更新原始数据,然后通过调用KeyDeleteAsync方法(指定密钥)从缓存中删除缓存项。
这个逻辑顺序很重要。如果在缓存更新之前删除该项目,则客户端应用程序在数据存储中的项目已更改之前,具有很短的时间来获取数据(因为它在高速缓存中未找到),从而导致缓存包含陈旧的数据。


public async Task UpdateEntityAsync(MyEntity entity)
{
    // Invalidate the current cache object
    var cache = Connection.GetDatabase();
    var id = entity.Id;
    var key = $"MyEntity:{id}"; // Get the correct key for the cached object.
    await cache.KeyDeleteAsync(key).ConfigureAwait(false);


    // Update the object in the original data store
    await this.store.UpdateEntityAsync(entity).ConfigureAwait(false); 
}


相关阅读
实现缓存模式时,可参考以下资料:
缓存(https://docs.microsoft.com/en-us/azure/architecture/best-practices/caching)。介绍了如何在云环境实现缓存,以及实现时应考虑的问题。


数据一致性介绍(https://msdn.microsoft.com/library/dn589800.aspx)。云应用程序通常使用分布式存储。管理和维护环境中的数据一致性是系统的关键问题,特别是其中可能会出现的并发和可用性问题。本引言介绍了有关分布式存储的一致性的问题,并总结了应用程序如何实现最终一致性来保持可用性。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值