用 .net Core 实现简单的缓存,在小型单体项目中替代Redis

用 .net Core 实现简单的缓存,在小型项目中替代Redis

想法背景

在做小型单体,用户量,并发量都不大的项目时,有需要用到少量缓存的需求,同时希望安装部署简单,那么直接上Redis就不合适了,毕竟Redis需要独立安装,那么实现一个可集成到项目中,简单轻量的缓存,是一个不错的选择

特点说明

无依赖 .net Core 6/7/8/9/10 都能用
支持并发
实现了简单的过期数据删除
适合小型单体项目,少量缓存,如果需要复杂的缓存功能,还是用Redis更好
在小型单体项目,用户量不大的情况下,基本够用

上代码

csharp 复制代码
    /// <summary>
    /// 一个简单的缓存
    /// 主要用于存放 验证码/登录Token 等短期内容,
    /// 复杂的缓存操作,请使用 Redis
    /// </summary>
    public class CacheBase
    {

        /// <summary>
        /// 缓存持久化保存路径
        /// </summary>
        public string? SavePath = null;

        /// <summary>
        /// 锁
        /// </summary>
        private static readonly object _persistLock = new object();

        /// <summary>
        /// 读取指定键的值
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public string? this[string key]
        {
            get
            {
                if (!List.ContainsKey(key)) { return null; }
                var data = Get(key);
                return data;
            }
        }

        /// <summary>
        /// 缓存集合
        /// </summary>
        private ConcurrentDictionary<string, CacheItem> List = new ConcurrentDictionary<string, CacheItem>();

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="savePath"></param>
        public CacheBase(string savePath)
        {
            SavePath = savePath;
            // 创建目录
            var dir = Path.GetDirectoryName(savePath);
            Directory.CreateDirectory(dir);

            // 从文件读取
            if (!string.IsNullOrEmpty(SavePath))
            {
                var text= Text.Read(SavePath);
                if (!string.IsNullOrEmpty(text))
                {
                    List = text.ToEntity<ConcurrentDictionary<string, CacheItem>>();
                }
            }
        }

        /// <summary>
        /// 持久化缓存
        /// </summary>
        /// <returns></returns>
        public async Task PersistCache()
        {
            lock (_persistLock)
            {

                // 保存到文件
                if (!string.IsNullOrEmpty(SavePath))
                {
                    Text.Write(List.ToJson(), SavePath);
                }
            }
        }

        /// <summary>
        /// 删除缓存
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public async Task Delete(string key)
        {
            if (List.ContainsKey(key))
            {
                this.List.TryRemove(key, out CacheItem i);
            }
            PersistCache();
        }

        /// <summary>
        /// 获取所有缓存
        /// </summary>
        /// <returns></returns>
        public allCache GetAll()
        {
            DelItem();
            return new allCache()
            {
                Items = List,
                Count = List.Count,
            };
        }

        /// <summary>
        /// 获取缓存
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public T? Get<T>(string key)
        {
            var item = Get(key);
            if (item != null)
            {
                return item.ToEntity<T>();
            }

            return default;
        }

        /// <summary>
        /// 获取缓存
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public string? Get(string key)
        {
            DelItem();
            var dt = DateTime.Now;
            if (List.ContainsKey(key))
            {
                var item = List[key];
                if (item.TTL == null)
                {
                    return item.Value;
                }

                if (item.TTL > dt)
                {
                    return item.Value;
                }
                return null;
            }
            return null;
        }

        /// <summary>
        /// 判断缓存是否存在
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public bool ContainsKey(string key)
        {
            if (List.ContainsKey(key))
            {
                return true;
            }
            return false;
        }

        /// <summary>
        /// 上次清理时间
        /// </summary>
        public DateTime _lastCleanupTime = DateTime.MinValue;

        /// <summary>
        /// 删除过期的缓存
        /// </summary>
        /// <returns></returns>
        private async Task DelItem()
        {
            var dt = DateTime.Now;
            // 满足条件时清理一次,降低系统的负载
            if (List.Count % 100 == 0 || (DateTime.Now - _lastCleanupTime).TotalMinutes >= 5)
            {
                // 收集所有需要移除的键
                var keysToRemove = List.Where(x => x.Value.TTL < DateTime.Now).Select(x => x.Key).ToList();

                // 移除过期的键
                foreach (var item in keysToRemove)
                {
                    List.TryRemove(item, out CacheItem i);
                }
                _lastCleanupTime = dt;

                PersistCache();
            }

        }

        /// <summary>
        /// 添加值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="ttl"></param>
        /// <returns></returns>
        public async Task Set<T>(string key, T value, int ttl = 0)
        {
            var val = value.ToJson();
            Set(key, val, ttl);
        }

        /// <summary>
        /// 添加值
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="ttl"></param>
        public async Task Set(string key, string value, int ttl = 0)
        {
            DelItem();
            DateTime? _ttl;
            if (ttl < 0) ttl = 0;
            if (ttl == 0)
            {
                _ttl = null;
            }
            else
            {
                _ttl = DateTime.Now.AddSeconds(ttl);
            }

            var item = new CacheItem()
            {
                TTL = _ttl,
                Value = value,
            };

            if (List.ContainsKey(key))
            {
                List[key] = item;
            }
            else
            {
                List.AddOrUpdate(key, item, (k, v) => v);
            }

            PersistCache();

        }

    }

    /// <summary>
    /// 缓存项
    /// </summary>
    public class CacheItem
    {
        /// <summary>
        /// 有效期
        /// </summary>
        public DateTime? TTL { get; set; }


        /// <summary>
        /// 缓存值
        /// </summary>
        public string? Value { get; set; }
    }

    /// <summary>
    /// 所有缓存
    /// </summary>
    public class allCache
    {
        /// <summary>
        /// 缓存数量
        /// </summary>
        public int Count { get; set; }

        /// <summary>
        /// 所有缓存
        /// </summary>
        public ConcurrentDictionary<string, CacheItem> Items { get; set; }
    }

