首页 > 系统相关 >内存缓存和分布式缓存

内存缓存和分布式缓存

时间:2024-03-14 17:44:24浏览次数:16  
标签:缓存 string memoryCache 过期 cacheKey 内存 public 分布式

参考官方文档:https://learn.microsoft.com/zh-cn/aspnet/core/performance/caching/overview?view=aspnetcore-6.0

内存中缓存可以存储任何对象。 分布式缓存接口仅限于 byte[],应用程序需要自行解决针对缓存对象的序列化和反序列化问题。 内存中和分布式缓存都将缓存项存储为键值对。

1、内存缓存

内存缓存就是把缓存数据放到应用程序内存中,利用内存读写比磁盘、网络请求快的特点来提高应用性能。不同程序内存缓存相互独立,一旦程序重启,内存缓存中的数据也会丢失。

1.1、过期策略

  • 绝对时间过期策略:不论缓存对象最近使用的频率如何,对应的 ICacheEntry 对象总是在指定的时间点之后过期。注意:当 AbsoluteExpiration 和 AbsoluteExpirationRelativeToNow 都设置值的时候,绝对过期时间取距离当前时间近的那个设置。
  • 滑动过期:如果在指定的时间内没有读取过该缓存条目,缓存将会过期。反之,针对缓存的每一次使用都会将过期时间向后延长SlidingExpiration时长。
  • 两种混用:如设置了绝对过期时间为1小时后,且滑动过期时间为5分钟,则过期采用的算法为:Min(AbsoluteExpiration - Now , SlidingExpiration)。注意:绝对过期时间一定要大于滑动过期时间。
  • 利用 IChangeToken 对象发送通知:在对象被修改之前永不过期,修改后更改缓存值。

 

1.2、缓存穿透和缓存雪崩

缓存穿透:一直查询不存在的值。
解决方案:把null值也当成一个数据存入缓存。使用 GetOrCreate()/GetOrCreateAsync() 方法即可,因为它会把 null 值也当成合法的缓存值

缓存雪崩:缓存项集中过期引起缓存雪崩。
解决方案:在基础过期时间之上,再加一个随机的过期时间。

 

1.3、代码中使用

添加 Nuget 引用:Microsoft.Extensions.Caching.Memory

在 startup.cs 服务配置里面加上:services.AddMemoryCache();

上面将服务添加到依赖注入容器后,在构造函数中请求 IMemoryCache 实例:

public class IndexModel : PageModel
{
    private readonly IMemoryCache _memoryCache;

    public IndexModel(IMemoryCache memoryCache) =>
        _memoryCache = memoryCache;

    // ...
}    

方法一:(推荐)使用 GetOrCreate 和 GetOrCreateAsync 扩展方法来缓存数据

public async Task OnGetCacheGetOrCreateAsync()
{
    var cachedValue = await _memoryCache.GetOrCreateAsync(
        CacheKeys.Entry,//自定义key,object
        cacheEntry =>
        {
            //设置绝对过期时间
            cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(20);
            //设置滑动过期时间
            cacheEntry.SlidingExpiration = TimeSpan.FromSeconds(3);
            return Task.FromResult(DateTime.Now);
        });

    // ...
}
//GetOrCreate类似

方法二:使用 TryGetValue 来检查缓存中是否包含时间。 如果未缓存时间,则创建一个新条目,并使用 Set 将该条目添加到缓存中

public void OnGet()
{
    CurrentDateTime = DateTime.Now;

    if (!_memoryCache.TryGetValue(CacheKeys.Entry, out DateTime cacheValue))
    {
        cacheValue = CurrentDateTime;

        //设置绝对过期时间
        _memoryCache.Set(CacheKeys.Entry, DateTime.Now, TimeSpan.FromDays(1));
        //设置滑动过期时间
        _memoryCache.Set(CacheKeys.Entry, DateTime.Now,new MemoryCacheEntryOptions() { SlidingExpiration=new TimeSpan(0,0,10)});
    }

    CacheCurrentDateTime = cacheValue;
}

 

2、分布式缓存

分布式缓存是由多个应用服务器共享的缓存,通常作为访问它的应用服务器的外部服务进行维护。

2.1、基于 Redis 的分布式缓存

