ASP.NET Core 中的分布式缓存Distributed caching in ASP.NET Core

作者: Luke LathamSteve SmithBy Luke Latham and Steve Smith

分布式缓存是由多个应用服务器共享的缓存,通常作为外部服务在访问它的应用服务器上维护。A distributed cache is a cache shared by multiple app servers, typically maintained as an external service to the app servers that access it. 分布式缓存可以提高 ASP.NET Core 应用程序的性能和可伸缩性,尤其是在应用程序由云服务或服务器场托管时。A distributed cache can improve the performance and scalability of an ASP.NET Core app, especially when the app is hosted by a cloud service or a server farm.

与其他缓存方案相比,分布式缓存具有多项优势,其中缓存的数据存储在单个应用服务器上。A distributed cache has several advantages over other caching scenarios where cached data is stored on individual app servers.

当分布式缓存数据时,数据将:When cached data is distributed, the data:

  • 跨多个服务器的请求具有连贯(一致)。Is coherent (consistent) across requests to multiple servers.
  • 置服务器重启和应用部署。Survives server restarts and app deployments.
  • 不使用本地内存。Doesn't use local memory.

分布式缓存配置是特定于实现的。Distributed cache configuration is implementation specific. 本文介绍如何配置 SQL Server 和 Redis 分布式缓存。This article describes how to configure SQL Server and Redis distributed caches. 第三方实现也可用,例如NCacheGitHub 上的 NCache)。Third party implementations are also available, such as NCache (NCache on GitHub). 无论选择哪种实现,应用都会使用IDistributedCache接口与缓存交互。Regardless of which implementation is selected, the app interacts with the cache using the IDistributedCache interface.

查看或下载示例代码如何下载View or download sample code (how to download)

先决条件Prerequisites

若要使用 SQL Server 分布式缓存,请将包引用添加到 "To use a SQL Server distributed cache, add a package reference to the Microsoft.Extensions.Caching.SqlServer package.

若要使用 Redis 分布式缓存,请将包引用添加到StackExchangeRedis包。To use a Redis distributed cache, add a package reference to the Microsoft.Extensions.Caching.StackExchangeRedis package.

若要使用 SQL Server 分布式缓存,请参考AspNetCore 元包,或添加对包的包引用。To use a SQL Server distributed cache, reference the Microsoft.AspNetCore.App metapackage or add a package reference to the Microsoft.Extensions.Caching.SqlServer package.

若要使用 Redis 分布式缓存,请参考AspNetCore 元包,并将包引用添加到包。To use a Redis distributed cache, reference the Microsoft.AspNetCore.App metapackage and add a package reference to the Microsoft.Extensions.Caching.StackExchangeRedis package. Redis 包不包含在Microsoft.AspNetCore.App包中,因此必须在项目文件中单独引用 Redis 包。The Redis package isn't included in the Microsoft.AspNetCore.App package, so you must reference the Redis package separately in your project file.

若要使用 SQL Server 分布式缓存,请参考AspNetCore 元包,或添加对包的包引用。To use a SQL Server distributed cache, reference the Microsoft.AspNetCore.App metapackage or add a package reference to the Microsoft.Extensions.Caching.SqlServer package.

若要使用 Redis 分布式缓存,请参考AspNetCore 元包,并将包引用添加到包。To use a Redis distributed cache, reference the Microsoft.AspNetCore.App metapackage and add a package reference to the Microsoft.Extensions.Caching.Redis package. Redis 包不包含在Microsoft.AspNetCore.App包中,因此必须在项目文件中单独引用 Redis 包。The Redis package isn't included in the Microsoft.AspNetCore.App package, so you must reference the Redis package separately in your project file.

IDistributedCache 接口IDistributedCache interface

IDistributedCache接口提供以下方法来处理分布式缓存实现中的项:The IDistributedCache interface provides the following methods to manipulate items in the distributed cache implementation:

  • Getbyte[]GetAsync 接受字符串键,并在缓存中找到缓存项作为数组。–Get, GetAsync – Accepts a string key and retrieves a cached item as a byte[] array if found in the cache.
  • SetSetAsync使用字符串键将项byte[] (作为数组)添加到缓存中– 。Set, SetAsync – Adds an item (as byte[] array) to the cache using a string key.
  • RefreshRefreshAsync ,–基于其键刷新缓存中的项,并重置其可调过期超时值(如果有)。Refresh, RefreshAsync – Refreshes an item in the cache based on its key, resetting its sliding expiration timeout (if any).
  • RemoveRemoveAsync ,–基于其字符串键删除缓存项。Remove, RemoveAsync – Removes a cache item based on its string key.

