一、介绍

  由于CPU从内存中读取数据的速度比从磁盘读取快几个数量级,并且存在内存中,减小了数据库访问的压力,所以缓存几乎每个项目都会用到。一般常用的有MemoryCache、Redis。MemoryCache将存入的对象都作为Object对象存储,Redis分为五种类型存储,在微软提供的缓存组件中也包含Redis和SQL Server缓存,具体下次文章详细讲解。微软缓存组件源码在https://github.com/aspnet/Caching

二、简单使用

  组件的使用很简单,只需要添加组件,在需要使用的时候,注入缓存组件。下面为一个简单的例子,新建Api项目,修改为下面代码

public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddMemoryCache();//添加MemoryCache组件
}

内存缓存过期时间有3种,绝对过期时间、相对现在的过期时间和滑动过期时间。

public class ValuesController : Controller
{
private readonly IMemoryCache _memoryCache;//注入MemoryCache
public ValuesController(IMemoryCache memoryCache)
{
this._memoryCache = memoryCache;
} [HttpGet]
public async Task<IEnumerable<string>> Get()
{
//1、绝对过期时间
_memoryCache.Set("key1", "", new DateTimeOffset(new DateTime(, , )));
//2、相对于现在的过期时间 缓存相对于现在的时间后两秒失效
_memoryCache.Set("key2", "", new TimeSpan(, , ));
//3、滑动过期时间 缓存在两秒内没有被命中,则失效,命中后缓存时间又变为2秒
_memoryCache.Set("key3", "", new MemoryCacheEntryOptions()
{
SlidingExpiration = new TimeSpan(, , ),
Priority = CacheItemPriority.NeverRemove
});
      
Thread.Sleep();
Console.WriteLine($@"value1:{_memoryCache.Get("key1")} value2:{_memoryCache.Get("key2")} value3:{_memoryCache.Get("key3")}");
//因为在1.5秒被命中 所以key3的值,缓存时间重新变为2秒
Thread.Sleep();
Console.WriteLine($@"value1:{_memoryCache.Get("key1")} value2:{_memoryCache.Get("key2")} value3:{_memoryCache.Get("key3")}");
       //创建一个相对现在时间2秒的缓存ke4,并注册缓存失效时的回调函数(注意此回调函数并不是真的当缓存失效立马调用,看下面源码详解)
_memoryCache.GetOrCreate("key4", cacheEntry =>
{
cacheEntry.AbsoluteExpirationRelativeToNow = new TimeSpan(, , );
cacheEntry.RegisterPostEvictionCallback(Test);//注册缓存过期时的回调函数
return cacheEntry.Value = "";
});
Thread.Sleep();
_memoryCache.Remove("key4"); return new string[] { "value1", "value2" };
}
//当缓存过期时触发此函数
public void Test(object key, object value, EvictionReason reason, object state)
{
Console.WriteLine($@"缓存{key}已经移除");
}
}

输出结果为:

value1:1 value2:2 value3:3
value1:1 value2:   value3:3  // 注意此处的value2的值为空
缓存key4已经移除

  关于绝对过期时间、相对现在的过期时间、滑动过期时间就不必多说了,上面注释已经写了。需要注意的是上面key4,设置了2秒后缓存过期,触发回调函数,但是如果代码改为下面形式(注释掉移除"key4”的代码),那么会一直不触发回调函数。

 _memoryCache.GetOrCreate("key4", cacheEntry =>
{
cacheEntry.AbsoluteExpirationRelativeToNow = new TimeSpan(, , );
cacheEntry.RegisterPostEvictionCallback(Test);//注册缓存过期时的回调函数
return cacheEntry.Value = "";
});
Thread.Sleep();
//_memoryCache.Remove("key4");

比如上面已经sleep 3秒了,key4的缓存时间为2秒,key4缓存的已经过期了,如果删除那一行代码,按理应该在第二秒是触发回调函数,但是测试不会。只有对key4操作的时候才会触发回调函数(后面源码详细分析)。

在MemoryCache类中有CreateEntry,这个方法比较特殊

var entry = _memoryCache.CreateEntry("key5");
entry.Value = "";
var a = _memoryCache.Get("key5");//这里输出为null,并不是我们想象的值

正确的使用方法为:需要entry.Dispose()或者使用using。(具体原因下面源码分析)

var entry = _memoryCache.CreateEntry("key5");
entry.Value = "";
entry.Dispose();
var a = _memoryCache.Get("key5");//输出为5

三、MemoryCache源码解析

  首先看我们在Startup中声明添加的内存缓存的对应的源码。

   services.AddMemoryCache();//添加MemoryCache组件
public static IServiceCollection AddMemoryCache(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
} services.AddOptions();
    //实际上就是注入一个单例的MemoryCache
services.TryAdd(ServiceDescriptor.Singleton<IMemoryCache, MemoryCache>());
return services;
}

