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

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

相关推荐
curd_boy6 小时前
【Redis】Redis从缓存到AI向量平台
人工智能·redis·缓存
kiracrimson6 小时前
从缓存的角度看链表与线性表的差异
数据结构·链表·缓存
2601_962301019 小时前
深入理解缓存(Cache):原理、应用与优化
缓存·计算机原理·优化策略·数据访问·系统性能
数智启示录10 小时前
PostgreSQL 计划缓存实战(第 10 篇):预编译 SQL 前五次都快,第六次为什么可能变慢
经验分享·sql·缓存·postgresql·面试
跨境数据猎手14 小时前
京东开放平台商品详情接口(jd.item_get):签名、缓存与批量采集
java·spring·缓存
xbgRS14 小时前
Redis基本概念及应用
redis·缓存
草莓熊Lotso14 小时前
【Redis 初阶】特殊数据类型、渐进式遍历与数据库操作生产指南
linux·网络·数据库·redis·tcp/ip·缓存·bootstrap
λqaq714 小时前
Redis 数据库基础:安装、5 大核心数据类型与常用命令
linux·数据库·redis·python·缓存
莫得感情 o15 小时前
Redis 04 · 执行模型:一条命令的生命周期与 I/O 多路复用
redis·缓存
ofoxcoding16 小时前
Claude Fable 5.1 API 接入指南:模型标识、五级定价策略、effort 参数与缓存 TTL 配置详解
java·spring·缓存·ai