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

发布于:2025-03-23 ⋅ 阅读:(21) ⋅ 点赞:(0)

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

想法背景

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

特点说明

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

上代码


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



实现多个实例

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

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