View a markdown version of this page

利用缓存来降低数据库需求 - AWS 规范性指导

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

利用缓存来降低数据库需求

概述

您可以将缓存作为一种有效的策略来用于降低您的 .NET 应用程序的成本。许多应用程序在需要频繁访问数据时会使用后端数据库,比如 SQL Server。维护这些后端服务以满足需求的成本可能会很高,但您可以采用有效的缓存策略,通过降低其规模和扩展需要来减轻后端数据库的负载。这能够帮助您降低成本并提升应用程序的性能。

缓存是一种非常有效的技术,能够节省因执行读取密集型工作负载(这类工作负载会消耗更多昂贵的资源,比如 SQL Server)而产生的相关成本。为您的工作负载采用正确的方法很重要。例如,本地缓存不具备可扩展性,而且需要您为每个应用程序实例维护一个本地缓存。您应当权衡其对性能影响与潜在成本之间的关系,这样底层数据来源的较低成本就能抵消与缓存机制相关的任何额外成本。

成本影响

SQL Server 要求您在确定数据库大小时将读取请求的因素考虑在内。这可能会对成本产生影响,因为您可能需要引入只读副本以承受负载。如果您正在使用只读副本,请务必清楚,这种功能仅在 SQL Server 企业版中可用。此版本所需的许可证费用比 SQL Server 标准版更高。

以下图表旨在帮助您了解缓存效果。它展示了 Amazon RDS for SQL Server,其中包含四个运行 SQL Server 企业版的 db.m4.2xlarge 节点。它部署在具有一个只读副本的 Multi-AZ 配置中。专属读取流量(例如,SELECT 查询)会被定向到只读副本。相比之下,Amazon DynamoDB 使用的是一个 r4.2xlarge 双节点 DynamoDB Accelerator(DAX)集群。

下图显示了取消使用专门负责处理高读取流量的只读副本这一需求后的结果。

显示移除专用只读副本结果的图表

通过使用本地缓存(无需只读副本)或者在 Amazon RDS 上将 DAX 与 SQL Server 并行配置为缓存层,您可以实现显著的成本节省。该层可将任务从 SQL Server 上移除,并减小运行数据库所需的 SQL Server 的规模。

成本优化建议

本地缓存

本地缓存是用于为部署在本地环境或部署在云中的应用程序缓存内容的最常用方式之一。这是因为它的实施相对简单且直观。本地缓存是指从数据库或其他来源获取内容,然后将其在内存或磁盘上进行本地缓存,以便于更快地访问。这种方法虽然易于实施,但对于某些使用案例而言却并非理想选择。例如,这包括那些需要使缓存内容长期保持不变的使用案例,比如保存应用程序状态或用户状态。另一种使用案例是:当需要从其他应用程序实例中访问缓存内容时。

下面的图表展示了一个由四个节点和两个只读副本组成的高可用性 SQL Server 集群。

具有 4 个节点和 2 个只读副本的高可用性 SQL Server 集群

使用本地缓存时,您可能需要将流量在多个 EC2 实例之间进行负载均衡。每个实例都必须维护自己的本地缓存。如果缓存中存储的是有状态信息,则需要定期将数据提交到数据库中,并且用户在每个后续请求中可能都需要被转发到同一个实例(即粘性会话)。这在尝试扩展应用程序时就带来了一种挑战,因为有些实例可能会被过度使用,而有些则由于流量分布不均而未得到充分利用。

您可以为 .NET 应用程序使用本地缓存,无论是内存缓存还是使用本地存储。为此,您可以添加功能,以便将对象存储在磁盘上并在需要时进行检索;或从数据库中查询数据并将其持久保存在内存中。例如,要在 C# 中对来自 SQL Server 的数据进行本地内存缓存和本地存储操作,您可以使用 MemoryCacheLiteDB 这两个库的组合。MemoryCache 提供内存缓存功能,而 LiteDB 则是一个嵌入式的基于磁盘的 NoSQL 数据库,速度快且轻量级。

要执行内存缓存,请使用 .NET 库 System.Runtime.MemoryCache。以下代码示例演示了如何使用 System.Runtime.Caching.MemoryCache 类将数据缓存在内存中。该类提供了一种在应用程序内存中临时存储数据的方法。这有助于提高应用程序的性能,因为它减少了从更昂贵的资源(如数据库或 API)获取数据的需求。

