用 .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明文保存,请注意数据安全,或者自行实现加密保存

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

相关推荐
YOU OU9 小时前
Redis基础常识与命令
数据库·redis·缓存
吳所畏惧13 小时前
宝塔面板Redis密码修改指南:SSH命令修改 vs 面板UI界面修改,哪个更靠谱?
运维·服务器·数据库·redis·缓存·ssh
Y38153266219 小时前
SERP API + Redis 缓存层:4 种方案对比与选型
数据库·redis·缓存
海兰21 小时前
【高速缓存】RedisVL 高级查询(全文搜索、混合搜索和 多向量搜索)
数据库·人工智能·redis·缓存
流星白龙1 天前
【Redis】4.基本全局命令
数据库·redis·缓存
流星白龙2 天前
【Redis】3.Redis安装与命令行客户端
数据库·redis·缓存
我不想名字重复2 天前
redis缓存和数据库数据保持一致
数据库·redis·缓存
时空无限2 天前
vllm 大模型启动缓存相关环境变量 export
linux·缓存·vllm
流星白龙2 天前
【Redis】6.计数其他命令
数据库·redis·缓存
正儿八经的少年2 天前
redis 的大 key 和热 key 详解
数据库·redis·缓存