1. 简介

Caching通过减少生成内容所需的工作,显著提高应用的性能和可伸缩性。 Caching适用于不经常更改且生成成本高的数据。

ASP.NET Core支持多个不同的缓存。 最简单的缓存基于 IMemoryCacheIMemoryCache 属于内存缓存,存在于应用内部。如果使用内存缓存,后续请求需要都请求到这一个应用上,才能有效利用这个缓存,否则会有缓存一致性的问题。如果没办法保证这一点,则可以使用分布式缓存来解决。对于某些应用,分布式缓存可以支持比内存中缓存更高的横向扩展。使用分布式缓存可将缓存内存卸载到外部进程。

内存缓存可以存储任何对象,分布式缓存一般只能存储byte[]。二者的储存方式一般都是键值对。

2. 内存缓存

2.1 使用 IMemoryCache

先介绍下过期时间:

  • 绝对过期时间:假如设置为10s,则10s之后此缓存数据就会被清除。
  • 滑动过期时间:假如设置为3s,如果3s之内有请求过来,则缓存失效日期为当前时间再加上3s。如果在有效时间内,没请求过来,则失效。
  • 二者都进行设置:以最短的有效时间为主。假如绝对过期时间为10s,滑动过期时间为3s。如果3s没请求过来,则失效。如果每3s内就会有一个请求过来,则超过10s也会失效。

对于大多数应用IMemoryCache默认已启用,可以直接使用(否则可以手动调用AddMemoryCache)。

public class HomeController : Controller
{
    private IMemoryCache _cache;

    public HomeController(IMemoryCache memoryCache)
    {
        _cache = memoryCache;
    }
}

2.2 获取缓存数据 GetOrCreateGetOrCreateAsyncGet

DateTime cacheEntry;
string key="_blablabla";
//获取cache
if (!_cache.TryGetValue(key, out cacheEntry))
{
    //没获取到,则分配一个值
    cacheEntry = DateTime.Now;
    //设置cache策略
    var cacheEntryOptions = new MemoryCacheEntryOptions()
        //设置3s的滑动过期时间(3s内如果有请求到,则自动再延长3s)
        .SetSlidingExpiration(TimeSpan.FromSeconds(3));
    //保存到cache
    _cache.Set(key, cacheEntry, cacheEntryOptions);
    //或者直接设置绝对过期时间
    _cache.Set(key, cacheEntry, TimeSpan.FromDays(1));
}

获取时如果不存在则同时创建:

public async Task<IActionResult> CacheGetOrCreateAsynchronous()
{
    var cacheEntry = await
        _cache.GetOrCreateAsync(key, entry =>
        {
            entry.SlidingExpiration = TimeSpan.FromSeconds(3);
            //也可以设置绝对过期时间(即使3s内一直有请求过来,超过10s也会过期。)
            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(10);
            return Task.FromResult(DateTime.Now);
        });

    return View("Cache", cacheEntry);
}

2.3 移除缓存

_cache.Remove(key)

2.4 缓存配置项 MemoryCacheEntryOptions

可以配置以下几个功能:

  • 设置过期时间:这个前面已经讲过了
  • 缓存优先级:共有四个优先级,低中高+永不移除(CacheItemPriority.NeverRemove)。当内存紧张上,系统会根据策略移除低中高优先级的缓存,不会移除NeverRemove优先级的缓存。
  • 缓存回调:可通过PostEvictionDelegate设置一个回调,当缓存失效时,此回调会在另外一个线程上运行。
public IActionResult CreateCallbackEntry()
{
    var cacheEntryOptions = new MemoryCacheEntryOptions()
        //永不移除
        .SetPriority(CacheItemPriority.NeverRemove)
        //设置回调
        .RegisterPostEvictionCallback(callback: EvictionCallback, state: this);

    _cache.Set(_key, DateTime.Now, cacheEntryOptions);

    return RedirectToAction("GetCallbackEntry");
}
private static void EvictionCallback(object key, object value,
    EvictionReason reason, object state)
{
    var message = $"Entry was evicted. Reason: {reason}.";
    ((HomeController)state)._cache.Set(_key, message);
}

2.5 设置缓存大小 SetSizeSizeLimit

这里的缓存大小并不是通常意义上的多少KB、MB。而是一个没有单位的相对值,因为框架并不会去计算每个缓存项的大小。

如果通过SizeLimit设置了总体缓存的大小,则必须通过SetSize设置每一个缓存项的大小。

考虑以下的配置代码:

public class MyMemoryCache 
{
    public MemoryCache Cache { get; private set; }
    public MyMemoryCache()
    {
        Cache = new MemoryCache(new MemoryCacheOptions
        {
            //设置总体缓存大小为600,无具体单位
            SizeLimit = 600
        });
    }
}

然后,注入到容器:

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddSingleton<MyMemoryCache>();
}

最后,设置每一个缓存项的大小:

var cacheEntryOptions = new MemoryCacheEntryOptions()
    //设置此项大小为1
    .SetSize(1)
    .SetSlidingExpiration(TimeSpan.FromSeconds(3));

_cache.Set(MyKey, cacheEntry, cacheEntryOptions);

如果每一个缓存项的大小都设置为1,则总体可以容纳600项,如果每个都是2,则可以容纳300项。

2.6 缓存依赖

缓存过期时,它的依赖项也会过期

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);
}

2.7 其它说明

缓存过期不会在后台自动发生,当调用Get、Set、Remove等方法是才会触发一次扫描过期缓存的操作。但是CancellationTokenSource过期时也会触发这个操作。

public IActionResult CacheAutoExpiringTryGetValueSet()
{
    DateTime cacheEntry;

    if (!_cache.TryGetValue(CacheKeys.Entry, out cacheEntry))
    {
        cacheEntry = DateTime.Now;

        var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));

        var cacheEntryOptions = new MemoryCacheEntryOptions()
            .AddExpirationToken(new CancellationChangeToken(cts.Token));

        _cache.Set(CacheKeys.Entry, cacheEntry, cacheEntryOptions);
    }

    return View("Cache", cacheEntry);
}

3. 分布式缓存

这个就比较多了,有基于SqlServer的、有基于Redis的、有基于NCache的。

下节将会以Redis为例进行讲解。

https://docs.microsoft.com/zh-cn/aspnet/core/performance/caching/distributed?view=aspnetcore-6.0


引用:https://docs.microsoft.com/zh-cn/aspnet/core/performance/caching/response?view=aspnetcore-6.0

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