MemoryCacheOptions类:主要是配置一些参数

  ExpirationScanFrequency:此字段表明隔一段时间扫描缓存,移除过期缓存,默认频率为一分钟。

  SizeLimit:设置缓存的大小。

  CompactionPercentage:压缩比例 默认为百分之五。

举例:

 public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddMemoryCache(x =>
{
x.ExpirationScanFrequency = new TimeSpan(, , );//设置扫描频率为3分钟
x.SizeLimit = * * ; //大小为500M
});
}

MemoryCache类:

  ConcurrentDictionary<object, CacheEntry> _entries:一个多线程安全的字典类型,其实缓存的本质就是这个字典,将所有缓存都放入这个字典中,然后通过字典的key(字典的key其实和缓存实体CacheEntry的key值一样)获取CacheEntry实体(CacheEntry实体包含key和value,也就是我们代码中设置的key和value)。

  _expirationScanFrequency:Span类型,表示扫描过期缓存的频率时间,此字段的值来自MemoryCacheOptions类的ExpirationScanFrequency。需要注意的是:假如设置为一分钟扫描一次缓存集合,这个扫描不是主动扫描的,只有当对缓存集合操作时才会扫描。比如增删改查缓存集合的时候,会判断上一次扫描的时间离现在过去多久了,如果超过扫描设置的时间,才会扫描。源代码如下:

    private void StartScanForExpiredItems()//扫描函数。在添加、查找、删除、修改缓存的时候,会调用此方法扫描过滤过期缓存,并不是主动隔一段时间执行
{
var now = _clock.UtcNow;
if (_expirationScanFrequency < now - _lastExpirationScan)//判断现在的时间和最后一次扫描时间,是不是大于设置的时间段
{
_lastExpirationScan = now;
Task.Factory.StartNew(state => ScanForExpiredItems((MemoryCache)state), this, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
}
}
private static void ScanForExpiredItems(MemoryCache cache)
{
var now = cache._clock.UtcNow;
foreach (var entry in cache._entries.Values)//然后遍历字典集合,移除过期缓存
{
if (entry.CheckExpired(now))
{
cache.RemoveEntry(entry);
}
}
}

  Action<CacheEntry> _setEntry:此委托就是将缓存实体放入上面的字典_entries中,并将字典的key值设置为缓存实体的key。

  Action<CacheEntry> _entryExpirationNotification:此委托是移除字典中缓存。

CacheEntry类:这个类主要就是存储缓存实体,它包含的key、value字段就是我们程序中传入的key、value。对于每一个缓存实体都有其过期时间,所以它还包含每种过期类型。它还包含2个重要的委托。

  Action<CacheEntry> _notifyCacheOfExpiration:此委托会在构造函数中被MemoryCache类中的SetEntry(CacheEntry entry)方法赋值。方法的实现将CacheEntry实体放入MemoryCache类字典中,也就是放入缓存中。

  Action<CacheEntry> _notifyCacheEntryDisposed:此委托会在构造函数中被MemoryCache类中的EntryExpired(CacheEntry entry)方法赋值,方法的实现是移除缓存。

  还包含CacheItemPriority类型的Priority,表示当内存不足时,会移除部分内存,会按照由低到高移除。所以当设置的某个缓存实体使用次数较多时,可以设置为High或NeverRemove。默认设置为Normal。

 public enum CacheItemPriority
{
Low,
Normal,
High,
NeverRemove,
}

  需要注意的是CacheEntry有一个重要的方法Dispose(),因为它继承IDisposable,在Dispost方法中调用了_notifyCacheEntryDisposed委托。

public void Dispose()
{
if (!_added)
{
_added = true;
_scope.Dispose();
_notifyCacheEntryDisposed(this);//在此调用委托,而此委托是被MemoryCache类中的SetEntry赋值。目的是将CacheEntry实体放入MemoryCache类的字典中,也就是放入缓存中。
PropagateOptions(CacheEntryHelper.Current);
}
}

这就解释了为我们上面举的例子需要Dispose(),它只有调用Dispose()才能将缓存实体放入字典中。

var entry = _memoryCache.CreateEntry("key5");
entry.Value = "";
entry.Dispose();
var a = _memoryCache.Get("key5");//输出为5

.Net Core缓存组件(MemoryCache)源码解析的更多相关文章

  1. .Net Core缓存组件(Redis)源码解析

    上一篇文章已经介绍了MemoryCache,MemoryCache存储的数据类型是Object,也说了Redis支持五中数据类型的存储,但是微软的Redis缓存组件只实现了Hash类型的存储.在分析源 ...

  2. Django 之 admin组件使用&源码解析

    admin组件使用 Django 提供了基于 web 的管理工具. Django 自动管理工具是 django.contrib 的一部分.可以在项目的 settings.py 中的 INSTALLED ...

  3. .Net Core中的配置文件源码解析

    一.配置简述 之前在.Net Framework平台开发时,一般配置文件都是xml格式的Web.config,而需要配置其他格式的文件就需要自己去读取内容,加载配置了..而Net Core支持从命令行 ...

  4. .Net Core缓存组件(MemoryCache)【缓存篇(二)】

    一.前言 .Net Core缓存源码 1.上篇.NET Core ResponseCache[缓存篇(一)]中我们提到了使用客户端缓存.和服务端缓存.本文我们介绍MemoryCache缓存组件,说到服 ...

  5. Netty高性能组件——FastThreadLocal源码解析(细微处见真章)

    1. 前言 netty自行封装了FastThreadLocal以替换jdk提供的ThreadLocal,结合封装的FastThreadLocalThread,在多线程环境下的变量提高了ThreadLo ...

  6. 26. SpringBoot 初识缓存及 SimpleCacheConfiguration源码解析

    1.引入一下starter: web.cache.Mybatis.MySQL @MapperScan("com.everjiankang.cache.dao") @SpringBo ...

  7. Spark Core 1.3.1源码解析及个人总结

    本篇源码基于赵星对Spark 1.3.1解析进行整理.话说,我不认为我这下文源码的排版很好,不能适应的还是看总结吧. 虽然1.3.1有点老了,但对于standalone模式下的Master.Worke ...

  8. Java并发包源码学习系列:同步组件CountDownLatch源码解析

    目录 CountDownLatch概述 使用案例与基本思路 类图与基本结构 void await() boolean await(long timeout, TimeUnit unit) void c ...

  9. Java并发包源码学习系列:同步组件CyclicBarrier源码解析

    目录 CyclicBarrier概述 案例学习 类图结构及重要字段 内部类Generation及相关方法 void reset() void breakBarrier() void nextGener ...

随机推荐

  1. redis在游戏服务器中的使用初探(四) redis应用

    文章系列先介绍环境搭建 介绍redis操作和代码编写运行  这是典型的实战工程过程.那么我们为何要使用redis而不是常规的数据库比如 mysql呢? 因为KV内存数据库最大的优势所有数据全部存储在内 ...

  2. JavaSE 初学进度条JProgressBar

    预备知识 创建进度条类后将其直接加入JFrame看看效果 public class JProgressBarDemo2 { public static void main(String args[]) ...

  3. oracle 为什么没有权限的用户也可以用sysdba登录

    我随便创建了一个用户,create user lisi identified by lisi; 当我用sqlplus登录的时候: cmd ->  sqlplus lisi/lisi  进不去   ...

  4. python中for循环的三种遍历方式

    #!/usr/bin/env python# -*- coding: utf-8 -*-if __name__ == '__main__': list = ['A', 'B', 'C', 'D'] # ...

  5. AOP打印请求日志,打印返回值

    @Aspect // 申明是个spring管理的bean @Component @Slf4j public class LogAspectServiceApi { private JSONObject ...

  6. Visual Studio2013 配置opencv3.3.0 x64系统

    注:小白一个,第一次写博客,可能会有一些理解上的错误,只此记录自己测试成功的坎坷之路,已备以后查看,同时给有需要之人. 我是win10 64 位,之前安装了visual studio 2013, 现在 ...

  7. 05 IO和管道

    目录   三种I/O设备 把I/O重定向至文件 使用管道   知识铺垫     1)查看fd-文件描述符 (L)   ll /proc/$$/fd   在Linux中,系统打开文件时会随机分配一个编号 ...

  8. Python开发——1.基础知识

    一.开发 开发语言分为高级语言和低级语言 高级语言:Python.Java.PHP.C++.C#.GO.Ruby等:低级语言:C.汇编语言. 高级语言对应的是字节码,是将代码编译成字节码,然后交给机器 ...

  9. windows10环境下安装Tensorflow

    1.什么是tensorflow TensorFlow是谷歌基于DistBelief进行研发的第二代人工智能学习系统,其命名来源于本身的运行原理.Tensor(张量)意味着N维数组,Flow(流)意味着 ...

  10. Spring 使用xml配置aop

    1.xml文件需要引入aop命名空间 2.xml内容: <?xml version="1.0" encoding="UTF-8"?> <bea ...