首页 > 系统相关 >ASP.NET Core :缓存系列(四):内存缓存 MemoryCache

ASP.NET Core :缓存系列(四):内存缓存 MemoryCache

时间:2022-08-18 17:11:52浏览次数:73  
标签:Core 缓存 memoryCache 过期 typeResult ASP entry cacheKey

System.Runtime.Caching/MemoryCache
ICacheEntry 接口中的属性:具体设置过期时间 可以参考:微软文档ICacheEntry 接口

缓存基本使用 (一) 绝对过期

AbsoluteExpirationRelativeToNow 绝对过期
在实际的使用中肯定是要使用 Id 之类的进行拼接

 var items = await memCache.GetOrCreateAsync("Test1", async (e) => {
            e.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(10);
            logger.LogInformation("从数据库中读取数据");
            return await dbCtx.Test.ToArrayAsync();
        });
缓存基本使用 (二) 设置一个滑动过期

SlidingExpiration 滑动过期
注意如果这个缓存一直被访问,一直不会过期,长时间会导致数据不一致问题!

 var items = await memCache.GetOrCreateAsync("Test2", async (e) => {
            e.SlidingExpiration = TimeSpan.FromSeconds(10);
            logger.LogInformation("Demo2从数据库中读取数据");
            return await dbCtx.Test.ToArrayAsync();
        });
缓存基本使用 (三)混合使用过期策略

比如这个接口访问比较频繁,我们可以使用滑动过期和绝对过期结合使用
注意:绝对过期时间一定要大于滑动过期时间

var items = await memCache.GetOrCreateAsync("Test3", async (e) => {
            e.SlidingExpiration = TimeSpan.FromSeconds(10);
            e.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(30);
            logger.LogInformation("Demo3从数据库中读取数据");
            return await dbCtx.Test.ToArrayAsync();
        });
 var items = await memCache.GetOrCreateAsync("AllBooks3", async (e) => {
            e.SlidingExpiration = TimeSpan.FromSeconds(10);
            e.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(30);
            logger.LogInformation("Demo3从数据库中读取数据");
            return await dbCtx.Books.ToArrayAsync();
        });

在设置一个滑动过期的时候 我发现了一个问题,就是random 随机数不支持 Double 导致时间无法精确,而且如果int 类型 缓存过多 生成的 随机数可能会导致重复,造成缓存雪崩
这里我们自己扩展一个随机Double传入(minValue,maxValue)

   public static class RondowExtensions
    {

        public static double NextDouble(this Random random, double minValue, double maxValue)
        {
            if (minValue >= maxValue) throw new ArgumentOutOfRangeException(nameof(minValue), "minValue annot be bigger than maxValue");
            double x = random.NextDouble();
            return x * maxValue + (1 - x) * minValue;
        }
    }
interface IMemoryCacheHelper
    public interface IMemoryCacheHelper
    {
        /// <summary>
        /// 从缓存中获取数据,如果缓存中没有数据,则调用valueFactory获取数据。
        /// 可以用AOP+Attribute的方式来修饰到Service接口中实现缓存,更加优美,但是没有这种方式更灵活。
        /// 默认最长的缓存过期时间是expireSeconds秒,当然也可以在领域事件的Handler中调用Update更新缓存,或者调用Remove删除缓存。
        /// 因为IMemoryCache会把null当成合法的值,因此不会有缓存穿透的问题,但是还是建议用我这里封装的ICacheHelper,原因如下:
        /// 1)可以切换别的实现类,比如可以保存到MemCached、Redis等地方。这样可以隔离变化。
        /// 2)IMemoryCache的valueFactory用起来麻烦,还要单独声明一个ICacheEntry参数,大部分时间用不到这个参数。
        /// 3)这里把expireSeconds加上了一个随机偏差,这样可以避免短时间内同样的请求集中过期导致“缓存雪崩”的问题
        /// 4)这里加入了缓存数据的类型不能是IEnumerable、IQueryable等类型的限制
        /// </summary>
        /// <typeparam name="TResult">缓存的值的类型</typeparam>
        /// <param name="cacheKey">缓存的key</param>
        /// <param name="valueFactory">提供数据的委托</param>
        /// <param name="expireSeconds">缓存过期秒数的最大值,实际缓存时间是在[expireSeconds,expireSeconds*2)之间,这样可以一定程度上避免大批key集中过期导致的“缓存雪崩” 
        ///  的问题</param>
        /// <returns></returns>
        TResult? GetOrCreate<TResult>(string cacheKey, Func<ICacheEntry, TResult?> valueFactory, int expireSeconds = 60);

        Task<TResult?> GetOrCreateAsync<TResult>(string cacheKey, Func<ICacheEntry, Task<TResult?>> valueFactory, int expireSeconds = 60);

        /// <summary>
        /// 删除缓存的值
        /// </summary>
        /// <param name="cacheKey"></param>
        void Remove(string cacheKey);
    }