建立分布式缓存服务Establish distributed caching services

在中IDistributedCache Startup.ConfigureServices注册的实现。Register an implementation of IDistributedCache in Startup.ConfigureServices. 本主题中所述的框架提供的实现包括:Framework-provided implementations described in this topic include:

分布式内存缓存Distributed Memory Cache

分布式内存缓存(AddDistributedMemoryCache)是一个框架提供的IDistributedCache实现,它将项存储在内存中。The Distributed Memory Cache (AddDistributedMemoryCache) is a framework-provided implementation of IDistributedCache that stores items in memory. 分布式内存缓存不是实际的分布式缓存。The Distributed Memory Cache isn't an actual distributed cache. 缓存项由应用程序实例存储在运行应用程序的服务器上。Cached items are stored by the app instance on the server where the app is running.

分布式内存缓存是一种有用的实现:The Distributed Memory Cache is a useful implementation:

  • 用于开发和测试方案。In development and testing scenarios.
  • 在生产环境中使用单一服务器并且内存消耗不是问题。When a single server is used in production and memory consumption isn't an issue. 实现分布式内存缓存会抽象化缓存的数据存储。Implementing the Distributed Memory Cache abstracts cached data storage. 如果需要多个节点或容错,可以在将来实现真正的分布式缓存解决方案。It allows for implementing a true distributed caching solution in the future if multiple nodes or fault tolerance become necessary.

当应用程序在的开发环境中Startup.ConfigureServices运行时,示例应用程序会使用分布式内存缓存:The sample app makes use of the Distributed Memory Cache when the app is run in the Development environment in Startup.ConfigureServices:

services.AddDistributedMemoryCache();
services.AddDistributedMemoryCache();

分布式 SQL Server 缓存Distributed SQL Server Cache

分布式 SQL Server 缓存实现(AddDistributedSqlServerCache)允许分布式缓存使用 SQL Server 数据库作为其后备存储。The Distributed SQL Server Cache implementation (AddDistributedSqlServerCache) allows the distributed cache to use a SQL Server database as its backing store. 若要在 SQL Server 实例中创建 SQL Server 缓存的项表,可以使用sql-cache工具。To create a SQL Server cached item table in a SQL Server instance, you can use the sql-cache tool. 该工具将创建一个表,其中包含指定的名称和架构。The tool creates a table with the name and schema that you specify.

通过运行sql-cache create命令在 SQL Server 中创建一个表。Create a table in SQL Server by running the sql-cache create command. 提供 SQL Server 实例Data Source()、数据库(Initial Catalog)、架构(例如, dbo)和表名( TestCache例如):Provide the SQL Server instance (Data Source), database (Initial Catalog), schema (for example, dbo), and table name (for example, TestCache):

dotnet sql-cache create "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=DistCache;Integrated Security=True;" dbo TestCache

将记录一条消息,指示该工具已成功:A message is logged to indicate that the tool was successful:

Table and index were created successfully.

sql-cache工具创建的表具有以下架构:The table created by the sql-cache tool has the following schema:

SqlServer 缓存表

备注

应用应使用的IDistributedCache实例(而不是SqlServerCache)来处理缓存值。An app should manipulate cache values using an instance of IDistributedCache, not a SqlServerCache.

示例应用在中SqlServerCache Startup.ConfigureServices的非开发环境中实现:The sample app implements SqlServerCache in a non-Development environment in Startup.ConfigureServices:

services.AddDistributedSqlServerCache(options =>
{
    options.ConnectionString = 
        _config["DistCache_ConnectionString"];
    options.SchemaName = "dbo";
    options.TableName = "TestCache";
});
services.AddDistributedSqlServerCache(options =>
{
    options.ConnectionString = 
        _config["DistCache_ConnectionString"];
    options.SchemaName = "dbo";
    options.TableName = "TestCache";
});

备注

SchemaName /(以及(可选TableName)通常存储在源代码管理的外部(例如,由机密管理器appsettingsappsettings 中存储)。 ConnectionString 环境} json文件)。A ConnectionString (and optionally, SchemaName and TableName) are typically stored outside of source control (for example, stored by the Secret Manager or in appsettings.json/appsettings.{ENVIRONMENT}.json files). 连接字符串可能包含应保留在源代码管理系统之外的凭据。The connection string may contain credentials that should be kept out of source control systems.