以下是该代码的工作原理:

  1. 创建一个名为 _memoryCacheMemoryCache 私有静态实例。为缓存赋予一个名称(dataCache)来标识它。然后,缓存存储和检索数据。

  2. GetData 方法是一个通用方法,它有两个参数:一个 string 键和一个名为 getDataFunc<T> 委派。键用于标识缓存的数据,而 getData 委派则代表了在数据未存在于缓存中时将执行的数据检索逻辑。

  3. 该方法首先通过 _memoryCache.Contains(key) 方法来检查数据是否存在于缓存中。如果数据在缓存中,该方法便会通过使用 _memoryCache.Get(key) 来获取数据,并将其转换为预期的类型 T。

  4. 如果数据不在缓存中,则该方法会调用 getData 委派来获取数据。然后,它使用 _memoryCache.Add(key, data, DateTimeOffset.Now.AddMinutes(10)) 将数据添加到缓存中。此调用规定,缓存条目应在 10 分钟后过期,届时数据将自动从缓存中移除。

  5. ClearCache 方法将 string 键作为参数,并使用 _memoryCache.Remove(key) 从缓存中移除与该键关联的数据。

using System; using System.Runtime.Caching; public class InMemoryCache { private static MemoryCache _memoryCache = new MemoryCache("dataCache"); public static T GetData<T>(string key, Func<T> getData) { if (_memoryCache.Contains(key)) { return (T)_memoryCache.Get(key); } T data = getData(); _memoryCache.Add(key, data, DateTimeOffset.Now.AddMinutes(10)); return data; } public static void ClearCache(string key) { _memoryCache.Remove(key); } }

您可以使用以下代码:

public class Program { public static void Main() { string cacheKey = "sample_data"; Func<string> getSampleData = () => { // Replace this with your data retrieval logic return "Sample data"; }; string data = InMemoryCache.GetData(cacheKey, getSampleData); Console.WriteLine("Data: " + data); } }

以下示例向您展示如何使用 LiteDB 在本地存储中缓存数据。您可以使用 LiteDB 作为内存缓存的替代或补充。以下代码演示了如何使用 LiteDB 库在本地存储中缓存数据。LocalStorageCache 类包含用于管理缓存的主要函数。

using System; using LiteDB; public class LocalStorageCache { private static string _liteDbPath = @"Filename=LocalCache.db"; public static T GetData<T>(string key, Func<T> getData) { using (var db = new LiteDatabase(_liteDbPath)) { var collection = db.GetCollection<T>("cache"); var item = collection.FindOne(Query.EQ("_id", key)); if (item != null) { return item; } } T data = getData(); using (var db = new LiteDatabase(_liteDbPath)) { var collection = db.GetCollection<T>("cache"); collection.Upsert(new BsonValue(key), data); } return data; } public static void ClearCache(string key) { using (var db = new LiteDatabase(_liteDbPath)) { var collection = db.GetCollection("cache"); collection.Delete(key); } } } public class Program { public static void Main() { string cacheKey = "sample_data"; Func<string> getSampleData = () => { // Replace this with your data retrieval logic return "Sample data"; }; string data = LocalStorageCache.GetData(cacheKey, getSampleData); Console.WriteLine("Data: " + data); } }

如果您有不会频繁变化的静态缓存或静态文件,也可以将这些文件存储在 Amazon Simple Storage Service(Amazon S3)的对象存储中。该应用程序可以在启动时获取静态缓存文件,并在本地使用该文件。有关如何使用 .NET 从 Amazon S3 检索文件的更多详细信息,请参阅 Amazon S3 文档中的下载对象

使用 DAX 进行缓存

您可以使用一个可跨所有应用程序实例共享的缓存层。DynamoDB Accelerator(DAX)是一款完全托管、高度可用的内存缓存服务,专为 DynamoDB 设计,能够实现十倍的性能提升。您可以使用 DAX 来降低成本,方法是减少在 DynamoDB 表中过度预调配读取容量单位的需求。这对于读取密集型且需要针对每个键进行重复读取的工作负载来说尤其有用。

DynamoDB 的定价方式为按需定价或按预调配容量计费,因此每月的读写次数会直接影响费用。如果您有读取密集型工作负载,DAX 集群可以通过减少 DynamoDB 表的读取次数,从而帮助降低成本。有关如何设置 DAX 的说明,请参阅 DynamoD B In-memory 文档中的使用 DynamoDB 加速器 (DAX) 进行加速。有关.NET 应用程序集成的信息,请观看将 Amazon DynamoDB DAX 集成到 ASP.NET 您的应用程序中。 YouTube

其他资源