使用 Azure) 生成Real-World云应用的分布式缓存 (

作者 :Rick AndersonTom Dykstra

下载修复项目下载电子书

使用 Azure 构建真实世界云应用 电子书基于 Scott Guthrie 开发的演示文稿。 本文介绍了 13 种模式和做法,可帮助你成功开发适用于云的 Web 应用。 有关电子书的信息,请参阅 第一章

上一章探讨了暂时性故障处理,并提到缓存作为断路器策略。 本章提供有关缓存的更多背景信息,包括何时使用它、使用它的常见模式以及如何在 Azure 中实现它。

什么是分布式缓存

缓存通过将数据存储在内存中,提供对经常访问的应用程序数据的高吞吐量、低延迟访问。 对于云应用,最有用的缓存类型是分布式缓存,这意味着数据不存储在单个 Web 服务器的内存中,而是存储在其他云资源上,缓存数据可供应用程序的所有 Web 服务器 (或应用程序) 使用的其他云 VM 使用。

显示多个 Web 服务器访问同一缓存服务器的示意图

当应用程序通过添加或删除服务器进行缩放时,或者当服务器因升级或故障而更换时,缓存的数据仍可供运行应用程序的每台服务器访问。

通过避免持久性数据存储的高延迟数据访问,缓存可以显著提高应用程序响应能力。 例如,从缓存中检索数据比从关系数据库检索数据快得多。

缓存的一个附带好处是减少了流向永久性数据存储的流量,这可能会导致成本降低,因为存在永久性数据存储的数据传出费用。

何时使用分布式缓存

缓存最适合用于读取数据多于写入数据的应用程序工作负载,以及数据模型支持用于在缓存中存储和检索数据的键/值组织。 当应用程序用户共享大量通用数据时,它也更有用;例如,如果每个用户通常检索该用户唯一的数据,则缓存不会提供同样多的好处。 例如,缓存可能非常有益的一个示例是产品目录,因为数据不会频繁更改,并且所有客户都在查看相同的数据。

随着永久性数据存储的吞吐量限制和延迟延迟对应用程序整体性能的限制,应用程序缩放越多,缓存的好处就会越来越明显。 但是,也可能出于性能以外的其他原因实现缓存。 对于在向用户显示时不必完全最新的数据,当永久性数据存储无响应或不可用时,缓存访问可以充当断路器。

为了能够从缓存中检索数据,必须先将其存储在缓存中。 有几种策略可用于将所需的数据引入缓存:

  • 按需/缓存旁

    应用程序尝试从缓存中检索数据,当缓存没有 (“未命中”) 的数据时,应用程序会将数据存储在缓存中,以便下次可以使用这些数据。 下次应用程序尝试获取相同的数据时,它会在缓存中查找的内容 (“命中”) 。 若要防止提取数据库中已更改的缓存数据,请在对数据存储进行更改时使缓存失效。

  • 后台数据推送

    后台服务定期将数据推送到缓存中,应用始终从缓存中拉取。 此方法适用于不需要始终返回最新数据的高延迟数据源。

  • 断路器

    应用程序通常直接与永久性数据存储通信,但当永久性数据存储出现可用性问题时,应用程序会从缓存中检索数据。 数据可能已使用缓存一边或后台数据推送策略放入缓存中。 这是故障处理策略,而不是性能增强策略。

为了使缓存中的数据保持最新状态,可以在应用程序创建、更新或删除数据时删除相关的缓存条目。 如果应用程序有时可以获取略微过期的数据,则可以依赖可配置的过期时间来设置缓存数据的过期时间限制。

可以配置自创建缓存项) 以来的绝对过期 (时间量,也可以配置自上次访问缓存项) 以来 (滑动过期时间。 如果依赖于缓存过期机制来防止数据过于陈旧,则使用绝对过期时间。 在“修复它”应用中,我们将手动逐出过时的缓存项,并使用滑动过期来保留缓存中的最新数据。 无论选择哪种过期策略,当达到缓存的内存限制时,缓存将自动逐出最早的 (最近使用时间或 LRU) 项。

用于 Fix It 应用的示例缓存端代码

在以下示例代码中,我们在检索“修复它”任务时首先检查缓存。 如果在缓存中找到该任务,我们将返回它;如果未找到,请从数据库获取它并将其存储在缓存中。 突出显示了将缓存添加到 FindTaskByIdAsync 方法的更改。

public async Task<FixItTask> FindTaskByIdAsync(int id)
 {
    FixItTask fixItTask = null;
    Stopwatch timespan = Stopwatch.StartNew();
    string hitMiss = "Hit";

    try
    {
       fixItTask = (FixItTask)cache.Get(id.ToString());
       if (fixItTask == null)
       {
          fixItTask = await db.FixItTasks.FindAsync(id);
          cache.Put(id.ToString(), fixItTask);
          hitMiss = "Miss";
       }

       timespan.Stop();
       log.TraceApi("SQL Database", "FixItTaskRepository.FindTaskByIdAsync", timespan.Elapsed, 
                    "cache {0}, id={1}", hitMiss, id);
    }
    catch (Exception e)
    {
       log.Error(e, "Error in FixItTaskRepository.FindTaskByIdAsynx(id={0})", id);
    }

    return fixItTask;
 }

更新或删除“修复它”任务时,必须使 (删除缓存任务) 失效。 否则,将来读取该任务的尝试将继续从缓存中获取旧数据。

public async Task UpdateAsync(FixItTask taskToSave)
{
   Stopwatch timespan = Stopwatch.StartNew();

   try
   {
      cache.Remove(taskToSave.FixItTaskId.ToString());
      db.Entry(taskToSave).State = EntityState.Modified;
      await db.SaveChangesAsync();

      timespan.Stop();
      log.TraceApi("SQL Database", "FixItTaskRepository.UpdateAsync", timespan.Elapsed, "taskToSave={0}", taskToSave);
   }
   catch (Exception e)
   {
      log.Error(e, "Error in FixItTaskRepository.UpdateAsync(taskToSave={0})", taskToSave);
   }
}

下面是演示简单缓存代码的示例:缓存尚未在可下载的 Fix It 项目中实现。

Azure 缓存服务

Azure 提供以下缓存服务: Azure Redis 缓存Azure 托管缓存。 Azure Redis 缓存基于热门开放源代码 Redis 缓存,是大多数缓存方案的首选。

使用缓存提供程序 ASP.NET 会话状态

Web 开发最佳做法一章所述,最佳做法是避免使用会话状态。 如果应用程序需要会话状态,下一个最佳做法是避免使用默认的内存中提供程序,因为这样不会 (多个 Web 服务器实例) 进行横向扩展。 ASP.NET SQL Server会话状态提供程序使在多个 Web 服务器上运行的站点可以使用会话状态,但与内存中提供程序相比,它会产生较高的延迟成本。 如果必须使用会话状态,最佳解决方案是使用缓存提供程序,例如 Azure 缓存的会话状态提供程序

总结

你已了解 Fix It 应用如何实现缓存,以改进响应时间和可伸缩性,并使应用能够在数据库不可用时继续响应读取操作。 在下一章中,我们将介绍如何进一步提高可伸缩性并使应用继续响应写入操作。

资源

有关缓存的详细信息,请参阅以下资源。

文档

视频

  • FailSafe:生成可缩放、可复原云服务。 由乌尔里希·霍曼、马克·莫库里和马克·西姆斯组成的九部分系列。 提供有关如何构建云应用的 400 级别的视图。 本系列侧重于理论和原因:有关操作方法的详细信息,请参阅 Mark Simms 的 Building Big 系列。 请参阅第 3 集中的缓存讨论,从 1:24:14 开始。
  • 构建大型:从 Azure 客户那里吸取的教训 - 第一部分。Simon Davies 从 46:00 开始讨论分布式缓存。 类似于故障安全系列,但会介绍更多操作方法详细信息。 演示文稿于 2012 年 10 月 31 日发布,因此它不包括 2013 年引入的 Azure 应用服务 中Web 应用的缓存服务。

代码示例