分布式 Redis 缓存Distributed Redis Cache

Redis是一种开源的内存中数据存储,通常用作分布式缓存。Redis is an open source in-memory data store, which is often used as a distributed cache. 可以在本地使用 Redis,也可以为 Azure 托管的 ASP.NET Core 应用配置Azure Redis 缓存You can use Redis locally, and you can configure an Azure Redis Cache for an Azure-hosted ASP.NET Core app.

应用使用中RedisCache AddStackExchangeRedisCache的非开发环境中的实例()Startup.ConfigureServices配置缓存实现:An app configures the cache implementation using a RedisCache instance (AddStackExchangeRedisCache) in a non-Development environment in Startup.ConfigureServices:

services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost";
    options.InstanceName = "SampleInstance";
});

应用使用中RedisCache AddStackExchangeRedisCache的非开发环境中的实例()Startup.ConfigureServices配置缓存实现:An app configures the cache implementation using a RedisCache instance (AddStackExchangeRedisCache) in a non-Development environment in Startup.ConfigureServices:

services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost";
    options.InstanceName = "SampleInstance";
});

应用使用RedisCache实例(AddDistributedRedisCache)配置缓存实现:An app configures the cache implementation using a RedisCache instance (AddDistributedRedisCache):

services.AddDistributedRedisCache(options =>
{
    options.Configuration = "localhost";
    options.InstanceName = "SampleInstance";
});

若要在本地计算机上安装 Redis:To install Redis on your local machine:

使用分布式缓存Use the distributed cache

若要使用IDistributedCache接口,请从应用程序IDistributedCache中的任何构造函数请求的实例。To use the IDistributedCache interface, request an instance of IDistributedCache from any constructor in the app. 实例通过依赖关系注入(DI)来提供。The instance is provided by dependency injection (DI).

示例应用启动时, IDistributedCache将插入到中。 Startup.ConfigureWhen the sample app starts, IDistributedCache is injected into Startup.Configure. 使用IHostApplicationLifetime缓存当前时间(有关详细信息,请参阅泛型主机:IHostApplicationLifetime):The current time is cached using IHostApplicationLifetime (for more information, see Generic Host: IHostApplicationLifetime):

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, 
    IHostApplicationLifetime lifetime, IDistributedCache cache)
{
    lifetime.ApplicationStarted.Register(() =>
    {
        var currentTimeUTC = DateTime.UtcNow.ToString();
        byte[] encodedCurrentTimeUTC = Encoding.UTF8.GetBytes(currentTimeUTC);
        var options = new DistributedCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromSeconds(20));
        cache.Set("cachedTimeUTC", encodedCurrentTimeUTC, options);
    });

示例应用启动时, IDistributedCache将插入到中。 Startup.ConfigureWhen the sample app starts, IDistributedCache is injected into Startup.Configure. 使用IApplicationLifetime缓存当前时间(有关详细信息,请参阅Web 主机:IApplicationLifetime 接口):The current time is cached using IApplicationLifetime (for more information, see Web Host: IApplicationLifetime interface):

public void Configure(IApplicationBuilder app, IHostingEnvironment env, 
    IApplicationLifetime lifetime, IDistributedCache cache)
{
    lifetime.ApplicationStarted.Register(() =>
    {
        var currentTimeUTC = DateTime.UtcNow.ToString();
        byte[] encodedCurrentTimeUTC = Encoding.UTF8.GetBytes(currentTimeUTC);
        var options = new DistributedCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromSeconds(20));
        cache.Set("cachedTimeUTC", encodedCurrentTimeUTC, options);
    });

示例应用将注入IDistributedCache IndexModel到中供索引页使用。The sample app injects IDistributedCache into the IndexModel for use by the Index page.

每次加载索引页时,都会在中OnGetAsync检查缓存时间的缓存。Each time the Index page is loaded, the cache is checked for the cached time in OnGetAsync. 如果缓存的时间未过期,则会显示时间。If the cached time hasn't expired, the time is displayed. 如果自上次访问缓存时间之后经过了20秒(最后一次加载此页),则页面显示缓存的时间已过期If 20 seconds have elapsed since the last time the cached time was accessed (the last time this page was loaded), the page displays Cached Time Expired.