首先请确保已经正常安装并启动了 Redis,并在项目中添加Nuget包:Microsoft.Extensions.Caching.Redis

关于这个组件工具的文档说明,参考:https://stackexchange.github.io/StackExchange.Redis/

//startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddDistributedRedisCache(option =>
    {
        option.Configuration = "localhost";
        option.InstanceName = "Demo_";//每个rediskey前面加上的字符串,用于区分不同应用系统。
    });

    services.AddControllersWithViews();
}
//HomeController.cs
private readonly IDistributedCache distributedCache;//Redis
public HomeController(IDistributedCache distributedCache)
{
    this.distributedCache = distributedCache;
}
public async Task<string> TestRedisCache()
{
    var time = await distributedCache.GetStringAsync("CurrentTime");
    if (null==time)
    {
        time = DateTime.Now.ToString();
        await distributedCache.SetAsync("CurrentTime", Encoding.UTF8.GetBytes(time));
        //设置带有过期时间的
        //redisCache.SetString("str", DateTime.Now.Ticks.ToString(),new DistributedCacheEntryOptions() { AbsoluteExpiration=DateTime.Now.AddSeconds(10)});
    }
    return $"缓存时间:{time}(,当前时间:{DateTime.Now})";
}
 

可以额外了解下:StackExchange.Redis 。它是 .NET 领域知名的 Redis 客户端框架。

 

3、封装

解决缓存穿透和缓存雪崩问题。

3.1、扩展Random.NextDouble()

扩展 random 随机数使其支持 Double ,避免int类型不精确或者缓存过多导致随机数重复,造成缓存雪崩。

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

 

3.2、内存缓存操作帮助类

IMemoryCacheHelper
public interface IMemoryCacheHelper
{
    /// <summary>
    /// 从缓存中获取数据,如果缓存中没有数据,则调用valueFactory获取数据。
    /// </summary>
    /// <remarks>
    /// 这里加入了缓存数据的类型不能是IEnumerable、IQueryable等类型的限制
    /// </remarks>
    /// <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>
/// IMemoryCacheHelper 内存缓存帮助实现类
/// </summary>
internal class MemoryCacheHelper : IMemoryCacheHelper
{
    private readonly IMemoryCache _memoryCache;
    public MemoryCacheHelper(IMemoryCache memoryCache)
    {
        _memoryCache = memoryCache;
    }

    public TResult? GetOrCreate<TResult>(string cacheKey, Func<ICacheEntry, TResult?> valueFactory, int expireSeconds = 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, expireSeconds);
            result = valueFactory(entry)!;
            entry.Value = result;
        }

        return result;
    }

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

        if (!_memoryCache.TryGetValue(cacheKey, out TResult result))
        {
            using ICacheEntry entry = _memoryCache.CreateEntry(cacheKey);
            InitCacheEntry(entry, expireSeconds);
            result = (await valueFactory(entry))!;
            entry.Value = result;
        }

        return result;
    }

    public void Remove(string cacheKey) => _memoryCache.Remove(cacheKey);

    /// <summary>
    /// 过期时间
    /// </summary>
    /// <remarks>
    /// Random.Shared 是.NET6新增的
    /// </remarks>
    /// <param name="entry">ICacheEntry</param>
    /// <param name="baseExpireSeconds">过期时间</param>
    private static void InitCacheEntry(ICacheEntry entry, int baseExpireSeconds) =>
        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(Random.Shared.NextDouble(baseExpireSeconds, baseExpireSeconds * 2));

    /// <summary>
    /// 验证值类型
    /// </summary>
    /// <typeparam name="TResult"></typeparam>
    /// <exception cref="InvalidOperationException"></exception>
    private static void ValidateValueType<TResult>()
    {
        // 因为IEnumerable、IQueryable等有延迟执行的问题,造成麻烦,因此禁止用这些类型
        Type typeResult = typeof(TResult);

        // 如果是IEnumerable<String>这样的泛型类型,则把String这样的具体类型信息去掉,再比较
        if (typeResult.IsGenericType)
        {
            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.");
        }
    }
}

 

3.3、分布式缓存操作帮助类

