14. 分布式缓存
14.1 什么是缓存
缓存可以减少生成内容所需的工作,从而显著提高应用程序的性能和可伸缩性。 缓存适用于不经常更改的数据,因为生成成本很高。 通过缓存,可比从数据源返回数据的副本速度快得多。 应该对应用进行编写和测试,使其不要永远依赖于缓存的数据。
14.2 缓存类型
- 内存缓存:顾名思义,就是缓存在应用部署所在服务器的内存中
- 分布式缓存:分布式缓存是由多个应用服务器共享的缓存
- 响应缓存:缓存服务器端
Not Modified
的数据
14.3 内存缓存使用
内存缓存是最常用的缓存方式,具有存取快,效率高特点。
内存缓存通过注入 IMemoryCache
方式注入即可。
在 Furion
框架中,内存缓存服务已经默认注册,无需手动注册。
14.3.1 基本使用
如,缓存当前时间:
using Furion.DynamicApiController;
using Microsoft.Extensions.Caching.Memory;
using System;
namespace Furion.Application
{
public class CacheServices : IDynamicApiController
{
private const string _timeCacheKey = "cache_time";
private readonly IMemoryCache _memoryCache;
public CacheServices(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
[ApiDescriptionSettings(KeepName = true)]
public DateTimeOffset GetOrCreate()
{
return _memoryCache.GetOrCreate(_timeCacheKey, entry =>
{
return DateTimeOffset.UtcNow;
});
}
}
}
14.3.2 设置缓存选项
内存缓存支持设置缓存时间、缓存大小、及绝对缓存过期时间等
_memoryCache.GetOrCreate(_timeCacheKey, entry =>
{
entry.SlidingExpiration = TimeSpan.FromSeconds(3); // 滑动缓存时间
return DateTimeOffset.UtcNow;
});
await _memoryCache.GetOrCreateAsync(_timeCacheKey, async entry =>
{
// 这里可以使用异步~~
});
具有可调过期的缓存项集存在过时的风险。 如果访问的时间比滑动过期时间间隔更频繁,则该项将永不过期。
将弹性过期与绝对过期组合在一起,以保证项目在其绝对过期时间通过后过期。 绝对过期会将项的上限设置为可缓存项的时间,同时仍允许项在可调整过期时间间隔内未请求时提前过期。
如果同时指定了绝对过期和可调过期时间,则过期时间以逻辑方式运算。 如果滑动过期时间间隔 或 绝对过期时间通过,则从缓存中逐出该项。
如:
_memoryCache.GetOrCreate(_timeCacheKey, entry =>
{
entry.SetSlidingExpiration(TimeSpan.FromSeconds(3));
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(20);
return DateTime.Now;
});
前面的代码保证数据的缓存时间不超过绝对时间。
14.3.3 手动设置缓存选项
除了上面的 Func<MemoryCacheEntryOptions, object>
方式设置缓存选项,我们可以手动创建并设置,如:
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromSeconds(3));
_memoryCache.Set(_timeCacheKey, DateTimeOffset.UtcNow, cacheEntryOptions);
14.3.4 缓存依赖关系
下面的示例演示如何在依赖条目过期后使缓存条目过期。 CancellationChangeToken
添加到缓存的项。 当 Cancel
在上调用时 CancellationTokenSource
,将逐出两个缓存项。
public IActionResult CreateDependentEntries()
{
var cts = new CancellationTokenSource();
_cache.Set(CacheKeys.DependentCTS, cts);
using (var entry = _cache.CreateEntry(CacheKeys.Parent))
{
// expire this entry if the dependant entry expires.
entry.Value = DateTime.Now;
entry.RegisterPostEvictionCallback(DependentEvictionCallback, this);
_cache.Set(CacheKeys.Child,
DateTime.Now,
new CancellationChangeToken(cts.Token));
}
return RedirectToAction("GetDependentEntries");
}
public IActionResult GetDependentEntries()
{
return View("Dependent", new DependentViewModel
{
ParentCachedTime = _cache.Get<DateTime?>(CacheKeys.Parent),
ChildCachedTime = _cache.Get<DateTime?>(CacheKeys.Child),
Message = _cache.Get<string>(CacheKeys.DependentMessage)
});
}
public IActionResult RemoveChildEntry()
{
_cache.Get<CancellationTokenSource>(CacheKeys.DependentCTS).Cancel();
return RedirectToAction("GetDependentEntries");
}
private static void DependentEvictionCallback(object key, object value,
EvictionReason reason, object state)
{
var message = $"Parent entry was evicted. Reason: {reason}.";
((HomeController)state)._cache.Set(CacheKeys.DependentMessage, message);
}
使用 CancellationTokenSource
允许将多个缓存条目作为一个组逐出。 using
在上面的代码中,在块中创建的缓存条目 using
将继承触发器和过期设置。
想了解更多 内存中的缓存
知识可查阅 ASP.NET Core - 内存缓存 章节。
14.4 分布式缓存
分布式缓存是由多个应用服务器共享的缓存,通常作为外部服务在访问它的应用服务器上维护。 分布式缓存可以提高 ASP.NET Core
应用程序的性能和可伸缩性,尤其是在应用程序由云服务或服务器场托管时。
与其他缓存方案相比,分布式缓存具有多项优势,其中缓存的数据存储在单个应用服务器上。
当分布式缓存数据时,数据将:
- (一致性) 跨多个 服务器的请求
- 存活在服务器重启和应用部署之间
- 不使用本地内存
分布式缓存配置是特定于实现的。 本文介绍如何配置 SQL Server
和 Redis
分布式缓存。 第三方实现也可用,例如 GitHub 上的 NCache (NCache) 。
无论选择哪种实现,应用都会使用接口与缓存交互 IDistributedCache
。
14.4.1 使用条件
- 若要使用
SQL Server
分布式缓存,则添加Microsoft.Extensions.Caching.SqlServer
包 - 若要使用
Redis
分布式缓存,则添加Microsoft.Extensions.Caching.StackExchangeRedis
包 - 若要使用
NCache
分布式缓存,则添加NCache.Microsoft.Extensions.Caching.OpenSource
包
14.4.2 IDistributedCache
IDistributedCache
接口提供以下方法来处理分布式缓存实现中的项:
Get/GetAsync
:接受字符串键,并检索缓存项作为byte[]
数组(如果在缓存中找到)Set/SetAsync
:使用字符串键将项 (作为byte[]
数组) 添加到缓存中Refresh/RefreshAsync
:根据项的键刷新缓存中的项,重置其滑动过期超时(如果有)Remove/RemoveAsync
:根据缓存项的字符串键删除缓存项
14.4.3 分布式内存缓存
分布式内存缓存(AddDistributedMemoryCache
)是一个框架提供的实现 IDistributedCache
,它将项存储在内存中 。 分布式内存缓存不是实际的分布式缓存,缓存项由应用程序实例存储在运行应用程序的服务器上。
分布式内存缓存优点:
- 用于开发和测试方案。
- 在生产环境中使用单一服务器并且内存消耗不是问题。 实现分布式内存缓存会抽象化缓存的数据存储。 如果需要多个节点或容错,可以在将来实现真正的分布式缓存解决方案。
在 Furion
框架中,分布式内存缓存服务已经默认注册,无需手动调用 services.AddDistributedMemoryCache();
注册。
14.4.4 分布式 SQL Server 缓存
分布式 SQL Server
缓存实现 (AddDistributedSqlServerCache
) 允许分布式缓存使用 SQL Server
数据库作为其后备存储。
若要在 SQL Server
实例中创建 SQL Server
缓存的项表,可以使用 sql-cache
工具。 该工具将创建一个表,其中包含指定的名称和架构。
通过运行命令 sql-cache create
创建一个表,提供 SQL Server
实例 (Data Source) 、数据库 (Initial Catalog) 、架构 (例如) dbo 和表名称。例 如 TestCache
:
dotnet sql-cache create "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=DistCache;Integrated Security=True;" dbo TestCache
创建成功后,在 Startup.cs
中注册即可:
services.AddDistributedSqlServerCache(options =>
{
options.ConnectionString =
_config["DistCache_ConnectionString"];
options.SchemaName = "dbo";
options.TableName = "TestCache";
});