通过选择 "重置缓存时间" 按钮立即将缓存的时间更新为当前时间。Immediately update the cached time to the current time by selecting the Reset Cached Time button. 按钮触发OnPostResetCachedTime处理程序方法。The button triggers the OnPostResetCachedTime handler method.

public class IndexModel : PageModel
{
    private readonly IDistributedCache _cache;

    public IndexModel(IDistributedCache cache)
    {
        _cache = cache;
    }

    public string CachedTimeUTC { get; set; }

    public async Task OnGetAsync()
    {
        CachedTimeUTC = "Cached Time Expired";
        var encodedCachedTimeUTC = await _cache.GetAsync("cachedTimeUTC");

        if (encodedCachedTimeUTC != null)
        {
            CachedTimeUTC = Encoding.UTF8.GetString(encodedCachedTimeUTC);
        }
    }

    public async Task<IActionResult> OnPostResetCachedTime()
    {
        var currentTimeUTC = DateTime.UtcNow.ToString();
        byte[] encodedCurrentTimeUTC = Encoding.UTF8.GetBytes(currentTimeUTC);
        var options = new DistributedCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromSeconds(20));
        await _cache.SetAsync("cachedTimeUTC", encodedCurrentTimeUTC, options);

        return RedirectToPage();
    }
}
public class IndexModel : PageModel
{
    private readonly IDistributedCache _cache;

    public IndexModel(IDistributedCache cache)
    {
        _cache = cache;
    }

    public string CachedTimeUTC { get; set; }

    public async Task OnGetAsync()
    {
        CachedTimeUTC = "Cached Time Expired";
        var encodedCachedTimeUTC = await _cache.GetAsync("cachedTimeUTC");

        if (encodedCachedTimeUTC != null)
        {
            CachedTimeUTC = Encoding.UTF8.GetString(encodedCachedTimeUTC);
        }
    }

    public async Task<IActionResult> OnPostResetCachedTime()
    {
        var currentTimeUTC = DateTime.UtcNow.ToString();
        byte[] encodedCurrentTimeUTC = Encoding.UTF8.GetBytes(currentTimeUTC);
        var options = new DistributedCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromSeconds(20));
        await _cache.SetAsync("cachedTimeUTC", encodedCurrentTimeUTC, options);

        return RedirectToPage();
    }
}

备注

无需为IDistributedCache实例使用 Singleton 或 Scoped 生命周期(至少对内置实现来说是这样的)。There's no need to use a Singleton or Scoped lifetime for IDistributedCache instances (at least for the built-in implementations).

您还可以创建IDistributedCache一个实例,而不是使用 DI,而是在代码中创建一个实例,从而使代码更难以测试,并违反显式依赖项原则You can also create an IDistributedCache instance wherever you might need one instead of using DI, but creating an instance in code can make your code harder to test and violates the Explicit Dependencies Principle.

建议Recommendations

确定最适合你的IDistributedCache应用的实现时,请考虑以下事项:When deciding which implementation of IDistributedCache is best for your app, consider the following:

  • 现有基础结构Existing infrastructure
  • 性能要求Performance requirements
  • 成本Cost
  • 团队体验Team experience

缓存解决方案通常依赖于内存中的存储以快速检索缓存的数据,但是,内存是有限的资源,并且很昂贵。Caching solutions usually rely on in-memory storage to provide fast retrieval of cached data, but memory is a limited resource and costly to expand. 仅将常用数据存储在缓存中。Only store commonly used data in a cache.

通常,Redis 缓存提供比 SQL Server 缓存更高的吞吐量和更低的延迟。Generally, a Redis cache provides higher throughput and lower latency than a SQL Server cache. 但是,通常需要进行基准测试来确定缓存策略的性能特征。However, benchmarking is usually required to determine the performance characteristics of caching strategies.

当 SQL Server 用作分布式缓存后备存储时,对缓存使用同一数据库,并且应用的普通数据存储和检索会对这两种情况的性能产生负面影响。When SQL Server is used as a distributed cache backing store, use of the same database for the cache and the app's ordinary data storage and retrieval can negatively impact the performance of both. 建议使用分布式缓存后备存储的专用 SQL Server 实例。We recommend using a dedicated SQL Server instance for the distributed cache backing store.

其他资源Additional resources