IDistributedCacheHelper
 public interface IDistributedCacheHelper
{
    /// <summary>
    /// 创建缓存
    /// </summary>
    /// <typeparam name="TResult"></typeparam>
    /// <param name="cacheKey"></param>
    /// <param name="valueFactory"></param>
    /// <param name="expireSeconds"></param>
    /// <returns></returns>
    TResult? GetOrCreate<TResult>(string cacheKey, Func<DistributedCacheEntryOptions, TResult?> valueFactory, int expireSeconds = 60);

    /// <summary>
    /// 创建缓存
    /// </summary>
    /// <typeparam name="TResult"></typeparam>
    /// <param name="cacheKey"></param>
    /// <param name="valueFactory"></param>
    /// <param name="expireSeconds"></param>
    /// <returns></returns>
    Task<TResult?> GetOrCreateAsync<TResult>(string cacheKey, Func<DistributedCacheEntryOptions, Task<TResult?>> valueFactory, int expireSeconds = 60);

    /// <summary>
    /// 删除缓存
    /// </summary>
    /// <param name="cacheKey"></param>
    void Remove(string cacheKey);

    /// <summary>
    /// 删除缓存
    /// </summary>
    /// <param name="cacheKey"></param>
    /// <returns></returns>
    Task RemoveAsync(string cacheKey);
}
DistributedCacheHelper
 /// <summary>
/// 分布式缓存帮助实现类
/// </summary>
public class DistributedCacheHelper : IDistributedCacheHelper
{
    private readonly IDistributedCache _distCache;

    public DistributedCacheHelper(IDistributedCache distCache)
    {
        _distCache = distCache;
    }

    public TResult? GetOrCreate<TResult>(string cacheKey, Func<DistributedCacheEntryOptions, TResult?> valueFactory, int expireSeconds = 60)
    {
        string jsonStr = _distCache.GetString(cacheKey);

        // 缓存中不存在
        if (string.IsNullOrEmpty(jsonStr))
        {
            var options = CreateOptions(expireSeconds);

            // 如果数据源中也没有查到,可能会返回null
            TResult? result = valueFactory(options);

            // null 会被 json 序列化为字符串 "null",所以可以防范“缓存穿透”
            string jsonOfResult = JsonSerializer.Serialize(result, typeof(TResult));
            _distCache.SetString(cacheKey, jsonOfResult, options);

            return result;
        }
        else
        {
            // "null"会被反序列化为null
            // TResult如果是引用类型,就有为null的可能性;如果TResult是值类型
            // 在写入的时候肯定写入的是0、1之类的值,反序列化出来不会是null
            // 所以如果obj这里为null,那么存进去的时候一定是引用类型
            _distCache.Refresh(cacheKey);//刷新,以便于滑动过期时间延期

            return JsonSerializer.Deserialize<TResult>(jsonStr)!;
        }
    }

    public async Task<TResult?> GetOrCreateAsync<TResult>(string cacheKey, Func<DistributedCacheEntryOptions, Task<TResult?>> valueFactory, int expireSeconds = 60)
    {
        string jsonStr = await _distCache.GetStringAsync(cacheKey);

        if (string.IsNullOrEmpty(jsonStr))
        {
            var options = CreateOptions(expireSeconds);

            TResult? result = await valueFactory(options);
            string jsonOfResult = JsonSerializer.Serialize(result, typeof(TResult));

            await _distCache.SetStringAsync(cacheKey, jsonOfResult, options);
            return result;
        }
        else
        {
            await _distCache.RefreshAsync(cacheKey);
            return JsonSerializer.Deserialize<TResult>(jsonStr)!;
        }
    }

    public void Remove(string cacheKey) => _distCache.Remove(cacheKey);

    public Task RemoveAsync(string cacheKey) => _distCache.RemoveAsync(cacheKey);

    private static DistributedCacheEntryOptions CreateOptions(int expireSeconds) => new()
    {
        // 过期时间.Random.Shared 是.NET6新增的
        AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(Random.Shared.NextDouble(expireSeconds, expireSeconds * 2))
    };
}

 

标签:缓存,string,memoryCache,过期,cacheKey,内存,public,分布式
From: https://www.cnblogs.com/xixi-in-summer/p/18073563