实现多个实例

csharp 复制代码
 public class Cache1: CacheBase
 {
     public Cache1():base("wwwroot/Cache/13.json")
     {
     }
 }

 public class Cache2: CacheBase
 {
     public Cache2():base("wwwroot/Cache/13.json")
     {
     }
 }

上面代码中的部分内容说明
ToJson 方法将实体转为json字符串
ToEntity 方法将json字符串转为指定的类型
Text.Read 方法从指定路径的文件读取文本
Text.Write 方法将字符串写入到指定路径的文件
上面代码中没有这些内容的实现,请自行实现替代
持久化以json明文保存,请注意数据安全,或者自行实现加密保存

不适合大型/分布式/需要复杂缓存 的项目

相关推荐
段帅龙呀1 小时前
Redis构建缓存服务器
服务器·redis·缓存
夜斗小神社17 小时前
【黑马点评】(二)缓存
缓存
Hello.Reader1 天前
Redis 延迟监控深度指南
数据库·redis·缓存
Hello.Reader1 天前
Redis 延迟排查与优化全攻略
数据库·redis·缓存
在肯德基吃麻辣烫2 天前
《Redis》缓存与分布式锁
redis·分布式·缓存
先睡2 天前
Redis的缓存击穿和缓存雪崩
redis·spring·缓存
CodeWithMe3 天前
【Note】《深入理解Linux内核》 Chapter 15 :深入理解 Linux 页缓存
linux·spring·缓存
大春儿的试验田3 天前
高并发收藏功能设计:Redis异步同步与定时补偿机制详解
java·数据库·redis·学习·缓存
likeGhee3 天前
python缓存装饰器实现方案
开发语言·python·缓存
C182981825753 天前
OOM电商系统订单缓存泄漏,这是泄漏还是溢出
java·spring·缓存