你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn。
此模式按需将数据从数据存储加载到缓存中。 使用此模式可以提高性能,并帮助保持缓存中的数据与基础数据存储中的数据之间的一致性。
上下文和问题
应用程序使用缓存来提高对数据存储中信息的重复访问的性能。 但缓存的数据并不总是与数据存储保持一致。 应用程序应实施一种策略,使缓存中的数据尽可能保持最新。 该策略还应检测缓存的数据何时过时并适当地处理它。
解决方案
许多商业缓存系统提供读穿透、写穿透或写后操作。 在这些系统中,应用程序通过引用缓存来检索数据。 如果数据不在缓存中,应用程序将从数据存储中检索数据并将其添加到缓存中。 系统会自动将对缓存数据所做的任何更改写回到数据存储。
对于不提供此功能的缓存,使用缓存的应用程序必须维护数据。
应用程序可以通过实现 Cache-Aside 模式来模拟读通缓存的功能。 此策略可按需将数据加载到缓存。 下图使用 Cache-Aside 模式将数据存储在缓存中。
应用程序通过尝试从缓存中读取,确定项当前是否驻留在缓存中。
如果该项不在缓存中(也称为 缓存未命中),应用程序将从数据存储中检索该项。
应用程序将项添加到缓存,然后将其返回到调用方。
如果应用程序更新信息,它可以通过修改数据存储并使缓存中的相应项失效来遵循写通策略。
当再次需要该项时,Cache-Aside 模式将从数据存储中检索更新的数据并将其添加到缓存中。
问题和注意事项
在决定如何实现此模式时,请考虑以下几点:
缓存数据的生存期: 许多缓存使用过期策略使数据失效,并在设置期间未访问数据时将其从缓存中删除。 若要使旁路缓存有效,请确保过期策略与应用程序的数据访问模式相匹配。 不要使过期期限过短,因为过早过期可能会导致应用程序不断从数据存储中检索数据并将其添加到缓存中。 同样,不要使过期期限过长,以至于缓存的数据变得过时。 缓存最适用于应用程序经常读取的相对静态数据或数据。
逐出数据: 与数据源自的数据存储相比,大多数缓存的大小有限。 如果缓存超出其大小限制,则会逐出数据。 大多数缓存采用最近使用最少的策略来选择要逐出的项目,但有些缓存允许自定义。
配置: 可以全局或每个缓存项配置缓存行为。 单个全局逐出策略可能不适合所有项目。 如果某个项检索成本高昂,请单独配置缓存项。 在这种情况下,将项保留在缓存中是有意义的,即使访问频率低于更便宜的项目也是如此。
缓存预填充: 许多解决方案会在应用程序启动处理过程中,将可能需要的数据预填充到缓存中。 当某些数据过期或被逐出时,Cache-Aside 模式仍然很有用。
一致性: Cache-Aside 模式不能保证数据存储和缓存之间的一致性。 例如,外部进程可以随时更改数据存储中的项。 在项再次加载之前,此更改不会显示在缓存中。 在跨数据存储复制数据的系统中,频繁同步可能会使一致性变得困难。
本地缓存: 缓存可以是应用程序实例的本地缓存,并存储在内存中。 如果应用程序重复访问相同的数据,则缓存端在此环境中非常有效。 但本地缓存是专用的,因此不同的应用程序实例可以具有相同缓存数据的副本。 此数据在缓存之间可能很快变得不一致,因此可能需要使专用缓存中的数据过期并更频繁地刷新数据。 在这些方案中,请考虑使用共享或分布式缓存机制。
语义缓存: 某些工作负荷可以从基于语义含义而不是确切键执行缓存检索中获益。 此方法减少了发送到语言模型的请求和令牌数。 仅当数据支持语义等效性时,才使用语义缓存,不返回不相关的响应,并且不包含私有和敏感数据。 例如,“我的每年带回家工资是多少?” 语义上类似于“我的每年带回家工资是多少?”但是,如果不同的用户提出这些问题,答案应该有所不同。 也不应在缓存中包含此敏感数据。
何时使用此模式
在以下情况下使用此模式:
缓存不提供本机直读和直写操作。
资源需求不可预知。 此模式可使应用程序按需加载数据。 它不假定应用程序提前需要哪些数据。
在以下情况下,此模式可能不适用:
数据是敏感的或与安全相关的。 在缓存中存储数据可能不合适,尤其是在多个应用程序或用户共享缓存时。 始终从主源检索此类数据。
缓存的数据集是静态的。 如果数据适合可用的缓存空间,请在启动时将数据加载到缓存中,并应用策略防止数据过期。
大多数请求不会遇到缓存命中。 在这种情况下,检查缓存并将数据加载到缓存中的开销可能超过缓存的好处。
在 Web 场中托管的 Web 应用程序中缓存会话状态信息。 在此环境中,请避免基于客户端-服务器相关性引入依赖项。
工作负荷设计
在工作负荷的设计中评估如何使用 Cache-Aside 模式以符合 Azure Well-Architected Framework 支柱中涵盖的目标和原则。 下表提供有关此模式如何支持每个支柱目标的指南。
| 支柱 | 此模式如何支持支柱目标 |
|---|---|
| 可靠性 设计决策有助于工作负荷在发生故障后 复原 ,并确保它在发生故障后 恢复到 正常运行状态。 | 缓存会复制数据。 在有限的方式下,如果源数据存储暂时不可用,它可以保留经常访问的数据的可用性。 如果缓存出现故障,工作负荷可以回退到源数据存储。 - RE:05 冗余 |
| 通过缩放、数据和代码的优化,性能效率可帮助工作负荷高效地满足需求。 | 缓存可提高读取密集型数据的性能,这些数据不经常更改并容忍一些过时。 - PE:08 数据性能 - PE:12 持续性能优化 |
如果此模式在某个支柱中引入权衡取舍,请将它们与其他支柱的目标进行对比。
示例
请考虑使用 Azure 托管 Redis 创建多个应用程序实例可以共享的分布式缓存。
以下示例使用 StackExchange.Redis 客户端,这是为 .NET 编写的 Redis 客户端库。 若要连接到 Azure 托管 Redis 实例,请调用静态 ConnectionMultiplexer.Connect 方法并传入连接字符串。 该方法返回表示连接的 ConnectionMultiplexer。
在应用程序中共享 ConnectionMultiplexer 实例的一种方法是使用静态属性,该属性返回一个已连接的实例,类似于以下示例。 此方法通过一种线程安全的方式,仅初始化单个已连接的实例。
private static ConnectionMultiplexer Connection;
// Redis connection string information
private static Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
{
string cacheConnection = ConfigurationManager.AppSettings["CacheConnection"].ToString();
return ConnectionMultiplexer.Connect(cacheConnection);
});
public static ConnectionMultiplexer Connection => lazyConnection.Value;
GetMyEntityAsync以下示例中的方法演示 Cache-Aside 模式的实现。 此方法使用读通方法从缓存中检索对象。
该方法通过使用整数 ID 作为键来标识对象。 它尝试使用此键从缓存中检索项。 如果缓存包含匹配项,则返回该项。 如果缓存不包含匹配项,该方法 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 is 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;
}
注意
这些示例使用 Azure 托管 Redis 访问存储和从缓存中检索信息。 有关详细信息,请参阅 创建 Azure 托管 Redis 实例 并使用 .NET Core 中的 Azure 托管 Redis。
以下 UpdateEntityAsync 方法演示如何在应用程序更改值时使缓存中的对象失效。 代码更新原始数据存储,然后从缓存中删除缓存的项。
public async Task UpdateEntityAsync(MyEntity entity)
{
// Update the object in the original data store.
await this.store.UpdateEntityAsync(entity).ConfigureAwait(false);
// Invalidate the current cache object.
var cache = Connection.GetDatabase();
var id = entity.Id;
var key = $"MyEntity:{id}"; // The key for the cached object.
await cache.KeyDeleteAsync(key).ConfigureAwait(false); // Delete this key from the cache.
}
注意
该步骤的顺序非常重要。 请先更新数据存储,然后再从缓存中删除项。 如果首先删除缓存项,则在更新数据存储之前,客户端可能会在短时间窗口内提取该项。 在这种情况下,提取会导致缓存丢失,因为该项不在缓存中。 缓存缺失会导致应用程序从数据存储中检索过时的项,并将其添加回缓存。 此序列会导致缓存中的过时数据。
后续步骤
数据一致性入门:此入门介绍跨分布式数据保持一致性的问题。 它还总结了应用程序如何实现最终一致性来维护数据的可用性。 云应用程序通常跨多个数据存储和位置存储数据。 必须在此环境中高效管理和维护数据一致性,特别是由于可能出现的并发性和可用性问题。
使用 Azure 托管 Redis 作为语义缓存:本教程介绍如何使用 Azure 托管 Redis 实现语义缓存。
相关资源
可靠 Web 应用模式:此模式将 Cache-Aside 模式应用于云中的 Web 应用程序。
缓存指南:本指南提供有关如何在云解决方案中缓存数据的详细信息,以及实现缓存时要考虑的问题。