相关文章

  • 开启mybatis二级缓存
    mybatis的二级缓存是mapper级别的,也就是同一个mapper下的查询,可以使用缓存里面的值下面就写个demo记录下没有使用缓存之前service@OverridepublicDevicegetUserById(Longid){DevicebyId=deviceMapper.getDeviceById(id);log.info("=======......
  • OceanBase初体验之部署生产标准的三节点分布式集群
    前置条件OceanBase数据库集群至少由三个节点组成,所以先准备好3台服务器:IP配置操作系统x.x.x.150Intelx8612C64G内存1TSSDCentOS7.9x.x.x.155Intelx8612C64G内存1TSSDCentOS7.9x.x.x.222Intelx8612C64G内存1TSSDCentOS7.9关于运行......
  • 容器集群实现多机多卡分布式微调大模型chatglm2-6b(deepseed + LLaMA + NCCL)
    环境信息2台物理机(187.135,187.136),各两张p4显卡,安装好docker=20.10.0,安装好nvidia驱动(driverversion=470.223.02,cudaversion=11.4)构造容器集群(dockerswarm187.136节点作为manager节点,187.135节点作为worker节点)[root@host-136~]#dockerswarminit--advertise-addr......
  • 滴水逆向笔记系列-c语言总结6-20.多级指针 数组指针 函数指针-21.位运算-22.内存分配
    第二十课c语言13多级指针数组指针函数指针1.多级指针反汇编一二级指针可以看到p1==*(p1+0)==p1[0]本来一直没想懂为什么是movsxecx,byteptr[eax],是byte,才发现p1是char类型,所以才得用movsx拓展(p1+2)==p1[2],指针可以用和[]取值,他们是一样的(((p3+1)+2)+3)==p3[......
  • Hadoop大数据应用:Linux 部署 HDFS 分布式集群
    目录  一、实验1.环境2.Linux部署HDFS分布式集群3.Linux使用 HDFS文件系统二、问题1.ssh-copy-id报错2.如何禁用sshkey检测3.HDFS有哪些配置文件4.hadoop查看版本报错5.启动集群报错6.hadoop的启动和停止命令7.上传文件报错8.HDFS使用命令  ......
  • 微服务分布式springcloud研究生志愿填报辅助系统
    本文讲述了研究生志愿填报辅助系统。结合电子管理系统的特点,分析了研究生志愿填报辅助系统的背景,给出了研究生志愿填报辅助系统实现的设计方案。本论文主要完成不同用户的权限划分,不同用户具有不同权限的操作功能,在用户模块,主要有用户进行注册和登录,用户可以实现查看院校信息......
  • pandas DataFrame内存优化技巧:让数据处理更高效
    Pandas无疑是我们数据分析时一个不可或缺的工具,它以其强大的数据处理能力、灵活的数据结构以及易于上手的API赢得了广大数据分析师和机器学习工程师的喜爱。然而,随着数据量的不断增长,如何高效、合理地管理内存,确保PandasDataFrame在运行时不会因内存不足而崩溃,成为我们每一个人......
  • 陌陌技术分享:陌陌IM在后端KV缓存架构上的技术实践
    本文由冀浩东分享,原题“单核QPS近6000S,陌陌基于OceanBase的持久化缓存探索与实践”,为了阅读便利,本文进行了排版和内容优化等。1、引言挚文集团于2011年8月推出了陌陌,这款立足地理位置服务的开放式移动视频IM应用在中国社交平台领域内独树一帜。陌陌和探探作为陌生人社交领......
  • 喜欢的音乐太多了 占用太多内存让电脑卡顿了怎么办?教你一键压缩 帮你搞定烦恼
    下载了很多音乐,发现真的太占空间了,但是又不舍得删除,该怎么办呢?其实我们可以压缩一下,对于喜欢听歌的小伙伴来说,手机里一定存了很多音乐吧,由于手机的存储空间有限,存的音乐越多,手机可用的空间就越小。为了解决手机里音频文件占用空间过大的问题,我们可以将手机里的音频进行压缩,这样......
  • 滴水逆向笔记系列 - 4.内存地址_堆栈-5.标志寄存器-6.JCC命令
    第四课内存地址_堆栈内存地址db与dd命令db:d表示查找,b表示bytedd:d表示查找,d表示dworddb命令在数据区找出目的内存地址,发现数据区内和堆栈区显示的是相反的反汇编窗口和寄存器窗口的都是从高位到低位,数据区反之(比如数据0x12345678,12是高位,8是低位)所以0012FFDC这块内存(1字节)......