MemoryCacheHelper
    /// <summary>
    /// 用ASP.NET的IMemoryCache实现的内存缓存
    /// </summary>
    public class MemoryCacheHelper : IMemoryCacheHelper
    {
        private readonly IMemoryCache memoryCache;
        public MemoryCacheHelper(IMemoryCache memoryCache)
        {
            this.memoryCache = memoryCache;
        }

        private static void ValidateValueType<TResult>()
        {
            //因为IEnumerable、IQueryable等有延迟执行的问题,造成麻烦,因此禁止用这些类型
            Type typeResult = typeof(TResult);
            if (typeResult.IsGenericType)//如果是IEnumerable<String>这样的泛型类型,则把String这样的具体类型信息去掉,再比较
            {
                typeResult = typeResult.GetGenericTypeDefinition();
            }
            //注意用相等比较,不要用IsAssignableTo
            if (typeResult == typeof(IEnumerable<>) || typeResult == typeof(IEnumerable)
                || typeResult == typeof(IAsyncEnumerable<TResult>)
                || typeResult == typeof(IQueryable<TResult>) || typeResult == typeof(IQueryable))
            {
                throw new InvalidOperationException($"TResult of {typeResult} is not allowed, please use List<T> or T[] instead.");
            }
        }

        private static void InitCacheEntry(ICacheEntry entry, int baseExpireSeconds)
        {
            //过期时间.Random.Shared 是.NET6新增的
            Random.Shared.Next(1.1, 1.0);
            double sec = Random.Shared.Next(baseExpireSeconds, baseExpireSeconds * 2);
            TimeSpan expiration = TimeSpan.FromSeconds(sec);
            entry.AbsoluteExpirationRelativeToNow = expiration;
        }

        public TResult? GetOrCreate<TResult>(string cacheKey, Func<ICacheEntry, TResult?> valueFactory, int baseExpireSeconds = 60)
        {
            ValidateValueType<TResult>();
            //因为IMemoryCache保存的是一个CacheEntry,所以null值也认为是合法的,因此返回null不会有“缓存穿透”的问题
            //不调用系统内置的CacheExtensions.GetOrCreate,而是直接用GetOrCreate的代码,这样免得包装一次委托
            if (!memoryCache.TryGetValue(cacheKey, out TResult result))
            {
                using ICacheEntry entry = memoryCache.CreateEntry(cacheKey);
                InitCacheEntry(entry, baseExpireSeconds);
                result = valueFactory(entry)!;
                entry.Value = result;
            }
            return result;
        }

        public async Task<TResult?> GetOrCreateAsync<TResult>(string cacheKey, Func<ICacheEntry, Task<TResult?>> valueFactory, int baseExpireSeconds = 60)
        {
            ValidateValueType<TResult>();
            if (!memoryCache.TryGetValue(cacheKey, out TResult result))
            {
                using ICacheEntry entry = memoryCache.CreateEntry(cacheKey);
                InitCacheEntry(entry, baseExpireSeconds);
                result = (await valueFactory(entry))!;
                entry.Value = result;
            }
            return result;
        }

        public void Remove(string cacheKey)
        {
            memoryCache.Remove(cacheKey);
        }
    }

标签:Core,缓存,memoryCache,过期,typeResult,ASP,entry,cacheKey
From: https://www.cnblogs.com/Bo-H/p/16599055.html

相关文章

  • ASP.NET Core :容器注入(二):生命周期作用域与对象释放
    //瞬时生命周期ServiceCollectionservices=newServiceCollection();services.AddTransient<TestServiceImpl>();using(ServiceProvidersp=services.BuildServic......
  • .NET Core:中间件系列(三):中间件限流
    中间件微软官网定义:中间件中间件意思就是处理请求和响应的软件:1、选择是否将请求传递到管道中的下一个组件。2、可在管道中的下一个组件前后执行工作。对中间件类......
  • asp.net获取当前网址url (2018-11-02 14:49:45)
    设当前页完整地址是:http://www.jb51.net/aaa/bbb.aspx?id=5&name=kelli "http://"是协议名 "www.jb51.net"是域名 "aaa"是站点名 "bbb.aspx"是页面名(文件名) "id=......
  • 在asp.net中开启后台任务
    开始后台任务一般是Task.Run()查在asp.net时进行可能会被回收,导致Task中断。在Asp,net中有专门的后台任务函数: System.Web.Hosting.HostingEnvironment.QueueBackgroun......
  • .Net core 利用Npoi.Mapper 生成Excel
    1.NuGet添加Npoi.Mapper引用   2.初始化privatevoidbutton1_Click(objectsender,EventArgse){//数据导出测试......
  • 【CV项目源码实现】Floating point exception (core dumped)
    前言cmd./darknetdetectordemocfg/tfl.datacfg/yolov3-tiny-tfl.cfgbackup/yolov3-tiny-tfl_500000.weightsdata/tfl.avierrorFloatingpointexception(cor......
  • KASP标记与农作物育种
    目录KASP的特点KASP的原理与步骤原理步骤(1)引物和探针设计(2)普通PCR扩增(3)荧光检测和分析KASP与农业育种KASP的特点在过去30年中,分子标记从低通量限制性片段长度多态性(RFLP......
  • AI Engine core 初识
    AIEnginecore初识     其余的端口需要通过软件平台来使能  ......
  • ASP.NET Core依赖注入系统学习教程:5.生命周期
    在现实生活中,生命周期一词往往代表着某些人或事物从生到死的过程,而在依赖注入框架中,生命周期中的“生与死”体现为服务实例的创建和释放。实际上对于介绍依赖注入框架的生......
  • .net core 6.0 应用session
    一、在Strartup类的ConfigureServices方法中添加:services.AddDistributedMemoryCache(); //添加内存缓存services.AddSession(); //添加Session服务 二、在Strartu......