.net core中使用缓存之MemoryCache(本机内存)
环境:.net core2.2nugt包依赖:1. Microsoft.Extensions.Caching.Abstractions2. Microsoft.Extensions.Caching.Memory**代码:CacheManager **using Microsoft.Extensions.Caching.Memory;using System;using System...
环境:.net core2.2
nugt包依赖:
- 1.Microsoft.Extensions.Caching.Abstractions
- 2.Microsoft.Extensions.Caching.Memory
参考:
.Net Core缓存组件(MemoryCache)源码解析
拥抱.NET Core系列:MemoryCache 缓存过期
一、根据时间过期的四种策略
首先说下:一般我们使用缓存都是根据时间设置过期策略的,常用的是以下四种过期策略:
1.1 永不过期:
永远不会过期
1.2 设置绝对过期时间点:
到期后就失效
1.3 设置过期滑动窗口:
只要在窗口期内访问,它的过期时间就一直向后顺延一个窗口长度
1.4 滑动窗口+绝对过期时间点:
只要在窗口期内访问,它的过期时间就一直向后顺延一个窗口长度,但最长不能超过绝对过期时间点
二、测试四种时间过期策略的代码
通过以下代码验证上述的四种时间过期策略。
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Threading.Tasks;
namespace ConsoleApp6
{
class Program
{
public static IMemoryCache _cache = new MemoryCache(new MemoryCacheOptions());
static async Task Main(string[] args)
{
//await Test1();
//await Test2();
//await Test3();
await Test4();
Console.WriteLine("ok");
Console.ReadLine();
}
/// <summary>
/// 4.验证滑动窗口+绝对过期时间(这里的绝对过期时间是缓存从开始到结束最长的时间,滑动窗口表示在滑动时间内只要没访问就立马过期)
/// </summary>
/// <returns></returns>
private async static Task Test4()
{
_cache.Set("xiaoming", "222", new MemoryCacheEntryOptions()
{
SlidingExpiration = TimeSpan.FromSeconds(1.5),
AbsoluteExpiration = DateTimeOffset.Now.AddMilliseconds(1000 * 3.5)
});
await Task.Delay(1000 * 1);
Console.WriteLine("1s:" + _cache.Get("xiaoming"));
await Task.Delay(1000 * 1);
Console.WriteLine("2s:" + _cache.Get("xiaoming"));
await Task.Delay(1000 * 1);
Console.WriteLine("3s:" + _cache.Get("xiaoming"));
await Task.Delay(1000 * 1);
Console.WriteLine("4s:" + _cache.Get("xiaoming"));
await Task.Delay(1000 * 1);
Console.WriteLine("5s:" + _cache.Get("xiaoming"));
}
/// <summary>
/// 3.验证滑动窗口过期
/// </summary>
/// <returns></returns>
private async static Task Test3()
{
_cache.Set("xiaoming", "222", new MemoryCacheEntryOptions()
{
SlidingExpiration = TimeSpan.FromSeconds(1.5)
});
await Task.Delay(1000 * 1);
Console.WriteLine("1s:" + _cache.Get("xiaoming"));
await Task.Delay(1000 * 1);
Console.WriteLine("2s:" + _cache.Get("xiaoming"));
await Task.Delay(1000 * 1);
Console.WriteLine("3s:" + _cache.Get("xiaoming"));
await Task.Delay(1000 * 1);
Console.WriteLine("4s:" + _cache.Get("xiaoming"));
await Task.Delay(1000 * 1);
Console.WriteLine("5s:" + _cache.Get("xiaoming"));
}
/// <summary>
/// 2.验证绝对过期时间
/// </summary>
/// <returns></returns>
private async static Task Test2()
{
_cache.Set("xiaoming", "222", TimeSpan.FromSeconds(3));
await Task.Delay(1000 * 1);
Console.WriteLine("1s:" + _cache.Get("xiaoming"));
await Task.Delay(1000 * 1);
Console.WriteLine("2s:" + _cache.Get("xiaoming"));
await Task.Delay(1000 * 1);
Console.WriteLine("3s:" + _cache.Get("xiaoming"));
await Task.Delay(1000 * 1);
Console.WriteLine("4s:" + _cache.Get("xiaoming"));
}
/// <summary>
/// 1.验证永不过期
/// </summary>
private async static Task Test1()
{
_cache.Set("xiaoming", "222");
await Task.Delay(1000 * 3);
Console.WriteLine(_cache.Get("xiaoming"));
}
}
}
三、封装的帮助类CacheManager
为了更方便的操作缓存我封装了下面的缓存帮助类,能直接按照上述几种策略进行缓存。
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
namespace server.Models
{
public class CacheManager
{
public static CacheManager Default = new CacheManager();
private IMemoryCache _cache = new MemoryCache(new MemoryCacheOptions());
/// <summary>
/// 取得缓存数据
/// </summary>
/// <typeparam name="T">类型值</typeparam>
/// <param name="key">关键字</param>
/// <returns></returns>
public T Get<T>(string key)
{
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentNullException(nameof(key));
T value;
_cache.TryGetValue<T>(key, out value);
return value;
}
/// <summary>
/// 设置缓存(永不过期)
/// </summary>
/// <param name="key">关键字</param>
/// <param name="value">缓存值</param>
public void Set_NotExpire<T>(string key, T value)
{
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentNullException(nameof(key));
T v;
if (_cache.TryGetValue(key, out v))
_cache.Remove(key);
_cache.Set(key, value);
}
/// <summary>
/// 设置缓存(滑动过期:超过一段时间不访问就会过期,一直访问就一直不过期)
/// </summary>
/// <param name="key">关键字</param>
/// <param name="value">缓存值</param>
public void Set_SlidingExpire<T>(string key, T value, TimeSpan span)
{
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentNullException(nameof(key));
T v;
if (_cache.TryGetValue(key, out v))
_cache.Remove(key);
_cache.Set(key, value, new MemoryCacheEntryOptions()
{
SlidingExpiration = span
});
}
/// <summary>
/// 设置缓存(绝对时间过期:从缓存开始持续指定的时间段后就过期,无论有没有持续的访问)
/// </summary>
/// <param name="key">关键字</param>
/// <param name="value">缓存值</param>
public void Set_AbsoluteExpire<T>(string key, T value, TimeSpan span)
{
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentNullException(nameof(key));
T v;
if (_cache.TryGetValue(key, out v))
_cache.Remove(key);
_cache.Set(key, value, span);
}
/// <summary>
/// 设置缓存(绝对时间过期+滑动过期:比如滑动过期设置半小时,绝对过期时间设置2个小时,那么缓存开始后只要半小时内没有访问就会立马过期,如果半小时内有访问就会向后顺延半小时,但最多只能缓存2个小时)
/// </summary>
/// <param name="key">关键字</param>
/// <param name="value">缓存值</param>
public void Set_SlidingAndAbsoluteExpire<T>(string key, T value, TimeSpan slidingSpan, TimeSpan absoluteSpan)
{
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentNullException(nameof(key));
T v;
if (_cache.TryGetValue(key, out v))
_cache.Remove(key);
_cache.Set(key, value, new MemoryCacheEntryOptions()
{
SlidingExpiration = slidingSpan,
AbsoluteExpiration = DateTimeOffset.Now.AddMilliseconds(absoluteSpan.TotalMilliseconds)
});
}
/// <summary>
/// 移除缓存
/// </summary>
/// <param name="key">关键字</param>
public void Remove(string key)
{
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentNullException(nameof(key));
_cache.Remove(key);
}
/// <summary>
/// 释放
/// </summary>
public void Dispose()
{
if (_cache != null)
_cache.Dispose();
GC.SuppressFinalize(this);
}
}
}
四、关于MemoryCacheOptions
这个类是在创建MemoryCache
实例的时候的一个参数new MemoryCache(new MemoryCacheOptions())
,那么它里面有几个属性还是要说明以下:
4.1 ExpirationScanFrequency:
表示定期扫描并移除过期缓存项的频率。默认的是1分钟。从字面上看应该是有一个定时器每个1分钟就会扫描,然而不是这样的,只有在你访问其中的缓存项的时候才检查上次的扫描时间,如果超过了1分钟就会重新扫描,由此可见它的最快扫描间隔是1分钟,如果你一直没访问缓存,那么它就一直不扫描。
4.2 SizeLimit
和Size
SizeLimit:
表示缓存中允许的总内容大小。
Size:
表示缓存项的大小。
注意,每个缓存项的大小不是自动计算出来的,而是你添加缓存项的时候指定的,如:_cache.Set<int>("name", 20, new MemoryCacheEntryOptions() { Size = 4 });
4.3 CompactionPercentage:
表示当缓存大小超出的时候压缩的比例,默认值0.05。
具体来说,你先要给MemoryCache设置一个SizeLimit,比如说100吧(默认是null,也就是不存在大小限制),这样当你每次添加缓存项的时候就会检查是否超出了大小(缓存项的Size之和>=SizeLimit
),然后CompactionPercentage使用默认的0.05,也就是每次超出100后就会把MemoryCache压缩到95以下(100*(1-0.05)
)。压缩的时候先检查有没有过期的,然后再按照缓存项的优先级(每个缓存项可以设置优先级,代码为:_cache.Set<int>("name", 20, new MemoryCacheEntryOptions() { Size = 4, Priority = CacheItemPriority.Low });
,默认优先级是Normal,总共四级:Low、Normal、High、NeverRemove)为顺序从低到高移除,直至容量降到95以下。
4.4 注意
一般我们创建缓存容器的时候代码写法是:new CacheManager(new MemoryCacheOptions())
,此时并没有设置SizeLimit,所以就不存在压缩问题了。
五、关于MemoryCacheEntryOptions
5.1 时间过期策略相关的属性
AbsoluteExpiration、AbsoluteExpirationRelativeToNow、SlidingExpiration和ExpirationTokens这几个属性属于时间过期策略的,这里不再说明其作用。
5.2 Size和Priority
Size表示的是这个缓存项占的大小,在计算压缩的时候会用到。
Priority表示缓存项的权重(也叫做优先级),当缓存容器大小不够时就会从低到高逐个移除,直到容量满足压缩比例要求时为止。
5.3 ExpirationTokens和PostEvictionCallbacks
看《六、拓展内容》
六、拓展内容
6.1 自定义过期策略
上面说了根据时间过期的4中策略对于一般来说足够使用了,但是如果你不想根据时间决定是否过期的话,微软提供了一个“自定义过期策略”,就是在需要过期的时候你自己通知缓存容器去触发过期。
直接看一个应用的代码:
class Program
{
public static IMemoryCache _cache = new MemoryCache(new MemoryCacheOptions());
static async Task Main(string[] args)
{
var cts = new CancellationTokenSource();
var token = new CancellationChangeToken(cts.Token);
var entry = _cache.CreateEntry("key");
entry.Value = "小明";
entry.ExpirationTokens.Add(token);
entry.Dispose();//将entry放到缓存到_cache容器中
Console.WriteLine("key=" + _cache.Get<string>("key"));
cts.Cancel();//通知容器,关联的缓存项已过期
Console.WriteLine("key=" + _cache.Get<string>("key"));
Console.ReadLine();
}
}
观察输出结果:
从上面可以看出,我们手动让缓存项“key”过期了,但是要注意用于缓存过期的token只能使用一次(参考:.netcore入门17:IChangeToken和ChangeToken用法简介)。
那么下面有个需求:我想监测配置文件的内容,并将它缓存到程序中怎么实现呢?(注意:不使用缓存完全可以,这里是为了演示自定义过期策略)
看如下代码:
class Program
{
public static IMemoryCache _cache = new MemoryCache(new MemoryCacheOptions());
static async Task Main(string[] args)
{
string _getConfig()
{
string dirPath = "c:\\temp";
string fileName = "demo.txt";
var fileProvider = new PhysicalFileProvider(dirPath);
string str = _cache.GetOrCreate<string>("text", entry =>
{
entry.AddExpirationToken(fileProvider.Watch(fileName));
return File.ReadAllText(Path.Combine(dirPath, fileName));
});
return str;
}
while (true)
{
Console.WriteLine("str=" + _getConfig());
Thread.Sleep(2000);
}
}
}
执行效果如下:
我们可以看到:每当文件内容发生变化时就会触发fileProvider.Watch(fileName)
返回的token发生变化,从而引起过期,当下次访问_getConfig()时又会有一个新的token被创建用于监测文件改变并且将读取到的文件内容缓存到了系统中。
6.2 缓存过期回调
很多时候我们希望缓存过期之后能做一些事情,比如重新写入缓存等等,MemoryCache提供了这样的机制。
直接看示例代码:
class Program
{
public static IMemoryCache _cache = new MemoryCache(new MemoryCacheOptions());
static async Task Main(string[] args)
{
var entry = _cache.CreateEntry("key");
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(1);
entry.RegisterPostEvictionCallback((key, value, reason, state) =>
{
Console.WriteLine("key=" + key);
Console.WriteLine("value=" + value);
Console.WriteLine("reason=" + reason);
Console.WriteLine("state=" + state);
}, "statestr");
entry.Dispose();//将entry缓存到_cache中
Thread.Sleep(1500);
_cache.Get("key");
Console.ReadLine();
}
}
执行效果:
从上面可以看出,缓存失效的话会触发回调。但是要注意:MemoryCache中没有定时器,只有我们访问缓存时才可能会触发过期检查,进而执行我们注册的回调!
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)